Skip to main content

Smoothing

smooth() extracts a trend line from a noisy numeric column. pond-ts ships three algorithms; this page is the place to figure out which one to pick.

const trend = cpu.smooth('cpu', 'ema', { alpha: 0.3, output: 'cpuTrend' });
// trend.schema gains { cpuTrend, number? } — source 'cpu' passes through.

The three algorithms

EMA — exponential moving average

Each output is alpha * raw + (1 - alpha) * previous. The bigger alpha, the more reactive (less smoothing).

cpu.smooth('cpu', 'ema', { alpha: 0.3, output: 'cpuTrend' });

What it actually does: every previous value contributes, with weight that decays geometrically. Recent values dominate; older values fade. Streaming-friendly — the math is O(1) per event with one state variable, so it works incrementally over a LiveSeries (write the closure inside live.map; see Live transforms → EMA).

The first ~1/alpha rows are noisy as the smoother seeds on the first raw value and converges. Use warmup: N to drop those:

cpu.smooth('cpu', 'ema', { alpha: 0.3, warmup: 4 });
// length === source.length - 4

Moving average — windowed arithmetic mean

cpu.smooth('cpu', 'movingAverage', {
window: '5m',
alignment: 'centered', // 'trailing' | 'leading' | 'centered'
output: 'cpuAvg',
});

What it actually does: at each event, average every value inside the window. Predictable lag (centered alignment removes phase shift; trailing introduces a half-window lag). Equal weighting of every sample inside the window — older and newer data treated the same, which makes it more stable than EMA but less reactive to recent trends.

LOESS — locally weighted regression

cpu.smooth('cpu', 'loess', {
span: 0.25, // fraction of series — smaller = more local
output: 'cpuLoess',
});

What it actually does: fits a low-degree polynomial through the neighborhood of each point, weighted by distance. High shape fidelity — preserves curves the other two flatten. Cost is per- point regression; not a streaming fit. Use it offline when the shape of the trend matters more than the speed.

Comparison

AspectEMAMoving averageLOESS
Cost per outputO(1)O(window size)*O(span × N)
Streaming-friendlywith care❌ (offline)
Reactivity to recentweighted recentequal in windowlocal fit
Lagnone, but skewedhalf-window (trailing) / 0 (centered)none
Shape fidelitylowmediumhigh
Tuning knobalphawindowspan
When to pickstreaming dashboard"average of last N min" semanticsoffline analysis where curves matter

* O(1) amortized via sliding-window deque under rolling('avg').

Picking one

  • Live dashboard, "give me the trend now": EMA, alpha ≈ 0.2–0.4.
  • Reporting "average over the last hour" with predictable bucketing: moving average.
  • Offline analysis, exploratory plots, where the shape of bumps matters: LOESS.

If you don't know yet: start with EMA, alpha: 0.3, and adjust if the trend feels too noisy or too slow.

Output column

All three smoothers take an optional output parameter:

  • Omitted — the source column is replaced in place.
  • String — a new column is appended; the source column passes through unchanged. Useful when you want both the raw and smoothed series for an overlay plot.

Further reading

For rolling stats other than smoothing — windowed sums, percentiles, counts — reach for rolling() directly.