Pyspark使用高阶SQL函数从数组创建直方图?

Pyspark使用高阶SQL函数从数组创建直方图?,pyspark,higher-order-functions,Pyspark,Higher Order Functions,我有一个数据框,它的列类型为array。 更具体地说,sometype=array,但我怀疑这与当前的挑战无关 我想通过仅使用高阶函数和线性时间为数组中的每个唯一元素指定出现的次数。结果列可以是一个映射或一个结构数组,这无关紧要。 例如: [“val1”、“val2”、“val1”、“val1”、“val3”、“val2”、“val1”]-->{“val1”:4,“val3”:1,“val2”:2} 我尝试用map\u concat聚合(添加带有递增计数器的单元素映射),但后者最终生成了一个多元

我有一个数据框,它的列类型为
array
。 更具体地说,
sometype=array
,但我怀疑这与当前的挑战无关

我想通过仅使用高阶函数和线性时间为数组中的每个唯一元素指定出现的次数。结果列可以是一个映射或一个结构数组,这无关紧要。 例如:

[“val1”、“val2”、“val1”、“val1”、“val3”、“val2”、“val1”]-->{“val1”:4,“val3”:1,“val2”:2}

我尝试用
map\u concat
聚合
(添加带有递增计数器的单元素映射),但后者最终生成了一个多元素映射,而不是用新值覆盖现有元素,从而使计划失败

还有其他建议吗


谢谢

试试这样的东西

df.show() #sample dataframe

#+---------------+
#|          array|
#+---------------+
#|      [1, 9, 1]|
#|[2, 2, 2, 1, 2]|
#|[3, 4, 4, 1, 4]|
#|         [1, 4]|
#|  [99, 99, 100]|
#|   [92, 11, 92]|
#|      [0, 0, 1]|
#+---------------+
使用
过滤器变换

from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> size(filter(array,y-> y=x))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> aggregate(array, 0,(acc,t)->acc+IF(t=x,1,0))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
使用
聚合进行转换

from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> size(filter(array,y-> y=x))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> aggregate(array, 0,(acc,t)->acc+IF(t=x,1,0))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
更新:

另一种方法是,如果我们确切地知道我们将要计算的所有元素,就可以做到这一点。这样我们可以更好地并行化,因为一个结构就像一个列,而一个结构的结构基本上是另一个结构中的一个数据帧

elements=[1,9,2,3,4,99,100,92,11,0]

df.show() #sample dataframe
#+---------------+
#|          array|
#+---------------+
#|      [1, 9, 1]|
#|[2, 2, 2, 1, 2]|
#|[3, 4, 4, 1, 4]|
#|         [1, 4]|
#|  [99, 99, 100]|
#|   [92, 11, 92]|
#|      [0, 0, 1]|
#+---------------+


from pyspark.sql import functions as F
df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .select("array",F.map_from_arrays(F.array(*[F.lit(x) for x in elements]),\
                                                       F.array(*[(F.col("struct.{}.col1".format(x)))\
                                          for x in elements])).alias("count")).show(truncate=False)

#+---------------+-------------------------------------------------------------------------------------+
#|array          |count                                                                                |
#+---------------+-------------------------------------------------------------------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[2, 2, 2, 1, 2]|[1 -> 1, 9 -> 0, 2 -> 4, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[3, 4, 4, 1, 4]|[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 1, 4 -> 3, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[1, 4]         |[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 1, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[99, 99, 100]  |[1 -> 0, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 2, 100 -> 1, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[92, 11, 92]   |[1 -> 0, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 2, 11 -> 1, 0 -> 0]|
#|[0, 0, 1]      |[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 2]|
#+---------------+-------------------------------------------------------------------------------------+
您还可以使用结构尝试此操作(
以获得每行的不同计数):

elements=[1,9,2,3,4,99,100,92,11,0]
from pyspark.sql import functions as F
collected=df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .withColumn("vals", F.array(*[(F.col("struct.{}.col1".format(x))) for x in elements]))\
            .select("array",F.arrays_zip(F.array(*[F.lit(x) for x in elements]),\
                                    F.col("vals")).alias("count"))\
            .withColumn("count", F.expr("""filter(count,x-> x.vals != 0)"""))\
            .show(truncate=False)
#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[[1, 2], [9, 1]]        |
#|[2, 2, 2, 1, 2]|[[1, 1], [2, 4]]        |
#|[3, 4, 4, 1, 4]|[[1, 1], [3, 1], [4, 3]]|
#|[1, 4]         |[[1, 1], [4, 1]]        |
#|[99, 99, 100]  |[[99, 2], [100, 1]]     |
#|[92, 11, 92]   |[[92, 2], [11, 1]]      |
#|[0, 0, 1]      |[[1, 1], [0, 2]]        |
#+---------------+------------------------+
或者,您可以将
map\u from\u条目
与结构逻辑一起使用:

