如何组合和聚合数据框行

时间:2015-08-20 11:06:12

标签: sql apache-spark apache-spark-sql

我有一个看起来像这样的数据框:

endPoint    power    time
device1      -4       0
device2       3       0
device3      -2       0
device4       0       0
device5       5       0
device6      -5       0
device1       4       1
device2      -3       1
device3       5       1
device4      -2       1
device5       1       1
device6       4       1
....
device1       6       x
device2      -5       x
device3       4       x
device4       3       x
device5      -1       x
device6       1       x

我想把它改成这样的东西:

span               powerAboveThreshold    time
device1-device3        true                 0
device2-device6        true                 0
...
devicex-devicey        false                w

我想将行聚合到两个新列中,并按时间和跨度对其进行分组。 powerAboveThreshold的值取决于范围内任一设备的power是否高于0 - 因此,如果devicexdevicey低于0,那么它将为false

作为旁注,有一个设备跨度包含4个设备 - 而其余设备只包含2个设备。我需要考虑到这一点。

device1-device3-device6-device2

我正在使用Apache Spark DataFrame API / Spark SQL来实现这一目标。

编辑:

我可以将DataFrame转换为RDD并以这种方式计算吗?

EDIT2:

Daniel L的后续问题:

似乎是我迄今为止所理解的一个很好的答案。我有几个问题:

  • 如果我从DataFrame转换它,RDD是否具有预期的结构?
  • 该计划的这一部分是怎么回事? .aggregateByKey(Result())((result, sample) => aggregateSample(result, sample), addResults)。我看到它与每个键值对(结果,样本)一起运行aggregateSample(),但addResults调用如何工作?是否会调用与键相关的每个项目,将aggregateSample生成的每个连续结果添加到以前的结果中?我不完全明白。
  • .map(_._2)在做什么?
  • result.span函数中,aggregateSample在什么情况下会为空?
  • res1.span函数中,addResults在哪种情况下为空?

很抱歉所有的问题,但我是函数式编程,Scala和Spark的新手,所以我有很多东西可以解决这个问题!

2 个答案:

答案 0 :(得分:2)

我不确定你是否可以在DataFrame上进行文本连接(也许你可以),但是在正常的RDD上你可以这样做:

val rdd = sc.makeRDD(Seq(
  ("device1", -4, 0),
  ("device2", 3, 0),
  ("device3", -2, 0),
  ("device4", 0, 0),
  ("device5", 5, 0),
  ("device6", -5, 0),
  ("device1", 4, 1),
  ("device2", -3, 1),
  ("device3", 5, 1),
  ("device4", 1, 1),
  ("device5", 1, 1),
  ("device6", 4, 1)))

val spanMap = Map(
"device1" -> 1,
"device2" -> 1,
"device3" -> 1,
"device4" -> 2,
"device5" -> 2,
"device6" -> 1
)

case class Result(var span: String = "", var aboveThreshold: Boolean = true, var time: Int = -1)
def aggregateSample(result: Result, sample: (String, Int, Int)) = {
  result.time = sample._3
  result.aboveThreshold = result.aboveThreshold && (sample._2 > 0)
  if(result.span.isEmpty)
    result.span += sample._1
  else
    result.span += "-" + sample._1
  result
}
def addResults(res1: Result, res2: Result) = {
  res1.aboveThreshold = res1.aboveThreshold && res2.aboveThreshold
  if(res1.span.isEmpty)
    res1.span += res2.span
  else
    res1.span += "-" + res2.span
  res1
}

val results = rdd
  .map(x => (x._3, spanMap.getOrElse(x._1, 0)) -> x)  // Create a key to agregate with, by time and span
  .aggregateByKey(Result())((result, sample) => aggregateSample(result, sample), addResults)
  .map(_._2)

results.collect().foreach(println(_))

它会打印出来,这就是我理解你需要的东西:

Result(device4-device5,false,0)
Result(device4-device5,true,1)
Result(device1-device2-device3-device6,false,0)
Result(device1-device2-device3-device6,false,1)

这里我使用的地图告诉我哪些设备在一起(对于你的配对和4设备例外),你可能想用其他功能替换它,硬编码为静态功能以避免序列化或使用广播变量

===================编辑========================== < / p>

  

似乎是迄今为止我所理解的一个很好的答案。

随意提出/接受它,帮助我寻找回答的人: - )

  

如果我从DataFrame转换它,RDD是否具有预期的结构?

是的,主要区别在于DataFrame包含一个模式,所以它可以更好地优化底层调用,应该可以直接使用这个模式或映射到我用过的元组作为一个例子,我这​​样做主要是为了方便。 Hernan刚刚发布了另一个答案,其中显示了其中一些(并且还复制了我为方便起见时使用的初始测试数据),所以我不会重复这一段,但正如他所提到的,你的设备跨度分组和演示是棘手的,因此,我更倾向于在RDD上使用更为必要的方法。

  

该计划的这一部分发生了什么? .aggregateByKey(Result())((result,sample)=&gt; aggregateSample(result,sample),addResults)。我看到它使用每个键值对(结果,样本)运行aggregateSample(),但addResults调用如何工作?是否会调用与键相关的每个项目,将aggregateSample生成的每个连续结果添加到以前的结果中?我不完全明白。

aggregateByKey是一个非常优化的功能。为了避免将所有数据从一个节点混洗到另一个节点以便稍后合并,它首先在本地(第一个函数)对每个键的单个结果进行局部样本聚合。他们将这些结果改组并添加(第二个功能)。

  

什么是.map(_._ 2)在做什么?

只需在聚合后丢弃密钥/值RDD中的密钥,就不再关心它了,所以我只保留结果。

  

在什么情况下,result.span在aggregateSample函数中是空的?   在什么情况下,res1.span在addResults函数中是空的?

当你进行聚合时,你需要提供一个&#34;零&#34;值。例如,如果你在汇总数字的地方,Spark会做(0 + firstValue)+ secondValue ...等等if子句阻止添加虚假的&#39; - &#39;在第一个设备名称之前,因为我们将它放在设备之间。与在项目列表中使用一个额外的逗号等进行处理没有什么不同。查看aggregateByKey的文档和示例,它将对您有所帮助。

答案 1 :(得分:2)

这是数据帧的实现(没有连接名称):

val data = Seq(
  ("device1", -4, 0),
  ("device2", 3, 0),
  ("device3", -2, 0),
  ("device4", 0, 0),
  ("device5", 5, 0),
  ("device6", -5, 0),
  ("device1", 4, 1),
  ("device2", -3, 1),
  ("device3", 5, 1),
  ("device4", 1, 1),
  ("device5", 1, 1),
  ("device6", 4, 1)).toDF("endPoint", "power", "time")

val mapping = Seq(
  "device1" -> 1,
  "device2" -> 1,
  "device3" -> 1,
  "device4" -> 2,
  "device5" -> 2,
  "device6" -> 1).toDF("endPoint", "span")

data.as("A").
  join(mapping.as("B"), $"B.endpoint" === $"A.endpoint", "inner").
  groupBy($"B.span", $"A.time").
  agg(min($"A.power" > 0).as("powerAboveThreshold")).
  show()

连接名称相当困难,这需要您编写自己的UDAF(在下一版本的Spark中支持),或者使用Hive函数的组合。