使用Python计算Spark中的Pairwise(K,V)RDD中每个KEY的平均值

时间:2015-04-28 21:18:12

标签: python apache-spark aggregate average rdd

我想与Python解决方案分享这个特定的Apache Spark,因为它的文档很差。

我想通过KEY计算K / V对(存储在Pairwise 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 ...
  ]

4 个答案:

答案 0 :(得分:41)

现在更好的方法是使用rdd.aggregateByKey()方法。因为Apache Spark中的方法文档记录很差 - ,这也是我编写此Q& A 的原因 - 直到最近我才使用上面的代码序列。但同样,它效率较低,因此除非必要,否则避免这样做。

以下是使用rdd.aggregateByKey()方法执行相同操作的方法(推荐)......

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

最后,计算每个KEY的平均值,并收集结果。

>>> 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()的这个问题和答案会有所帮助。

答案 1 :(得分:4)

在我看来,与具有两个lambda的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()

答案 2 :(得分:1)

只需添加关于此问题的直观且较短(但不好)解决方案的说明。书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不对映射器进行任何组合,并将所有单独的键值对带到reducer。

尽可能避免使用groupByKey。使用像@ pat's这样的reduceByKey解决方案。

答案 3 :(得分:0)

稍微改善了prismalytics.io的答案。

可能存在计算总和可能溢出数量的情况,因为我们总结了大量的值。我们可以保持平均值并保持计算平均值和两个部分的计数减少。

如果您有两个部分具有平均值并计为(a1,c1)和(a2,c2),则总体平均值为: 总计/计数=(总计1 +总计2)/(计数1 +计数2)=(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)

只需使用mapValues而不是map和reduceByKey而不是reduce,就可以将此方法转换为键值。

来自:https://www.knowbigdata.com/blog/interview-questions-apache-spark-part-2