elements=[1,9,2,3,4,99,100,92,11,0]
from pyspark.sql import functions as F
collected=df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .withColumn("vals", F.array(*[(F.col("struct.{}.col1".format(x))) for x in elements]))\
            .withColumn("elems", F.array(*[F.lit(x) for x in elements]))\
            .withColumn("count", F.map_from_entries(F.expr("""filter(arrays_zip(elems,vals),x-> x.vals != 0)""")))\
            .select("array","count")\
            .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[1 -> 1, 2 -> 4]        |
#|[3, 4, 4, 1, 4]|[1 -> 1, 3 -> 1, 4 -> 3]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[1 -> 1, 0 -> 2]        |
#+---------------+------------------------+

试试这样的

df.show() #sample dataframe

#+---------------+
#|          array|
#+---------------+
#|      [1, 9, 1]|
#|[2, 2, 2, 1, 2]|
#|[3, 4, 4, 1, 4]|
#|         [1, 4]|
#|  [99, 99, 100]|
#|   [92, 11, 92]|
#|      [0, 0, 1]|
#+---------------+
使用
过滤器变换

from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> size(filter(array,y-> y=x))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> aggregate(array, 0,(acc,t)->acc+IF(t=x,1,0))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
使用
聚合进行转换

from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> size(filter(array,y-> y=x))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
from pyspark.sql import functions as F
df\
  .withColumn("count",\
          F.expr("""map_from_arrays(array_distinct(array),transform(array_distinct(array),\
              x-> aggregate(array, 0,(acc,t)->acc+IF(t=x,1,0))))"""))\
  .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[2 -> 4, 1 -> 1]        |
#|[3, 4, 4, 1, 4]|[3 -> 1, 4 -> 3, 1 -> 1]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[0 -> 2, 1 -> 1]        |
#+---------------+------------------------+
更新:

另一种方法是,如果我们确切地知道我们将要计算的所有元素,就可以做到这一点。这样我们可以更好地并行化,因为一个结构就像一个列,而一个结构的结构基本上是另一个结构中的一个数据帧

elements=[1,9,2,3,4,99,100,92,11,0]

df.show() #sample dataframe
#+---------------+
#|          array|
#+---------------+
#|      [1, 9, 1]|
#|[2, 2, 2, 1, 2]|
#|[3, 4, 4, 1, 4]|
#|         [1, 4]|
#|  [99, 99, 100]|
#|   [92, 11, 92]|
#|      [0, 0, 1]|
#+---------------+


from pyspark.sql import functions as F
df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .select("array",F.map_from_arrays(F.array(*[F.lit(x) for x in elements]),\
                                                       F.array(*[(F.col("struct.{}.col1".format(x)))\
                                          for x in elements])).alias("count")).show(truncate=False)

#+---------------+-------------------------------------------------------------------------------------+
#|array          |count                                                                                |
#+---------------+-------------------------------------------------------------------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[2, 2, 2, 1, 2]|[1 -> 1, 9 -> 0, 2 -> 4, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[3, 4, 4, 1, 4]|[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 1, 4 -> 3, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[1, 4]         |[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 1, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[99, 99, 100]  |[1 -> 0, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 2, 100 -> 1, 92 -> 0, 11 -> 0, 0 -> 0]|
#|[92, 11, 92]   |[1 -> 0, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 2, 11 -> 1, 0 -> 0]|
#|[0, 0, 1]      |[1 -> 1, 9 -> 0, 2 -> 0, 3 -> 0, 4 -> 0, 99 -> 0, 100 -> 0, 92 -> 0, 11 -> 0, 0 -> 2]|
#+---------------+-------------------------------------------------------------------------------------+
您还可以使用结构尝试此操作(
以获得每行的不同计数):

elements=[1,9,2,3,4,99,100,92,11,0]
from pyspark.sql import functions as F
collected=df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .withColumn("vals", F.array(*[(F.col("struct.{}.col1".format(x))) for x in elements]))\
            .select("array",F.arrays_zip(F.array(*[F.lit(x) for x in elements]),\
                                    F.col("vals")).alias("count"))\
            .withColumn("count", F.expr("""filter(count,x-> x.vals != 0)"""))\
            .show(truncate=False)
