Scala Spark:基于前几行中的开始时间和持续时间值,以30分钟为间隔计算事件结束时间
我有一个带有event_time字段的文件,每个记录每30分钟生成一次,并指示事件持续了多少秒。 例如: 我需要将连续事件转换为只有持续时间的事件。输出文件应如下所示:Scala Spark:基于前几行中的开始时间和持续时间值,以30分钟为间隔计算事件结束时间,scala,apache-spark,dataframe,hadoop,apache-spark-sql,Scala,Apache Spark,Dataframe,Hadoop,Apache Spark Sql,我有一个带有event_time字段的文件,每个记录每30分钟生成一次,并指示事件持续了多少秒。 例如: 我需要将连续事件转换为只有持续时间的事件。输出文件应如下所示: Event_time_start | event_time_end | event_duration_seconds 09:00 | 11:00 | 5300 12:00 | 12:30 | 1000 13:00 | 13:30
Event_time_start | event_time_end | event_duration_seconds
09:00 | 11:00 | 5300
12:00 | 12:30 | 1000
13:00 | 13:30 | 1000
在Scala Spark中是否有方法将数据帧记录与下一个数据帧记录进行比较
我尝试使用
foreach
循环,但这不是一个好的选择,因为它需要处理大量的数据这不是一个小问题,但这里有一个解决方案,其步骤如下:
java.time
API计算下一个最近的30分钟事件结束时间event\ts\u end
lag
when/other
生成带有null
值的列event\u tsu\u start
last(event\u ts\u start,ignoreNulls=true)
将null
s与上次event\u start
值回填事件开始
对数据进行分组,以聚合事件持续时间
和事件结束
import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
import spark.implicits._
val df = Seq(
(101, "2019-04-01 09:00", 800),
(101, "2019-04-01 09:30", 1800),
(101, "2019-04-01 10:00", 2700),
(101, "2019-04-01 12:00", 1000),
(101, "2019-04-01 13:00", 1000),
(220, "2019-04-02 10:00", 1500),
(220, "2019-04-02 10:30", 2400)
).toDF("event_id", "event_time", "event_duration")
请注意,示例数据集已略微泛化为包含多个事件,并使事件时间包含date
info以涵盖跨越给定日期的事件的情况
步骤1
:
import java.sql.Timestamp
def get_next_closest(seconds: Int) = udf{ (ts: Timestamp, duration: Int) =>
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
val iter = Iterator.iterate(ts.toLocalDateTime)(_.plusSeconds(seconds)).
dropWhile(_.isBefore(ts.toLocalDateTime.plusSeconds(duration)))
iter.next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}
val winSpec = Window.partitionBy("event_id").orderBy("event_time")
val seconds = 30 * 60
df.
withColumn("event_ts", to_timestamp($"event_time", "yyyy-MM-dd HH:mm")).
withColumn("event_ts_end", get_next_closest(seconds)($"event_ts", $"event_duration")).
withColumn("prev_event_ts", lag($"event_ts", 1).over(winSpec)).
withColumn("event_ts_start", when($"prev_event_ts".isNull ||
unix_timestamp($"event_ts") - unix_timestamp($"prev_event_ts") =!= seconds, $"event_ts"
)).
withColumn("event_ts_start", last($"event_ts_start", ignoreNulls=true).over(winSpec)).
groupBy($"event_id", $"event_ts_start").agg(
sum($"event_duration").as("event_duration"), max($"event_ts_end").as("event_ts_end")
).show
// +--------+-------------------+--------------+-------------------+
// |event_id| event_ts_start|event_duration| event_ts_end|
// +--------+-------------------+--------------+-------------------+
// | 101|2019-04-01 09:00:00| 5300|2019-04-01 11:00:00|
// | 101|2019-04-01 12:00:00| 1000|2019-04-01 12:30:00|
// | 101|2019-04-01 13:00:00| 1000|2019-04-01 13:30:00|
// | 220|2019-04-02 10:00:00| 3900|2019-04-02 11:30:00|
// +--------+-------------------+--------------+-------------------+
步骤2-5
:
import java.sql.Timestamp
def get_next_closest(seconds: Int) = udf{ (ts: Timestamp, duration: Int) =>
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
val iter = Iterator.iterate(ts.toLocalDateTime)(_.plusSeconds(seconds)).
dropWhile(_.isBefore(ts.toLocalDateTime.plusSeconds(duration)))
iter.next.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
}
val winSpec = Window.partitionBy("event_id").orderBy("event_time")
val seconds = 30 * 60
df.
withColumn("event_ts", to_timestamp($"event_time", "yyyy-MM-dd HH:mm")).
withColumn("event_ts_end", get_next_closest(seconds)($"event_ts", $"event_duration")).
withColumn("prev_event_ts", lag($"event_ts", 1).over(winSpec)).
withColumn("event_ts_start", when($"prev_event_ts".isNull ||
unix_timestamp($"event_ts") - unix_timestamp($"prev_event_ts") =!= seconds, $"event_ts"
)).
withColumn("event_ts_start", last($"event_ts_start", ignoreNulls=true).over(winSpec)).
groupBy($"event_id", $"event_ts_start").agg(
sum($"event_duration").as("event_duration"), max($"event_ts_end").as("event_ts_end")
).show
// +--------+-------------------+--------------+-------------------+
// |event_id| event_ts_start|event_duration| event_ts_end|
// +--------+-------------------+--------------+-------------------+
// | 101|2019-04-01 09:00:00| 5300|2019-04-01 11:00:00|
// | 101|2019-04-01 12:00:00| 1000|2019-04-01 12:30:00|
// | 101|2019-04-01 13:00:00| 1000|2019-04-01 13:30:00|
// | 220|2019-04-02 10:00:00| 3900|2019-04-02 11:30:00|
// +--------+-------------------+--------------+-------------------+
我想您正在寻找scala中的窗口函数:
event\u time\u end
是如何计算的?谢谢您的帮助,我正在尝试这个解决方案,但有一些问题,我想这是因为我们的scala版本是1。6@mabe在Spark 2.2之前,到_时间戳
不可用。您可以将替换为\u时间戳($“event\u time”,“yyyy-MM-dd HH:MM”)
替换为\u unixtime(unix\u时间戳($“event\u time”,“yyyy-MM-dd HH:MM”)。嘿@Leo,我尝试了您的解决方案,将其调整为spark 1.6.0,我就快到了!!但是最后一个问题是函数last。我正在使用的sql库不支持ignoreNulls参数,因此在尝试设置事件开始值时无法筛选空值。你能帮我选择这个步骤吗。我想这是目前为止我唯一缺少的东西。非常感谢。你好我终于可以解决这个问题了。我不必编写UDF来计算结束时间,我可以在配置单元上下文中的SQLText中运行日期计算。最困难的部分是聚合记录,但要识别连续块。感谢您的帮助。这是代码`val newDF:DataFrame=records.withColumn(“dtu事件”start),when((lag(records(“dtu real”).over(winSpec”).isNull)或(lag(records(“dt\u real”).over(winSpec)!==records(“dt\u prv\u calc”)、records(“dt\u real”))。withColumn(“dt_event_start”,org.apache.spark.sql.functions.max(“dt_event_start”).over(winSpec))。groupBy(“dia”、“eutrancellfdd”、“dt\U事件开始”).agg(“dt\U nxt\U计算”->“最大”、“注册”->“总和”)`