使用Apache Spark将键值对减少为键列表对

时间:2014-11-18 19:15:19

标签: python apache-spark mapreduce pyspark rdd

我正在编写Spark应用程序,并希望将一组键值对(K, V1), (K, V2), ..., (K, Vn)合并到一个Key-Multivalue对(K, [V1, V2, ..., Vn])中。我觉得我应该能够使用具有某种风味的reduceByKey函数来做到这一点:

My_KMV = My_KV.reduce(lambda a, b: a.append([b]))

发生这种情况时我得到的错误是:

  

' NoneType'对象没有属性'追加'。

我的键是整数,值V1,...,Vn是元组。我的目标是使用密钥和值列表(元组)创建一对。

9 个答案:

答案 0 :(得分:46)

Map and ReduceByKey

reduce的输入类型和输出类型必须相同,因此,如果要聚合列表,则必须map列表输入。然后将列表合并到一个列表中。

合并列表

您需要一种方法将列表合并到一个列表中。 Phyton提供了一些methods to combine lists

append修改第一个列表,并始终返回None

x = [1, 2, 3]
x.append([4, 5])
# x is [1, 2, 3, [4, 5]]

extend执行相同操作,但打开列表:

x = [1, 2, 3]
x.extend([4, 5])
# x is [1, 2, 3, 4, 5]

两个方法都返回None,但您需要一个返回组合列表的方法,因此只需use the plus sign

x = [1, 2, 3] + [4, 5]
# x is [1, 2, 3, 4, 5]

<强>火花

file = spark.textFile("hdfs://...")
counts = file.flatMap(lambda line: line.split(" ")) \
         .map(lambda actor: (actor.split(",")[0], actor)) \ 

         # transform each value into a list
         .map(lambda nameTuple: (nameTuple[0], [ nameTuple[1] ])) \

         # combine lists: ([1,2,3] + [4,5]) becomes [1,2,3,4,5]
         .reduceByKey(lambda a, b: a + b)

<强> CombineByKey

也可以使用combineByKey解决此问题,reduceByKey在内部用于实现groupByKey,但它更复杂且"using one of the specialized per-key combiners in Spark can be much faster"。您的用例对于上层解决方案来说非常简单。

<强> GroupByKey

也可以使用{{1}},but it reduces parallelization来解决此问题,因此对于大数据集来说可能要慢得多。

答案 1 :(得分:13)

我对谈话有点迟,但这是我的建议:

>>> foo = sc.parallelize([(1, ('a','b')), (2, ('c','d')), (1, ('x','y'))])
>>> foo.map(lambda (x,y): (x, [y])).reduceByKey(lambda p,q: p+q).collect()
[(1, [('a', 'b'), ('x', 'y')]), (2, [('c', 'd')])]

答案 2 :(得分:13)

tl; dr 如果您确实需要这样的操作,请as suggested使用groupByKey @MariusIon。与直接分组相比,这里提出的所有其他解决方案要么直率低效,要么至少是次优的。

带有列表连接的

reduceByKey不是可接受的解决方案,因为:

  • 需要初始化 O(N)列表。
  • +对一对列表的每次应用都需要两个列表的完整副本( O(N)),从而有效地将总体复杂性提高到 O(N 2功能
  • 未解决groupByKey引入的任何问题。必须改组的数据量以及最终结构的大小是相同的。
  • suggested by one of the answers不同,使用reduceByKeygroupByKey的实施之间的并行度水平没有差异。
带有combineByKey

list.extend是次优解决方案,因为:

  • MergeValue中创建 O(N)列表对象(可以直接在新项目上使用list.append进行优化)。
  • 如果使用list.append进行优化,则它完全等同于groupByKey的旧(Spark&lt; = 1.3)实现,并忽略SPARK-3074引入的所有优化,这些优化启用了外部(磁盘上) )对大于内存的结构进行分组。

答案 3 :(得分:11)

您可以使用RDD groupByKey方法。

输入:

data = [(1, 'a'), (1, 'b'), (2, 'c'), (2, 'd'), (2, 'e'), (3, 'f')]
rdd = sc.parallelize(data)
result = rdd.groupByKey().collect()

输出:

[(1, ['a', 'b']), (2, ['c', 'd', 'e']), (3, ['f'])]

答案 4 :(得分:3)

如果你想做一个reduceByKey,其中减少的KV对中的类型不同于原始KV对中的类型,那么可以使用函数combineByKey。该函数的作用是采用KV对并将它们(通过Key)组合成KC对,其中C是与V不同的类型。

一个指定3个函数,createCombiner,mergeValue,mergeCombiners。第一个指定如何将类型V转换为类型C,第二个描述如何将类型C与类型V组合,最后一个指定如何将类型C与另一个类型C组合。我的代码创建KV对:

定义3个函数如下:

def Combiner(a):    #Turns value a (a tuple) into a list of a single tuple.
    return [a]

def MergeValue(a, b): #a is the new type [(,), (,), ..., (,)] and b is the old type (,)
    a.extend([b])
    return a

