如何根据以前的连续行过滤掉行?

时间:2018-05-29 17:49:31

标签: apache-spark apache-spark-sql

我有一个要求,即数据框按col1(时间戳)排序,我需要按col2过滤。

任何行,其中col2值小于前一行的col2值,我需要过滤掉该行。结果应该是单调增加col2值。

请注意,这不仅仅是两行。

例如,假设4行的col2值为4,2,3,5。结果应为4,5,因为第2行和第3行都小于4(第一行值)。

val input = Seq(
  (1,4), (2,2), (3,3), (4,5), (5, 1), (6, 9), (7, 6)
).toDF("timestamp", "value")
scala> input.show
+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        2|    2|
|        3|    3|
|        4|    5|
|        5|    1|
|        6|    9|
|        7|    6|
+---------+-----+

val expected = Seq((1,4), (4,5), (6, 9)).toDF("timestamp", "value")
scala> expected.show
+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        4|    5|
|        6|    9|
+---------+-----+

请注意:

  • 第2行和第3行过滤掉,因为其值小于第1行中的值,即4
  • 第5行被过滤掉,因为其值小于第4行中的值,即6

一般来说,有没有办法根据一行的值与前一行中的值进行比较来过滤行?

2 个答案:

答案 0 :(得分:1)

我认为你所追求的是最大值(在running total之后)。这总是让我使用窗口聚合

// I made the input a bit more tricky
val input = Seq(
  (1,4), (2,2), (3,3), (4,5), (5, 1), (6, 9), (7, 6)
).toDF("timestamp", "value")
scala> input.show
+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        2|    2|
|        3|    3|
|        4|    5|
|        5|    1|
|        6|    9|
|        7|    6|
+---------+-----+

我的目标是以下预期结果。如果我错了,请纠正我。

val expected = Seq((1,4), (4,5), (6, 9)).toDF("timestamp", "value")
scala> expected.show
+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        4|    5|
|        6|    9|
+---------+-----+

用于“运行”问题的技巧是在定义窗口规范时使用rangeBetween

import org.apache.spark.sql.expressions.Window
val ts = Window
  .orderBy("timestamp")
  .rangeBetween(Window.unboundedPreceding, Window.currentRow)

使用窗口规范,您可以过滤掉想要从结果中删除的内容,然后就完成了。

val result = input
  .withColumn("running_max", max("value") over ts)
  .where($"running_max" === $"value")
  .select("timestamp", "value")

scala> result.show
18/05/29 22:09:18 WARN WindowExec: No Partition Defined for Window operation! Moving all data to a single partition, this can cause serious performance degradation.
+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        4|    5|
|        6|    9|
+---------+-----+

正如您所看到的那样效率不高,因为它只使用一个分区(导致单线程执行不佳,因此与在一台机器上运行实验没有多大差别)。

我认为我们可以对输入进行分区,然后部分计算运行最大值,然后将部分结果联合起来并再次运行运行最大值计算。只是一个想法,我没有尝试过自己。

答案 1 :(得分:1)

检查与运行最大值的相等性应该可以解决问题:

val input = Seq((1,4), (2,2), (3,3), (4,5), (5, 1), (6, 9), (7, 6)).toDF("timestamp", "value")

input.show()

+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        2|    2|
|        3|    3|
|        4|    5|
|        5|    1|
|        6|    9|
|        7|    6|
+---------+-----+


input
  .withColumn("max",max($"value").over(Window.orderBy($"timestamp")))
  .where($"value"===$"max").drop($"max")
  .show()

+---------+-----+
|timestamp|value|
+---------+-----+
|        1|    4|
|        4|    5|
|        6|    9|
+---------+-----+