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.
BEARBEITET:
Ich habe die Logik dahingehend geändert, dass der Durchschnitt nur aus dem gefilterten dataframe
berechnet 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:
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|
+----------+---------+---------------+-----------------+
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.