web-dev-qa-db-de.com

wie werden Aggregationen in einem Fenster berechnet, wenn keine Sensorwerte gesendet werden, die sich seit dem letzten Ereignis nicht geändert haben?

Wie kann ich Aggregationen in einem Fenster von einem Sensor berechnen, wenn neue Ereignisse nur gesendet werden, wenn sich der Sensorwert seit dem letzten Ereignis geändert hat? Die Sensorablesungen werden zu festen Zeiten vorgenommen, z. alle 5 Sekunden, werden aber nur weitergeleitet, wenn sich der Messwert seit dem letzten Messwert ändert.

Wenn ich also einen Durchschnitt von signal_stength für jedes Gerät erstellen möchte:

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

Zum Beispiel Ereignisse, die vom Gerät für ein einminütiges Fenster gesendet wurden:

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

Derselbe Datensatz mit den Ereignissen, die nicht tatsächlich gesendet wurden, wird ausgefüllt:

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

Die Signalstärke sum ist 57 und die avg ist 57/12

Wie können diese fehlenden Daten durch funkenstrukturiertes Streaming abgeleitet und der Durchschnitt aus den abgeleiteten Werten berechnet werden?

Hinweis : Ich habe den Durchschnitt als Beispiel für eine Aggregation verwendet, aber die Lösung muss für jede Aggregationsfunktion funktionieren.

9
Chris Snow

BEARBEITET:

Ich habe die Logik dahingehend geändert, dass der Durchschnitt nur aus dem gefilterten dataframeberechnet wird, sodass die Lücken geschlossen werden.

//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

Die Idee ist, zwei neue Spalten zu berechnen:

  1. totalRowCount: Die laufende Gesamtzahl der Zeilen, die vorhanden sein sollen, wenn Sie nicht gefiltert haben.
  2. total_signal_strength: die bisher laufende Summe von signal_strength. (Dies beinhaltet auch die fehlenden Zeilensummen).

Es wird berechnet von:

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

format des Zwischenzustandes:

+----------+---------+---------------+---------------------+--------+
|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|
+----------+---------+---------------+---------------------+--------+

endausgabe:

+----------+---------+---------------+-----------------+
|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
vdep

Mathematisch äquivalent zu einem gewichteten Durchschnittsproblem basierend auf der Dauer:

avg=(signal_strength*duration)/60

die Herausforderung hierbei ist, die Dauer für jedes Signal zu ermitteln, eine Option ist hier für jede Mikro-Charge, das Ergebnis im Treiber zu erfassen, dann ist alles ein statistisches Problem. Um die Dauer zu ermitteln, können Sie eine Linksverschiebung zur Startzeit durchführen und dann etwas abziehen diese:

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

was würde dir geben:

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

Ab Spark Structured Streaming 2.3.2 müssen Sie Ihre eigene angepasste Senke schreiben, um das Ergebnis jeder Phase für den Fahrer zu erfassen und die mathematische Arbeit auf diese Weise auszuführen.

0
dunlu_98k