Scala 查找每5小时间隔的最小值
我的df 在这种情况下,第一个间隔从1小时开始。所以每一行直到6(1+5)都是这个间隔的一部分 但是8-1>5,所以第二个间隔从8开始,上升到13 然后我看到14-8>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),
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 |
+---+----+--------+