Pyspark数据帧的GroupBy替代方案?
我有这样一个数据集:Pyspark数据帧的GroupBy替代方案?,pyspark,group-by,pyspark-sql,Pyspark,Group By,Pyspark Sql,我有这样一个数据集: timestamp vars 2 [1,2] 2 [1,2] 3 [1,2,3] 3 [1,2] 我想要一个这样的数据帧。基本上,上述数据帧中的每个值都是一个索引,该值的频率就是该索引处的值。这个计算是在每个唯一的时间戳上完成的 timestamp vars 2 [0, 2, 2] 3 [0,2,2,1] 现
timestamp vars
2 [1,2]
2 [1,2]
3 [1,2,3]
3 [1,2]
我想要一个这样的数据帧。基本上,上述数据帧中的每个值都是一个索引,该值的频率就是该索引处的值。这个计算是在每个唯一的时间戳上完成的
timestamp vars
2 [0, 2, 2]
3 [0,2,2,1]
现在,我正在按时间戳分组,并对变量进行聚合/展平(得到类似(1,2,1,2表示时间戳2或1,2,3,1,2表示时间戳3)的内容,然后我有一个使用集合的udf。Counter to获得键->值dict。然后我将此dict转换为我想要的格式
groupBy/agg可以任意大(数组大小可以达到数百万),这对于窗口函数来说似乎是一个很好的用例,但我不确定如何将其组合在一起
认为还值得一提的是,我尝试过重新分区、转换为RDD和使用groupByKey。两者都非常慢(>24小时)在大型数据集上。编辑:如评论中所述,原始方法的问题可能来自使用过滤器或聚合函数的
count
,这会触发不必要的数据扫描。下面我们分解数组并在创建最终数组列之前进行聚合(计数):
from pyspark.sql.functions import collect_list, struct
df = spark.createDataFrame([(2,[1,2]), (2,[1,2]), (3,[1,2,3]), (3,[1,2])],['timestamp', 'vars'])
df.selectExpr("timestamp", "explode(vars) as var") \
.groupby('timestamp','var') \
.count() \
.groupby("timestamp") \
.agg(collect_list(struct("var","count")).alias("data")) \
.selectExpr(
"timestamp",
"transform(data, x -> x.var) as indices",
"transform(data, x -> x.count) as values"
).selectExpr(
"timestamp",
"transform(sequence(0, array_max(indices)), i -> IFNULL(values[array_position(indices,i)-1],0)) as new_vars"
).show(truncate=False)
+---------+------------+
|timestamp|new_vars |
+---------+------------+
|3 |[0, 2, 2, 1]|
|2 |[0, 2, 2] |
+---------+------------+
其中:
(1) 我们分解数组并对每个时间戳+var
(2) groupbytimestamp
并创建一个包含两个字段的结构数组var
和count
(3) 将结构数组转换为两个数组:索引和值(类似于我们定义的SparseVector)
(4) 转换序列序列(0,数组_max(索引))
,对于序列中的每个i,使用在索引数组中查找i
的索引,然后从相同位置的值数组中检索值,如下所示:
IFNULL(values[array_position(indices,i)-1],0)
请注意函数array_position使用基于1的索引,而数组索引使用基于0的索引,因此在上面的表达式中有一个-1
旧方法:
(1) 使用变换+过滤器/大小
from pyspark.sql.functions import flatten, collect_list
df.groupby('timestamp').agg(flatten(collect_list('vars')).alias('data')) \
.selectExpr(
"timestamp",
"transform(sequence(0, array_max(data)), x -> size(filter(data, y -> y = x))) as vars"
).show(truncate=False)
+---------+------------+
|timestamp|vars |
+---------+------------+
|3 |[0, 2, 2, 1]|
|2 |[0, 2, 2] |
+---------+------------+
(2) 使用功能:
df.groupby('timestamp').agg(flatten(collect_list('vars')).alias('data')) \
.selectExpr("timestamp", """
aggregate(
data,
/* use an array as zero_value, size = array_max(data))+1 and all values are zero */
array_repeat(0, int(array_max(data))+1),
/* increment the ith value of the Array by 1 if i == y */
(acc, y) -> transform(acc, (x,i) -> IF(i=y, x+1, x))
) as vars
""").show(truncate=False)
编辑:如评论中所述,原始方法的问题可能来自使用过滤器或聚合函数(触发不必要的数据扫描)的count
。下面我们分解数组并在创建最终数组列之前进行聚合(计数):
from pyspark.sql.functions import collect_list, struct
df = spark.createDataFrame([(2,[1,2]), (2,[1,2]), (3,[1,2,3]), (3,[1,2])],['timestamp', 'vars'])
df.selectExpr("timestamp", "explode(vars) as var") \
.groupby('timestamp','var') \
.count() \
.groupby("timestamp") \
.agg(collect_list(struct("var","count")).alias("data")) \
.selectExpr(
"timestamp",
"transform(data, x -> x.var) as indices",
"transform(data, x -> x.count) as values"
).selectExpr(
"timestamp",
"transform(sequence(0, array_max(indices)), i -> IFNULL(values[array_position(indices,i)-1],0)) as new_vars"
).show(truncate=False)
+---------+------------+
|timestamp|new_vars |
+---------+------------+
|3 |[0, 2, 2, 1]|
|2 |[0, 2, 2] |
+---------+------------+
其中:
(1) 我们分解数组并对每个时间戳+var
(2) groupbytimestamp
并创建一个包含两个字段的结构数组var
和count
(3) 将结构数组转换为两个数组:索引和值(类似于我们定义的SparseVector)
(4) 转换序列序列(0,数组_max(索引))
,对于序列中的每个i,使用在索引数组中查找i
的索引,然后从相同位置的值数组中检索值,如下所示:
IFNULL(values[array_position(indices,i)-1],0)
请注意函数array_position使用基于1的索引,而数组索引使用基于0的索引,因此在上面的表达式中有一个-1
旧方法:
(1) 使用变换+过滤器/大小
from pyspark.sql.functions import flatten, collect_list
df.groupby('timestamp').agg(flatten(collect_list('vars')).alias('data')) \
.selectExpr(
"timestamp",
"transform(sequence(0, array_max(data)), x -> size(filter(data, y -> y = x))) as vars"
).show(truncate=False)
+---------+------------+
|timestamp|vars |
+---------+------------+
|3 |[0, 2, 2, 1]|
|2 |[0, 2, 2] |
+---------+------------+
(2) 使用功能:
df.groupby('timestamp').agg(flatten(collect_list('vars')).alias('data')) \
.selectExpr("timestamp", """
aggregate(
data,
/* use an array as zero_value, size = array_max(data))+1 and all values are zero */
array_repeat(0, int(array_max(data))+1),
/* increment the ith value of the Array by 1 if i == y */
(acc, y) -> transform(acc, (x,i) -> IF(i=y, x+1, x))
) as vars
""").show(truncate=False)
对于索引2,它是如何从1,2,1,2发展到[0,2,2]?带有partitionby子句的窗口应该比groupby执行得更好,如果您不使用udf,而是使用spark内置函数来实现我们的目标,[1,2,1,2]中有21和22。因此在索引1中,我将2(频率)放在在索引2,我把2放进去。因为没有0,索引0仍然是这样。因此,[0,2,2]。我不知道如何使用partitionBy和window从[1,2]和[1,2]到[1,2,1,2]。尝试过,但它只对和有效。对于索引2,它如何从1,2,1,2到[0,2,2]?带有partitionby子句的窗口应该比groupby执行得更好,如果您不使用udf,而是使用spark内置函数来实现您的目标,那么性能可能会更好[1,2,1,2]中有2个1和2个2。因此在索引1中,我放2(频率),在索引2中,我放2。因为没有0,索引0仍然是这样。因此,[0,2,2].我不知道如何从[1,2]和[1,2]到[1,2,1,2]使用partitionBy和window。尝试过,但它只适用于求和。这是一个让我大开眼界的方法,让我知道如何在转换中使用过滤器。非常好的解决方案谢谢你!它有帮助,但仍然会在聚合中爆炸,因为列表的长度可以让我以百万计。有没有办法通过时间戳引入窗口函数/分区?顺便说一句。数组_max(数据)的最大值是多少?您提到的数组中的数百万项是在聚合之前还是聚合之后?数组_max(数据)的值可以达到一百万。转换表达式确实是大型数据集的瓶颈。需要尝试并优化它。@tanyabrown,我看到了现有方法的问题,如果M是数组中所有项的数量,N是数组的最大值(数据),那么每个时间戳要扫描/比较的数据将是O(M*N),这对于大M和N来说效率很低。最好先分解数组进行聚合,然后创建数组。这可能是O(N)对于每一行。我回家后会检查此方法。顺便说一句。是否可以为您的任务创建一个固定大小的SparseVector列,而不是可变大小的ArrayType列?这让我大开眼界,了解如何在转换中使用过滤器。很好的解决方案谢谢您!这很有帮助,但仍然会在聚合时爆炸因为该列表的长度可以以百万计。有没有办法通过时间戳引入窗口函数/分区?顺便问一下。数组_max(数据)的最大值是多少?您提到的数组中的数百万项是在聚合之前还是聚合之后?数组_max(数据)的值转换表达式是lar上的瓶颈