web-dev-qa-db-ja.com

Spark with Python

この特定のApache Spark=とPythonソリューションを共有したい。

KEYにより、K/Vペア(ペアワイズRDDに格納)の平均値を計算したかった。サンプルデータは次のようになります。

>>> rdd1.take(10) # Show a small sample.
[(u'2013-10-09', 7.60117302052786),
(u'2013-10-10', 9.322709163346612),
(u'2013-10-10', 28.264462809917358),
(u'2013-10-07', 9.664429530201343),
(u'2013-10-07', 12.461538461538463),
(u'2013-10-09', 20.76923076923077),
(u'2013-10-08', 11.842105263157894),
(u'2013-10-13', 32.32514177693762),
(u'2013-10-13', 26.249999999999996),
(u'2013-10-13', 10.693069306930692)]

これで、次のコードシーケンスは最適以下の方法ですが、動作します。より良い解決策を見つける前に、私がやっていました。ひどいことではありませんが、答えのセクションで見るように、もっと簡潔で効率的な方法があります。

>>> import operator
>>> countsByKey = sc.broadcast(rdd1.countByKey()) # SAMPLE OUTPUT of countsByKey.value: {u'2013-09-09': 215, u'2013-09-08': 69, ... snip ...}
>>> rdd1 = rdd1.reduceByKey(operator.add) # Calculate the numerators (i.e. the SUMs).
>>> rdd1 = rdd1.map(lambda x: (x[0], x[1]/countsByKey.value[x[0]])) # Divide each SUM by it's denominator (i.e. COUNT)
>>> print(rdd1.collect())
  [(u'2013-10-09', 11.235365503035176),
   (u'2013-10-07', 23.39500642456595),
   ... snip ...
  ]
27
NYCeyes

これを行うためのはるかに良い方法は、rdd.aggregateByKey()メソッドを使用することです。その方法は、Apache Spark with Python documentation-そして私がこのQ&Aを書いた理由-最近までは上記のコードシーケンスを使用していましたが、やはり効率が悪いため、avoid必要な場合を除いてそのように実行します。

rdd.aggregateByKey()メソッド(recommended)を使用して同じことを行う方法は次のとおりです...

KEYにより、SUM(計算する平均の分子)とCOUNT(計算する平均の分母)を同時に計算します。

_>>> aTuple = (0,0) # As of Python3, you can't pass a literal sequence to a function.
>>> rdd1 = rdd1.aggregateByKey(aTuple, lambda a,b: (a[0] + b,    a[1] + 1),
                                       lambda a,b: (a[0] + b[0], a[1] + b[1]))
_

上記のabの各ペアの意味について次のことが当てはまります(そのため、何が起こっているかを視覚化できます)。

_   First lambda expression for Within-Partition Reduction Step::
   a: is a Tuple that holds: (runningSum, runningCount).
   b: is a SCALAR that holds the next Value

   Second lambda expression for Cross-Partition Reduction Step::
   a: is a Tuple that holds: (runningSum, runningCount).
   b: is a Tuple that holds: (nextPartitionsSum, nextPartitionsCount).
_

最後に、各キーの平均を計算し、結果を収集します。

_>>> finalResult = rdd1.mapValues(lambda v: v[0]/v[1]).collect()
>>> print(finalResult)
      [(u'2013-09-09', 11.235365503035176),
       (u'2013-09-01', 23.39500642456595),
       (u'2013-09-03', 13.53240060820617),
       (u'2013-09-05', 13.141148418977687),
   ... snip ...
  ]
_

この質問とaggregateByKey()の回答が役立つことを願っています。

43
NYCeyes

私の考えでは、2つのラムダを持つaggregateByKeyと同等の読みやすいものは次のとおりです。

rdd1 = rdd1 \
    .mapValues(lambda v: (v, 1)) \
    .reduceByKey(lambda a,b: (a[0]+b[0], a[1]+b[1]))

このようにして、全体の平均計算は次のようになります。

avg_by_key = rdd1 \
    .mapValues(lambda v: (v, 1)) \
    .reduceByKey(lambda a,b: (a[0]+b[0], a[1]+b[1])) \
    .mapValues(lambda v: v[0]/v[1]) \
    .collectAsMap()
4
pat

この問題に対する直観的で短い(ただし悪い)ソリューションに関するメモを追加するだけです。本 Sam's Teach Yourself Apache Spark in 24 Hours は、この問題を前の章で十分に説明しています。

groupByKeyを使用すると、次のように問題を簡単に解決できます。

rdd = sc.parallelize([
        (u'2013-10-09', 10),
        (u'2013-10-09', 10),
        (u'2013-10-09', 13),
        (u'2013-10-10', 40),
        (u'2013-10-10', 45),
        (u'2013-10-10', 50)
    ])

rdd \
.groupByKey() \
.mapValues(lambda x: sum(x) / len(x)) \
.collect()

出力:

[('2013-10-10', 45.0), ('2013-10-09', 11.0)]

これは直感的で魅力的ですが、使用しないでくださいgroupByKeyはマッパーでの結合を行わず、すべての個々のキーと値のペアをリデューサーにもたらします。

groupByKeyをできるだけ避けてください。 @patのようなreduceByKeyソリューションを使用します。

1
arun

Prismalytics.ioの答えがわずかに強化されました。

膨大な数の値を合計しているため、合計を計算すると数値がオーバーフローする場合があります。代わりに、平均値を保持し、平均から2つの部分のカウントが減少することから平均を計算し続けることができます。

2つの部分に平均があり、(a1、c1)および(a2、c2)としてカウントされる場合、全体の平均は次のようになります:total/counts =(total1 + total2)/(count1 + counts2)=(a1 * c1 + a2 * c2)/(c1 + c2)

R = c2/c1とマークすると、a1 /(1 + R)+ a2 * R /(1 + R)としてさらに書き直すことができます。Riを1 /(1 + R)とさらにマークすると、 a1 * Ri + a2 * R * Riと書く

myrdd = sc.parallelize([1.1, 2.4, 5, 6.0, 2, 3, 7, 9, 11, 13, 10])
sumcount_rdd = myrdd.map(lambda n : (n, 1))
def avg(A, B):
    R = 1.0*B[1]/A[1]
    Ri = 1.0/(1+R);
    av = A[0]*Ri + B[0]*R*Ri
    return (av, B[1] + A[1]);

(av, counts) = sumcount_rdd.reduce(avg)
print(av)

このアプローチは、mapの代わりにmapValuesを使用し、reduceの代わりにreduceByKeyを使用するだけで、Key-Valueに変換できます。

これは: https://www.knowbigdata.com/blog/interview-questions-Apache-spark-part-2

0
Sandeep Giri