将数据帧中带有时间戳的多行事件转换为具有开始和结束日期时间的单行

时间:2018-09-25 06:06:30

标签: python apache-spark pyspark

我有一个设备的行,并且我希望将依次发生的所有相同事件分组。

我也想用pyspark做到这一点

因此,给出以下内容:

+--------------------+-------+
|      datetime      | event |
+--------------------+-------+
| 12-02-18T08:20:00  |     1 |
| 12-02-18T08:25:00  |     1 |
| 12-02-18T08:30:00  |     1 |
| 12-02-18T09:00:00  |     2 |
| 12-02-18T09:05:00  |     2 |
| 12-02-18T09:10:00  |     1 |
| 12-02-18T09:15:00  |     1 |
+--------------------+-------+

我想得出以下结论:

+-------------------+-------------------+-------+
|    start_time     |     end_time      | event |
+-------------------+-------------------+-------+
| 12-02-18T08:20:00 | 12-02-18T09:00:00 |     1 |
| 12-02-18T09:00:00 | 12-02-18T09:10:00 |     2 |
| 12-02-18T09:10:00 | null              |     1 |
+-------------------+-------------------+-------+

不会有重叠事件,因此不需要考虑。我曾教过使用UDF进行此操作,但想知道是否有人知道一种更优雅/更有效的方法。

2 个答案:

答案 0 :(得分:2)

使用Florian提供的方法(窗口函数),可以通过在事件中获取具有已更改事件的行,然后在Scala上获取下一个更改的日期来实现:

val df = List(
  ("12-02-18T08:20:00", 1),
  ("12-02-18T08:25:00", 1),
  ("12-02-18T08:30:00", 1),
  ("12-02-18T09:00:00", 2),
  ("12-02-18T09:05:00", 2),
  ("12-02-18T09:10:00", 1),
  ("12-02-18T09:15:00", 1)
).toDF("datetime", "event")
df.show(false)

val w = Window.orderBy("datetime")
val changedRowsOnlyDF = df.withColumn("changed", $"event" =!= lag($"event", 1, 0).over(w))
  .where($"changed")

val result = changedRowsOnlyDF
  .withColumn("end_time", lead($"datetime", 1).over(w))
  .drop("changed")
  .withColumnRenamed("datetime", "start_time")
result.show(false)

输出:

+-----------------+-----+-----------------+
|start_time       |event|end_time         |
+-----------------+-----+-----------------+
|12-02-18T08:20:00|1    |12-02-18T09:00:00|
|12-02-18T09:00:00|2    |12-02-18T09:10:00|
|12-02-18T09:10:00|1    |null             |
+-----------------+-----+-----------------+

免责声明:这种方法可用于少量数据,并通过消息通知Spark:

  

WARN org.apache.spark.sql.execution.window.WindowExec:未为窗口操作定义分区!将所有数据移至单个分区,这可能会导致严重的性能下降。

答案 1 :(得分:1)

您可以使用UDF而不是使用Window来查找事件的过渡,并从中创建一个新列以用于分组(请参见this answer)。然后,我们可以汇总找到每个事件的最小和最大时间。下面给出一个可行的示例,希望对您有所帮助!

import pyspark.sql.functions as F
from pyspark.sql import Window

# SAMPLE DATA
df = spark.sparkContext.parallelize([
    ('2018-07-20T01:00:00.000Z','1'),
    ('2018-07-20T02:00:00.000Z','1'),
    ('2018-07-20T03:00:00.000Z','2'),
    ('2018-07-20T04:00:00.000Z','2'),
    ('2018-07-20T05:00:00.000Z','1')
]).toDF(("datetime","event" ))

# CALCULATE START AND END TIMES
w = Window.orderBy('datetime')
df_result = (df
    .withColumn("changed", (F.col('event') != F.lag('event', 1, 0).over(w)).cast('int'))
    .withColumn("group_id", F.sum("changed").over(w)).drop("changed")
    .groupBy('group_id','event').agg(
    F.min('datetime').alias('start'),
    F.max('datetime').alias('end'))
    .drop('group_id'))

df_result.show()

输出:

+-----+--------------------+--------------------+
|event|               start|                 end|
+-----+--------------------+--------------------+
|    1|2018-07-20T01:00:...|2018-07-20T02:00:...|
|    2|2018-07-20T03:00:...|2018-07-20T04:00:...|
|    1|2018-07-20T05:00:...|2018-07-20T05:00:...|
+-----+--------------------+--------------------+