我是这样的PairRDD(word,wordCount)。现在,我需要为每个单词计算单词总数中出现次数的百分比,获得这样的最终PairRDD(单词,(wordCount,百分比))。
我尝试过:
val pairs = .... .cache() // (word, wordCount)
val wordsCount = pairs.map(_._2).reduce(_+_)
pairs.map{
case (word, count) => (word, (count, BigDecimal(count/wordsCount.toDouble * 100).setScale(3, BigDecimal.RoundingMode.HALF_UP).toDouble))
}
但它看起来效率不高(我是初学者)。有没有更好的方法呢?
答案 0 :(得分:3)
在计算wordsCount
时,map(_._2).reduce(_ + _)
更方便地表达aggregate
:
val pairs = .... .cache() // (word, wordCount)
val wordsCount = pairs.aggregate(0)((sum, pair) => sum + pair._2, _ + _)
第一个参数是初始总计数,应为零。接下来的两个参数位于单独的参数列表中,并为对RDD 的每个成员执行。第二组参数中的第一个((sum, pair) => sum + pair._2
,称为序列运算符)将分区中的每个值添加到该分区的当前总和值;而第二个(_ + _
,称为组合运算符)组合了来自不同分区的计数。主要好处是,此操作由每个分区并行执行,并保证不会对该数据进行昂贵的重新分区(a.k.a。 shuffling )。 (当需要在群集中的节点之间传输数据时,会发生重新分区,并且由于网络通信而导致数据非常慢。)
虽然aggregate
不会影响数据的分区,但您应该注意,如果您的数据已分区,map
操作将删除分区方案 - 因为的可能性对RDD 中的密钥可能会被转换更改。如果您对map
转换的结果执行任何进一步的转换,这可能会导致后续的重新分区。也就是说,map
操作后跟reduce
不会导致重新分区,但由于额外的步骤(但不是很大),效果可能略低一些。
如果您担心总字数可能会溢出Int
类型,则可以使用BigInt
代替:
val wordsCount = pairs.aggregate(BigInt(0))((sum, pair) => sum + pair._2, _ + _)
至于使用单词count和百分比作为值生成对RDD ,您应该使用mapValues
而不是map
。同样mapValues
将保留任何现有的分区方案(因为它保证密钥不会被转换更改),而map
将删除它。此外,mapValues
更简单,因为您不需要处理键值:
pairs.mapValues(c => (c, c * 100.0 / wordsCount))
这应该为您的目的提供足够的准确性。在检索和输出值时,我只会担心舍入和使用BigDecimal
。但是,如果您确实需要,您的代码将如下所示:
pairs.mapValues{c =>
(c, BigDecimal(c * 100.0 / wordsCount).setScale(3, BigDecimal.RoundingMode.HALF_UP).toDouble))
}