Conditional value selection and replacement — mirrors pandas.Series.where and pandas.DataFrame.mask.
whereSeries(series, cond) keeps each element where cond is true and replaces it with null (or a custom other) where cond is false.
import { Series, whereSeries } from "tsb";
const scores = new Series({ data: [45, 72, 58, 88, 91, 30], name: "score" });
// Keep only passing scores (>= 60); replace failing scores with null
const passing = whereSeries(scores, (v) => v >= 60);
console.log("passing:", [...passing.values]);
// → [null, 72, null, 88, 91, null]
// Replace failing scores with 0 instead of null
const zeroFail = whereSeries(scores, (v) => v >= 60, { other: 0 });
console.log("zero-fail:", [...zeroFail.values]);
// → [0, 72, 0, 88, 91, 0]
maskSeries is the inverse of whereSeries: it replaces where cond is true and keeps where cond is false.
import { Series, maskSeries } from "tsb";
const temps = new Series({ data: [-5, 12, -3, 20, 7], name: "temp_C" });
// Mask (hide) sub-zero temperatures
const noFrost = maskSeries(temps, (v) => v < 0);
console.log("no frost:", [...noFrost.values]);
// → [null, 12, null, 20, 7]
// Replace sub-zero with a sentinel value
const clamped = maskSeries(temps, (v) => v < 0, { other: 0 });
console.log("clamped: ", [...clamped.values]);
// → [0, 12, 0, 20, 7]
Pass a Series<boolean> (or a plain boolean array) as the condition for position-aligned filtering.
import { Series, whereSeries, maskSeries } from "tsb";
const prices = new Series({ data: [100, 200, 150, 80, 300], name: "price" });
const inStock = new Series({ data: [true, false, true, false, true] });
// Keep prices only for in-stock items
const available = whereSeries(prices, inStock);
console.log("in-stock prices:", [...available.values]);
// → [100, null, 150, null, 300]
// Mask out-of-stock prices (same result — cond is inverted)
const masked = maskSeries(prices, inStock.values.map((v) => !v));
console.log("masked: ", [...masked.values]);
// → [100, null, 150, null, 300]
whereDataFrame(df, cond) applies the condition independently to each cell across all columns.
import { DataFrame, whereDataFrame } from "tsb";
const df = DataFrame.fromColumns({
a: [1, -2, 3],
b: [-4, 5, -6],
c: [ 7, 8, 9],
});
// Keep non-negative values; replace negatives with null
const positive = whereDataFrame(df, (v) => v >= 0);
console.log("a:", [...positive.col("a").values]); // [1, null, 3]
console.log("b:", [...positive.col("b").values]); // [null, 5, null]
console.log("c:", [...positive.col("c").values]); // [7, 8, 9]
import { DataFrame, maskDataFrame } from "tsb";
const df = DataFrame.fromColumns({
revenue: [100, 0, 250, -50, 0],
cost: [ 80, 0, 200, 30, 0],
});
// Mask zeros (replace with null to mark as missing)
const noZeros = maskDataFrame(df, (v) => v === 0);
console.log("revenue:", [...noZeros.col("revenue").values]);
// → [100, null, 250, -50, null]
console.log("cost: ", [...noZeros.col("cost").values]);
// → [80, null, 200, 30, null]
Pass a boolean DataFrame as the condition for per-cell control.
import { DataFrame, whereDataFrame } from "tsb";
const data = DataFrame.fromColumns({
x: [10, 20, 30],
y: [40, 50, 60],
});
// Custom boolean mask per cell
const cond = DataFrame.fromColumns({
x: [true, false, true],
y: [false, true, true],
});
const result = whereDataFrame(data, cond);
console.log("x:", [...result.col("x").values]); // [10, null, 30]
console.log("y:", [...result.col("y").values]); // [null, 50, 60]
Chaining whereSeries and maskSeries is a clean way to apply lower and upper bounds.
import { Series, whereSeries, maskSeries } from "tsb";
const raw = new Series({ data: [-10, 0, 5, 15, 100, 3], name: "value" });
const LO = 0, HI = 10;
// 1) Replace values below lower bound with LO
const step1 = whereSeries(raw, (v) => (v as number) >= LO, { other: LO });
// 2) Replace values above upper bound with HI
const clamped = whereSeries(step1, (v) => (v as number) <= HI, { other: HI });
console.log("clamped:", [...clamped.values]);
// → [0, 0, 5, 10, 10, 3]
clip() for simple numeric lower/upper bounds.where() / mask() for arbitrary conditions — including non-numeric types,
string patterns, or per-cell boolean DataFrames.
import { Series, whereSeries, clip } from "tsb";
const s = new Series({ data: [-3, 1, 5, 10], name: "val" });
// clip is concise for numeric bounds
const clipped = clip(s, { lower: 0, upper: 6 });
console.log("clipped: ", [...clipped.values]); // [0, 1, 5, 6]
// where gives full control — replace out-of-range with null instead of clamping
const filtered = whereSeries(s, (v) => (v as number) >= 0 && (v as number) <= 6);
console.log("filtered:", [...filtered.values]); // [null, 1, 5, null]