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はタプルです。私の目標は、キーと値のリスト(タプル)を持つ単一のペアを作成することです。
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
、 ただし、並列化が減少する でこれを解決することも可能であるため、ビッグデータセットの場合ははるかに遅くなる可能性があります。
私は会話に少し遅れていますが、ここに私の提案があります:
>>> 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')])]
tl; drこのような操作が本当に必要な場合は、groupByKey
提案どおり by @ MariusIon 。ここで提案されている他のすべてのソリューションは、直接グループ化と比較すると、明らかに非効率的であるか、少なくとも準最適です。
reduceByKey
リスト連結を使用することは、以下の理由により受け入れられない解決策です。
+
の各適用には、両方のリストの完全なコピーが必要です(O(N))。全体的な複雑さをO(N2)。groupByKey
によって導入された問題を解決しません。シャッフルする必要があるデータの量と最終構造のサイズは同じです。reduceByKey
とgroupByKey
を使用した実装の並列性のレベルに違いはありません。combineByKey
with list.extend
は次の理由で最適ではないソリューションです。
MergeValue
にO(N)リストオブジェクトを作成します(これは、新しいアイテムでlist.append
を直接使用して最適化できます)。list.append
で最適化すると、groupByKey
の古い(Spark <= 1.3)実装とまったく同じになり、外部(ディスク上の)グループ化を可能にするSPARK-3074によって導入されたすべての最適化を無視します。メモリよりも大きい構造。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'])]
削減された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))
を呼び出します(ここでラムダ関数を使用する必要はないかもしれませんが、この場合はテストしていません。)
エラーメッセージは、クロージャの「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
私は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でキャプチャしたアキュムレータ値にマージする関数を定義します注:-最初の手順でアキュムレータ値がリストに変換されたため、この関数のリストに値を変換します
個々のパーティションのコンバイナ出力をマージする関数を定義します。
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
の結果は観察したくない場合があります。
同じ問題の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);
}
}