【Spark原理系列】 Window窗口计算原理用法示例源码分析

本文深入探讨Spark窗口计算的原理,包括数据分区、排序和窗口函数计算。介绍了Window对象、WindowSpec和sparksql window function的相关概念,并通过示例和源码分析展示了窗口函数如RowNumber、CumeDist、NTile等的用法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spark Window窗口计算原理用法示例源码分析 点击这里免费看全文

原理

Spark的窗口计算是通过以下步骤实现的:

  1. 数据分区:首先,数据集根据指定的分区键进行分区。每个分区中的数据将作为一个独立的窗口进行计算。

  2. 排序:在每个分区内,根据指定的排序规则对数据进行排序。这确保了在窗口函数应用之前,数据按照正确的顺序进行处理。

  3. 窗口框架定义:根据窗口规范中定义的窗口帧类型、起始边界和结束边界,确定每个数据行所属的窗口范围。

  4. 窗口函数计算:在每个窗口内,应用指定的窗口函数进行计算。窗口函数可以是聚合函数(如SUMAVG)或分析函数(如LEADLAG)。

  5. 窗口函数结果返回:计算完窗口函数后,将结果返回给调用方。通常,结果会随着输入数据的顺序进行排序,并按照指定的输出模式进行返回。

Spark的窗口计算是通过在各个阶段之间进行数据洗牌和排序来实现的。这涉及到对数据进行划分、排序和重新组合的操作,因此需要一定的计算和资源开销。

窗口计算在Spark中非常有用,它可以支持各种分析和处理需求,如行级别的聚合、排名、窗口函数等。通过使用窗口计算,可以在数据集上进行更精细和灵活的操作,并提供更详细的分析结果。

用法

Spark sql functions中的窗口函数

窗口函数是一种只能在窗口操作符的上下文中计算的函数。窗口函数提供了对数据分区内的数据进行聚合、排序和分析的功能。

常用的窗口函数,包括RowNumber、CumeDist、NTile、Rank、DenseRank和PercentRank等。这些窗口函数都继承自AggregateWindowFunction类,并实现了特定的逻辑来计算窗口函数的结果。

1.RowNumber函数

为每一行分配一个唯一的连续编号,根据窗口分区内的行的顺序进行编号。

2.CumeDist函数

计算值相对于分区中所有值的位置。它返回在分区中按顺序排列的当前行之前或与当前行相等的行数除以窗口分区中的总行数。

3.NTile函数

将窗口分区的行划分为指定数量的桶,范围从1到最多n。如果分区中的行数不能被均匀分成桶数,则余数值将按顺序分布在每个桶中,从第一个桶开始。

4.Rank函数

计算值在一组值中的排名。结果是在分区的排序中在当前行之前或与当前行相等的行数加上1。

5.DenseRank函数

计算值在一组值中的密集排名。结果是先前分配的排名值加1。与Rank不同,DenseRank不会在排名序列中产生间隙。

6.PercentRank函数

计算值在一组值中的百分比排名。结果是(rank - 1) / (n - 1),其中rank为排名值,n为窗口分区中的总行数。如果分区只包含一行,则函数返回0。

7.lag函数

返回当前行之前的offset行的值。如果当前行之前不足offset行,则返回null或指定的默认值。

8.lead函数

返回当前行之后的offset行的值。如果当前行之后不足offset行,则返回null或指定的默认值。

示例1

package org.example.spark

import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._
import org.apache.spark.sql.{
   Column, SparkSession}

object WindowMethodsExample extends App {
   
  val spark = SparkSession.builder()
    .appName("WindowMethodsExample")
    .master("local[*]")
    .getOrCreate()

  import spark.implicits._

  // 创建示例数据集
  val data = Seq(
    ("A", 10),
    ("A", 20),
    ("B", 15),
    ("B", 25),
    ("C", 30),
    ("C", 40)
  )
  val df = data.toDF("group", "value")

  // 定义窗口规范
  val windowSpec = Window.partitionBy($"group").orderBy($"value")

  // 使用 orderBy 方法指定排序列(字符串形式)
  val orderedByString = df.withColumn("ordered_by_string", row_number().over(windowSpec.orderBy("value")))
  orderedByString.show()
//  +-----+-----+-----------------+
//  |group|value|ordered_by_string|
//  +-----+-----+-----------------+
//  |    B|   15|                1|
//  |    B|   25|                2|
//  |    C|   30|                1|
//  |    C|   40|                2|
//  |    A|   10|                1|
//  |    A|   20|                2|
//  +-----+-----+-----------------+

