同时从数据集中聚合多个字段

时间:2018-07-10 06:55:11

标签: apache-spark apache-spark-sql

我有一个采用以下方案的数据:

sourceip
destinationip
packets sent

我想从这些数据中计算出几个聚合字段,并具有以下模式:

ip 
packets sent as sourceip
packets sent as destination

在RDD的快乐日子里,我可以使用aggregate,定义{ip-> []}的映射,并计算在相应数组位置中的出现次数。

在“数据集/数据框”聚合中不再可用,而是可以使用UDAF,不幸的是,根据我对UDAF的经验,它们是不可变的,意味着它们无法使用(必须在每次地图更新时创建一个新实例) example + explanation here

一方面,从技术上讲,我可以将数据集转换为RDD,进行聚合等,然后返回数据集。我希望这会导致性能下降,因为数据集更加优化。由于复制,UDAF毫无疑问。

还有其他执行聚合的方法吗?

4 个答案:

答案 0 :(得分:3)

听起来您需要标准的meltHow to melt Spark DataFrame?)和pivot组合:

val df = Seq(
  ("192.168.1.102", "192.168.1.122", 10),
  ("192.168.1.122", "192.168.1.65", 10),
  ("192.168.1.102", "192.168.1.97", 10)
).toDF("sourceip", "destinationip", "packets sent")


df.melt(Seq("packets sent"), Seq("sourceip", "destinationip"), "type", "ip")
  .groupBy("ip")
  .pivot("type", Seq("sourceip", "destinationip"))
  .sum("packets sent").na.fill(0).show

// +-------------+--------+-------------+             
// |           ip|sourceip|destinationip|
// +-------------+--------+-------------+
// | 192.168.1.65|       0|           10|
// |192.168.1.102|      20|            0|
// |192.168.1.122|      10|           10|
// | 192.168.1.97|       0|           10|
// +-------------+--------+-------------+

答案 1 :(得分:2)

一种无需任何自定义聚合的方法是使用flatMap(或将explode用于数据帧),如下所示:

case class Info(ip : String, sent : Int, received : Int)
case class Message(from : String, to : String, p : Int)
val ds = Seq(Message("ip1", "ip2", 5), 
             Message("ip2", "ip3", 7), 
             Message("ip2", "ip1", 1), 
             Message("ip3", "ip2", 3)).toDS()

ds
    .flatMap(x => Seq(Info(x.from, x.p, 0), Info(x.to, 0, x.p)))
    .groupBy("ip")
    .agg(sum('sent) as "sent", sum('received) as "received")
    .show


// +---+----+--------+
// | ip|sent|received|
// +---+----+--------+
// |ip2|   8|       8|
// |ip3|   3|       7|
// |ip1|   5|       1|
// +---+----+--------+

就性能而言,我不确定flatMap与自定义聚合相比是否有所改进。

答案 2 :(得分:1)

这是使用explode的pyspark版本。它比较冗长,但逻辑与flatMap版本完全相同,仅使用纯数据帧代码。

sc\
  .parallelize([("ip1", "ip2", 5), ("ip2", "ip3", 7), ("ip2", "ip1", 1), ("ip3", "ip2", 3)])\
  .toDF(("from", "to", "p"))\
  .select(F.explode(F.array(\
      F.struct(F.col("from").alias("ip"),\
               F.col("p").alias("received"),\
               F.lit(0).cast("long").alias("sent")),\
      F.struct(F.col("to").alias("ip"),\
               F.lit(0).cast("long").alias("received"),\
               F.col("p").alias("sent")))))\
  .groupBy("col.ip")\
  .agg(F.sum(F.col("col.received")).alias("received"), F.sum(F.col("col.sent")).alias("sent"))

// +---+----+--------+
// | ip|sent|received|
// +---+----+--------+
// |ip2|   8|       8|
// |ip3|   3|       7|
// |ip1|   5|       1|
// +---+----+--------+

答案 3 :(得分:-1)

由于您没有提及上下文和聚合,因此您可以执行以下操作,

val df = ??? // your dataframe/ dataset

从Spark来源:

  

(特定于标度的)通过从列中指定映射来计算聚合   聚合方法的名称。结果数据框还将包含   分组列。可用的汇总方法为avg,max,   最小,总和,计数。

     

//选择最老员工的年龄和总费用   对于每个部门

 df
 .groupBy("department")
 .agg(Map(
      "age" -> "max",
      "expense" -> "sum"   
     ))