【问题标题】:Apache Spark find first different preceding row in DataframeApache Spark 在 Dataframe 中找到第一个不同的前行
【发布时间】:2018-03-04 08:58:47
【问题描述】:

我有一个以下格式的 Apache Spark 数据框

| ID |  groupId  | phaseName |
|----|-----------|-----------|
| 10 | someHash1 | PhaseA    |
| 11 | someHash1 | PhaseB    |
| 12 | someHash1 | PhaseB    |
| 13 | someHash2 | PhaseX    |
| 14 | someHash2 | PhaseY    |

每一行代表一个过程中发生的一个阶段,该过程由多个这些阶段组成。 ID 列表示相的顺序,groupId 列显示哪些相属于一起。

我想向数据框添加一个新列:previousPhaseName。此列应指明同一程序的先前不同阶段。进程的第一阶段(具有最小 ID 的阶段)将 null 作为前一阶段。当一个阶段出现两次或更多次时,第二次(第三次...)出现将具有相同的 previousPhaseName 例如:

df = 
| ID |  groupId  | phaseName | prevPhaseName |
|----|-----------|-----------|---------------|
| 10 | someHash1 | PhaseA    | null          |
| 11 | someHash1 | PhaseB    | PhaseA        |
| 12 | someHash1 | PhaseB    | PhaseA        |
| 13 | someHash2 | PhaseX    | null          |
| 14 | someHash2 | PhaseY    | PhaseX        |

我不确定如何实现这一点。我的第一种方法是:

  • 创建第二个空数据框 df2
  • 对于 df 中的每一行:
    找到 groupId = row.groupId, ID
  • 将此行添加到 df2
  • 加入 df1 和 df2

使用窗口函数的部分解决方案

我用Window Functions聚合了上一个阶段的名称,该组中当前阶段以前出现的次数(不一定是一行)以及当前和上一个阶段名称是否相等的信息:

WindowSpec windowSpecPrev = Window
  .partitionBy(df.col("groupId"))
  .orderBy(df.col("ID"));
WindowSpec windowSpecCount = Window
  .partitionBy(df.col("groupId"), df.col("phaseName"))
  .orderBy(df.col("ID"))
  .rowsBetween(Long.MIN_VALUE, 0);

df
  .withColumn("prevPhase", functions.lag("phaseName", 1).over(windowSpecPrev))
  .withColumn("phaseCount", functions.count("phaseId").over(windowSpecCount))
  .withColumn("prevSame", when(col("prevPhase").equalTo(col("phaseName")),1).otherwise(0))

df = 
| ID |  groupId  | phaseName | prevPhase   | phaseCount | prevSame |
|----|-----------|-----------|-------------|------------|----------|
| 10 | someHash1 | PhaseA    | null        |  1         |  0       |
| 11 | someHash1 | PhaseB    | PhaseA      |  1         |  0       |
| 12 | someHash1 | PhaseB    | PhaseB      |  2         |  1       |
| 13 | someHash2 | PhaseX    | null        |  1         |  0       |
| 14 | someHash2 | PhaseY    | PhaseX      |  1         |  0       |

这仍然不是我想要达到的目标,但现在已经足够了

进一步的想法

为了获得前一个不同阶段的名称,我看到了三种我没有彻底调查过的可能性:

  • 实现一个自己的lag函数,它不使用偏移量,而是递归检查前一行,直到找到与给定行不同的值。 (虽然我认为在 Spark SQL 中无法使用自己的分析窗口函数)
  • 想办法根据phaseCount的值动态设置lag函数的偏移量。 (如果之前出现的相同 phaseName 没有出现在单个序列中,这可能会失败)
  • 在存储第一个给定输入的 ID 和 phaseName 的窗口上使用 UserDefinedAggregateFunction,并寻找具有不同 phaseName 的最高 ID。

【问题讨论】:

    标签: java sql apache-spark apache-spark-sql gaps-and-islands


    【解决方案1】:

    我猜你可以使用 Spark 窗口(行框)功能。检查 api 文档和以下帖子。

    https://databricks.com/blog/2015/07/15/introducing-window-functions-in-spark-sql.html

    【讨论】:

    • 感谢您的建议。我还没有解决这个问题,但它肯定把我推向了正确的方向。
    【解决方案2】:

    我能够通过以下方式解决这个问题:

    1. 获取(普通)上一阶段。
    2. 引入了一个新的 ID,用于对按顺序发生的阶段进行分组。 (借助answer)。这需要两个步骤。首先检查当前和前一个阶段名称是否相同,并相应地分配一个 groupCount 值。其次计算该值的累积和。
    3. 将顺序组第一行的前一个阶段分配给其所有成员。

    实施

    WindowSpec specGroup = Window.partitionBy(col("groupId"))  
                                 .orderBy(col("ID"));
    WindowSpec specSeqGroupId = Window.partitionBy(col("groupId")) 
                                      .orderBy(col("ID"))
                                      .rowsBetween(Long.MIN_VALUE, 0);
    WindowSpec specPrevDiff = Window.partitionBy(col("groupId"), col("seqGroupId"))
                                    .orderBy(col("ID"))
                                    .rowsBetween(Long.MIN_VALUE, 0);
    
    df.withColumn("prevPhase", coalesce(lag("phaseName", 1).over(specGroup), lit("NO_PREV"))) 
      .withColumn("seqCount", when(col("prevPhase").equalTo(col("phaseName")).or(col("prevPhase").equalTo("NO_PREV")),0).otherwise(1))
      .withColumn("seqGroupId", sum("seqCount").over(specSeqGroupId))
      .withColumn("prevDiff", first("prevPhase").over(specPrevDiff));
    

    结果

    df = 
    | ID |  groupId  | phaseName | prevPhase | seqCount | seqGroupId | prevDiff |
    |----|-----------|-----------|-----------|----------|------------|----------|
    | 10 | someHash1 | PhaseA    | NO_PREV   |  0       |  0         | NO_PREV  |
    | 11 | someHash1 | PhaseB    | PhaseA    |  1       |  1         | PhaseA   |
    | 12 | someHash1 | PhaseB    | PhaseA    |  0       |  1         | PhaseA   |
    | 13 | someHash2 | PhaseX    | NO_PREV   |  0       |  0         | NO_PREV  |
    | 14 | someHash2 | PhaseY    | PhaseX    |  1       |  1         | PhaseX   |
    

    感谢任何建议,特别是在这些操作的效率方面。

    【讨论】:

    • 我找不到其他方法来解决这个问题,您有没有找到更好的方法?顺便说一句,非常聪明地获得了变化发生时的第一个(prev..)
    • 不幸的是我没有深入研究它,不过它对你有帮助很酷
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-10-24
    • 1970-01-01
    • 1970-01-01
    • 2017-06-02
    • 1970-01-01
    • 2019-07-27
    • 1970-01-01
    相关资源
    最近更新 更多