[英]sum of a pyspark dataframe column containing dictionaries
我有一個只包含一列的數據框,其中包含MapType(StringType(), IntegerType())
類型的元素。 我想獲得該列的累積總和,其中sum
操作意味着添加兩個詞典。
最小的例子
a = [{'Maps': ({'a': 1, 'b': 2, 'c': 3})}, {'Maps': ({'a': 2, 'b': 4, 'd': 6})}]
df = spark.createDataFrame(a)
df.show(5, False)
+---------------------------+
|Maps |
+---------------------------+
|Map(a -> 1, b -> 2, c -> 3)|
|Map(a -> 2, b -> 4, d -> 6)|
+---------------------------+
如果我要獲得列Maps
的累積總和,我應該得到以下結果。
+-----------------------------------+
|Maps |
+-----------------------------------+
|Map(a -> 3, b -> 6, c -> 3, d -> 6)|
+-----------------------------------+
PS我使用的是Python 2.6,因此collections.Counter
不可用。 如果絕對必要,我可以安裝它。
我的嘗試:
我嘗試過基於accumulator
的方法和使用fold
的方法。
累加器
def addDictFun(x):
global v
v += x
class DictAccumulatorParam(AccumulatorParam):
def zero(self, d):
return d
def addInPlace(self, d1, d2):
for k in d1:
d1[k] = d1[k] + (d2[k] if k in d2 else 0)
for k in d2:
if k not in d1:
d1[k] = d2[k]
return d1
v = sc.accumulator(MapType(StringType(), IntegerType()), DictAccumulatorParam())
cumsum_dict = df.rdd.foreach(addDictFun)
現在最后,我應該在v
得到結果字典。 相反,我得到錯誤MapType
不可迭代(主要在函數addInPlace
for k in d1
中的for k in d1
行)。
rdd.fold
基於rdd.fold
的方法如下:
def add_dicts(d1, d2):
for k in d1:
d1[k] = d1[k] + (d2[k] if k in d2 else 0)
for k in d2:
if k not in d1:
d1[k] = d2[k]
return d1
cumsum_dict = df.rdd.fold(MapType(StringType(), IntegerType()), add_dicts)
但是,我在這里獲得相同的MapType is not iterable
錯誤。 知道我哪里錯了嗎?
pyspark.sql.types
是模式描述符,不是集合或外部語言表示,因此不能與fold
或Accumulator
一起使用。
最直接的解決方案是explode
和聚合
from pyspark.sql.functions import explode
df = spark.createDataFrame(
[{'a': 1, 'b': 2, 'c': 3}, {'a': 2, 'b': 4, 'd': 6}],
"map<string,integer>"
).toDF("Maps")
df.select(explode("Maps")).groupBy("key").sum("value").rdd.collectAsMap()
# {'d': 6, 'c': 3, 'b': 6, 'a': 3}
使用RDD
您可以執行類似的操作:
from operator import add
df.rdd.flatMap(lambda row: row.Maps.items()).reduceByKey(add).collectAsMap()
# {'b': 6, 'c': 3, 'a': 3, 'd': 6}
或者如果你真的想fold
from operator import attrgetter
from collections import defaultdict
def merge(acc, d):
for k in d:
acc[k] += d[k]
return acc
df.rdd.map(attrgetter("Maps")).fold(defaultdict(int), merge)
# defaultdict(int, {'a': 3, 'b': 6, 'c': 3, 'd': 6})
@ user8371915使用explode
的答案更通用,但如果你提前知道密鑰,這里的另一種方法可能會更快:
import pyspark.sql.functions as f
myKeys = ['a', 'b', 'c', 'd']
df.select(*[f.sum(f.col('Maps').getItem(k)).alias(k) for k in myKeys]).show()
#+---+---+---+---+
#| a| b| c| d|
#+---+---+---+---+
#| 3| 6| 3| 6|
#+---+---+---+---+
如果你想在MapType()
得到結果,你可以使用pyspark.sql.functions.create_map
如:
from itertools import chain
df.select(
f.create_map(
list(
chain.from_iterable(
[[f.lit(k), f.sum(f.col('Maps').getItem(k))] for k in myKeys]
)
)
).alias("Maps")
).show(truncate=False)
#+-----------------------------------+
#|Maps |
#+-----------------------------------+
#|Map(a -> 3, b -> 6, c -> 3, d -> 6)|
#+-----------------------------------+
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.