关于每个组的组外值的描述性统计

时间:2018-03-26 22:25:09

标签: apache-spark pyspark spark-dataframe

我有一个像这样的Spark qqqq345623457612

已修改:每个DataFrame可以在任何name中多次出现。

org

我想为每个df = sqlContext.createDataFrame( [ ('org_1', 'a', 1), ('org_1', 'a', 2), ('org_1', 'a', 3), ('org_1', 'b', 4), ('org_1', 'c', 5), ('org_2', 'a', 7), ('org_2', 'd', 4), ('org_2', 'e', 5), ('org_2', 'e', 10) ], ["org", "name", "value"] ) org计算:其余namemeanstddev count每个values中不包含该名称的名称。例如。对于org,请注明org_1b

DataFrame有大约4.5亿行。我不能使用矢量化的pandas_UDF,因为我的Spark版本是2.2。还有mean = (1+2+3+5)/4 spark.driver.maxResultSize的约束。

我在4.0 GB上尝试了这一点(在群组中过滤行并采用均值/标准/计数)Pandas只有两列(DataFramename)。我还没有弄清楚如何使用两级分组列(valueorg)来执行此操作。

name

我可以在Spark上定义类似的UDF功能吗? (此函数必须包含多个列)。否则,有什么更有效的方法呢?

2 个答案:

答案 0 :(得分:0)

简单UDF也可以。

import pyspark.sql.functions as F
import numpy as np
from pyspark.sql.types import *

df = sql.createDataFrame(
[
    ('org_1', 'a', 1),
    ('org_1', 'a', 2),
    ('org_1', 'a', 3),
    ('org_1', 'b', 4),
    ('org_1', 'c', 5),

    ('org_2', 'a', 7),
    ('org_2', 'd', 4),
    ('org_2', 'e', 5),
    ('org_2', 'e', 10)
],
["org", "name", "value"]
                         )

+-----+----+-----+
|  org|name|value|
+-----+----+-----+
|org_1|   a|    1|
|org_1|   a|    2|
|org_1|   a|    3|
|org_1|   b|    4|
|org_1|   c|    5|
|org_2|   a|    7|
|org_2|   d|    4|
|org_2|   e|    5|
|org_2|   e|   10|
+-----+----+-----+

应用groupby并收集list中的所有元素后,我们应用udf查找统计信息。之后,列将展开并拆分为多个列。

def _find_stats(a,b):
    dict_ = zip(a,b)
    stats = []
    for name in a:
        to_cal = [v for k,v in dict_ if k != name]
        stats.append((name,float(np.mean(to_cal))\
                          ,float(np.std(to_cal))\
                          ,len(to_cal)))
    print stats 
    return stats
find_stats = F.udf(_find_stats,ArrayType(ArrayType(StringType())))

cols = ['name', 'mean', 'stddev', 'count']
splits = [F.udf(lambda val:val[0],StringType()),\
          F.udf(lambda val:val[1],StringType()),\
          F.udf(lambda val:val[2],StringType()),\
          F.udf(lambda val:val[3],StringType())]

df = df.groupby('org').agg(*[F.collect_list('name').alias('name'), F.collect_list('value').alias('value')])\
       .withColumn('statistics', find_stats(F.col('name'), F.col('value')))\
       .drop('name').drop('value')\
       .select('org', F.explode('statistics').alias('statistics'))\
       .select(['org']+[split_('statistics').alias(col_name) for split_,col_name in zip(splits,cols)])\
       .dropDuplicates()

df.show()

+-----+----+-----------------+------------------+-----+                         
|  org|name|             mean|            stddev|count|
+-----+----+-----------------+------------------+-----+
|org_1|   c|              2.5| 1.118033988749895|    4|
|org_2|   e|              5.5|               1.5|    2|
|org_2|   a|6.333333333333333|2.6246692913372702|    3|
|org_2|   d|7.333333333333333|2.0548046676563256|    3|
|org_1|   a|              4.5|               0.5|    2|
|org_1|   b|             2.75| 1.479019945774904|    4|
+-----+----+-----------------+------------------+-----+

如果您还想要'value'列,可以在udf函数的元组中插入它,并添加一个拆分udf。 此外,由于重复名称会导致数据框中出现重复,因此您可以使用dropDuplicates删除它们。

