【问题标题】:How to split an array into chunks and find the sum of the chunks and store the output as an array in pyspark如何将数组拆分为块并找到块的总和并将输出存储为pyspark中的数组
【发布时间】:2023-03-22 02:24:01
【问题描述】:

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

+-----+------------------------+
|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 并传递 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)]

【问题讨论】:

    标签: apache-spark pyspark apache-spark-sql pyspark-dataframes


    【解决方案1】:

    spark 2.4+可以试试sequence + transform:

    from pyspark.sql.function import expr
    
    df = spark.createDataFrame([
      (1, [0, 2, 0, 3, 1, 4, 2, 7]),
      (2, [0, 4, 4, 3, 4, 2, 2, 5])
    ], ["Index", "finalArray"])
    
    df.withColumn("finalArray", expr("""
        transform(
          sequence(0,ceil(size(finalArray)/2)-1), 
          i -> finalArray[2*i] + ifnull(finalArray[2*i+1],0))
     """)).show(truncate=False)
    +-----+------------+
    |Index|finalArray  |
    +-----+------------+
    |1    |[2, 3, 5, 9]|
    |2    |[4, 7, 6, 7]|
    +-----+------------+
    

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

    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]|
    +-----+----------+
    

    【讨论】:

    • 有什么方法可以使用序列+变换找到每个块的最大值。我可以使用 udf 来做到这一点,但由于我的数据框包含大约 12-18 百万行,因此希望尽可能避免使用 udf。任何帮助表示赞赏
    • 检查:sql_expr = "transform(sequence(0,ceil(size(finalArray)/{0})-1), i -> array_max(slice(finalArray,i*{0}+1,{0})))".format(N)
    • 它就像魅力一样。非常感谢您的迅速和高效的响应
    • 完成了,没想到还没有这样做。再次感谢一吨
    【解决方案2】:

    这是@jxc 解决方案的一个稍微不同的版本,它使用slice 函数和transformaggregate 函数。

    逻辑是对于数组的每个元素,我们检查它的索引是否是chunk size 的倍数,并使用slice 得到chunk size 的子数组。使用aggregate,我们将每个子数组的元素相加。最后使用filter去除空值(对应不满足i % chunk = 0的索引。

    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]|
    #+-----+------------+
    

    【讨论】:

    • 如果我的数组包含十进制值,是否有办法使您提供的解决方案起作用。在上面的代码中,0L 适用于整数,0D 适用于双精度数,但我有精度为十进制类型的数组元素(38,15),并且在求和之后,如果可能的话,我希望该值具有相同的类型和精度。另外它有一些文档可以更好地理解会很棒
    • @Saikat 使用CAST(0 AS DECIMAL(38,15)) 作为零值而不是0L。并确保在聚合函数的第三个参数中将acc + y 更改为CAST(acc + y AS DECIMAL(38,15)),以避免类型不匹配错误。
    • 感谢@blackbishop 的及时回复。非常感谢
    猜你喜欢
    • 1970-01-01
    • 2018-02-25
    • 2022-01-06
    • 1970-01-01
    • 2013-06-27
    • 1970-01-01
    相关资源
    最近更新 更多