如何在pyspark中聚合连续的行

时间:2019-08-20 17:56:09

标签: pyspark pyspark-sql

我有大量的用户数据(数十亿行),在这里我需要总结每个用户在特定状态下花费的时间。

假设它是历史Web数据,我想总结每个用户在该网站上花费的时间。数据仅显示用户是否在场。

df = spark.createDataFrame([("A", 1), ("A", 2), ("A",  3),("B", 4 ),("B", 5 ),("A", 6 ),("A", 7 ),("A", 8 )], ["user","timestamp"])

+----+---------+
|user|timestamp|
+----+---------+
|   A|        1|
|   A|        2|
|   A|        3|
|   B|        4|
|   B|        5|
|   A|        6|
|   A|        7|
|   A|        8|
+----+---------+

正确的答案将是这样,因为我要对每个连续段求和。

+----+---------+
|user|   ttl   |
+----+---------+
|   A|        4|
|   B|        1|
+----+---------+

我尝试做一个max()-min()和groupby,但是导致段A为8-1并给出了错误的答案。

在sqlite中,我能够通过创建分区号然后找到差异并求和来获得答案。我用这个创建分区...

SELECT
COUNT(*) FILTER (WHERE a.user <>
  ( SELECT b.user
    FROM foobar AS b
    WHERE a.timestamp > b.timestamp
    ORDER BY b.timestamp DESC
    LIMIT 1
  ))  
    OVER (ORDER BY timestamp) c,
user,
timestamp
FROM foobar a;

这给了我...

+----+---------+---+
|user|timestamp| c |  
+----+---------+---+
|   A|        1| 1 |
|   A|        2| 1 |
|   A|        3| 1 |
|   B|        4| 2 |
|   B|        5| 2 |
|   A|        6| 3 |
|   A|        7| 3 |
|   A|        8| 3 |
+----+---------+---+

然后sql中的LAST()-FIRST()函数使其易于完成。

关于如何缩放并在pyspark中实现的任何想法?我似乎找不到足够的替代品来提供“ count(*)where(...)” sqlite

1 个答案:

答案 0 :(得分:1)

我们可以这样做:

  1. 创建数据框
from pyspark.sql.window import Window
from pyspark.sql.functions import max, min
from pyspark.sql import functions as F
df = spark.createDataFrame([("A", 1), ("A", 2), ("A",  3),("B", 4 ),("B", 5 ),("A", 6 ),("A", 7 ),("A", 8 )], ["user","timestamp"])
df.show()

+----+---------+
|user|timestamp|
+----+---------+
|   A|        1|
|   A|        2|
|   A|        3|
|   B|        4|
|   B|        5|
|   A|        6|
|   A|        7|
|   A|        8|
+----+---------+
  1. 为每行分配row_number,该行按timestamp排序。使用列dummy可以使用window函数row_number
df = df.withColumn('dummy', F.lit(1))
w1 = Window.partitionBy('dummy').orderBy('timestamp')
df = df.withColumn('row_number', F.row_number().over(w1))
df.show()

+----+---------+-----+----------+
|user|timestamp|dummy|row_number|
+----+---------+-----+----------+
|   A|        1|    1|         1|
|   A|        2|    1|         2|
|   A|        3|    1|         3|
|   B|        4|    1|         4|
|   B|        5|    1|         5|
|   A|        6|    1|         6|
|   A|        7|    1|         7|
|   A|        8|    1|         8|
+----+---------+-----+----------+

  1. 我们想在这里在每个用户组中创建一个子组。

(1)对于每个user组,计算当前行的row_number与上一行的row_number之差。因此,任何大于1的差异都表明存在一个新的连续组。结果为diff,请注意,每组的第一行的值为-1

(2)然后,我们将null分配给diff==1的每一行。结果列diff2

(3)接下来,我们使用last函数使用列diff2 == null中的最后一个非空值用diff2填充行。结果为subgroupid

这是我们要为每个用户组创建的子组。

w2 = Window.partitionBy('user').orderBy('timestamp')
df = df.withColumn('diff', df['row_number'] - F.lag('row_number').over(w2)).fillna(-1)
df = df.withColumn('diff2', F.when(df['diff']==1, None).otherwise(F.abs(df['diff'])))
df = df.withColumn('subgroupid', F.last(F.col('diff2'), True).over(w2))
df.show()

+----+---------+-----+----------+----+-----+----------+
|user|timestamp|dummy|row_number|diff|diff2|subgroupid|
+----+---------+-----+----------+----+-----+----------+
|   B|        4|    1|         4|  -1|    1|         1|
|   B|        5|    1|         5|   1| null|         1|
|   A|        1|    1|         1|  -1|    1|         1|
|   A|        2|    1|         2|   1| null|         1|
|   A|        3|    1|         3|   1| null|         1|
|   A|        6|    1|         6|   3|    3|         3|
|   A|        7|    1|         7|   1| null|         3|
|   A|        8|    1|         8|   1| null|         3|
+----+---------+-----+----------+----+-----+----------+

  1. 我们现在将usersubgroupid进行分组,以计算每个用户在每个连续时间间隔上花费的时间。

最后,我们按user分组只是为了总结每个用户所花费的总时间。

s = "(max('timestamp') - min('timestamp'))"
df = df.groupBy(['user', 'subgroupid']).agg(eval(s))
s = s.replace("'","")
df = df.groupBy('user').sum(s).select('user', F.col("sum(" + s + ")").alias('total_time'))
df.show()

+----+----------+
|user|total_time|
+----+----------+
|   B|         1|
|   A|         4|
+----+----------+