detect_anomaly
Flag outlier points in numeric series using Z-score or IQR. Returns indices and values to identify anomalies in metrics, fraud signals, or sensor noise.
Instructions
[Premium] Flag outlier points in a numeric series using Z-score (parametric, assumes ~normal) or IQR (robust to skew). Use for monitoring metrics, fraud signals, sensor noise, quality control. Z-score is faster and tighter on near-normal data; IQR is the right default when the distribution has heavy tails or known outliers. Returns indices + values + the underlying statistics. For projecting a series forward, use predict_forecast. Requires ORACLAW_API_KEY.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| data | Yes | Numeric series to scan. | |
| method | No | Default: zscore. | |
| threshold | No | Z-score: standard deviations above mean (default: 3.0). IQR: multiplier on IQR (default: 1.5). |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| method | Yes | ||
| anomalies | Yes | ||
| stats | No | For zscore: {mean, stdDev, threshold}. For iqr: {q1, q3, iqr, lowerBound, upperBound}. | |
| totalPoints | No | ||
| anomalyCount | Yes |
Implementation Reference
- Core Z-Score anomaly detection handler. Computes mean & stdDev from data using simple-statistics, then flags points whose absolute z-score exceeds threshold.
export function detectAnomaliesZScore( data: number[], threshold: number = 3.0, ): AnomalyResult { if (data.length < 2) { return { anomalies: [], mean: data[0] ?? 0, stdDev: 0, threshold }; } const mu = ssMean(data); const sigma = ssStdDev(data); if (sigma === 0) { return { anomalies: [], mean: mu, stdDev: 0, threshold }; } const anomalies: Array<{ index: number; value: number; zScore: number }> = []; for (let i = 0; i < data.length; i++) { const z = (data[i]! - mu) / sigma; if (Math.abs(z) > threshold) { anomalies.push({ index: i, value: data[i]!, zScore: z }); } } return { anomalies, mean: mu, stdDev: sigma, threshold }; } - Core IQR anomaly detection handler. Computes Q1, Q3, and IQR using simple-statistics, then flags points outside [Q1-k*IQR, Q3+k*IQR].
export function detectAnomaliesIQR( data: number[], k: number = 1.5, ): IQRAnomalyResult { if (data.length < 4) { const q1 = data.length > 0 ? ssQuantile(data, 0.25) : 0; const q3 = data.length > 0 ? ssQuantile(data, 0.75) : 0; const iqr = q3 - q1; return { anomalies: [], q1, q3, iqr, lowerBound: q1 - k * iqr, upperBound: q3 + k * iqr, }; } const q1 = ssQuantile(data, 0.25); const q3 = ssQuantile(data, 0.75); const iqr = ssIQR(data); const lowerBound = q1 - k * iqr; const upperBound = q3 + k * iqr; const anomalies: Array<{ index: number; value: number }> = []; for (let i = 0; i < data.length; i++) { if (data[i]! < lowerBound || data[i]! > upperBound) { anomalies.push({ index: i, value: data[i]! }); } } return { anomalies, q1, q3, iqr, lowerBound, upperBound }; } - Full anomaly detector module with types (AnomalyResult, IQRAnomalyResult, StreamingResult) and the StreamingAnomalyDetector class (Welford's online algorithm).
/** * Anomaly Detection * * Three complementary methods for identifying outliers in numeric data: * 1. Z-Score — parametric, assumes approximate normality * 2. IQR (Interquartile Range) — non-parametric, robust to skew * 3. StreamingAnomalyDetector — Welford's online algorithm for real-time feeds * * Uses `simple-statistics` for robust z-score and IQR primitives. */ import { mean as ssMean, standardDeviation as ssStdDev, quantile as ssQuantile, interquartileRange as ssIQR, } from "simple-statistics"; // ── Types ──────────────────────────────────────────────── /** Result of a batch z-score anomaly scan */ export interface AnomalyResult { /** Detected anomalies with index, value, and z-score */ anomalies: Array<{ index: number; value: number; zScore: number }>; /** Population mean */ mean: number; /** Population standard deviation */ stdDev: number; /** Z-score threshold that was applied */ threshold: number; } /** Result of a batch IQR anomaly scan */ export interface IQRAnomalyResult { /** Detected anomalies with index and value */ anomalies: Array<{ index: number; value: number }>; /** First quartile (25th percentile) */ q1: number; /** Third quartile (75th percentile) */ q3: number; /** Interquartile range (q3 - q1) */ iqr: number; /** Lower fence: q1 - k * iqr */ lowerBound: number; /** Upper fence: q3 + k * iqr */ upperBound: number; } /** Single observation result from the streaming detector */ export interface StreamingResult { /** Whether this value was flagged as an anomaly */ isAnomaly: boolean; /** Absolute z-score of this observation */ zScore: number; /** The observed value */ value: number; /** Running mean after incorporating this value */ mean: number; /** Running standard deviation after incorporating this value */ stdDev: number; } // ── Z-Score Detector ───────────────────────────────────── /** * Detect anomalies using the z-score method. * * A data point is an anomaly if its absolute z-score exceeds `threshold`. * Default threshold of 3.0 corresponds to ~0.27 % of a normal distribution. * * @param data - Numeric observations * @param threshold - z-score cutoff (default 3.0) * @returns AnomalyResult with flagged indices, mean, stdDev, and threshold * * O(n) time, O(k) space where k = number of anomalies */ export function detectAnomaliesZScore( data: number[], threshold: number = 3.0, ): AnomalyResult { if (data.length < 2) { return { anomalies: [], mean: data[0] ?? 0, stdDev: 0, threshold }; } const mu = ssMean(data); const sigma = ssStdDev(data); if (sigma === 0) { return { anomalies: [], mean: mu, stdDev: 0, threshold }; } const anomalies: Array<{ index: number; value: number; zScore: number }> = []; for (let i = 0; i < data.length; i++) { const z = (data[i]! - mu) / sigma; if (Math.abs(z) > threshold) { anomalies.push({ index: i, value: data[i]!, zScore: z }); } } return { anomalies, mean: mu, stdDev: sigma, threshold }; } // ── IQR Detector ───────────────────────────────────────── /** * Detect anomalies using the Interquartile Range (IQR) method. * * Values outside [Q1 - k*IQR, Q3 + k*IQR] are flagged. The default * multiplier k = 1.5 flags "mild" outliers; k = 3.0 flags "extreme" outliers. * * @param data - Numeric observations * @param k - IQR multiplier (default 1.5) * @returns IQRAnomalyResult with flagged indices and fence values * * O(n log n) due to quantile computation, O(k) space */ export function detectAnomaliesIQR( data: number[], k: number = 1.5, ): IQRAnomalyResult { if (data.length < 4) { const q1 = data.length > 0 ? ssQuantile(data, 0.25) : 0; const q3 = data.length > 0 ? ssQuantile(data, 0.75) : 0; const iqr = q3 - q1; return { anomalies: [], q1, q3, iqr, lowerBound: q1 - k * iqr, upperBound: q3 + k * iqr, }; } const q1 = ssQuantile(data, 0.25); const q3 = ssQuantile(data, 0.75); const iqr = ssIQR(data); const lowerBound = q1 - k * iqr; const upperBound = q3 + k * iqr; const anomalies: Array<{ index: number; value: number }> = []; for (let i = 0; i < data.length; i++) { if (data[i]! < lowerBound || data[i]! > upperBound) { anomalies.push({ index: i, value: data[i]! }); } } return { anomalies, q1, q3, iqr, lowerBound, upperBound }; }