Spark Window窗口计算原理用法示例源码分析 点击这里免费看全文
原理
Spark的窗口计算是通过以下步骤实现的:
-
数据分区:首先,数据集根据指定的分区键进行分区。每个分区中的数据将作为一个独立的窗口进行计算。
-
排序:在每个分区内,根据指定的排序规则对数据进行排序。这确保了在窗口函数应用之前,数据按照正确的顺序进行处理。
-
窗口框架定义:根据窗口规范中定义的窗口帧类型、起始边界和结束边界,确定每个数据行所属的窗口范围。
-
窗口函数计算:在每个窗口内,应用指定的窗口函数进行计算。窗口函数可以是聚合函数(如
SUM
、AVG
)或分析函数(如LEAD
、LAG
)。 -
窗口函数结果返回:计算完窗口函数后,将结果返回给调用方。通常,结果会随着输入数据的顺序进行排序,并按照指定的输出模式进行返回。
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
对象还提供其他用于构建窗口规范的方法,例如rangeBetween
、rowsBetween
等。
总结起来,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