web-dev-qa-db-fra.com

Calcul des moyennes pour chaque KEY dans un RDD Pairwise (K, V) dans Spark with Python

Je veux partager cette Apache particulière Spark avec Python solution car la documentation est assez pauvre).

Je voulais calculer la valeur moyenne des paires K/V (stockées dans un RDD Pairwise), par KEY. Voici à quoi ressemblent les exemples de données:

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

Maintenant, la séquence de code suivante est une façon moins qu'optimale de le faire, mais cela fonctionne. C'est ce que je faisais avant de trouver une meilleure solution. Ce n'est pas terrible mais - comme vous le verrez dans la section des réponses - il existe un moyen plus concis et efficace.

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

Maintenant, une bien meilleure façon de procéder consiste à utiliser la méthode rdd.aggregateByKey(). Parce que cette méthode est si mal documentée dans Apache Spark with Python - et c'est pourquoi j'ai écrit ce Q&R - jusqu'à récemment, j'avais utilisé la séquence de code ci-dessus. Mais encore une fois, c'est moins efficace, donc éviter le faire de cette façon, sauf si cela est nécessaire.

Voici comment faire de même en utilisant la méthode rdd.aggregateByKey() (recommandé) ...

Par KEY, calculez simultanément le SUM (le numérateur pour la moyenne que nous voulons calculer) et COUNT (le dénominateur pour la moyenne que nous voulons calculer):

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

Où ce qui suit est vrai sur la signification de chaque paire a et b ci-dessus (afin que vous puissiez visualiser ce qui se passe):

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

Enfin, calculez la moyenne de chaque CLÉ et collectez les résultats.

>>> 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 ...
  ]

J'espère que cette question et réponse avec aggregateByKey() vous aidera.

43
NYCeyes

À mon avis, un équivalent plus lisible d'un agrégatByKey avec deux lambdas est:

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

De cette façon, l'ensemble du calcul moyen serait:

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

Il suffit d'ajouter une note sur une solution intuitive et plus courte (mais mauvaise) à ce problème. Le livre Sam's Teach Yourself Apache Spark en 24 heures a bien expliqué ce problème dans le dernier chapitre.

En utilisant groupByKey, on peut résoudre le problème facilement comme ceci:

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

Sortie:

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

C'est intuitif et attrayant, mais ne l'utilisez pas ! groupByKey n'effectue aucune combinaison sur les mappeurs et apporte toutes les paires de valeurs de clés individuelles au réducteur.

Évitez autant que possible groupByKey. Allez avec la solution reduceByKey comme @ pat.

1
arun

Une légère amélioration de la réponse de prismalytics.io.

Il pourrait y avoir un cas où le calcul de la somme pourrait dépasser le nombre car nous sommons un grand nombre de valeurs. Nous pourrions plutôt conserver les valeurs moyennes et continuer à calculer la moyenne à partir de la moyenne et le nombre de deux parties se réduisant.

Si vous avez deux parties ayant une moyenne et compte comme (a1, c1) et (a2, c2), la moyenne globale est: total/comptes = (total1 + total2)/(nombre1 + comptes2) = (a1 * c1 + a2 * c2)/(c1 + c2)

Si nous marquons R = c2/c1, il peut être réécrit plus loin comme a1/(1 + R) + a2 * R/(1 + R) Si nous marquons encore Ri comme 1/(1 + R), nous pouvons l'écrire comme 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)

Cette approche peut être convertie en valeur-clé en utilisant simplement mapValues ​​au lieu de map et ReduceByKey au lieu de réduire.

Cela vient de: https://www.knowbigdata.com/blog/interview-questions-Apache-spark-part-2

0
Sandeep Giri