Pyspark-从每一列中选择不同的值

时间:2018-09-13 18:41:35

标签: python python-3.x apache-spark pyspark pyspark-sql

我试图在数据框中的每一列中找到所有不同的值,并在一个表中显示。

示例数据:

|-----------|-----------|-----------|
|   COL_1   |   COL_2   |   COL_3   | 
|-----------|-----------|-----------|
|     A     |     C     |     D     |
|     A     |     C     |     D     |
|     A     |     C     |     E     |
|     B     |     C     |     E     |
|     B     |     C     |     F     |
|     B     |     C     |     F     |
|-----------|-----------|-----------|

示例输出:

|-----------|-----------|-----------|
|   COL_1   |   COL_2   |   COL_3   | 
|-----------|-----------|-----------|
|     A     |     C     |     D     |
|     B     |           |     E     |
|           |           |     F     |
|-----------|-----------|-----------|

这甚至可能吗?我已经可以在单独的表中完成此操作,但是将其放在一个表中会更好。

有什么想法吗?

1 个答案:

答案 0 :(得分:2)

这里最简单的方法是在所有列上使用pyspark.sql.functions.collect_set

import pyspark.sql.functions as f
df.select(*[f.collect_set(c).alias(c) for c in df.columns]).show()
#+------+-----+---------+
#| COL_1|COL_2|    COL_3|
#+------+-----+---------+
#|[B, A]|  [C]|[F, E, D]|
#+------+-----+---------+

很明显,这将数据返回为一行。

如果相反,您想要的是您在问题中所写的输出(每列每个唯一值一行),它是可行的,但需要大量的pyspark体操(而且任何解决方案的效率都可能大大降低)。

尽管如此,我还是为您提供一些选择:

选项1:爆炸并加入

您可以使用pyspark.sql.functions.posexplode爆炸每一列的值集中的元素以及数组中的索引。分别对每个列执行此操作,然后使用functools.reduce将结果的DataFrame列表外部连接在一起:

from functools import reduce 

unique_row = df.select(*[f.collect_set(c).alias(c) for c in df.columns])

final_df = reduce(
    lambda a, b: a.join(b, how="outer", on="pos"),
    (unique_row.select(f.posexplode(c).alias("pos", c)) for c in unique_row.columns)
).drop("pos")

final_df.show()
#+-----+-----+-----+
#|COL_1|COL_2|COL_3|
#+-----+-----+-----+
#|    A| null|    E|
#| null| null|    D|
#|    B|    C|    F|
#+-----+-----+-----+

选项2:按位置选择

首先计算最大数组的大小,并将其存储在新列max_length中。然后,如果该索引处存在值,则从每个数组中选择元素。

我们再次使用pyspark.sql.functions.posexplode,但这一次只是创建一个列来表示要提取的每个数组中的索引。

最后,我们使用this trick,它允许您将列值用作参数。

final_df= df.select(*[f.collect_set(c).alias(c) for c in df.columns])\
    .withColumn("max_length", f.greatest(*[f.size(c) for c in df.columns]))\
    .select("*", f.expr("posexplode(split(repeat(',', max_length-1), ','))"))\
    .select(
        *[
            f.expr(
                "case when size({c}) > pos then {c}[pos] else null end AS {c}".format(c=c))
            for c in df.columns
        ]
    )

final_df.show()
#+-----+-----+-----+
#|COL_1|COL_2|COL_3|
#+-----+-----+-----+
#|    B|    C|    F|
#|    A| null|    E|
#| null| null|    D|
#+-----+-----+-----+