tseda.anomaly
tseda.anomaly
Point and contextual anomaly detection for time series.
Public API
- AnomalyReport
Frozen dataclass with mask, indices, timestamps, values, scores, method, and n_anomalies.
- AnomalyDetector
Stateless detector: rolling IQR, rolling Z-score, STL-residual, and GESD methods.
- class tseda.anomaly.AnomalyReport(mask, indices, timestamps, values, scores, method, n_anomalies)[source]
Bases:
objectImmutable anomaly detection result.
- Parameters:
- mask
Boolean array of shape
(n,);Truewhere an anomaly was detected.- Type:
- indices
Integer positions (0-based) of detected anomalies.
- Type:
- timestamps
Timestamps of detected anomalies.
- Type:
- values
Observed values at anomaly positions.
- Type:
- scores
Continuous anomaly score in [0, 1] for each observation. Higher values indicate stronger evidence of anomaly.
0for normal observations.- Type:
- timestamps: DatetimeIndex
- class tseda.anomaly.AnomalyDetector[source]
Bases:
objectDetect point and contextual anomalies in a
TimeSeries.This class is stateless — one instance, many series.
- rolling_iqr(ts, window, k)[source]
Rolling Tukey IQR fence.
- Parameters:
ts (TimeSeries)
window (int)
k (float)
center (bool)
min_periods (int | None)
- Return type:
- rolling_z(ts, window, threshold)[source]
Rolling mean ± k × std.
- Parameters:
ts (TimeSeries)
window (int)
threshold (float)
center (bool)
min_periods (int | None)
- Return type:
- stl_residual(ts, period, method, threshold)[source]
STL decompose then flag large residuals.
- Parameters:
ts (TimeSeries)
period (int | None)
residual_method (str)
k (float)
robust (bool)
- Return type:
- gesd(ts, alpha, max_outliers)[source]
Global Generalized ESD test (re-uses quality module).
- Parameters:
ts (TimeSeries)
alpha (float)
max_outliers (int)
- Return type:
- remove(ts, report)[source]
Replace anomaly positions with NaN.
- Parameters:
ts (TimeSeries)
report (AnomalyReport)
- Return type:
- label(ts, report)[source]
Return a 0/1
TimeSeriesof anomaly labels.- Parameters:
ts (TimeSeries)
report (AnomalyReport)
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector
>>> rng = np.random.default_rng(1) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[20] = 12.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> r = det.rolling_z(ts, window=30) >>> 20 in r.indices True
- rolling_iqr(ts, window=30, *, k=2.5, center=True, min_periods=None)[source]
Detect anomalies using a rolling IQR fence.
An observation
y[t]is flagged when it falls outside[Q1(t) − k×IQR(t), Q3(t) + k×IQR(t)]where the quartiles are computed over a rolling window centred att.- Parameters:
ts (TimeSeries) – Input series.
window (int, optional) – Rolling window width in observations. Default 30.
k (float, optional) – Fence multiplier. Default 2.5 (tighter than the global 1.5 because the window is local and thus more sensitive).
center (bool, optional) – Centre the window on each observation. Default
True.min_periods (int, optional) – Minimum non-NaN observations required in each window. Defaults to
window // 2.
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If fewer than 8 non-NaN observations or k ≤ 0.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(0) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[40] = 10.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().rolling_iqr(ts) >>> 40 in r.indices True
- rolling_z(ts, window=30, *, threshold=3.0, center=True, min_periods=None)[source]
Detect anomalies using a rolling Z-score.
An observation is flagged when
|y[t] − μ(t)| / σ(t) > threshold, whereμandσare the rolling mean and standard deviation computed over a window.- Parameters:
ts (TimeSeries) – Input series.
window (int, optional) – Rolling window width. Default 30.
threshold (float, optional) – Z-score cut-off. Default 3.0.
center (bool, optional) – Centre the window. Default
True.min_periods (int, optional) – Minimum non-NaN observations per window. Defaults to
window // 2.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(2) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[60] = -9.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().rolling_z(ts) >>> 60 in r.indices True
- stl_residual(ts, period=None, *, residual_method='iqr', k=3.0, robust=True)[source]
Detect anomalies in the STL residual component.
Decomposes ts into trend + seasonal + residual using STL, then flags residual values that are extreme under the chosen criterion.
- Parameters:
ts (TimeSeries) – Input series. Must be long enough for STL decomposition (at least 2 × period observations).
period (int, optional) – Seasonal period. Inferred from
ts.freqwhen omitted.residual_method (str, optional) –
Criterion to flag residuals:
"iqr"— Tukey IQR fence with multiplier k (default)."mad"— Median Absolute Deviation threshold k."z"— Z-score threshold k.
k (float, optional) – Threshold multiplier / fence factor. Default 3.0.
robust (bool, optional) – Use robust LOESS in STL. Default
True.
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If residual_method is not recognised.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(3) >>> n = 60 >>> seas = np.tile(np.sin(2*np.pi*np.arange(12)/12)*5, 5) >>> vals = seas + rng.standard_normal(n) * 0.3 >>> vals[25] = 20.0 >>> idx = pd.date_range("2018-01", periods=n, freq="MS") >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().stl_residual(ts, period=12) >>> 25 in r.indices True
- gesd(ts, *, alpha=0.05, max_outliers=10)[source]
Global GESD anomaly detection.
Delegates to
tseda.quality.OutlierDetector.gesd()and wraps the result as anAnomalyReport. Best suited for stationary series where the number of anomalies is small relative to n.- Parameters:
ts (TimeSeries) – Input series.
alpha (float, optional) – Significance level. Default 0.05.
max_outliers (int, optional) – Maximum number of anomalies to test for. Default 10.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(0) >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = rng.standard_normal(50) >>> vals[10] = 12.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().gesd(ts) >>> 10 in r.indices True
- remove(ts, report)[source]
Replace detected anomaly values with NaN.
- Parameters:
ts (TimeSeries) – The original series.
report (AnomalyReport) – Output of any detection method.
- Returns:
A new series with anomaly positions set to NaN.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = np.zeros(50) >>> vals[5] = 100.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> cleaned = det.remove(ts, det.rolling_iqr(ts)) >>> cleaned.has_nan True
- label(ts, report)[source]
Return a 0/1
TimeSeriesof anomaly labels.- Parameters:
ts (TimeSeries) – The original series (used for index and metadata).
report (AnomalyReport) – Output of any detection method.
- Returns:
Values are
1at anomaly positions,0elsewhere. Name is"{ts.name}_anomaly_label".- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = np.zeros(50); vals[5] = 100.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> lbl = det.label(ts, det.rolling_iqr(ts)) >>> lbl.values[5] 1.0 >>> lbl.values[0] 0.0
Report
- class tseda.anomaly.detector.AnomalyReport(mask, indices, timestamps, values, scores, method, n_anomalies)[source]
Bases:
objectImmutable anomaly detection result.
- Parameters:
- mask
Boolean array of shape
(n,);Truewhere an anomaly was detected.- Type:
- indices
Integer positions (0-based) of detected anomalies.
- Type:
- timestamps
Timestamps of detected anomalies.
- Type:
- values
Observed values at anomaly positions.
- Type:
- scores
Continuous anomaly score in [0, 1] for each observation. Higher values indicate stronger evidence of anomaly.
0for normal observations.- Type:
- timestamps: DatetimeIndex
Detector
- class tseda.anomaly.detector.AnomalyDetector[source]
Bases:
objectDetect point and contextual anomalies in a
TimeSeries.This class is stateless — one instance, many series.
- rolling_iqr(ts, window, k)[source]
Rolling Tukey IQR fence.
- Parameters:
ts (TimeSeries)
window (int)
k (float)
center (bool)
min_periods (int | None)
- Return type:
- rolling_z(ts, window, threshold)[source]
Rolling mean ± k × std.
- Parameters:
ts (TimeSeries)
window (int)
threshold (float)
center (bool)
min_periods (int | None)
- Return type:
- stl_residual(ts, period, method, threshold)[source]
STL decompose then flag large residuals.
- Parameters:
ts (TimeSeries)
period (int | None)
residual_method (str)
k (float)
robust (bool)
- Return type:
- gesd(ts, alpha, max_outliers)[source]
Global Generalized ESD test (re-uses quality module).
- Parameters:
ts (TimeSeries)
alpha (float)
max_outliers (int)
- Return type:
- remove(ts, report)[source]
Replace anomaly positions with NaN.
- Parameters:
ts (TimeSeries)
report (AnomalyReport)
- Return type:
- label(ts, report)[source]
Return a 0/1
TimeSeriesof anomaly labels.- Parameters:
ts (TimeSeries)
report (AnomalyReport)
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector
>>> rng = np.random.default_rng(1) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[20] = 12.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> r = det.rolling_z(ts, window=30) >>> 20 in r.indices True
- rolling_iqr(ts, window=30, *, k=2.5, center=True, min_periods=None)[source]
Detect anomalies using a rolling IQR fence.
An observation
y[t]is flagged when it falls outside[Q1(t) − k×IQR(t), Q3(t) + k×IQR(t)]where the quartiles are computed over a rolling window centred att.- Parameters:
ts (TimeSeries) – Input series.
window (int, optional) – Rolling window width in observations. Default 30.
k (float, optional) – Fence multiplier. Default 2.5 (tighter than the global 1.5 because the window is local and thus more sensitive).
center (bool, optional) – Centre the window on each observation. Default
True.min_periods (int, optional) – Minimum non-NaN observations required in each window. Defaults to
window // 2.
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If fewer than 8 non-NaN observations or k ≤ 0.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(0) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[40] = 10.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().rolling_iqr(ts) >>> 40 in r.indices True
- rolling_z(ts, window=30, *, threshold=3.0, center=True, min_periods=None)[source]
Detect anomalies using a rolling Z-score.
An observation is flagged when
|y[t] − μ(t)| / σ(t) > threshold, whereμandσare the rolling mean and standard deviation computed over a window.- Parameters:
ts (TimeSeries) – Input series.
window (int, optional) – Rolling window width. Default 30.
threshold (float, optional) – Z-score cut-off. Default 3.0.
center (bool, optional) – Centre the window. Default
True.min_periods (int, optional) – Minimum non-NaN observations per window. Defaults to
window // 2.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(2) >>> idx = pd.date_range("2020", periods=100, freq="D") >>> vals = rng.standard_normal(100) >>> vals[60] = -9.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().rolling_z(ts) >>> 60 in r.indices True
- stl_residual(ts, period=None, *, residual_method='iqr', k=3.0, robust=True)[source]
Detect anomalies in the STL residual component.
Decomposes ts into trend + seasonal + residual using STL, then flags residual values that are extreme under the chosen criterion.
- Parameters:
ts (TimeSeries) – Input series. Must be long enough for STL decomposition (at least 2 × period observations).
period (int, optional) – Seasonal period. Inferred from
ts.freqwhen omitted.residual_method (str, optional) –
Criterion to flag residuals:
"iqr"— Tukey IQR fence with multiplier k (default)."mad"— Median Absolute Deviation threshold k."z"— Z-score threshold k.
k (float, optional) – Threshold multiplier / fence factor. Default 3.0.
robust (bool, optional) – Use robust LOESS in STL. Default
True.
- Return type:
- Raises:
TypeError – If ts is not a
TimeSeries.ValueError – If residual_method is not recognised.
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(3) >>> n = 60 >>> seas = np.tile(np.sin(2*np.pi*np.arange(12)/12)*5, 5) >>> vals = seas + rng.standard_normal(n) * 0.3 >>> vals[25] = 20.0 >>> idx = pd.date_range("2018-01", periods=n, freq="MS") >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().stl_residual(ts, period=12) >>> 25 in r.indices True
- gesd(ts, *, alpha=0.05, max_outliers=10)[source]
Global GESD anomaly detection.
Delegates to
tseda.quality.OutlierDetector.gesd()and wraps the result as anAnomalyReport. Best suited for stationary series where the number of anomalies is small relative to n.- Parameters:
ts (TimeSeries) – Input series.
alpha (float, optional) – Significance level. Default 0.05.
max_outliers (int, optional) – Maximum number of anomalies to test for. Default 10.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> rng = np.random.default_rng(0) >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = rng.standard_normal(50) >>> vals[10] = 12.0 >>> ts = TimeSeries(vals, index=idx) >>> r = AnomalyDetector().gesd(ts) >>> 10 in r.indices True
- remove(ts, report)[source]
Replace detected anomaly values with NaN.
- Parameters:
ts (TimeSeries) – The original series.
report (AnomalyReport) – Output of any detection method.
- Returns:
A new series with anomaly positions set to NaN.
- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = np.zeros(50) >>> vals[5] = 100.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> cleaned = det.remove(ts, det.rolling_iqr(ts)) >>> cleaned.has_nan True
- label(ts, report)[source]
Return a 0/1
TimeSeriesof anomaly labels.- Parameters:
ts (TimeSeries) – The original series (used for index and metadata).
report (AnomalyReport) – Output of any detection method.
- Returns:
Values are
1at anomaly positions,0elsewhere. Name is"{ts.name}_anomaly_label".- Return type:
Examples
>>> import numpy as np, pandas as pd >>> from tseda import TimeSeries >>> from tseda.anomaly.detector import AnomalyDetector >>> idx = pd.date_range("2020", periods=50, freq="D") >>> vals = np.zeros(50); vals[5] = 100.0 >>> ts = TimeSeries(vals, index=idx) >>> det = AnomalyDetector() >>> lbl = det.label(ts, det.rolling_iqr(ts)) >>> lbl.values[5] 1.0 >>> lbl.values[0] 0.0