#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[[1, 2], [9, 1]]        |
#|[2, 2, 2, 1, 2]|[[1, 1], [2, 4]]        |
#|[3, 4, 4, 1, 4]|[[1, 1], [3, 1], [4, 3]]|
#|[1, 4]         |[[1, 1], [4, 1]]        |
#|[99, 99, 100]  |[[99, 2], [100, 1]]     |
#|[92, 11, 92]   |[[92, 2], [11, 1]]      |
#|[0, 0, 1]      |[[1, 1], [0, 2]]        |
#+---------------+------------------------+
或者,您可以将
map\u from\u条目
与结构逻辑一起使用:

elements=[1,9,2,3,4,99,100,92,11,0]
from pyspark.sql import functions as F
collected=df.withColumn("struct", F.struct(*[(F.struct(F.expr("size(filter(array,x->x={}))"\
                                                    .format(y))).alias(str(y))) for y in elements]))\
            .withColumn("vals", F.array(*[(F.col("struct.{}.col1".format(x))) for x in elements]))\
            .withColumn("elems", F.array(*[F.lit(x) for x in elements]))\
            .withColumn("count", F.map_from_entries(F.expr("""filter(arrays_zip(elems,vals),x-> x.vals != 0)""")))\
            .select("array","count")\
            .show(truncate=False)

#+---------------+------------------------+
#|array          |count                   |
#+---------------+------------------------+
#|[1, 9, 1]      |[1 -> 2, 9 -> 1]        |
#|[2, 2, 2, 1, 2]|[1 -> 1, 2 -> 4]        |
#|[3, 4, 4, 1, 4]|[1 -> 1, 3 -> 1, 4 -> 3]|
#|[1, 4]         |[1 -> 1, 4 -> 1]        |
#|[99, 99, 100]  |[99 -> 2, 100 -> 1]     |
#|[92, 11, 92]   |[92 -> 2, 11 -> 1]      |
#|[0, 0, 1]      |[1 -> 1, 0 -> 2]        |
#+---------------+------------------------+

虽然仍然希望看到关于线性时间解决方案的建议,但我将在这里发布一个非常丑陋的版本,我目前正在使用它来解决N*log(N)中的问题:

  • 确定是否需要非平凡的聚合
  • 对于需要进行聚合的情况:

    2.1。对元素列表进行排序(这是花费时间最多的地方)

    2.2。为即将重复的元素指定一个特殊的空值,同时记录它们在排序列表中的位置

    2.3。删除带有空值的元素条目并收缩其余列表

    2.4。根据剩余元素位置值的差异,得出每个n克的重复次数


  • 虽然仍然希望看到关于线性时间解决方案的建议,但我将在这里发布一个非常丑陋的版本,我目前正在使用它来解决N*log(N)中的问题:

  • 确定是否需要非平凡的聚合
  • 对于需要进行聚合的情况:

    2.1。对元素列表进行排序(这是花费时间最多的地方)

    2.2。为即将重复的元素指定一个特殊的空值,同时记录它们在排序列表中的位置

    2.3。删除带有空值的元素条目并收缩其余列表

    2.4。根据剩余元素位置值的差异,得出每个n克的重复次数


  • 您好,Mohammad,谢谢,但我认为您的解决方案在元素数量上是二次的,而不是线性的:对于(uniqued)数组的每个元素,您都在迭代整个数组。从spark的角度来看,您不太确定这是什么意思,但是我所发布的应该是通过
    spark2.4+
    和更高阶函数实现这一点的最有效的spark方法。可以说,您有N个元素,它们都是唯一的。您的第一个
    转换
    将应用于一行中的所有N个(据我所知,在该级别上没有并行化)。在每个
    transform
    下,您还拥有迭代所有N个元素的
    aggregate
    。因此,您最终总共执行了N^2个操作。将此与基于哈希映射的解决方案进行比较,其中每个更新(内部循环)都是O(1),因此总时间是O(N)。在我看来,还有一个选择,如果我们知道列表中的所有元素,使用结构,我们可以更好地并行化它。我已经更新了。你可以对它们的性能进行基准测试,因为它们的词汇量达到了万亿,所以显式枚举是行不通的。在这里,重要的是找到一个O(N)解。我确实有一些想法如何在N*log(N)(带排序)中解决这个问题,但在常规编程语言(可以更新哈希映射)中,这将是一个O(N)任务。正如我的问题所述,O(N)是我正在寻找的高阶函数的解决方案。如果没有它,我很遗憾无法将答案标记为已接受,尽管我真的很感激您为提供Iti Mohammad所做的大量工作,谢谢,但我认为您的解决方案在元素数量上是二次的,而不是线性的:对于(uniqued)数组的每个元素,您正在迭代整个阵列。从spark的角度来看,您不太确定这是什么意思,但我发布的内容应该是通过
    spark2.4+
    和更高阶函数实现这一点的最有效的spark方法。您有N个元素,它们都是唯一的。您的第一次
    转换
    将应用于所有N个