Hier ist eine (nicht idiomatische) Scala-Version des Algorithmus für geglättete Z-Scores:
/**
* Schamlos kopierter geglätteter Z-Score-Algorithmus von https://stackoverflow.com/a/22640362/6029703
* Verwendet einen gleitenden Mittelwert und eine gleitende Standardabweichung (getrennt), um Spitzen in einem Vektor zu identifizieren
*
* @param y - Der Eingabevektor, der analysiert werden soll
* @param lag - Die Verzögerung des gleitenden Fensters (d. h. wie groß das Fenster ist)
* @param threshold - Der Z-Score, bei dem der Algorithmus Signale anzeigt (d. h. wie viele Standardabweichungen vom gleitenden Mittelwert eine Spitze (oder ein Signal) entfernt ist)
* @param influence - Der Einfluss (zwischen 0 und 1) neuer Signale auf den Mittelwert und die Standardabweichung (wie sehr eine Spitze (oder ein Signal) andere Werte in ihrer Nähe beeinflussen sollte)
* @return - Die berechneten Durchschnitte (avgFilter) und Abweichungen (stdFilter) sowie die Signale (signals)
*/
private def smoothedZScore(y: Seq[Double], lag: Int, threshold: Double, influence: Double): Seq[Int] = {
val stats = new SummaryStatistics()
// die Ergebnisse (Spitzen, 1 oder -1) unseres Algorithmus
val signals = mutable.ArrayBuffer.fill(y.length)(0)
// Filtern der Signale (Spitzen) aus unserer Originalliste (unter Verwendung der influence-Argumente)
val filteredY = y.to[mutable.ArrayBuffer]
// der aktuelle Durchschnitt des gleitenden Fensters
val avgFilter = mutable.ArrayBuffer.fill(y.length)(0d)
// die aktuelle Standardabweichung des gleitenden Fensters
val stdFilter = mutable.ArrayBuffer.fill(y.length)(0d)
// avgFilter und stdFilter initialisieren
y.take(lag).foreach(s => stats.addValue(s))
avgFilter(lag - 1) = stats.getMean
stdFilter(lag - 1) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() verwendet Stichprobenvarianz (nicht das, was wir wollen)
// Schleife, die bei Eingabe am Ende des gleitenden Fensters beginnt
y.zipWithIndex.slice(lag, y.length - 1).foreach {
case (s: Double, i: Int) =>
// wenn der Abstand zwischen dem aktuellen Wert und dem Durchschnitt ausreichend Standardabweichungen (threshold) beträgt
if (Math.abs(s - avgFilter(i - 1)) > threshold * stdFilter(i - 1)) {
// dies ist ein Signal (dh Spitze), bestimmen Sie, ob es ein positives oder negatives Signal ist
signals(i) = if (s > avgFilter(i - 1)) 1 else -1
// das Signal mit influence filtern
filteredY(i) = (influence * s) + ((1 - influence) * filteredY(i - 1))
} else {
// sicherstellen, dass dieses Signal null bleibt
signals(i) = 0
// sicherstellen, dass dieser Wert nicht gefiltert wird
filteredY(i) = s
}
// gleitenden Durchschnitt und Standardabweichung aktualisieren
stats.clear()
filteredY.slice(i - lag, i).foreach(s => stats.addValue(s))
avgFilter(i) = stats.getMean
stdFilter(i) = Math.sqrt(stats.getPopulationVariance) // getStandardDeviation() verwendet Stichprobenvarianz (nicht das, was wir wollen)
}
println(y.length)
println(signals.length)
println(signals)
signals.zipWithIndex.foreach {
case(x: Int, idx: Int) =>
if (x == 1) {
println(idx + " " + y(idx))
}
}
val data =
y.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "y", "row" -> "data") } ++
avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> s, "name" -> "avgFilter", "row" -> "data") } ++
avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s - threshold * stdFilter(i)), "name" -> "lower", "row" -> "data") } ++
avgFilter.zipWithIndex.map { case (s: Double, i: Int) => Map("x" -> i, "y" -> (s + threshold * stdFilter(i)), "name" -> "upper", "row" -> "data") } ++
signals.zipWithIndex.map { case (s: Int, i: Int) => Map("x" -> i, "y" -> s, "name" -> "signal", "row" -> "signal") }
Vegas("Geglättetes Z")
.withData(data)
.mark(Line)
.encodeX("x", Quant)
.encodeY("y", Quant)
.encodeColor(
field="name",
dataType=Nominal
)
.encodeRow("row", Ordinal)
.show
return signals
}
Hier ist ein Test, der dieselben Ergebnisse wie die Python- und Groovy-Versionen liefert:
val y = List(1d, 1d, 1.1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1d, 1.1d, 1d, 1d,
1d, 1d, 1.1d, 0.9d, 1d, 1.1d, 1d, 1d, 0.9d, 1d, 1.1d, 1d, 1d, 1.1d, 1d, 0.8d, 0.9d, 1d, 1.2d, 0.9d, 1d,
1d, 1.1d, 1.2d, 1d, 1.5d, 1d, 3d, 2d, 5d, 3d, 2d, 1d, 1d, 1d, 0.9d, 1d,
1d, 3d, 2.6d, 4d, 3d, 3.2d, 2d, 1d, 1d, 0.8d, 4d, 4d, 2d, 2.5d, 1d, 1d, 1d)
val lag = 30
val threshold = 5d
val influence = 0d
smoothedZScore(y, lag, threshold, influence)
Gist hier