我有一个看起来像这样的数据框:
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 - 因此,如果devicex
或devicey
低于0,那么它将为false
作为旁注,有一个设备跨度包含4个设备 - 而其余设备只包含2个设备。我需要考虑到这一点。
device1-device3-device6-device2
我正在使用Apache Spark DataFrame API / Spark SQL来实现这一目标。
编辑:
我可以将DataFrame转换为RDD并以这种方式计算吗?
EDIT2:
Daniel L的后续问题:
似乎是我迄今为止所理解的一个很好的答案。我有几个问题:
.aggregateByKey(Result())((result, sample) => aggregateSample(result, sample), addResults)
。我看到它与每个键值对(结果,样本)一起运行aggregateSample()
,但addResults
调用如何工作?是否会调用与键相关的每个项目,将aggregateSample
生成的每个连续结果添加到以前的结果中?我不完全明白。 .map(_._2)
在做什么?result.span
函数中,aggregateSample
在什么情况下会为空? res1.span
函数中,addResults
在哪种情况下为空?很抱歉所有的问题,但我是函数式编程,Scala和Spark的新手,所以我有很多东西可以解决这个问题!
答案 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函数的组合。