我对火花比较新,我试图同时按多个键对数据进行分组。
我有一些我映射的数据,所以最终看起来像这样:
((K1,K2,K3),(V1,V2))
我的目标是按(K1,K2,K3)分组,并分别将V1和V2加起来:
((K1,K2,K3),(SUM(V1),SUM(V2))
这是我到目前为止的代码:
val filepath = "file.avro"
val sc = new SparkContext(sparkConf)
val sqlContext = new SQLContext(sc)
val data = sqlContext.read.avro(filepath)
val dataRDD = data.rdd
val mappedDataRDD = dataRDD.map{
case (v, w, x, y, z) => ((v,w,x), (y, z))
}.reduceByKey((x,y)=> ???)
所以我正在寻找如何reduceByKey,以便我可以按(v,w,x)键分组并对y和z求和。
答案 0 :(得分:4)
我认为您正在寻找和应该使用的是aggregateByKey
。
此方法有两个参数组。第一个参数组取累加器的起始值。第二个参数组有两个函数,
现在您可以按照以下方式使用它,
val (accZeroY, accZeroZ): (Long, Long) = (0, 0)
val mappedDataRDD = dataRDD
.map({
case (v, w, x, y, z) => ((v,w,x), (y, z))
})
.aggregateByKey((accZeroY, accZeroZ))(
{ case ((accY, accZ), (y, z)) => (accY + y, accZ + z) }
{ case ((accY1, accZ1), (accY2, accZ2)) => (accY1 + accY2, accZ1 + accZ2) }
)
正如您应该观察到的那样,第二个参数组中的两个函数在这种情况下实际上是相同的。只有当type of the needed accumulation
与key-value-RDD
或PairRDD
中的值类型相同时,才有可能实现此目的。
在这种情况下,您也可以使用reduceByKey
,您可以将其视为aggregateByKey
,并且函数参数传递的函数相同,
val mappedDataRDD = dataRDD
.map({
case (v, w, x, y, z) => ((v,w,x), (y, z))
})
.reduceByKey(
{ case ((accY, accZ), (y, z)) => (accY + y, accZ + z) }
)
但在我看来,should NOT
使用reduceBykey
。我建议使用aggregateByKey
的原因是因为在大型数据集上积累值有时会产生超出类型范围的结果。
例如,在您的情况下,我怀疑您的(x, y)
实际上是(Int, Int)
,并且您希望使用(v, w, x)
作为关键积累它。但是,无论何时大量添加Int
,请记住,结果最终可能会超过Int
可以处理的结果。
所以...你会希望你的积累类型更像(Int, Int)
(Long, Long)
reduceByKey
,而aggregateByKey
不允许你这样做。所以...我会说,也许你正在寻找并应该使用To
答案 1 :(得分:1)
你也可以使用reduceByKey
,你只需要小心你想要的东西。我简化了这个例子,但它暴露了你想要的东西。
val rdd = sc.parallelize(List(
(1, 2, 1, 1, 1),
(1, 2, 1, 2, 2),
(1, 3, 2, 4, 4)))
rdd.map {
case (k1, k2, k3, v1, v2) => ((k1, k2, k3), (v1, v2))
}.reduceByKey {
// You receive two values which are actually tuples, so we treat them like that.
case ((x1, y1), (x2, y2)) => (x1 + x2, y1 + y2)
}.collect()
//res0: Array[((Int, Int), (Int, Int))] = Array(((1,2,1),(3,3)), ((1,3,2),(4,4)))