我有如下数据
-----------------------------
place | key | weights
----------------------------
amazon | lion | [ 34, 23, 56 ]
north | bear | [ 90, 45]
amazon | lion | [ 38, 30, 50 ]
amazon | bear | [ 45 ]
amazon | bear | [ 40 ]
我试图得到如下结果
-----------------------------
place | key | average
----------------------------
amazon | lion1 | 36.0 #(34 + 38)/2
amazon | lion2 | 26.5 #(23 + 30)/2
amazon | lion3 | 53.0 #(50 + 56)/2
north | bear1 | 90 #(90)/1
north | bear2 | 45 #(45)/1
amazon | bear1 | 42.5 #(45 + 40)/2
我的意思是,首先我必须对列place
和key
进行分组,然后必须对基于索引的数组元素取平均值。
例如lion1是数组[ 34, 23, 56 ]
和[ 38, 30, 50 ]
中的第一索引元素。
我已经有一个使用posexplode
的解决方案,但是问题在于真实数据weights
的数组列大小非常大,因为posexplode
增加了更多的行,因此数据大小从1000万行到12亿行,无法在当前群集上的可靠时间内进行计算。
我认为添加多于行的列然后取消透视列会更好,但是我不知道如何使用pyspark或spark SQL 2.2.1实现这一点。
答案 0 :(得分:0)
您可以通过functions.size()找到数组列中的最大元素数,然后展开该列:
设置数据
from pyspark.sql import functions as F
df = spark.createDataFrame([
('amazon', 'lion', [ 34, 23, 56 ])
, ('north', 'bear', [ 90, 45])
, ('amazon', 'lion', [ 38, 30, 50 ])
, ('amazon', 'bear', [ 45 ])
, ('amazon', 'bear', [ 40 ])
], ['place', 'key', 'average'])
查找数组字段“平均值”中的最大元素数
n = df.select(F.max(F.size('average')).alias('n')).first().n
>>> n
3
将数组列转换为n列
df1 = df.select('place', 'key', *[F.col('average')[i].alias('val_{}'.format(i+1)) for i in range(n)])
>>> df1.show()
+------+----+-----+-----+-----+
| place| key|val_1|val_2|val_3|
+------+----+-----+-----+-----+
|amazon|lion| 34| 23| 56|
| north|bear| 90| 45| null|
|amazon|lion| 38| 30| 50|
|amazon|bear| 45| null| null|
|amazon|bear| 40| null| null|
+------+----+-----+-----+-----+
计算新列上的均值聚合
df2 = df1.groupby('place', 'key').agg(*[ F.mean('val_{}'.format(i+1)).alias('average_{}'.format(i+1)) for i in range(n)])
>>> df2.show()
+------+----+---------+---------+---------+
| place| key|average_1|average_2|average_3|
+------+----+---------+---------+---------+
|amazon|bear| 42.5| null| null|
| north|bear| 90.0| 45.0| null|
|amazon|lion| 36.0| 26.5| 53.0|
+------+----+---------+---------+---------+
使用select + union + reduce取消旋转列
from functools import reduce
df_new = reduce(lambda x,y: x.union(y), [
df2.select('place', F.concat('key', F.lit(i+1)).alias('key'), F.col('average_{}'.format(i+1)).alias('average')) \
.dropna(subset=['average']) for i in range(n)
])
>>> df_new.show()
+------+-----+-------+
| place| key|average|
+------+-----+-------+
|amazon|bear1| 42.5|
| north|bear1| 90.0|
|amazon|lion1| 36.0|
| north|bear2| 45.0|
|amazon|lion2| 26.5|
|amazon|lion3| 53.0|
+------+-----+-------+
答案 1 :(得分:-1)
一个选择是将给定位置,组合键的所有array
合并到一个数组中。在此阵列数组上,您可以使用udf
计算所需的平均值,最后使用posexplode
获得所需的结果。
from pyspark.sql.functions import collect_list,udf,posexplode,concat
from pyspark.sql.types import ArrayType,DoubleType
#Grouping by place,key to get an array of arrays
grouped_df = df.groupBy(df.place,df.key).agg(collect_list(df.weights).alias('all_weights'))
#Define UDF
zip_mean = udf(lambda args: [sum(i)/len(i) for i in zip(*args)],ArrayType(DoubleType()))
#Apply UDF on the array of array column
res = grouped_df.select('*',zip_mean(grouped_df.all_weights).alias('average'))
#POS explode to explode the average values and get the position for key concatenation
res = res.select('*',posexplode(res.average))
#Final result
res.select(res.place,concat(res.key,res.pos+1).alias('key'),res.col).show()