如果自上次事件以来传感器读数没有更改,则如何在未发送传感器读数时计算窗口上的聚合?

Posted

技术标签:

【中文标题】如果自上次事件以来传感器读数没有更改,则如何在未发送传感器读数时计算窗口上的聚合?【英文标题】:how to calculate aggregations on a window when sensor readings are not sent if they haven't changed since last event? 【发布时间】:2018-10-10 19:00:51 【问题描述】:

只有在传感器值自上次事件以来发生变化时才发送新事件时,如何从传感器计算窗口上的聚合?传感器读数在固定时间获取,例如每 5 秒一次,但仅在自上次读数后读数发生变化时才会转发。

所以,如果我想为每个设备创建一个平均信号强度:

eventsDF = ... 
avgSignalDF = eventsDF.groupBy("deviceId").avg("signal_strength")

例如设备在一分钟窗口内发送的事件:

event_time  device_id  signal_strength
12:00:00    1          5
12:00:05    1          4
12:00:30    1          5
12:00:45    1          6
12:00:55    1          5

填充了实际未发送的事件的同一数据集:

event_time  device_id  signal_strength
12:00:00    1          5
12:00:05    1          4
12:00:10    1          4
12:00:15    1          4
12:00:20    1          4
12:00:25    1          4
12:00:30    1          5
12:00:35    1          5
12:00:40    1          5
12:00:45    1          6
12:00:50    1          6
12:00:55    1          5

信号强度sum57avg57/12

如何通过 Spark 结构化流式传输和根据推断值计算平均值来推断这些缺失数据?

注意:我使用平均值作为聚合的示例,但该解决方案需要适用于任何聚合函数。

【问题讨论】:

您需要数据集的设备 ID 具有先前的平均值并左连接到接收到的事件,从左侧获取设备 ID,从右侧获取值,如果它为空,则使用右侧的先前平均值跨度> 您能用示例数据集和代码 sn-p 来展示这一点吗? Arbitrary Stateful Operations? 【参考方案1】:

已编辑:

我已修改逻辑以仅根据过滤后的dataframe 计算平均值,以便解决差距。

//input structure
case class StreamInput(event_time: Long, device_id: Int, signal_strength: Int)
//columns for which we want to maintain state
case class StreamState(prevSum: Int, prevRowCount: Int, prevTime: Long, prevSignalStrength: Int, currentTime: Long, totalRow: Int, totalSum: Int, avg: Double)
//final result structure
case class StreamResult(event_time: Long, device_id: Int, signal_strength: Int, avg: Double)

val filteredDF = ???  //get input(filtered rows only)

val interval = 5  // event_time interval

// using .mapGroupsWithState to maintain state for runningSum & total row count till now

// you need to set the timeout threshold to indicate how long you wish to maintain the state
val avgDF = filteredDF.groupByKey(_.device_id)
  .mapGroupsWithState[StreamState, StreamResult](GroupStateTimeout.NoTimeout()) 

  case (id: Int, eventIter: Iterator[StreamInput], state: GroupState[StreamState]) => 
    val events = eventIter.toSeq

    val updatedSession = if (state.exists) 
      //if state exists update the state with the new values
      val existingState = state.get

      val prevTime = existingState.currentTime
      val currentTime = events.map(x => x.event_time).last
      val currentRowCount = (currentTime - prevTime)/interval
      val rowCount = existingState.rowCount + currentRowCount.toInt
      val currentSignalStength = events.map(x => x.signal_strength).last

      val total_signal_strength = currentSignalStength + 
        (existingState.prevSignalStrength * (currentRowCount -1)) + 
        existingState.total_signal_strength

      StreamState(
        existingState.total_signal_strength,
        existingState.rowCount,
        prevTime,
        currentSignalStength,
        currentTime,
        rowCount,
        total_signal_strength.toInt,
        total_signal_strength/rowCount.toDouble
      )

     else 
      // if there are no earlier state
      val runningSum = events.map(x => x.signal_strength).sum
      val size = events.size.toDouble
      val currentTime = events.map(x => x.event_time).last
      StreamState(0, 1, 0, runningSum, currentTime, 1, runningSum, runningSum/size)
    

    //save the updated state
    state.update(updatedSession)
    StreamResult(
      events.map(x => x.event_time).last,
      id,
      events.map(x => x.signal_strength).last,
      updatedSession.avg
    )
  


val result = avgDF
  .writeStream
  .outputMode(OutputMode.Update())
  .format("console")
  .start

想法是计算两个新列:

    totalRowCount:如果您没有过滤,则应该存在的行数的总和。 total_signal_strength:到目前为止signal_strength 的运行总数。 (这也包括错过的行总数)。

计算公式:

total_signal_strength = 
  current row's signal_strength  +  
  (total_signal_strength of previous row * (rowCount -1)) + 
  //rowCount is the count of missed rows computed by comparing previous and current event_time.
  previous total_signal_strength

中间状态的格式:

+----------+---------+---------------+---------------------+--------+
|event_time|device_id|signal_strength|total_signal_strength|rowCount|
+----------+---------+---------------+---------------------+--------+
|         0|        1|              5|                    5|       1|
|         5|        1|              4|                    9|       2|
|        30|        1|              5|                   30|       7|
|        45|        1|              6|                   46|      10|
|        55|        1|              5|                   57|      12|
+----------+---------+---------------+---------------------+--------+

最终输出:

+----------+---------+---------------+-----------------+
|event_time|device_id|signal_strength|              avg|
+----------+---------+---------------+-----------------+
|         0|        1|              5|              5.0|
|         5|        1|              4|              4.5|
|        30|        1|              5|4.285714285714286|
|        45|        1|              6|              4.6|
|        55|        1|              5|             4.75|
+----------+---------+---------------+-----------------+

【讨论】:

【参考方案2】:

数学上等价于基于持续时间的加权平均问题:

avg=(signal_strength*duration)/60

这里的挑战是获取每个信号的持续时间,这里的一个选项是针对每个微批次,在驱动程序中收集结果然后这都是统计问题,要获得持续时间,您可以在开始时间进行左移然后减去,类似这样:

window.start.leftShift(1)-window.start

这会给你:

event_time  device_id  signal_strength duration
12:00:00    1          5                  5(5-0)
12:00:05    1          4                  25(30-5)
12:00:30    1          5                  15(45-30)
12:00:45    1          6                  10(55-45)
12:00:55    1          5                  5 (60-55)

(5*5+4*25+5*15+6*10+5*5)/60=57/12

从 Spark 结构化流式传输 2.3.2 开始,您需要编写自己的自定义接收器来收集每个阶段的结果到驱动程序并进行类似的数学运算。

【讨论】:

以上是关于如果自上次事件以来传感器读数没有更改,则如何在未发送传感器读数时计算窗口上的聚合?的主要内容,如果未能解决你的问题,请参考以下文章

VB - 检索自上次更改以来对 .txt 文件的新增内容

如何计算自上次事件==1 以来的天数?

Git:如何将自上次提交以来的更改移动到新分支

跟踪自上次尝试______以来的时间

如何计算自特定列中发生事件以来经过的时间 - Pandas DataFrames

sh 自上次更改某个文件以来获取所有提交