def MergeCombiners(a, b): #a is the new type [(,),...,(,)] and so is b, combine them
    a.extend(b)
    return a

然后,My_KMV = My_KV.combineByKey(Combiner, MergeValue, MergeCombiners)

我在使用此功能时找到的最佳资源是:http://abshinn.github.io/python/apache-spark/2014/10/11/using-combinebykey-in-apache-spark/

正如其他人所指出的那样,a.append(b)a.extend(b)会返回None。因此reduceByKey(lambda a, b: a.append(b))在第一对KV对上返回None,然后在第二对上失败,因为None.append(b)失败。您可以通过定义单独的函数来解决此问题:

 def My_Extend(a,b):
      a.extend(b)
      return a

然后调用reduceByKey(lambda a, b: My_Extend(a,b))(这里使用lambda函数可能是不必要的,但我没有测试过这种情况。)

答案 5 :(得分:1)

确定。我希望,我做对了。您的输入是这样的:

kv_input = [("a", 1), ("a", 2), ("a", 3), ("b", 1), ("b", 5)]

你希望得到这样的东西:

kmv_output = [("a", [1, 2, 3]), ("b", [1, 5])]

然后这可以完成这项工作(见here):

d = dict()
for k, v in kv_input:
    d.setdefault(k, list()).append(v)
kmv_output = list(d.items())

如果我弄错了,请告诉我,所以我可能会根据您的需要进行调整。

P.S。:a.append([b])始终返回None。您可能希望观察[b]a,而不是append的结果。

答案 6 :(得分:1)

错误消息源于&#39; a&#39;在你的关闭。

 My_KMV = My_KV.reduce(lambda a, b: a.append([b]))

让pySpark明确地将a评估为列表。例如,

My_KMV = My_KV.reduceByKey(lambda a,b:[a].extend([b]))

在许多情况下,reduceByKey优于groupByKey,请参阅: http://databricks.gitbooks.io/databricks-spark-knowledge-base/content/best_practices/prefer_reducebykey_over_groupbykey.html

答案 7 :(得分:1)

我尝试使用combineByKey,这是我的步骤

combineddatardd=sc.parallelize([("A", 3), ("A", 9), ("A", 12),("B", 4), ("B", 10), ("B", 11)])

combineddatardd.combineByKey(lambda v:[v],lambda x,y:x+[y],lambda x,y:x+y).collect()

输出:

[('A', [3, 9, 12]), ('B', [4, 10, 11])]
  1. 为组合器定义一个函数,它将累加器设置为它在分区内遇到的第一个键值对,在此步骤中将值转换为列表

  2. 定义一个函数,它将同一个键的新值合并到步骤1中捕获的累加器值注意: - 将值转换为此函数中的列,因为累加器值已在第一步中转换为列表

  3. 定义合并各个分区的合并器输出的函数。

答案 8 :(得分:0)

我在查找相同问题的java示例时点击此页面。 (如果你的情况类似,这是我的例子)

诀窍是 - 你需要分组钥匙。

import org.apache.spark.SparkConf;
import org.apache.spark.api.java.JavaPairRDD;
import org.apache.spark.api.java.JavaRDD;
import org.apache.spark.api.java.JavaSparkContext;
import scala.Tuple2;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

public class SparkMRExample {

    public static void main(String[] args) {
        // spark context initialisation
        SparkConf conf = new SparkConf()
                .setAppName("WordCount")
                .setMaster("local");
        JavaSparkContext context = new JavaSparkContext(conf);

        //input for testing;
        List<String> input = Arrays.asList("Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
                "Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.",
                "It has survived not only for centuries, but also the leap into electronic typesetting, remaining essentially unchanged.",
                "It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing");
        JavaRDD<String> inputRDD = context.parallelize(input);


        // the map phase of word count example
        JavaPairRDD<String, Integer> mappedRDD =
                inputRDD.flatMapToPair( line ->                      // for this input, each string is a line
                        Arrays.stream(line.split("\\s+"))            // splitting into words, converting into stream
                                .map(word -> new Tuple2<>(word, 1))  // each word is assigned with count 1
                                .collect(Collectors.toList()));      // stream to iterable

        // group the tuples by key
        // (String,Integer) -> (String, Iterable<Integer>)
        JavaPairRDD<String, Iterable<Integer>> groupedRDD = mappedRDD.groupByKey();

        // the reduce phase of word count example
        //(String, Iterable<Integer>) -> (String,Integer)
        JavaRDD<Tuple2<String, Integer>> resultRDD =
                groupedRDD.map(group ->                                      //input is a tuple (String, Iterable<Integer>)
                        new Tuple2<>(group._1,                              // the output key is same as input key
                        StreamSupport.stream(group._2.spliterator(), true)  // converting to stream
                                .reduce(0, (f, s) -> f + s)));              // the sum of counts
        //collecting the RRD so that we can print
        List<Tuple2<String, Integer>> result = resultRDD.collect();
        // print each tuple
        result.forEach(System.out::println);
    }
}