  // 使用 orderBy 方法指定排序列(Column 对象形式)
  val orderedByColumn = df.withColumn("ordered_by_column", row_number().over(windowSpec.orderBy($"value")))
  orderedByColumn.show()
//  +-----+-----+-----------------+
//  |group|value|ordered_by_column|
//  +-----+-----+-----------------+
//  |    B|   15|                1|
//  |    B|   25|                2|
//  |    C|   30|                1|
//  |    C|   40|                2|
//  |    A|   10|                1|
//  |    A|   20|                2|
//  +-----+-----+-----------------+


  // 使用 partitionBy 方法指定分区列(字符串形式)
  val partitionedByString = df.withColumn("partitioned_by_string", sum($"value").over(windowSpec.partitionBy("group")))
  partitionedByString.show()
//  +-----+-----+---------------------+
//  |group|value|partitioned_by_string|
//  +-----+-----+---------------------+
//  |    B|   15|                   15|
//  |    B|   25|                   40|
//  |    C|   30|                   30|
//  |    C|   40|                   70|
//  |    A|   10|                   10|
//  |    A|   20|                   30|
//  +-----+-----+---------------------+

  // 使用 partitionBy 方法指定分区列(Column 对象形式)
  val partitionedByColumn = df.withColumn("partitioned_by_column", sum($"value").over(windowSpec.partitionBy($"group")))
  partitionedByColumn.show()
//
//  +-----+-----+---------------------+
//  |group|value|partitioned_by_column|
//  +-----+-----+---------------------+
//  |    B|   15|                   15|
//  |    B|   25|                   40|
//  |    C|   30|                   30|
//  |    C|   40|                   70|
//  |    A|   10|                   10|
//  |    A|   20|                   30|
//  +-----+-----+---------------------+

  // 使用 rangeBetween 方法指定窗口范围(Column 对象形式)
  val rangeBetweenColumn = df.withColumn("range_between_column", sum($"value").over(windowSpec.rangeBetween(-3, 3)))
  rangeBetweenColumn.show()

//  +-----+-----+--------------------+
//  |group|value|range_between_column|
//  +-----+-----+--------------------+
//  |    B|   15|                  15|
//  |    B|   25|                  25|
//  |    C|   30|                  30|
//  |    C|   40|                  40|
//  |    A|   10|                  10|
//  |    A|   20|                  20|
//  +-----+-----+--------------------+


  // 使用 rangeBetween 方法指定窗口范围(Long 值形式)
  val rangeBetweenLong = df.withColumn("range_between_long", sum($"value").over(windowSpec.rangeBetween(Window.unboundedPreceding, Window.currentRow)))
  rangeBetweenLong.show()
//  +-----+-----+------------------+
//  |group|value|range_between_long|
//  +-----+-----+------------------+
//  |    B|   15|                15|
//  |    B|   25|                40|
//  |    C|   30|                30|
//  |    C|   40|                70|
//  |    A|   10|                10|
//  |    A|   20|                30|
//  +-----+-----+------------------+

  // 使用 rowsBetween 方法指定窗口范围
  val rowsBetween = df.withColumn("rows_between", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.currentRow)))
  rowsBetween.show()

//  +-----+-----+------------+
//  |group|value|rows_between|
//  +-----+-----+------------+
//  |    B|   15|          15|
//  |    B|   25|          40|
//  |    C|   30|          30|
//  |    C|   40|          70|
//  |    A|   10|          10|
//  |    A|   20|          30|
//  +-----+-----+------------+
  

  // 使用 unboundedFollowing 和 unboundedPreceding 方法指定无界边界
  val unboundedFollowing = df.withColumn("unbounded_following", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing)))
  unboundedFollowing.show()
//  +-----+-----+-------------------+
//  |group|value|unbounded_following|
//  +-----+-----+-------------------+
//  |    B|   15|                 40|
//  |    B|   25|                 40|
//  |    C|   30|                 70|
//  |    C|   40|                 70|
//  |    A|   10|                 30|
//  |    A|   20|                 30|
//  +-----+-----+-------------------+
  
  val unboundedPreceding = df.withColumn("unbounded_preceding", sum($"value").over(windowSpec.rowsBetween(Window.unboundedPreceding, Window.currentRow)))
  unboundedPreceding.show()
