///|
pub struct RobustPipeline {
window : Int
trim_percent : Double
threshold : Double
detector : AnomalyDetector
mut fitted : Bool
}
///|
pub struct PipelineResult {
cleaned : Array[Double]
scores : Array[Double]
flags : Array[Bool]
baseline : Array[Double]
}
///|
pub fn RobustPipeline::new(
window : Int,
trim_percent : Double,
threshold : Double,
) -> RobustPipeline {
if window <= 0 {
abort("window must be positive")
}
validate_trim(trim_percent)
if threshold <= 0.0 {
abort("threshold must be positive")
}
{
window,
trim_percent,
threshold,
detector: AnomalyDetector::new(threshold~),
fitted: false,
}
}
///|
pub fn RobustPipeline::fit(
self : RobustPipeline,
reference : Array[Double],
) -> Unit {
self.detector.fit(reference)
self.fitted = reference.length() > 0
}
///|
pub fn RobustPipeline::transform(
self : RobustPipeline,
data : Array[Double],
) -> PipelineResult {
let cleaned = hampel_filter(data, self.window, threshold=self.threshold)
let scores = rolling_mad_residuals(data, self.window)
let flags = rolling_outlier_flags(data, self.window, threshold=self.threshold)
let baseline = rolling_winsorized_mean(data, self.window, self.trim_percent)
{ cleaned, scores, flags, baseline }
}
///|
pub fn RobustPipeline::fit_transform(
self : RobustPipeline,
reference : Array[Double],
) -> PipelineResult {
self.fit(reference)
self.transform(reference)
}
///|
pub fn RobustPipeline::predict_next(
self : RobustPipeline,
data : Array[Double],
) -> Double {
robust_forecast_next(data, self.window)
}
///|
pub fn RobustPipeline::summary(
self : RobustPipeline,
data : Array[Double],
) -> DatasetSummary {
summarize(self.transform(data).cleaned, outlier_threshold=self.threshold)
}
///|
pub fn RobustPipeline::is_fitted(self : RobustPipeline) -> Bool {
self.fitted
}
///|
pub fn RobustPipeline::window(self : RobustPipeline) -> Int {
self.window
}
///|
pub fn RobustPipeline::trim_percent(self : RobustPipeline) -> Double {
self.trim_percent
}
///|
pub fn RobustPipeline::threshold(self : RobustPipeline) -> Double {
self.threshold
}
///|
pub fn pipeline_clean(
data : Array[Double],
window : Int,
threshold : Double,
) -> Array[Double] {
hampel_filter(data, window, threshold~)
}
///|
pub fn pipeline_score(data : Array[Double], window : Int) -> Array[Double] {
rolling_mad_residuals(data, window)
}
///|
pub fn pipeline_flags(
data : Array[Double],
window : Int,
threshold : Double,
) -> Array[Bool] {
rolling_outlier_flags(data, window, threshold~)
}
///|
pub fn pipeline_baseline(
data : Array[Double],
window : Int,
trim_percent : Double,
) -> Array[Double] {
rolling_winsorized_mean(data, window, trim_percent)
}
///|
pub fn pipeline_quality(
data : Array[Double],
window : Int,
trim_percent : Double,
threshold : Double,
) -> Array[Double] {
let cleaned = pipeline_clean(data, window, threshold)
let summary = summarize(cleaned, outlier_threshold=threshold)
[
summary.mad,
summary.iqr,
summary.outlier_fraction,
robust_signal_quality(cleaned),
abs_double(mean(data) - mean(cleaned)),
abs_double(median(data) - median(cleaned)),
trim_percent,
window.to_double(),
threshold,
]
}
///|
pub fn pipeline_residual_report(
data : Array[Double],
window : Int,
threshold : Double,
) -> Array[Double] {
let residuals = robust_residuals(data, window)
[
mean_absolute_error(data, difference_from_baseline(data, mean(residuals))),
mad(residuals),
maximum_influence(residuals),
robust_signal_quality(residuals),
threshold,
]
}
///|
pub fn pipeline_compare(
data : Array[Double],
window : Int,
trim_percent : Double,
threshold : Double,
) -> Array[Array[Double]] {
let raw = summarize(data, outlier_threshold=threshold)
let cleaned = summarize(
pipeline_clean(data, window, threshold),
outlier_threshold=threshold,
)
[
[raw.mean, raw.median, raw.mad, raw.outlier_fraction],
[cleaned.mean, cleaned.median, cleaned.mad, cleaned.outlier_fraction],
[
raw.standard_deviation,
cleaned.standard_deviation,
trim_percent,
threshold,
],
]
}
///|
pub fn pipeline_stability(
data : Array[Double],
window : Int,
threshold : Double,
) -> Double {
let cleaned = pipeline_clean(data, window, threshold)
1.0 / (1.0 + mean_absolute_error(data, cleaned))
}
///|
pub fn pipeline_resample(
data : Array[Double],
window : Int,
threshold : Double,
replicates : Int,
seed? : Int = 12345,
) -> BootstrapInterval {
let samples = bootstrap_replicates(data, replicates, seed~)
let estimates = []
for sample in samples {
estimates.push(mean(pipeline_clean(sample, window, threshold)))
}
if estimates.length() == 0 {
return {
estimate: 0.0,
lower: 0.0,
upper: 0.0,
confidence: 0.95,
replicates: 0,
}
}
{
estimate: mean(pipeline_clean(data, window, threshold)),
lower: quantile(estimates, 0.025),
upper: quantile(estimates, 0.975),
confidence: 0.95,
replicates,
}
}
///|
pub fn pipeline_apply_to_segments(
data : Array[Double],
segments : Int,
window : Int,
threshold : Double,
) -> Array[Array[Double]] {
let result = []
for values in segment_values(data, segments) {
result.push(pipeline_quality(values, window, 0.1, threshold))
}
result
}
///|
pub fn pipeline_alert_count(
data : Array[Double],
window : Int,
threshold : Double,
) -> Int {
let mut count = 0
for flag in pipeline_flags(data, window, threshold) {
if flag {
count += 1
}
}
count
}
///|
pub fn pipeline_alert_rate(
data : Array[Double],
window : Int,
threshold : Double,
) -> Double {
if data.length() == 0 {
0.0
} else {
pipeline_alert_count(data, window, threshold).to_double() /
data.length().to_double()
}
}
///|
pub fn pipeline_has_alert(
data : Array[Double],
window : Int,
threshold : Double,
) -> Bool {
pipeline_alert_count(data, window, threshold) > 0
}
///|
pub fn pipeline_change_points(
data : Array[Double],
window : Int,
threshold : Double,
) -> Array[Int] {
change_point_indices(data, window, threshold)
}
///|
pub fn pipeline_forecast_error(data : Array[Double], window : Int) -> Double {
let errors = rolling_forecast_errors(data, window)
mad(errors)
}
///|
pub fn pipeline_risk(data : Array[Double]) -> Array[Double] {
robust_risk_summary(data)
}