Scala 查找每5小时间隔的最小值

Scala 查找每5小时间隔的最小值,scala,apache-spark,apache-spark-sql,Scala,Apache Spark,Apache Spark Sql,我的df 在这种情况下,第一个间隔从1小时开始。所以每一行直到6(1+5)都是这个间隔的一部分 但是8-1>5,所以第二个间隔从8开始,上升到13 然后我看到14-8>5,第三个开始,依此类推 期望的结果 val df = Seq( ("1", 1), ("1", 1), ("1", 2), ("1", 4), ("1", 5), ("1", 6),

我的df

在这种情况下,第一个间隔从1小时开始。所以每一行直到6(1+5)都是这个间隔的一部分

但是8-1>5,所以第二个间隔从8开始,上升到13

然后我看到14-8>5,第三个开始,依此类推

期望的结果

val df = Seq(
  ("1", 1),
  ("1", 1),
  ("1", 2),
  ("1", 4),
  ("1", 5),
  ("1", 6),
  ("1", 8),
  ("1", 12),
  ("1", 12),
  ("1", 13),
  ("1", 14),
  ("1", 15),
  ("1", 16)
).toDF("id", "time")
我试着用min函数,但不知道如何解释这种情况

+---+----+--------+
|id |time|min_time|
+---+----+--------+
|1  |1   |1       |
|1  |1   |1       |
|1  |2   |1       |
|1  |4   |1       |
|1  |5   |1       |
|1  |6   |1       |
|1  |8   |8       |
|1  |12  |8       |
|1  |12  |8       |
|1  |13  |8       |
|1  |14  |14      |
|1  |15  |14      |
|1  |16  |14      |
+---+----+--------+
输入数据

+---+----+--------+
|id |time|min_time|
+---+----+--------+
|1  |1   |1       |
|1  |1   |1       |
|1  |2   |1       |
|1  |4   |1       |
|1  |5   |1       |
|1  |6   |1       |
|1  |8   |8       |
|1  |12  |12      |
|1  |12  |12      |
|1  |13  |13      |
|1  |14  |14      |
|1  |15  |15      |
|1  |16  |16      |
+---+----+--------+
必须对数据进行排序,否则结果将不正确

val df = Seq(
  ("1", 1),
  ("1", 1),
  ("1", 2),
  ("1", 4),
  ("1", 5),
  ("1", 6),
  ("1", 8),
  ("1", 12),
  ("1", 12),
  ("1", 13),
  ("1", 14),
  ("1", 15),
  ("1", 16),
  ("2", 4),
  ("2", 8),
  ("2", 10),
  ("2", 11),
  ("2", 11),
  ("2", 12),
  ("2", 13),
  ("2", 20)
).toDF("id", "time")
之后,我创建了一个case类
var min
用于保存最小值,仅在满足条件时更新

val window = Window.partitionBy($"id").orderBy($"time")
df
  .withColumn("min", row_number().over(window))
  .as[Row]
  .map(_.getMin)
  .show(40)
结果

case class Row(id:String, time:Int, min:Int){
  def getMin: Row = {
    if(time - Row.min > 5 || Row.min == -99 || min == 1){
      Row.min = time
    }
    Row(id, time, Row.min)
  }
}

object Row{
  var min: Int = -99
}

您可以继续您的第一个想法,即在应用程序上使用聚合函数。但是,您可以定义自己的(UDAF),而不是使用Spark已经定义的函数的某些组合

分析 正如您正确地假设的,我们应该在窗口上使用一种min函数。在此窗口的行上,我们希望实现以下规则:

给定按
time
排序的行,如果前一行的
min\u time
与当前行的
time
之间的差值大于5,则当前行的
min\u time
应为当前行的
time
,否则,当前行的
min\u时间
应该是前一行的
min\u时间

但是,使用Spark提供的聚合函数,我们无法访问前一行的
min\u time
。它存在一个
lag
函数,但使用该函数,我们只能访问前几行中已经存在的值。由于上一行的
min\u time
不存在,我们无法访问它

因此,我们必须定义自己的聚合函数

解决方案 定义定制的聚合函数 为了定义聚合函数,我们需要创建一个扩展抽象类的类。以下是完整的实施:

import org.apache.spark.sql.expressions.Aggregator
导入org.apache.spark.sql.{Encoder,Encoders}
对象MinByInterval扩展聚合器[整数,整数,整数]{
def zero:Integer=null
def REDUCT(缓冲区:整数,时间:整数):整数={
如果(buffer==null | | time-buffer>5)time-else-buffer
}
def合并(b1:整数,b2:整数):整数={
抛出新的NotImplementedError(“不应用作常规聚合”)
}
def完成(减少:整数):整数=减少
def bufferEncoder:Encoder[Integer]=Encoders.INT
def outputEncoder:Encoder[Integer]=Encoders.INT
}
我们使用
Integer
作为输入、缓冲和输出类型。我们选择了
Integer
,因为它是一个可为空的
Int
。我们本可以使用
Option[Int]
,但是Spark的文档建议不要在聚合器方法中重新创建对象以解决性能问题,如果我们使用像
Option
这样的复杂类型会发生什么

我们实现了
reduce
方法中分析部分定义的规则:

def reduce(缓冲区:整数,时间:整数):整数={
如果(buffer==null | | time-buffer>5)time-else-buffer
}
此处,
time
是当前行的列time中的值,
buffer
是先前计算的值,因此对应于前一行的列min\u time。在我们的窗口中,我们按
时间
对行进行排序,
时间
总是大于
缓冲区
。空缓冲区情况仅在处理第一行时发生

在窗口上使用聚合函数时,不使用方法
merge
,因此我们不实现它

finish
方法是identity方法,因为我们不需要对聚合值执行最终计算,输出和缓冲区编码器是
编码器。INT

调用用户定义的聚合函数 现在,我们可以使用以下代码调用用户定义的聚合函数:

import org.apache.spark.sql.expressions.Window
导入org.apache.spark.sql.functions.{col,udaf}
val minTime=udaf(最小间隔)
val window=window.partitionBy(“id”).orderBy(“时间”)
df.带列(“最小时间”,最小时间(列(“时间”))。超过(窗口))
跑 给定问题中的输入数据帧,我们得到:

+---+----+---+
| id|time|min|
+---+----+---+
|  1|   1|  1|
|  1|   1|  1|
|  1|   2|  1|
|  1|   4|  1|
|  1|   5|  1|
|  1|   6|  1|
|  1|   8|  8|
|  1|  12|  8|
|  1|  12|  8|
|  1|  13|  8|
|  1|  14| 14|
|  1|  15| 14|
|  1|  16| 14|
|  2|   4|  4|
|  2|   8|  4|
|  2|  10| 10|
|  2|  11| 10|
|  2|  11| 10|
|  2|  12| 10|
|  2|  13| 10|
|  2|  20| 20|
+---+----+---+

这是否适用于源数据帧中的不同ID?我不确定。我甚至感到惊讶的是,这个解决方案实际上是有效的。如果您在集群上工作,我认为不能保证“映射”将按顺序执行。@Simon修改了它,使它可以与不同的id一起工作。我正在本地运行所有测试,所以我还不知道它是否能在集群上正确运行。欢迎任何替代方案。
+---+----+---+
| id|time|min|
+---+----+---+
|  1|   1|  1|
|  1|   1|  1|
|  1|   2|  1|
|  1|   4|  1|
|  1|   5|  1|
|  1|   6|  1|
|  1|   8|  8|
|  1|  12|  8|
|  1|  12|  8|
|  1|  13|  8|
|  1|  14| 14|
|  1|  15| 14|
|  1|  16| 14|
|  2|   4|  4|
|  2|   8|  4|
|  2|  10| 10|
|  2|  11| 10|
|  2|  11| 10|
|  2|  12| 10|
|  2|  13| 10|
|  2|  20| 20|
+---+----+---+
+---+----+--------+
|id |time|min_time|
+---+----+--------+
|1  |1   |1       |
|1  |1   |1       |
|1  |2   |1       |
|1  |4   |1       |
|1  |5   |1       |
|1  |6   |1       |
|1  |8   |8       |
|1  |12  |8       |
|1  |12  |8       |
|1  |13  |8       |
|1  |14  |14      |
|1  |15  |14      |
|1  |16  |14      |
+---+----+--------+