我有一个看起来像这样的数据框:
country | user | count
----------------------
Germany | Sarah| 2
China | Paul | 1
Germany | Alan | 3
Germany | Paul | 1
...
我想做的是将这个数据帧转换为另一个看起来像这样的数据:
dimension | value
--------------------------------------------
Country | [Germany -> 4, China -> 1]
--------------------------------------------
User | [Sarah -> 2, Paul -> 2, Alan -> 3]
...
起初,我试图这样做:
var newDF = Seq.empty[(String, Map[String,Long])].toDF("dimension", "value")
df.collect()
.foreach(row => { Array(0,1)
.map(pos =>
newDF = newDF.union(Seq((df.columns.toSeq(pos).toString, Map(row.mkString(",").split(",")(pos) -> row.mkString(",").split(",")(2).toLong))).toDF())
)
})
val newDF2 = newDF.groupBy("dimension").agg(collect_list("value")).as[(String, Seq[Map[String, Long]])].map {case (id, list) => (id, list.reduce(_ |+| _))}.toDF("dimension", "value")
但是collect()
杀死了我的驾驶员。因此,我尝试这样做:
class DimItem[T](val dimension: String, val value: String, val metric: T)
val items: RDD[DimItem[Long]] = df.rdd.flatMap(row => {
dims.zipWithIndex.map{case (dim, i) =>
new DimItem(dim, row(i).toString, row(13).asInstanceOf[Long])
}
})
// with the format [ DimItem(Country, Germany, 2), DimItem(User, Sarah, 2)], ...
val itemsGrouped: RDD[((String, String), Iterable[DimItem[Long]])] = items.groupBy(x => (x.dimension, x.value))
val aggregatedItems: RDD[DimItem[Long]] = itemsGrouped.map{case (key, items) => new DimItem(key._1, key._2, items.reduce((a,b) => a.metric + b.metric)}
这个想法是在一个RDD对象中保存(Country,China,1),(Country,Germany,3),(Country,Germany,1),...,然后按2个第一个键将其分组(国家/地区,中国),(国家/地区,德国),...分组后,求和。例如:拥有(Country,Germany,3),(Country,Germany,1)会变成(Country,Germany,4)。
但是一旦到达这里,它就会告诉我在items.reduce()
中存在不匹配:它期望DimItem [Long]但会得到Long。
下一步将通过键“维度”将其分组,并在“值”列中创建Map[String, Int]()
格式并将其转换为DF。
我有2个问题。
第一个:这最后一个代码正确吗?
第二:如何将该MapPartitionsRDD转换为DF?
答案 0 :(得分:1)
这是一种基于数据帧API的解决方案:
import org.apache.spark.sql.functions.{lit, map_from_arrays, collect_list}
def transform(df :DataFrame, colName: String) : DataFrame =
df.groupBy(colName)
.agg{sum("count").as("sum")}
.agg{
map_from_arrays(
collect_list(colName),
collect_list("sum")
).as("value")
}.select(lit(colName).as("dimension"), $"value")
val countryDf = transform(df, "country")
val userDf = transform(df, "user")
countryDf.unionByName(userDf).show(false)
// +---------+----------------------------------+
// |dimension|value |
// +---------+----------------------------------+
// |Country |[Germany -> 6, China -> 1] |
// |User |[Sarah -> 2, Alan -> 3, Paul -> 2]|
// +---------+----------------------------------+
分析:首先,我们按国家和用户分组分别获得按国家和用户分组的总和。接下来,我们向管道添加另一个自定义聚合,该聚合将先前的结果收集到地图中。地图将通过Spark 2.4.0中的map_from_arrays函数进行填充。我们使用collect_list收集地图的键/值。最后,我们将两个数据框结合起来以填充最终结果。