Principle:Online ml River Anomaly Scoring Interface
| Knowledge Sources | River River Docs |
|---|---|
| Domains | Online Machine Learning, Anomaly Detection, API Design, Design Patterns |
| Last Updated | 2026-02-08 16:00 GMT |
Overview
Abstract interface for online anomaly detectors defining the score_one/learn_one contract for streaming anomaly scoring, establishing the common pattern that all River anomaly detectors must implement.
Description
The Anomaly Scoring Interface is the foundational design pattern underlying all anomaly detection in River. It defines a two-method contract that every anomaly detector must satisfy:
learn_one(x: dict) -> None-- Update the model with a single observation.score_one(x: dict) -> float-- Return an anomaly score for a single observation, where high scores indicate anomalies and low scores indicate normal observations.
This interface is intentionally minimal and consistent with River's broader philosophy of one-observation-at-a-time processing. It enables composability: any anomaly detector can be wrapped by an AnomalyFilter (ThresholdFilter or QuantileFilter), plugged into a pipeline, or evaluated with progressive_val_score.
The interface is extended by the AnomalyFilter abstract class, which adds:
classify(score: float) -> bool-- Convert a continuous score to a binary label.- A
protect_anomaly_detectormechanism that conditionally gates learning.
This is a Pattern Doc -- it documents the interface that users must implement when creating custom anomaly detectors or consume when using built-in detectors.
Usage
The anomaly scoring interface applies when:
- You are building a custom anomaly detector and need to know which methods to implement
- You want to understand how River's anomaly detection ecosystem fits together
- You need to compose anomaly detectors with filters, pipelines, or evaluation functions
- You are designing the score-classify-alert-learn deployment loop
Theoretical Basis
The AnomalyDetector contract:
class AnomalyDetector(base.Estimator):
"""An anomaly detector."""
def learn_one(self, x: dict) -> None:
"""Update the model with a single observation.
Parameters
----------
x : dict
A dictionary of features.
"""
...
def score_one(self, x: dict) -> float:
"""Return an outlier score.
A high score is indicative of an anomaly.
A low score corresponds to a normal observation.
Parameters
----------
x : dict
A dictionary of features.
Returns
-------
float
An anomaly score.
"""
...
The AnomalyFilter extension:
class AnomalyFilter(base.Wrapper, base.Estimator):
"""Wraps an AnomalyDetector with binary classification."""
def __init__(self, anomaly_detector, protect_anomaly_detector=True):
...
def classify(self, score: float) -> bool:
"""Classify a score as anomalous (True) or normal (False)."""
...
def score_one(self, *args, **kwargs) -> float:
"""Delegates to wrapped anomaly_detector.score_one."""
return self.anomaly_detector.score_one(*args, **kwargs)
def learn_one(self, *args, **learn_kwargs) -> None:
"""Conditionally updates the wrapped detector."""
if self.protect_anomaly_detector and not self.classify(self.score_one(*args)):
self.anomaly_detector.learn_one(*args, **learn_kwargs)
Canonical deployment pattern:
The recommended deployment loop for anomaly detection in River follows this sequence:
for each observation x:
1. SCORE: score = model.score_one(x)
2. CLASSIFY: is_anomaly = filter.classify(score)
3. ALERT: if is_anomaly: trigger_alert(x, score)
4. LEARN: model.learn_one(x) # or filter.learn_one(x) with protection
Key design decisions:
- Score before learn: The observation is scored before the model learns from it, ensuring the prediction is made on unseen data (progressive validation).
- High = anomalous: All River anomaly detectors follow the convention that higher scores indicate more anomalous observations. (Note:
OneClassSVM.score_onereturns raw decision function values where lower = more anomalous, but when wrapped in a QuantileFilter, the classification still works correctly.) - Unsupervised:
learn_onetakes only features (no target), making anomaly detection fully unsupervised. - Composable: Detectors can be piped into filters, which can be piped into supervised models.
Implementors of the interface:
| Class | Type | Score Range |
|---|---|---|
| anomaly.HalfSpaceTrees | AnomalyDetector | [0, 1] |
| anomaly.OneClassSVM | AnomalyDetector | unbounded (raw decision function) |
| anomaly.ThresholdFilter | AnomalyFilter | delegates to wrapped detector |
| anomaly.QuantileFilter | AnomalyFilter | delegates to wrapped detector |