NaN handling#
Real time series have gaps: a failed sensor, a market halt, a missing tick. Every screamer function declares how it treats a NaN on its input, and there are three policies:
ignore: the NaN is skipped and the output is NaN at that point, but the internal state is untouched, so the next value recovers. Used by the summary statistics (rolling, EW, bands, oscillators, filters).propagate: the NaN flows through the formula, so it lingers as long as it sits in the lookback, then clears. Used by positional functions (Diff,Lag,Return, and similar).nan-aware: the function's purpose is to remove NaN.FillNareplaces each gap with a constant;Ffillcarries the last finite value forward. Neither looks ahead.
Which function uses which policy, and the exact rules, are in NaN and warmup. A single series with one gap is enough to see all three.
import numpy as np
import pandas as pd
from screamer import RollingMean, Diff, FillNa, Ffill, Dropna
x = np.array([1.0, 2.0, 3.0, np.nan, 5.0, 6.0]) # one gap, at t = 3
The three policies on one gap#
Running one representative function per policy on the same series lines the
behaviours up side by side. Watch the gap at t = 3, and how far the NaN spreads
after it.
table = pd.DataFrame({
"input": x,
"ignore: RollingMean(3)": RollingMean(3)(x).round(3),
"propagate: Diff(1)": Diff(1)(x),
"nan-aware: FillNa(0)": FillNa(0.0)(x),
"nan-aware: Ffill": Ffill()(x),
})
table.index.name = "t"
table
| input | ignore: RollingMean(3) | propagate: Diff(1) | nan-aware: FillNa(0) | nan-aware: Ffill | |
|---|---|---|---|---|---|
| t | |||||
| 0 | 1.0 | NaN | NaN | 1.0 | 1.0 |
| 1 | 2.0 | NaN | 1.0 | 2.0 | 2.0 |
| 2 | 3.0 | 2.000 | 1.0 | 3.0 | 3.0 |
| 3 | NaN | NaN | NaN | 0.0 | 3.0 |
| 4 | 5.0 | 3.333 | NaN | 5.0 | 5.0 |
| 5 | 6.0 | 4.667 | 1.0 | 6.0 | 6.0 |
Each policy responds differently at the gap (t = 3) and immediately after:
ignore (
RollingMean) is NaN only att = 3. The window skipped the gap, sot = 4is finite again.propagate (
Diff) is NaN att = 3andt = 4.Diff(1)subtracts the previous value from each current one, so the gap spoils two outputs before it slides out att = 5. Surfacing the NaN is safer than computing a difference across the gap without flagging it.nan-aware (
FillNa,Ffill) has no NaN at all: the gap is replaced by a constant or by the last finite value.
Dropping gaps across streams: Dropna#
The three policies act within a single series. The multi-stream layer adds
Dropna, which removes events whose value is NaN entirely, producing a shorter,
gap-free stream. It changes the number of events (fewer out than in) and returns
results as a (values, index) pair. The full alignment model is in
Streams, values, and alignment.
idx = np.arange(x.size)
values, kept_idx = Dropna()(x, index=idx)
print("input :", x)
print("kept values :", values)
print("kept index :", kept_idx)
print(f"length {x.size} -> {values.size} (one gap removed)")
input : [ 1. 2. 3. nan 5. 6.]
kept values : [1. 2. 3. 5. 6.]
kept index : [0 1 2 4 5]
length 6 -> 5 (one gap removed)