PySpark加入数据框并合并特定列的内容

时间:2018-07-11 16:51:02

标签: pyspark apache-spark-sql

我的目标是合并id列上的两个数据帧,并在另一个包含JSON的列上执行一个稍微复杂的合并,我们可以调用data

假设我有一个看起来像这样的DataFrame df1

id | data
---------------------------------
42 | {'a_list':['foo'],'count':1}
43 | {'a_list':['scrog'],'count':0}

我有兴趣与相似但不同的DataFrame df2合并:

id | data
---------------------------------
42 | {'a_list':['bar'],'count':2}
44 | {'a_list':['baz'],'count':4}

我想要下面的DataFrame,在id匹配的JSON数据中加入和合并属性,但在id不匹配的地方保留行,并将data列保持为-是:

id | data
---------------------------------------
42 | {'a_list':['foo','bar'],'count':3}  <-- where 'bar' is added to 'foo', and count is summed
43 | {'a_list':['scrog'],'count':1}
44 | {'a_list':['baz'],'count':4}

可以看到id是42的地方,我必须将一些逻辑应用于JSON的合并方式。

我的想法很奇怪,我想提供一个lambda / udf来合并data列,但不确定在联接期间如何考虑。

或者,我可以将JSON中的属性分为几列,这可能是更好的方法?

df1

id | a_list    | count
----------------------
42 | ['foo']   | 1
43 | ['scrog'] | 0

df2

id | a_list   | count
---------------------
42 | ['bar']  | 2
44 | ['baz']  | 4

结果:

id | a_list         | count
---------------------------
42 | ['foo', 'bar'] | 3
43 | ['scrog']      | 0
44 | ['baz']        | 4

如果我走这条路,那么我将不得不将列a_listcount再次合并到一个单独的列data下的JSON中,但是我可以将其包裹为一个相对简单的map函数。

更新:扩展问题

实际上,我在列表中将有n个DataFrame,例如df_list = [df1, df2, df3],形状相同。在n个数据帧上执行这些相同操作的有效方法是什么?

更新到更新

不确定这样做的效率如何,或者不确定是否有其他类似的方法,但是结合接受的答案,这似乎可以解决问题:

for i in range(0, (len(validations) - 1)):  

    # set dfs
    df1 = validations[i]['df']
    df2 = validations[(i+1)]['df']

    # joins here...

    # update new_df
    new_df = df2

1 个答案:

答案 0 :(得分:2)

这是完成第二种方法的一种方法:

分解列表列,然后unionAll分解两个DataFrame。下一组通过“ id”列并使用pyspark.sql.functions.collect_list()pyspark.sql.functions.sum()

import pyspark.sql.functions as f
new_df = df1.select("id", f.explode("a_list").alias("a_values"), "count")\
    .unionAll(df2.select("id", f.explode("a_list").alias("a_values"), "count"))\
    .groupBy("id")\
    .agg(f.collect_list("a_values").alias("a_list"), f.sum("count").alias("count"))

new_df.show(truncate=False)
#+---+----------+-----+
#|id |a_list    |count|
#+---+----------+-----+
#|43 |[scrog]   |0    |
#|44 |[baz]     |4    |
#|42 |[foo, bar]|3    |
#+---+----------+-----+

最后,您可以使用pyspark.sql.functions.struct()pyspark.sql.functions.to_json()将此中间DataFrame转换为所需的结构:

new_df = new_df.select("id", f.to_json(f.struct("a_list", "count")).alias("data"))
new_df.show()
#+---+----------------------------------+
#|id |data                              |
#+---+----------------------------------+
#|43 |{"a_list":["scrog"],"count":0}    |
#|44 |{"a_list":["baz"],"count":4}      |
#|42 |{"a_list":["foo","bar"],"count":3}|
#+---+----------------------------------+

更新

如果您在df_list中有一个数据帧列表,则可以执行以下操作:

from functools import reduce   # for python3
df_list = [df1, df2]
new_df = reduce(lambda a, b: a.unionAll(b), df_list)\
    .select("id", f.explode("a_list").alias("a_values"), "count")\
    .groupBy("id")\
    .agg(f.collect_list("a_values").alias("a_list"), f.sum("count").alias("count"))\
    .select("id", f.to_json(f.struct("a_list", "count")).alias("data"))