web-dev-qa-db-de.com

Reduzieren Sie ein Schlüsselwertpaar mit Apache Spark in ein Schlüssellistenpaar

Ich schreibe eine Spark-Anwendung und möchte einen Satz von Schlüssel-Wert-Paaren (K, V1), (K, V2), ..., (K, Vn) in einem Key-Multivalue-Paar (K, [V1, V2, ..., Vn]) kombinieren. Ich habe das Gefühl, ich sollte dies mit der Funktion reduceByKey mit etwas Geschmack tun können:

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

Der Fehler, den ich bekomme, wenn dies auftritt, ist:

Das Objekt 'NoneType' hat kein Attribut 'anhängen'.

Meine Schlüssel sind Ganzzahlen und die Werte V1, ..., Vn sind Tupel. Mein Ziel ist es, ein einzelnes Paar mit dem Schlüssel und einer Liste der Werte (Tupel) zu erstellen.

39
TravisJ

Map und ReduceByKey

Der Eingabetyp und der Ausgabetyp von reduce müssen identisch sein. Wenn Sie also eine Liste aggregieren möchten, müssen Sie die Eingabe in Listen map. Anschließend kombinieren Sie die Listen zu einer Liste.

Listen kombinieren

Sie benötigen eine Methode, um Listen in einer Liste zusammenzufassen. Phyton bietet einige Methoden zum Kombinieren von Listen .

append ändert die erste Liste und gibt immer None zurück.

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

extend macht dasselbe, packt aber die Listen aus:

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

Beide Methoden geben None zurück, aber Sie benötigen eine Methode, die die kombinierte Liste zurückgibt. Verwenden Sie daher einfach das Pluszeichen .

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

Funke

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

Es ist auch möglich, dies mit combineByKey zu lösen, das intern verwendet wird, um reduceByKey zu implementieren, aber es ist komplexer und "mit einem der spezialisierten pro -Tasten-Combiner in Spark kann viel schneller sein " . Ihr Anwendungsfall ist für die obere Lösung einfach genug.

GroupByKey

Es ist auch möglich, dies mit groupByKey, zu lösen, aber es verringert die Parallelisierung und kann daher für große Datenmengen viel langsamer sein.

48

Ich komme zu spät zum Gespräch, aber hier ist mein Vorschlag:

>>> 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')])]
13
alreich

tl; dr Wenn Sie wirklich eine Operation wie diese benötigen, verwenden Sie groupByKeywie vorgeschlagen von @MariusIon . Jede andere hier vorgeschlagene Lösung ist entweder geradezu ineffizient im Vergleich zur direkten Gruppierung zumindest suboptimal.

reduceByKey mit List-Verkettung ist keine akzeptable Lösung, weil:

  • Erfordert die Initialisierung von O(N) Listen.
  • Jede Anwendung von + auf ein Listenpaar erfordert eine vollständige Kopie beider Listen (O(N)), wodurch die Gesamtkomplexität auf O (N) erhöht wird2).
  • Behebt keines der Probleme, die durch groupByKey eingeführt wurden. Die Datenmenge, die gemischt werden muss, sowie die Größe der endgültigen Struktur sind gleich.
  • Anders als vorgeschlagen von einer der Antworten unterscheidet sich der Grad der Parallelität nicht zwischen der Implementierung mit reduceByKey und groupByKey.

combineByKey mit list.extend ist eine suboptimale Lösung, weil:

  • Erstellt O(N) Listenobjekte in MergeValue (dies kann durch die Verwendung von list.append direkt für das neue Element optimiert werden).
  • Wenn mit list.append optimiert, entspricht dies genau einer alten (Spark <= 1.3) -Implementierung einer groupByKey und ignoriert alle durch SPARK-3074 eingeführten Optimierungen, wodurch eine externe Gruppierung ("On-Disk") der Strukturen mit mehr als Speicher möglich ist.
13
zero323

Sie können die RDD groupByKey -Methode verwenden.

Eingabe:

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

Ausgabe:

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

