Dataframe 整个数据帧上的Pyspark窗口函数
考虑pyspark数据帧。我想总结每列的整个数据帧,并为每行追加结果Dataframe 整个数据帧上的Pyspark窗口函数,dataframe,apache-spark,pyspark,apache-spark-sql,window-functions,Dataframe,Apache Spark,Pyspark,Apache Spark Sql,Window Functions,考虑pyspark数据帧。我想总结每列的整个数据帧,并为每行追加结果 +-----+----------+-----------+ |index| col1| col2 | +-----+----------+-----------+ | 0.0|0.58734024|0.085703015| | 1.0|0.67304325| 0.17850411| 预期结果 +-----+----------+-----------+-----------+-----------+
+-----+----------+-----------+
|index| col1| col2 |
+-----+----------+-----------+
| 0.0|0.58734024|0.085703015|
| 1.0|0.67304325| 0.17850411|
预期结果
+-----+----------+-----------+-----------+-----------+-----------+-----------+
|index| col1| col2 | col1_min | col1_mean |col2_min | col2_mean
+-----+----------+-----------+-----------+-----------+-----------+-----------+
| 0.0|0.58734024|0.085703015| -5 | 2.3 | -2 | 1.4 |
| 1.0|0.67304325| 0.17850411| -5 | 2.3 | -2 | 1.4 |
据我所知,我需要窗口函数,将整个数据帧作为窗口,以保留每行的结果(而不是,例如,分别进行统计,然后返回以复制每行)
我的问题是:
w = Window.partitionBy("col1", "col2").orderBy(desc("col1"))
df = df.withColumn("col1_mean", mean("col1").over(w)))
如何编写一个将所有内容都作为一个分区的窗口
df = df.withColumn("col1_mean", mean("col1").over(w))).withColumn("col1_min", min("col2").over(w)).withColumn("col2_mean", mean().over(w)).....
假设我希望每个列有多个统计信息,因此每个
colx
将生成colx\u min、colx\u max、colx\u mean
我们还可以在窗口函数中不使用orderby、partitionBy子句指定
min(“”.over()
//sample data
val df=Seq((1,2,3),(4,5,6)).toDF("i","j","k")
val df1=df.columns.foldLeft(df)((df, c) => {
df.withColumn(s"${c}_min",min(col(s"${c}")).over()).
withColumn(s"${c}_max",max(col(s"${c}")).over()).
withColumn(s"${c}_mean",mean(col(s"${c}")).over())
})
df1.show()
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
//| i| j| k|i_min|i_max|i_mean|j_min|j_max|j_mean|k_min|k_max|k_mean|
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
//| 1| 2| 3| 1| 4| 2.5| 2| 5| 3.5| 3| 6| 4.5|
//| 4| 5| 6| 1| 4| 2.5| 2| 5| 3.5| 3| 6| 4.5|
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
示例:
//sample data
val df=Seq((1,2,3),(4,5,6)).toDF("i","j","k")
val df1=df.columns.foldLeft(df)((df, c) => {
df.withColumn(s"${c}_min",min(col(s"${c}")).over()).
withColumn(s"${c}_max",max(col(s"${c}")).over()).
withColumn(s"${c}_mean",mean(col(s"${c}")).over())
})
df1.show()
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
//| i| j| k|i_min|i_max|i_mean|j_min|j_max|j_mean|k_min|k_max|k_mean|
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
//| 1| 2| 3| 1| 4| 2.5| 2| 5| 3.5| 3| 6| 4.5|
//| 4| 5| 6| 1| 4| 2.5| 2| 5| 3.5| 3| 6| 4.5|
//+---+---+---+-----+-----+------+-----+-----+------+-----+-----+------+
不使用窗口,您可以通过结合交叉连接的自定义聚合实现相同的效果:
导入pyspark.sql.F函数
从pyspark.sql.functions导入广播
来自itertools进口链
df=spark.createDataFrame([
[1, 2.3, 1],
[2, 5.3, 2],
[3, 2.1, 4],
[4, 1.5, 5]
],[“索引”、“col1”、“col2”])
累计成本=[(
F.min(c).别名(“min_uu”+c),
F.max(c).别名(“max_uuz”+c),
F.平均值(c).别名(“平均值”+c))
对于df.columns中的c,如果c.startswith('col')]
统计数据df=df.agg(*列表(链(*agg_cols)))
#交叉连接不会对性能产生影响,因为我们在右侧表中只广播了一行(Spark很可能会广播)
交叉连接(广播(stats_df)).show()
# +-----+----+----+--------+--------+---------+--------+--------+---------+
#|指数| col1 | col2 |最小值|最大值|平均值|最小值|最大值|平均值||
# +-----+----+----+--------+--------+---------+--------+--------+---------+
# | 1| 2.3| 1| 1.5| 5.3| 2.8| 1| 5| 3.0|
# | 2| 5.3| 2| 1.5| 5.3| 2.8| 1| 5| 3.0|
# | 3| 2.1| 4| 1.5| 5.3| 2.8| 1| 5| 3.0|
# | 4| 1.5| 5| 1.5| 5.3| 2.8| 1| 5| 3.0|
# +-----+----+----+--------+--------+---------+--------+--------+---------+
注1:使用广播我们将避免洗牌,因为广播的df将发送给所有执行者
注2:使用chain(*agg_cols)
我们将上一步创建的元组列表展平
更新:
以下是上述计划的执行计划:
== Physical Plan ==
*(3) BroadcastNestedLoopJoin BuildRight, Cross
:- *(3) Scan ExistingRDD[index#196L,col1#197,col2#198L]
+- BroadcastExchange IdentityBroadcastMode, [id=#274]
+- *(2) HashAggregate(keys=[], functions=[finalmerge_min(merge min#233) AS min(col1#197)#202, finalmerge_max(merge max#235) AS max(col1#197)#204, finalmerge_avg(merge sum#238, count#239L) AS avg(col1#197)#206, finalmerge_min(merge min#241L) AS min(col2#198L)#208L, finalmerge_max(merge max#243L) AS max(col2#198L)#210L, finalmerge_avg(merge sum#246, count#247L) AS avg(col2#198L)#212])
+- Exchange SinglePartition, [id=#270]
+- *(1) HashAggregate(keys=[], functions=[partial_min(col1#197) AS min#233, partial_max(col1#197) AS max#235, partial_avg(col1#197) AS (sum#238, count#239L), partial_min(col2#198L) AS min#241L, partial_max(col2#198L) AS max#243L, partial_avg(col2#198L) AS (sum#246, count#247L)])
+- *(1) Project [col1#197, col2#198L]
+- *(1) Scan ExistingRDD[index#196L,col1#197,col2#198L]
这里我们看到一个
单分区的广播交换
,它广播一行,因为统计数据可以放入单分区
。因此,这里被洗牌的数据只有一行(最小可能) 我记得读交叉连接很贵。Window函数是为以下目的创建的:将agg值带到每一行。在本例中不是@Kenny。这里的交叉连接将发生在df和stats_df之间,最后一个只有一行。在这种情况下,程序将stats_df视为广播值。该行将在每个执行器上进行broacasted,联接只会将stats_df行附加到df@Kenny我修改了答案,添加了查询的执行计划,该计划显示将不会出现洗牌,因为这将只是一个广播交换。如果您需要有关交换类型的更多信息,请注意David VrbaThis提供的是最好的解决方案,如果您正在整数/双精度列上查找最大/最小值。如果需要在时间戳列中查找max,那么这不是最佳解决方案。时间戳上的连接可能会导致大量数据帧出现问题。我个人经历了这样的连接数据分裂。所以在这种情况下,我建议使用Window。。。。或者至少在加入之前投一个长时间戳!我忽略了()缺少1个必需的位置参数:“window”。似乎需要一扇窗户?我正在使用PysparkYou可以这样做:1)为整个数据帧创建具有相同值的列。withColumn(“all”,lit(“1”))2)将此列用于窗口:val window=window.partitionBy(“all”)