我正在学习火花,并且没有使用hadoop的经验。
问题
我正在尝试在对AggregateByKey的同一调用中计算总和和平均值。
让我分享到目前为止我所做的一切。
设置数据
val categoryPrices = List((1, 20), (1, 25), (1, 10), (1, 45))
val categoryPricesRdd = sc.parallelize(categoryPrices)
尝试在对AggregateByKey的同一调用中计算平均值。这不起作用。
val zeroValue1 = (0, 0, 0.0) // (count, sum, average)
categoryPricesRdd.
aggregateByKey(zeroValue1)(
(tuple, prevPrice) => {
val newCount = tuple._1 + 1
val newSum = tuple._2 + prevPrice
val newAverage = newSum/newCount
(newCount, newSum, newAverage)
},
(tuple1, tuple2) => {
val newCount1 = tuple1._1 + tuple2._1
val newSum1 = tuple1._2 + tuple2._2
// TRYING TO CALCULATE THE RUNNING AVERAGE HERE
val newAverage1 = ((tuple1._2 * tuple1._1) + (tuple2._2 * tuple2._1))/(tuple1._1 + tuple2._1)
(newCount1, newSum1, newAverage1)
}
).
collect.
foreach(println)
结果:每次打印不同的平均值
只需先进行求和,然后在单独的操作中计算平均值。可行。
val zeroValue2 = (0, 0) // (count, sum, average)
categoryPricesRdd.
aggregateByKey(zeroValue2)(
(tuple, prevPrice) => {
val newCount = tuple._1 + 1
val newSum = tuple._2 + prevPrice
(newCount, newSum)
},
(tuple1, tuple2) => {
val newCount1 = tuple1._1 + tuple2._1
val newSum1 = tuple1._2 + tuple2._2
(newCount1, newSum1)
}
).
map(rec => {
val category = rec._1
val count = rec._2._1
val sum = rec._2._2
(category, count, sum, sum/count)
}).
collect.
foreach(println)
每次都打印相同的结果: (1,4,100,25)
我想我了解seqOp and CombOp之间的区别。鉴于操作可以在不同服务器上的多个分区之间拆分数据,我的理解是seqOp对单个分区中的数据进行操作,然后combOp合并从不同分区接收的数据。如果这是错误的,请更正。
但是,我不了解某些非常基本的内容。看起来我们无法在同一调用中同时计算总和和平均值。如果是这样,请帮助我理解原因。
答案 0 :(得分:1)
与您在average
中的seqOp
聚合相关的计算:
val newAverage = newSum/newCount
和combOp
中的
val newAverage1 = ((tuple1._2 * tuple1._1) + (tuple2._2 * tuple2._1)) / (tuple1._1 + tuple2._1)
不正确。
让我们说前三个元素在一个分区中,最后一个元素在另一个分区中。您的seqOp
会生成(计数,总和,平均值)元组,如下所示:
Partition #1: [20, 25, 10]
--> (1, 20, 20/1)
--> (2, 45, 45/2)
--> (3, 55, 55/3)
Partition #2: [45]
--> (1, 45, 45/1)
接下来,交叉分区combOp
将合并两个分区中的2个元组以得出:
((55 * 3) + (45 * 1)) / 4
// Result: 52
从以上步骤可以看到,如果RDD元素的顺序或分区不同,则average
值可能会不同。
您的第二种方法行之有效,因为average
的定义是总和超过总计数,因此在先计算总和和计数值之后可以更好地进行计算。