//  +-----+-----+-------------------+
//  |group|value|unbounded_preceding|
//  +-----+-----+-------------------+
//  |    B|   15|                 15|
//  |    B|   25|                 40|
//  |    C|   30|                 30|
//  |    C|   40|                 70|
//  |    A|   10|                 10|
//  |    A|   20|                 30|
//  +-----+-----+-------------------+


  spark.stop()
}

示例2

object WindowFunctionsDemo {
   
  def main(args: Array[String]): Unit = {
   
    val spark = SparkSession.builder()
      .appName("Window Functions Demo")
      .master("local")
      .getOrCreate()

    import spark.implicits._

    // 创建示例数据
    val data = Seq(
      ("Alice", "A", 100),
      ("Bob", "B", 200),
      ("Charlie", "C", 150),
      ("Dave", "A", 300),
      ("Eve", "B", 250),
      ("Frank", "C", 400)
    )
    val df = data.toDF("Name", "Group", "Value")

    // 定义窗口规范
    val windowSpec = Window.partitionBy("Group").orderBy("Value")

    // 使用窗口函数进行分析
    val result = df.withColumn("CumeDist", functions.cume_dist().over(windowSpec))
      .withColumn("DenseRank", functions.dense_rank().over(windowSpec))
      .withColumn("LagValue", functions.lag($"Value", 1).over(windowSpec))
      .withColumn("LeadValue", functions.lead($"Value", 1).over(windowSpec))
      .withColumn("Ntile", functions.ntile(3).over(windowSpec))
      .withColumn("PercentRank", functions.percent_rank().over(windowSpec))
      .withColumn("Rank", functions.rank().over(windowSpec))
      .withColumn("RowNumber", functions.row_number().over(windowSpec))

    result.show()

//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
//    |   Name|Group|Value|CumeDist|DenseRank|LagValue|LeadValue|Ntile|PercentRank|Rank|RowNumber|
//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
//    |    Bob|    B|  200|     0.5|        1|    null|      250|    1|        0.0|   1|        1|
//    |    Eve|    B|  250|     1.0|        2|     200|     null|    2|        1.0|   2|        2|
//    |Charlie|    C|  150|     0.5|        1|    null|      400|    1|        0.0|   1|        1|
//    |  Frank|    C|  400|     1.0|        2|     150|     null|    2|        1.0|   2|        2|
//    |  Alice|    A|  100|     0.5|        1|    null|      300|    1|        0.0|   1|        1|
//    |   Dave|    A|  300|     1.0|        2|     100|     null|    2|        1.0|   2|        2|
//    +-------+-----+-----+--------+---------+--------+---------+-----+-----------+----+---------+
  }
}

中文源码

Window

DataFrame中定义窗口的实用函数Window对象包含了多个静态方法,用于构建窗口规范。

其中,orderBy方法用于指定窗口规范中的排序列。它接受一个或多个列名,并将它们转换为Column对象,然后使用functions.sort方法对这些列进行排序。最终,它返回一个Column对象,该对象可以用作窗口规范中的排序规则。

partitionBy方法用于指定窗口规范中的分区列。它接受一个或多个列名,并将它们转换为Column对象,然后使用这些列创建一个新的WindowSpec对象。这个WindowSpec对象表示窗口规范中的分区规则。

除了上述两个方法外,Window对象还提供其他用于构建窗口规范的方法,例如rangeBetweenrowsBetween等。

总结起来,Window对象是一个辅助类,用于创建窗口规范。它提供了多个静态方法,用于构建窗口规范中的排序列和分区列,并且还提供了其他方法用于定义窗口帧的范围。

/**
 * DataFrame中定义窗口的实用函数。
 *
 * {
   {
   {
 *   // 按照country分区,按照date排序,行范围在无界前至当前行之间
 *   Window.partitionBy("country").orderBy("date")
 *     .rowsBetween(Window.unboundedPreceding, Window.currentRow)
 *
 *   // 按照country分区,按照date排序,行范围在3行前至3行后之间
 *   Window.partitionBy("country").orderBy("date").rowsBetween(-3, 3)
 * }}}
 *
 * @note 当未定义排序时,默认使用无界窗口帧(rowFrame,unboundedPreceding,unboundedFollowing)。
 *       当定义了排序时,默认使用增长型窗口帧(rangeFrame,unboundedPreceding,currentRow)。
 *
 * @since 1.4.0
 */
@InterfaceStability.Stable
object Window 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

BigDataMLApplication

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值