答案 1 :(得分:0)

以下是仅使用DataFrame函数执行此操作的方法。

只需在org列上将您的DataFrame加入自身,并使用where子句指定name列应该不同。然后我们选择('l.org', 'l.name', 'r.name', 'r.value')的不同行 - 基本上,我们忽略l.value列,因为我们希望避免对同一(org, name)对进行重复计算。

例如,您可以使用以下方法收集每个('org', 'name')对的其他值:

import pyspark.sql.functions as f

df.alias('l').join(df.alias('r'), on='org')\
    .where('l.name != r.name')\
    .select('l.org', 'l.name', 'r.name', 'r.value')\
    .distinct()\
    .groupBy('l.org', 'l.name')\
    .agg(f.collect_list('r.value').alias('other_values'))\
    .show()
#+-----+----+------------+
#|  org|name|other_values|
#+-----+----+------------+
#|org_1|   a|      [4, 5]|
#|org_1|   b|[1, 2, 3, 5]|
#|org_1|   c|[1, 2, 3, 4]|
#|org_2|   a|  [4, 5, 10]|
#|org_2|   d|  [7, 5, 10]|
#|org_2|   e|      [7, 4]|
#+-----+----+------------+

对于描述性统计信息,您可以使用mean中的stddevcountpyspark.sql.functions函数:

df.alias('l').join(df.alias('r'), on='org')\
    .where('l.name != r.name')\
    .select('l.org', 'l.name', 'r.name', 'r.value')\
    .distinct()\
    .groupBy('l.org', 'l.name')\
    .agg(
        f.mean('r.value').alias('mean'),
        f.stddev('r.value').alias('stddev'),
        f.count('r.value').alias('count')
    )\
    .show()
#+-----+----+-----------------+------------------+-----+
#|  org|name|             mean|            stddev|count|
#+-----+----+-----------------+------------------+-----+
#|org_1|   a|              4.5|0.7071067811865476|    2|
#|org_1|   b|             2.75| 1.707825127659933|    4|
#|org_1|   c|              2.5|1.2909944487358056|    4|
#|org_2|   a|6.333333333333333|3.2145502536643185|    3|
#|org_2|   d|7.333333333333333|2.5166114784235836|    3|
#|org_2|   e|              5.5|2.1213203435596424|    2|
#+-----+----+-----------------+------------------+-----+

请注意pyspark.sql.functions.stddev()返回无偏样本标准差。如果您想要人口标准偏差,请使用pyspark.sql.functions.stddev_pop()

df.alias('l').join(df.alias('r'), on='org')\
    .where('l.name != r.name')\
    .groupBy('l.org', 'l.name')\
    .agg(
        f.mean('r.value').alias('mean'),
        f.stddev_pop('r.value').alias('stddev'),
        f.count('r.value').alias('count')
    )\
    .show()
#+-----+----+-----------------+------------------+-----+
#|  org|name|             mean|            stddev|count|
#+-----+----+-----------------+------------------+-----+
#|org_1|   a|              4.5|               0.5|    2|
#|org_1|   b|             2.75| 1.479019945774904|    4|
#|org_1|   c|              2.5| 1.118033988749895|    4|
#|org_2|   a|6.333333333333333|2.6246692913372702|    3|
#|org_2|   d|7.333333333333333|2.0548046676563256|    3|
#|org_2|   e|              5.5|               1.5|    2|
#+-----+----+-----------------+------------------+-----+

编辑

@NaomiHuang中提及comments时,您还可以在加入之前将l缩减为不同的org/name对:

df.select('org', 'name')\
    .distinct()\
    .alias('l')\
    .join(df.alias('r'), on='org')\
    .where('l.name != r.name')\
    .groupBy('l.org', 'l.name')\
    .agg(f.collect_list('r.value').alias('other_values'))\
    .show()
#+-----+----+------------+
#|  org|name|other_values|
#+-----+----+------------+
#|org_1|   a|      [5, 4]|
#|org_1|   b|[5, 1, 2, 3]|
#|org_1|   c|[1, 2, 3, 4]|
#|org_2|   a|  [4, 5, 10]|
#|org_2|   d|  [7, 5, 10]|
#|org_2|   e|      [7, 4]|
#+-----+----+------------+