Wenn Sie ein reduByKey ausführen möchten, bei dem der Typ in den reduzierten KV-Paaren von dem Typ in den ursprünglichen KV-Paaren abweicht, können Sie die Funktion combineByKey verwenden. Die Funktion besteht darin, KV-Paare zu nehmen und diese (nach Schlüssel) zu KC-Paaren zu kombinieren, wobei C ein anderer Typ als V ist.

Eine gibt drei Funktionen an, createCombiner, mergeValue, mergeCombiners. Im ersten Abschnitt wird angegeben, wie ein Typ V in einen Typ C umgewandelt wird. Im zweiten Abschnitt wird beschrieben, wie ein Typ C mit einem Typ V kombiniert wird. Im letzten Abschnitt wird angegeben, wie ein Typ C mit einem anderen Typ C kombiniert wird. Mein Code erstellt die K-V-Paare:

Definieren Sie die 3 Funktionen wie folgt:

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

Dann My_KMV = My_KV.combineByKey(Combiner, MergeValue, MergeCombiners)

Die beste Ressource, die ich bei der Verwendung dieser Funktion gefunden habe, ist: http://abshinn.github.io/python/Apache-spark/2014/10/11/using-combinebykey-in-Apache-spark/

Wie andere darauf hingewiesen haben, geben a.append(b) oder a.extend(b)None zurück. Daher gibt reduceByKey(lambda a, b: a.append(b)) None für das erste Paar von KV-Paaren zurück und schlägt dann für das zweite Paar fehl, da None.append (b) fehlschlägt. Sie können dies umgehen, indem Sie eine eigene Funktion definieren:

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

Dann rufen Sie reduceByKey(lambda a, b: My_Extend(a,b)) auf (Die Verwendung der Lambda-Funktion ist hier möglicherweise nicht erforderlich, aber ich habe diesen Fall nicht getestet.)

3
TravisJ

Die Fehlermeldung stammt von dem Typ für 'a' in Ihrem Abschluss.

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

Lassen Sie pySpark eine Liste explizit auswerten. Zum Beispiel,

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

In vielen Fällen ist der Einsatz von reduByKey gegenüber groupByKey vorzuziehen, siehe: http://databricks.gitbooks.io/databricks-spark-knowledge-base/content/best_practices/prefer_reducebykey_over_groupbykey.html

1
Seung-Hwan Lim

Ich habe es mit joinByKey ausprobiert, hier sind meine Schritte 

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()

Ausgabe:

[('A', [3, 9, 12]), ('B', [4, 10, 11])]
  1. Definieren Sie eine Funktion für den Combiner, die den Akkumulator auf das erste Schlüsselwertpaar setzt, auf das er in der Partition trifft, und den Wert in die Liste in diesem Schritt konvertieren

  2. Definieren Sie eine Funktion, die den neuen Wert desselben Schlüssels mit dem in Schritt 1 erfassten Akkumulatorwert zusammenfasst. Hinweis: Konvertieren Sie den in diese Funktion aufzunehmenden Wert, da der Akkumulatorwert im ersten Schritt in eine Liste konvertiert wurde 

  3. Definieren Sie die Funktion zum Zusammenführen der Combiner-Ausgaben einzelner Partitionen.

1
krishna rachur

OK. Ich hoffe, ich habe das richtig verstanden. Ihre Eingabe sieht so aus:

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

und du möchtest so etwas bekommen:

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

Dann könnte dies den Job erledigen (siehe hier ):

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

Wenn ich das falsch verstanden habe, sag es mir bitte, damit ich es an deine Bedürfnisse anpassen kann.

P.S .: a.append([b]) gibt immer None zurück. Möglicherweise möchten Sie entweder [b] oder a beobachten, aber nicht das Ergebnis von append.

1
Dave J

Ich stieß auf diese Seite, während ich nach Java-Beispiel für dasselbe Problem suchte. (Wenn Ihr Fall ähnlich ist, hier ist mein Beispiel)

Der Trick ist - Sie müssen nach Schlüsseln gruppieren.

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);
    }
}
0
Thamme Gowda