web-dev-qa-db-ja.com

Apache Sparkを使用して、キーと値のペアをキーとリストのペアに縮小します

Sparkアプリケーションを作成しており、キーと値のペアのセット(K, V1), (K, V2), ..., (K, Vn)を1つのキーと多値のペア(K, [V1, V2, ..., Vn])に結合したいと考えています。私はreduceByKey関数を何かフレーバーとともに使用してこれを行うことができるように感じています:

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

これが発生したときに表示されるエラーは次のとおりです。

'NoneType'オブジェクトには属性 'append'がありません。

私のキーは整数であり、値V1、...、Vnはタプルです。私の目標は、キーと値のリスト(タプル)を持つ単一のペアを作成することです。

41
TravisJ

MapおよびReduceByKey

reduceの入力タイプと出力タイプは同じでなければならないため、リストを集約する場合は、リストへの入力をmapする必要があります。その後、リストを1つのリストに結合します。

リストの結合

リストを1つのリストに結合するメソッドが必要です。 Phytonはいくつかの リストを結合する方法 を提供します。

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を返しますが、結合されたリストを返すメソッドが必要になるため、単に プラス記号を使用 にします。

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でこれを解決することもできますが、より複雑で、特殊なキーごとの1つを使用して " Sparkのコンバイナははるかに高速になります」 。ユースケースは、上位のソリューションに対して十分に単純です。

GroupByKey

groupByKeyただし、並列化が減少する でこれを解決することも可能であるため、ビッグデータセットの場合ははるかに遅くなる可能性があります。

48

私は会話に少し遅れていますが、ここに私の提案があります:

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

tl; drこのような操作が本当に必要な場合は、groupByKey提案どおり by @ MariusIon 。ここで提案されている他のすべてのソリューションは、直接グループ化と比較すると、明らかに非効率的であるか、少なくとも準最適です。

reduceByKeyリスト連結を使用することは、以下の理由により受け入れられない解決策です。

  • O(N)リストの初期化が必要です。
  • リストのペアへの+の各適用には、両方のリストの完全なコピーが必要です(O(N))。全体的な複雑さをO(N2
  • groupByKeyによって導入された問題を解決しません。シャッフルする必要があるデータの量と最終構造のサイズは同じです。
  • 回答の1つで推奨 とは異なり、reduceByKeygroupByKeyを使用した実装の並列性のレベルに違いはありません。

combineByKey with list.extendは次の理由で最適ではないソリューションです。

  • MergeValueO(N)リストオブジェクトを作成します(これは、新しいアイテムでlist.appendを直接使用して最適化できます)。
  • list.appendで最適化すると、groupByKeyの古い(Spark <= 1.3)実装とまったく同じになり、外部(ディスク上の)グループ化を可能にするSPARK-3074によって導入されたすべての最適化を無視します。メモリよりも大きい構造。
14
zero323

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'])]
11
Marius Ion

削減されたKVペアのタイプが元のKVペアのタイプと異なる場合にreduceByKeyを実行する場合は、関数combineByKeyを使用できます。この関数が行うことは、KVペアを取得し、それらを(キーによって)KCペアに結合することです(CはVとは異なるタイプです)。

1つは、createCombiner、mergeValue、mergeCombinersの3つの関数を指定します。最初はタイプVをタイプCに変換する方法を指定し、2番目はタイプCとタイプVを組み合わせる方法を説明し、最後はタイプCと別のタイプCを組み合わせる方法を指定します。私のコードはK-Vペアを作成します。

次の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)が失敗するため、2番目のペアで失敗します。これを回避するには、別の関数を定義します。

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

次にreduceByKey(lambda a, b: My_Extend(a,b))を呼び出します(ここでラムダ関数を使用する必要はないかもしれませんが、この場合はテストしていません。)

3
TravisJ

エラーメッセージは、クロージャの「a」のタイプに由来します。

 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

1
Seung-Hwan Lim

私はcombinedByKeyで試しました、ここに私のステップがあります

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. 個々のパーティションのコンバイナ出力をマージする関数を定義します。

1
krishna rachur

OK。私はこれが正しいことを願っています。入力は次のようになります。

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

これを間違えた場合は教えてください。あなたのニーズに合わせて調整するかもしれません。

追伸:a.append([b])は常にNoneを返します。 [b]またはaのいずれかを観察したいが、appendの結果は観察したくない場合があります。

1
Dave J

同じ問題のJavaの例を探しているときにこのページにアクセスしました。 (あなたのケースが似ている場合、ここに私の例があります)

秘isは、キーごとにグループ化する必要があることです。

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