///|
/// Fixed-capacity rolling numeric window for dashboards and online validation.
pub struct RollingWindow {
capacity : Int
values : Array[Double]
} derive(Debug, Eq, ToJson, FromJson)
///|
pub fn RollingWindow::new(capacity : Int) -> RollingWindow {
if capacity <= 0 {
abort("RollingWindow: capacity must be positive")
}
{ capacity, values: [] }
}
///|
pub fn RollingWindow::capacity(self : RollingWindow) -> Int {
self.capacity
}
///|
pub fn RollingWindow::len(self : RollingWindow) -> Int {
self.values.length()
}
///|
pub fn RollingWindow::is_full(self : RollingWindow) -> Bool {
self.len() == self.capacity
}
///|
pub fn RollingWindow::push(
self : RollingWindow,
value : Double,
) -> RollingWindow {
let next = self.values.copy()
next.push(value)
if next.length() > self.capacity {
ignore(next.remove(0))
}
{ ..self, values: next }
}
///|
pub fn RollingWindow::push_all(
self : RollingWindow,
values : Array[Double],
) -> RollingWindow {
values.fold(init=self, (window, value) => window.push(value))
}
///|
pub fn RollingWindow::values(self : RollingWindow) -> Array[Double] {
self.values.copy()
}
///|
pub fn RollingWindow::stats(self : RollingWindow) -> OnlineStats {
OnlineStats::new().push_all(self.values)
}
///|
pub fn RollingWindow::mean(self : RollingWindow) -> Double {
self.stats().mean()
}
///|
pub fn RollingWindow::variance(self : RollingWindow) -> Double {
self.stats().variance()
}
///|
pub fn RollingWindow::trend(self : RollingWindow) -> Double {
if self.values.length() < 2 {
0.0
} else {
self.values[self.values.length() - 1] - self.values[0]
}
}
///|
pub fn RollingWindow::min(self : RollingWindow) -> Double {
self.stats().min()
}
///|
pub fn RollingWindow::max(self : RollingWindow) -> Double {
self.stats().max()
}
///|
pub fn RollingWindow::quantile(
self : RollingWindow,
probability : Double,
) -> Double {
quantile(self.values, probability)
}
///|
pub fn RollingWindow::map(
self : RollingWindow,
transform : (Double) -> Double,
) -> RollingWindow {
{ ..self, values: self.values.map(transform) }
}
///|
pub fn RollingWindow::clear(self : RollingWindow) -> RollingWindow {
{ ..self, values: [] }
}
///|
pub fn exponential_moving_average(
values : Array[Double],
decay : Double,
) -> Array[Double] {
if decay <= 0.0 || decay > 1.0 {
abort("exponential_moving_average: decay must be in (0, 1]")
}
if values.length() == 0 {
return []
}
let result = Array::make(values.length(), 0.0)
result[0] = values[0]
for index in 1.. Array[Double] {
if decay <= 0.0 || decay > 1.0 {
abort("exponentially_weighted_variance: invalid decay")
}
if values.length() == 0 {
return []
}
let result = Array::make(values.length(), 0.0)
let averages = exponential_moving_average(values, decay)
for index in 1..