Standalone functions for applying custom aggregation logic over sliding
windows, mirroring
pandas.Series.rolling().apply()
and
Rolling.agg()
.
rollingApply — Custom Function Per WindowApply any aggregation function to each rolling window. The function receives the valid (non-null, non-NaN) numeric values in the window and must return a single number.
import { rollingApply } from "tsb";
const prices = new Series({ data: [10, 12, 11, 15, 14, 16], name: "price" });
// Custom: range (max - min) over each 3-day window
const range = (w) => Math.max(...w) - Math.min(...w);
rollingApply(prices, 3, range).toArray();
// [null, null, 2, 4, 4, 5]
// ↑↑ insufficient data (need 3 observations)
| Option | Default | Description |
|---|---|---|
minPeriods | window | Minimum valid observations to compute (null otherwise) |
center | false | Centre the window (symmetric) instead of trailing |
raw | false | Pass full window including nulls (filtered to valid nums before fn call) |
// minPeriods=1 → start computing from the very first position
rollingApply(prices, 3, range, { minPeriods: 1 }).toArray();
// [0, 2, 2, 4, 4, 5]
// center=true → symmetric window around each point
rollingApply(prices, 3, range, { center: true }).toArray();
// [null, 2, 4, 4, 5, null]
rollingAgg — Multiple Aggregations at Once
Apply several named aggregation functions in a single pass over a Series,
returning a DataFrame where each column holds one
aggregation result.
import { rollingAgg } from "tsb";
const s = new Series({ data: [1, 2, 3, 4, 5, 6, 7, 8] });
const result = rollingAgg(s, 3, {
mean: (w) => w.reduce((a, b) => a + b, 0) / w.length,
max: (w) => Math.max(...w),
min: (w) => Math.min(...w),
range:(w) => Math.max(...w) - Math.min(...w),
});
// result is a DataFrame with columns: "mean", "max", "min", "range"
// result.col("mean").toArray() → [null, null, 2, 3, 4, 5, 6, 7]
// result.col("range").toArray() → [null, null, 2, 2, 2, 2, 2, 2]
s.rolling(3).agg({"mean": np.mean, "max": np.max, "min": np.min})
dataFrameRollingApply — Apply Per ColumnApply a single custom function independently to each column of a DataFrame, returning a new DataFrame of the same shape.
import { dataFrameRollingApply } from "tsb";
const df = DataFrame.fromColumns({
open: [100, 102, 101, 105, 103],
close: [101, 103, 100, 106, 104],
});
// Pairwise range within each 2-step window per column
const range = (w) => Math.max(...w) - Math.min(...w);
dataFrameRollingApply(df, 2, range);
// open close
// 0 null null
// 1 2 2
// 2 1 3
// 3 4 6
// 4 2 2
dataFrameRollingAgg — Multi-Agg Per Column
Apply multiple named aggregation functions to every column of a
DataFrame. The result has columns named
{originalColumn}_{aggName}.
import { dataFrameRollingAgg } from "tsb";
const df = DataFrame.fromColumns({
A: [1, 2, 3, 4, 5],
B: [10, 20, 30, 40, 50],
});
const out = dataFrameRollingAgg(df, 3, {
sum: (w) => w.reduce((a, b) => a + b, 0),
mean: (w) => w.reduce((a, b) => a + b, 0) / w.length,
});
// Columns: "A_sum", "A_mean", "B_sum", "B_mean"
// A_sum: [null, null, 6, 9, 12]
// A_mean: [null, null, 2, 3, 4]
// B_sum: [null, null, 60, 90, 120]
// B_mean: [null, null, 20, 30, 40]
| tsb | pandas |
|---|---|
rollingApply(s, w, fn) |
s.rolling(w).apply(fn, raw=True) |
rollingApply(s, w, fn, {minPeriods:1}) |
s.rolling(w, min_periods=1).apply(fn) |
rollingAgg(s, w, {f1, f2}) |
s.rolling(w).agg({"f1": f1, "f2": f2}) |
dataFrameRollingApply(df, w, fn) |
df.rolling(w).apply(fn) |
dataFrameRollingAgg(df, w, {f1, f2}) |
df.rolling(w).agg({"f1": f1, "f2": f2}) |
import { rollingAgg } from "tsb";
// Bollinger Band width = (upper - lower) / middle
// where upper = mean + 2·std, lower = mean - 2·std
const prices = new Series({
data: [20, 21, 22, 20, 19, 21, 23, 24, 22, 21],
name: "price",
});
const stats = rollingAgg(prices, 5, {
mean: (w) => w.reduce((a, b) => a + b, 0) / w.length,
std: (w) => {
const m = w.reduce((a, b) => a + b, 0) / w.length;
return Math.sqrt(w.reduce((a, b) => a + (b - m) ** 2, 0) / (w.length - 1));
},
});
// Bollinger Band width = 4 * std / mean
const bw = stats.col("std").toArray().map((std, i) => {
const mean = stats.col("mean").toArray()[i];
if (std === null || mean === null || mean === 0) return null;
return (4 * (std as number)) / (mean as number);
});