# AdaptiveThreshold — Z-Score Anomaly Detection
**Detects anomalous samples using a self-learning baseline.** Internally
combines EMA (baseline) + Welford (std dev) to compute z-scores.
| Update cost | ~15 cycles |
| Memory | ~56 bytes |
| Types | `AdaptiveThresholdF64`, `AdaptiveThresholdF32` |
| Priming | Default: 20 samples (needs enough data for std_dev) |
| Output | `Option<Direction>` — `Normal`, `High`, or `Low` |
## What It Does
```
z-score = (sample - baseline) / std_dev
Value
130 ┤ · ← z > 3 → Direction::Rising
┤ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ +3σ
115 ┤
110 ┤ · · ·
105 ┤ · · · · · · · Normal range
100 ┤────────────────────────────────── baseline (EMA)
95 ┤ · · ·
┤ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ -3σ
70 ┤ · ← z < -3 → Direction::Falling
└──────────────────────────────────── t
```
Reports **direction**: `High` (above baseline) or `Low` (below baseline).
This matters — a latency spike (High) is different from a latency improvement (Low).
## When to Use It
- "Is this sample unusual given recent behavior?"
- You don't know the baseline in advance (it's learned)
- You want directional anomaly classification
## When to Use Something Else
- You know the baseline → [CUSUM](cusum.md) (persistent shifts) or [MultiGate](multi-gate.md) (graded)
- You want robust detection (outlier-resistant) → [RobustZScore](robust-z-score.md) (MAD-based)
## Configuration
```rust
let mut at = AdaptiveThresholdF64::builder()
.span(50) // EMA baseline smoothing
.z_threshold(3.0) // standard 3-sigma threshold
.min_samples(20) // warmup for std_dev
.build().unwrap();
// Or seed from known baseline:
let at = AdaptiveThresholdF64::builder()
.span(50)
.z_threshold(3.0)
.seed(100.0, 5.0) // baseline_mean=100, baseline_std=5
.build().unwrap();
```
## Performance
| `AdaptiveThresholdF64::update` | ~15 cycles | ~20 cycles |
EMA update + Welford update + z-score computation + comparison.