Dataframe 整个数据帧上的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| 预期结果 +-----+----------+-----------+-----------+-----------+

考虑pyspark数据帧。我想总结每列的整个数据帧,并为每行追加结果

+-----+----------+-----------+
|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)))
    
    如何编写一个将所有内容都作为一个分区的窗口

  • 任何动态写入所有列的方法 假设我有500个专栏,重复写作看起来不太好

    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”)