给出如下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两次)。
我们想知道:
会话对于分析而言并不有趣,应将其忽略。 (例如,会话3仅具有“ X”和“ Y”,而没有“ A”,因此应将其忽略。)
但是,如果会话中确实包含“ A”(在任何位置),我们对此感兴趣,并想知道会话中的“ A”之一是否在第一位置。
在上面的示例中,预期输出如下:
+---------+-------------+
+ session | a was first |
+---------+-------------+
| 1 | true |
| 2 | true |
| 4 | false |
+---------+-------------+
我的问题是,在Spark中解决此问题的好方法是什么?我有一些基本的想法,可以使用窗口函数和/或groupBy
进行尝试,但是在制定详细信息时会遇到麻烦。
任何建议都会非常有帮助,谢谢!
答案 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)=1
,min(1,null)=1
,min(null,null,null,null)=null
和min(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
将其分组。对于具有A
和actionNr == 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"))