///|
pub struct WindowedAnomalyDetector {
window : StreamingWindow
threshold : Double
mut total_seen : Int
mut total_anomalies : Int
}
///|
pub fn WindowedAnomalyDetector::new(
capacity : Int,
threshold? : Double = 3.5,
) -> WindowedAnomalyDetector {
if threshold <= 0.0 {
abort("threshold must be positive")
}
{
window: StreamingWindow::new(capacity),
threshold,
total_seen: 0,
total_anomalies: 0,
}
}
///|
pub fn WindowedAnomalyDetector::observe(
self : WindowedAnomalyDetector,
value : Double,
) -> Bool {
let values = self.window.values()
let center = median(values)
let scale = mad(values)
let score = robust_z_score(value, center, scale)
let zero_scale_anomaly = scale == 0.0 &&
values.length() > 2 &&
value != center
let anomaly = self.window.len() > 2 &&
(zero_scale_anomaly || abs_double(score) > self.threshold)
self.window.push(value)
self.total_seen += 1
if anomaly {
self.total_anomalies += 1
}
anomaly
}
///|
pub fn WindowedAnomalyDetector::observe_many(
self : WindowedAnomalyDetector,
data : Array[Double],
) -> Array[Bool] {
let result = []
for value in data {
result.push(self.observe(value))
}
result
}
///|
pub fn WindowedAnomalyDetector::window_values(
self : WindowedAnomalyDetector,
) -> Array[Double] {
self.window.values()
}
///|
pub fn WindowedAnomalyDetector::seen(self : WindowedAnomalyDetector) -> Int {
self.total_seen
}
///|
pub fn WindowedAnomalyDetector::anomalies(
self : WindowedAnomalyDetector,
) -> Int {
self.total_anomalies
}
///|
pub fn WindowedAnomalyDetector::rate(self : WindowedAnomalyDetector) -> Double {
if self.total_seen == 0 {
0.0
} else {
self.total_anomalies.to_double() / self.total_seen.to_double()
}
}
///|
pub fn WindowedAnomalyDetector::reset(self : WindowedAnomalyDetector) -> Unit {
self.window.clear()
self.total_seen = 0
self.total_anomalies = 0
}
///|
pub struct AdaptiveClipper {
mut center : Double
mut scale : Double
alpha : Double
threshold : Double
mut initialized : Bool
}
///|
pub fn AdaptiveClipper::new(
alpha : Double,
threshold? : Double = 3.5,
) -> AdaptiveClipper {
if alpha <= 0.0 || alpha > 1.0 {
abort("alpha must be in (0, 1]")
}
if threshold <= 0.0 {
abort("threshold must be positive")
}
{ center: 0.0, scale: 0.0, alpha, threshold, initialized: false }
}
///|
pub fn AdaptiveClipper::update(
self : AdaptiveClipper,
value : Double,
) -> Double {
if !self.initialized {
self.center = value
self.scale = 0.0
self.initialized = true
return value
}
let deviation = value - self.center
let limit = if self.scale == 0.0 {
self.threshold
} else {
self.threshold * self.scale
}
let clipped = self.center + clamp_double(deviation, -limit, limit)
self.center = self.center + self.alpha * (clipped - self.center)
self.scale = self.scale +
self.alpha * (abs_double(clipped - self.center) - self.scale)
clipped
}
///|
pub fn AdaptiveClipper::transform(
self : AdaptiveClipper,
data : Array[Double],
) -> Array[Double] {
let result = []
for value in data {
result.push(self.update(value))
}
result
}
///|
pub fn AdaptiveClipper::location(self : AdaptiveClipper) -> Double {
self.center
}
///|
pub fn AdaptiveClipper::scale(self : AdaptiveClipper) -> Double {
self.scale
}
///|
pub fn AdaptiveClipper::is_initialized(self : AdaptiveClipper) -> Bool {
self.initialized
}
///|
pub fn robust_online_mean(data : Array[Double], clip_limit : Double) -> Double {
let state = StreamingRobustStats::new(clip_limit)
state.push_many(data)
state.mean()
}
///|
pub fn robust_online_variance(
data : Array[Double],
clip_limit : Double,
) -> Double {
let state = StreamingRobustStats::new(clip_limit)
state.push_many(data)
state.variance()
}
///|
pub fn online_covariance(x : Array[Double], y : Array[Double]) -> Double {
let state = StreamingCovariance::new()
for index = 0; index < x.length() && index < y.length(); index = index + 1 {
state.push(x[index], y[index])
}
state.covariance()
}
///|
pub fn online_correlation(x : Array[Double], y : Array[Double]) -> Double {
let state = StreamingCovariance::new()
for index = 0; index < x.length() && index < y.length(); index = index + 1 {
state.push(x[index], y[index])
}
state.correlation()
}