Apache spark 如何将数组拆分为块并找到块的总和,并将输出存储为pyspark中的数组

Apache spark 如何将数组拆分为块并找到块的总和,并将输出存储为pyspark中的数组,apache-spark,pyspark,apache-spark-sql,pyspark-dataframes,Apache Spark,Pyspark,Apache Spark Sql,Pyspark Dataframes,我有一个数据框,如下所示: +-----+------------------------+ |Index| finalArray | +-----+------------------------+ |1 |[0, 2, 0, 3, 1, 4, 2, 7]| |2 |[0, 4, 4, 3, 4, 2, 2, 5]| +-----+------------------------+ 我想将数组分成2个块,然后找到每个块的总和,并将结果数组存储在finalA

我有一个数据框,如下所示:

+-----+------------------------+
|Index|   finalArray           |
+-----+------------------------+
|1    |[0, 2, 0, 3, 1, 4, 2, 7]|
|2    |[0, 4, 4, 3, 4, 2, 2, 5]|
+-----+------------------------+
我想将数组分成2个块,然后找到每个块的总和,并将结果数组存储在finalArray列中。它将如下所示:

+-----+---------------------+
|Index|    finalArray       |
+-----+---------------------+
|1    |[2, 3, 5, 9]         |
|2    |[4, 7, 6, 7]         |
+-----+---------------------+
我可以通过创建一个UDF来做到这一点,但我正在寻找一种更好、优化的方法。如果我可以使用withColumn和passing flagArray来处理它,而不必编写UDF,那就更好了

@udf(ArrayType(DoubleType()))
def aggregate(finalArray,chunkSize):
   n = int(chunkSize)
   aggsum = []
   final = [finalArray[i * n:(i + 1) * n] for i in range((len(finalArray) + n - 1) // n )]
   for item in final:
      agg = 0
      for j in item:
         agg += j
         aggsum.append(agg)
   return aggsum
我不能在UDF中使用下面的表达式,因此我使用了循环

[sum(finalArray[x:x+2]) for x in range(0, len(finalArray), chunkSize)]

对于spark 2.4+,您可以尝试+:

对于任意N的块大小,使用函数进行小计:

N = 3

sql_expr = """
    transform(
      /* create a sequence from 0 to number_of_chunks-1 */
      sequence(0,ceil(size(finalArray)/{0})-1),
      /* iterate the above sequence */
      i -> 
        /* create a sequence from 0 to chunk_size-1 
           calculate the sum of values containing every chunk_size items by their indices
         */
        aggregate(
          sequence(0,{0}-1),
          0L, 
          (acc, y) -> acc + ifnull(finalArray[i*{0}+y],0)
        )
    )
"""
df.withColumn("finalArray", expr(sql_expr.format(N))).show()                                                        
+-----+----------+
|Index|finalArray|
+-----+----------+
|    1| [2, 8, 9]|
|    2| [8, 9, 7]|
+-----+----------+

对于spark 2.4+,您可以尝试+:

对于任意N的块大小,使用函数进行小计:

N = 3

sql_expr = """
    transform(
      /* create a sequence from 0 to number_of_chunks-1 */
      sequence(0,ceil(size(finalArray)/{0})-1),
      /* iterate the above sequence */
      i -> 
        /* create a sequence from 0 to chunk_size-1 
           calculate the sum of values containing every chunk_size items by their indices
         */
        aggregate(
          sequence(0,{0}-1),
          0L, 
          (acc, y) -> acc + ifnull(finalArray[i*{0}+y],0)
        )
    )
"""
df.withColumn("finalArray", expr(sql_expr.format(N))).show()                                                        
+-----+----------+
|Index|finalArray|
+-----+----------+
|    1| [2, 8, 9]|
|    2| [8, 9, 7]|
+-----+----------+

这里是@jxc解决方案的一个稍有不同的版本,它使用带有转换和聚合函数的函数

逻辑是对于数组的每个元素,我们检查其索引是否是块大小的倍数,并使用slice获得块大小的子数组。使用聚合,我们对每个子数组的元素求和。最后,使用删除与不满足i%chunk=0的索引对应的空值


这里是@jxc解决方案的一个稍有不同的版本,它使用带有转换和聚合函数的函数

逻辑是对于数组的每个元素,我们检查其索引是否是块大小的倍数,并使用slice获得块大小的子数组。使用聚合,我们对每个子数组的元素求和。最后,使用删除与不满足i%chunk=0的索引对应的空值


是否有任何方法可以使用sequence+transform为每个块找到最大值。我可以使用udf来实现,但是因为我的数据框包含大约1200-1800万行,所以我希望尽可能避免使用udf。非常感谢您的帮助检查:sql\u expr=transformsequence0,ceilsizefinalArray/{0}-1,i->array\u maxslicefinalArray,i*{0}+1,{0}。formatNit的工作原理与charm类似。非常感谢您的提示和高效的回复。我不知道您还没有这样做。非常感谢。有没有什么方法可以让我使用sequence+transform为每个块找到最大值。我可以使用udf来实现,但是因为我的数据框包含大约1200-1800万行,所以我希望尽可能避免使用udf。非常感谢您的帮助检查:sql\u expr=transformsequence0,ceilsizefinalArray/{0}-1,i->array\u maxslicefinalArray,i*{0}+1,{0}。formatNit的工作原理与charm类似。非常感谢您的提示和高效的回复。我不知道您还没有这样做。非常感谢。如果我的数组包含十进制值,是否有办法使您提供的解决方案有效。在上面的代码中,0L用于整数,0D用于双精度,但我有精度为38,15的十进制类型的数组元素,在进行求和之后,如果可能的话,我希望值的类型和精度相同。另外,如果有一些文档可以提供更好的understanding@Saikat使用CAST0作为小数38,15作为零值而不是0升。并确保在聚合函数的第三个参数中将acc+y更改为CASTacc+y(小数点38,15),以避免类型不匹配错误。感谢@Blackishop的及时响应。谢谢,如果我的数组包含十进制值,有没有办法使您提供的解决方案起作用。在上面的代码中,0L用于整数,0D用于双精度,但我有精度为38,15的十进制类型的数组元素,在进行求和之后,如果可能的话,我希望值的类型和精度相同。另外,如果有一些文档可以提供更好的understanding@Saikat使用CAST0作为小数38,15作为零值而不是0升。并确保在聚合函数的第三个参数中将acc+y更改为CASTacc+y(小数点38,15),以避免类型不匹配错误。感谢@Blackishop的及时响应。谢谢
chunk = 2

transform_expr = f"""
filter(transform(finalArray, 
                 (x, i) -> IF (i % {chunk} = 0, 
                               aggregate(slice(finalArray, i+1, {chunk}), 0L, (acc, y) -> acc + y),
                               null
                              )
                ),
      x -> x is not null)
"""

df.withColumn("finalArray", expr(transform_expr)).show()

#+-----+------------+
#|Index|  finalArray|
#+-----+------------+
#|    1|[2, 3, 5, 9]|
#|    2|[4, 7, 6, 7]|
#+-----+------------+