减少(key,val)RDD的列表中的值,因为这些列表是(key,val)RDD的另一个列表中的值

时间:2019-10-01 20:37:47

标签: apache-spark pyspark rdd

我已经为此花了一段时间-非常感谢任何建议! 抱歉,我的长标题很抱歉,我希望我在下面构造的一个简短示例可以更好地说明这一点。

假设我们具有以下形式的RDD:

data = sc.parallelize([(1,[('k1',4),('k2',3),('k1',2)]),\
           (2,[('k3',1),('k3',8),('k1',6)])])
data.collect()

输出:

[(1, [('k1', 4), ('k2', 3), ('k1', 2)]),
 (2, [('k3', 1), ('k3', 8), ('k1', 6)])]

我希望对(key,val)RDD的最深列表进行以下操作

.reduceByKey(lambda a, b: a + b)

(即按键减少这些RDD的值以按键获取总和,同时保留与初始较高级别RDD的键所映射的结果,这将产生以下输出):

[(1, [('k1', 6), ('k2', 3)]),
 (2, [('k3', 9), ('k1', 6)])]

我是PySpark的新手,可能这里缺少一些基本知识,但是我已经尝试了很多不同的方法,但是本质上找不到找到和减少列表中(key,val)RDD的reduceByKey的方法。 ,它本身就是另一个RDD的值。

非常感谢!

Denys

3 个答案:

答案 0 :(得分:1)

您想要做的是:您的值(在输入K,V中)是一个 iterable (可迭代),您想在其上对内键求和并将结果返回==

(outer_key(例如1,2 )->列表(Inner_Key(例如“ K1”,“ K2” ),Summed_value))

如您所见,总和是根据内部键V 计算的, 我们可以通过

从每个列表项中首先剥离元素

=>新建一个(外键,内键)

=>对(outer_key,inner_key)->值求和

=>将数据格式更改回(outer_key->(inner_key,summed_value))

再次

=>再次对外键进行分组

我不确定使用Python,但是相信仅用python替换Scala集合语法就足够了,这就是解决方案

SCALA版本

scala> val keySeq = Seq((1,List(("K1",4),("K2",3),("K1",2))),
     | (2,List(("K3",1),("K3",8),("K1",6))))
keySeq: Seq[(Int, List[(String, Int)])] = List((1,List((K1,4), (K2,3), (K1,2))), (2,List((K3,1), (K3,8), (K1,6))))

scala> val inRdd = sc.parallelize(keySeq)
inRdd: org.apache.spark.rdd.RDD[(Int, List[(String, Int)])] = ParallelCollectionRDD[111] at parallelize at <console>:26

scala> inRdd.take(10)
res64: Array[(Int, List[(String, Int)])] = Array((1,List((K1,4), (K2,3), (K1,2))), (2,List((K3,1), (K3,8), (K1,6))))


// And solution :
scala> inRdd.flatMap { case (i,l) => l.map(l => ((i,l._1),l._2)) }.reduceByKey(_+_).map(x => (x._1._1 ->(x._1._2,x._2))).groupByKey.map(x => (x._1,x._2.toList.sortBy(x =>x))).collect()

// RESULT ::
res65: Array[(Int, List[(String, Int)])] = Array((1,List((K1,6), (K2,3))), (2,List((K1,6), (K3,9))))

UPDATE => Python解决方案

>>> data = sc.parallelize([(1,[('k1',4),('k2',3),('k1',2)]),\
...            (2,[('k3',1),('k3',8),('k1',6)])])
>>> data.collect()
[(1, [('k1', 4), ('k2', 3), ('k1', 2)]), (2, [('k3', 1), ('k3', 8), ('k1', 6)])]

# Similar operation

>>> data.flatMap(lambda x : [ ((x[0],y[0]),y[1]) for y in x[1]]).reduceByKey(lambda a,b : (a+b)).map(lambda x : [x[0][0],(x[0][1],x[1])]).groupByKey().mapValues(list).collect()

# RESULT 
[(1, [('k1', 6), ('k2', 3)]), (2, [('k3', 9), ('k1', 6)])]

答案 1 :(得分:0)

您应该映射数据集而不是减少数据集,因为示例中的行数与源数据集中的行数相同,因此在map内部您可以将值减少为python list

答案 2 :(得分:0)

使用 mapValues() + itertools.groupby()

from itertools import groupby

data.mapValues(lambda x: [ (k, sum(f[1] for f in g)) for (k,g) in groupby(sorted(x), key=lambda d: d[0]) ]) \
    .collect()
#[(1, [('k1', 6), ('k2', 3)]), (2, [('k1', 6), ('k3', 9)])]

使用 itertools.groupby ,我们将元组的第一项用作分组键k,然后将每个g中元组的第二项相加。 >

编辑:对于大型数据集,使用itertools.groupby进行排序非常昂贵,只需编写一个不带排序功能的函数即可处理该问题:

def merge_tuples(x):
    d = {}
    for (k,v) in x: 
        d[k] = d.get(k,0) + v
    return d.items()

data.mapValues(merge_tuples).collect()
#[(1, [('k2', 3), ('k1', 6)]), (2, [('k3', 9), ('k1', 6)])]