确定动作A是会话中第一个动作的频率

时间:2019-06-09 22:48:01

标签: apache-spark apache-spark-sql

给出如下Spark数据框:

val data = Seq(
    (1, 1, "A"),
    (1, 2, "A"),
    (1, 3, "X"),
    (1, 4, "A"),
    (2, 1, "A"),
    (2, 2, "X"),
    (2, 3, "Y"),
    (3, 1, "X"),
    (3, 2, "Y"),
    (4, 1, "X"),
    (4, 2, "A"),
    (4, 3, "Y")
  )

val df = data.toDF("session", "actionNr", "action")
df =
+---------+----------+--------+
+ session | actionNr | action |
+---------+----------+--------+
| 1       | 1        | A      |
| 1       | 2        | A      |
| 1       | 3        | X      |
| 1       | 4        | A      |
| 2       | 1        | A      |
| 2       | 2        | X      |
| 2       | 3        | Y      |
| 3       | 1        | X      |
| 3       | 2        | Y      |
| 4       | 1        | X      |
| 4       | 2        | A      |
| 4       | 3        | Y      |
+---------+----------+--------+

在每个session中,可以记录一个或多个actions(例如A,X,Y和Z)。每个会话可以多次执行相同的操作(例如,会话1的操作A两次)。

我们想知道:

  • 操作“ A”在会话中的第一个操作多久一次(也就是说,它具有actionNr == 1)?
  • 会话中的第一个动作(“ A”出现在后面的位置)是“ A”以外的其他动作吗?
不包含 any 动作“ A”的

会话对于分析而言并不有趣,应将其忽略。 (例如,会话3仅具有“ X”和“ Y”,而没有“ A”,因此应将其忽略。)

但是,如果会话中确实包含“ A”(在任何位置),我们对此感兴趣,并想知道会话中的“ A”之一是否在第一位置。

在上面的示例中,预期输出如下:

+---------+-------------+
+ session | a was first | 
+---------+-------------+
| 1       | true        |
| 2       | true        |
| 4       | false       |
+---------+-------------+

我的问题是,在Spark中解决此问题的好方法是什么?我有一些基本的想法,可以使用窗口函数和/或groupBy进行尝试,但是在制定详细信息时会遇到麻烦。

任何建议都会非常有帮助,谢谢!

2 个答案:

答案 0 :(得分:3)

您可以利用以下事实:(大多数)SQL聚合会跳过空值。如果我们有这样的事情

+---------+----------+--------+
| session | actionNr | action |
+---------+----------+--------+
| 1       | 1        | A      |
| 1       | 2        | A      |
| 1       | null     | X      |
| 1       | 4        | A      |
| 2       | 1        | A      |
| 2       | null     | X      |
| 2       | null     | Y      |
| 3       | null     | X      |
| 3       | null     | Y      |
| 4       | null     | X      |
| 4       | 2        | A      |
| 4       | null     | Y      |
+---------+----------+--------+

问题开始变得容易得多。如果我们将session分组并取最小的actionNr,则会得到min(1,2,null,4)=1min(1,null)=1min(null,null,null,null)=nullmin(null,2,null)=2

df.groupBy("session")
  .agg(min(when($"action" === "A", $"actionNr")) as "first_a")
  .show()
+-------+-------+
|session|first_a|
+-------+-------+
|      1|      1|
|      3|   null|
|      4|      2|
|      2|      1|
+-------+-------+

可以将其抛光为所需的格式:

df.groupBy("session")
  .agg(min(when($"action" === "A", $"actionNr")) as "first_a")
  .filter($"first_a".isNotNull)
  .select($"session", when($"first_a" === 1, true).otherwise(false) as "a was first")
  .show()
+-------+-----------+
|session|a was first|
+-------+-----------+
|      1|       true|
|      4|      false|
|      2|       true|
+-------+-----------+

答案 1 :(得分:2)

这里仅关注action等于A的行,因此请首先将其过滤掉。然后使用1创建一个新列,其中actionNr等于1,否则等于0,然后按session将其分组。对于具有AactionNr == 1的会话,该字段将为1,而所有其他行均为0。最后,转换为布尔值。

这可以如下进行:

df.filter($"action" === "A")
  .withColumn("first", when($"actionNr" === 1, 1).otherwise(0))
  .groupBy("session").agg(sum("first").as("first"))
  .select($"session", when($"first" === 1, true).otherwise(false).as("a was first"))