pipe_apply — Functional Pipeline & Apply Utilities
Standalone equivalents of the pandas
DataFrame.pipe()
/
Series.pipe()
chaining pattern plus various
apply()
/
applymap()
operations — usable without method-call syntax.
df.pipe(fn1).pipe(fn2). tsb provides a module-level
pipe(value, fn1, fn2, …) that works on any value,
not just DataFrames. All functions are pure — inputs are never mutated.
| Function | Pandas equivalent | Description |
|---|---|---|
| pipe(value, fn1, fn2, …) | df.pipe(fn).pipe(fn2) | Variadic type-safe pipeline — passes value through fns left-to-right |
| seriesApply(s, fn) | s.apply(fn) | Element-wise; fn receives (value, label, position) |
| seriesTransform(s, fn) | s.transform(fn) | Element-wise scalar→scalar; simpler than seriesApply |
| dataFrameApply(df, fn, axis?) | df.apply(fn, axis=0|1) | Apply fn to each column (axis=0) or row (axis=1) → Series of results |
| dataFrameApplyMap(df, fn) | df.applymap(fn) / df.map(fn) | Apply fn to every cell; fn receives (value, rowLabel, colName) |
| dataFrameTransform(df, fn) | df.transform(fn) | Replace each column with fn(col) — must return same-length Series |
| dataFrameTransformRows(df, fn) | df.apply(fn, axis=1, result_type='expand') | Replace each row with fn(rowRecord) — partial updates allowed |
import { pipe } from "tsb";
import { DataFrame } from "tsb";
// Type-safe pipeline with up to 8 steps (return type inferred at each step)
const result = pipe(
rawData,
(df) => df.dropna(), // DataFrame → DataFrame
(df) => df.assign({ z: df.col("x").add(df.col("y")).values }), // DataFrame → DataFrame
(df) => df.head(10), // DataFrame → DataFrame
(df) => df.sum(), // DataFrame → Series
);
// Works on any value — not just DataFrames
const n = pipe(
3,
(x) => x + 1, // 4
(x) => x * x, // 16
(x) => x - 1, // 15
);
// n === 15
import { seriesApply, seriesTransform } from "tsb";
import { Series } from "tsb";
const temps = new Series({ data: [22.1, 23.5, null, 21.8], name: "temp_C" });
// Element-wise with (value, label, position) context
const fahrenheit = seriesApply(temps, (v) => v === null ? null : (v as number) * 9/5 + 32);
// [71.78, 74.3, null, 71.24]
// Simple scalar transform (no label/position needed)
const rounded = seriesTransform(temps, (v) => v === null ? null : Math.round(v as number));
// [22, 24, null, 22]
// Using position to build cumulative logic
const withPos = seriesApply(
new Series({ data: [10, 20, 30] }),
(v, _label, pos) => (v as number) + pos * 100,
);
// [10, 120, 230]
import { dataFrameApply } from "tsb";
import { DataFrame } from "tsb";
const df = DataFrame.fromColumns({
score: [85, 92, 78, 95],
weight: [1.0, 1.2, 0.8, 1.5],
});
// axis=0 (default): apply fn to each column → Series indexed by column names
const colMax = dataFrameApply(df, (col) => col.max() ?? null);
// colMax.at("score") === 95
// colMax.at("weight") === 1.5
// axis=1: apply fn to each row → Series indexed by row labels
const weightedScore = dataFrameApply(
df,
(row) => (row.at("score") as number) * (row.at("weight") as number),
1,
);
// [85, 110.4, 62.4, 142.5]
import { dataFrameApplyMap } from "tsb";
import { DataFrame } from "tsb";
const df = DataFrame.fromColumns({
a: [1, -2, 3],
b: [-4, 5, -6],
});
// Zero out all negative values (like pandas df.applymap(lambda x: max(x, 0)))
const clipped = dataFrameApplyMap(df, (v) => {
return typeof v === "number" && v < 0 ? 0 : v;
});
// a: [1, 0, 3]
// b: [0, 5, 0]
// fn receives full context: (value, rowLabel, colName)
const tagged = dataFrameApplyMap(df, (v, row, col) => `${col}[${row}]=${v}`);
// a: ["a[0]=1", "a[1]=-2", "a[2]=3"]
// b: ["b[0]=-4", "b[1]=5", "b[2]=-6"]
import { dataFrameTransform, seriesTransform } from "tsb";
import { DataFrame } from "tsb";
const df = DataFrame.fromColumns({
x: [1, 2, 3, 4, 5],
y: [10, 20, 30, 40, 50],
});
// Z-score normalize each column
const normalized = dataFrameTransform(df, (col) => {
const mu = col.mean();
const sd = col.std();
return seriesTransform(col, (v) =>
typeof v === "number" && sd > 0 ? (v - mu) / sd : v
);
});
// Bin each column into quartiles
const binned = dataFrameTransform(df, (col) => {
const q1 = col.quantile(0.25);
const q2 = col.quantile(0.5);
const q3 = col.quantile(0.75);
return seriesTransform(col, (v) => {
const n = v as number;
if (n <= q1) return "Q1";
if (n <= q2) return "Q2";
if (n <= q3) return "Q3";
return "Q4";
});
});
import { dataFrameTransformRows } from "tsb";
import { DataFrame } from "tsb";
const df = DataFrame.fromColumns({
first: ["alice", "bob", "carol"],
last: ["smith", "jones", "white"],
score: [88, 75, 92],
});
// Normalise scores relative to the row's position (illustrative)
const updated = dataFrameTransformRows(df, (row, _label, pos) => ({
// Only return keys you want to change — others are preserved as-is
score: (row["score"] as number) + pos,
}));
// scores become [88, 76, 94]
// first and last columns are unchanged
// Full row transformation (compute full name)
const withFull = dataFrameTransformRows(df, (row) => ({
first: row["first"],
last: row["last"],
score: row["score"],
full: `${row["first"]} ${row["last"]}`,
}));
import { pipe, dataFrameApplyMap, dataFrameTransform, seriesTransform } from "tsb";
import { DataFrame } from "tsb";
const raw = DataFrame.fromColumns({
price: [9.99, -1, 24.5, null, 49.0],
quantity: [3, 5, null, 2, 1],
});
// Clean → impute → normalise in one readable pipeline
const clean = pipe(
raw,
// 1. zero out invalid prices/quantities
(df) => dataFrameApplyMap(df, (v) =>
v === null || (typeof v === "number" && v < 0) ? 0 : v
),
// 2. add derived revenue column
(df) => df.assign({
revenue: df.col("price").mul(df.col("quantity")).values,
}),
// 3. round everything to 2 dp
(df) => dataFrameTransform(df, (col) =>
seriesTransform(col, (v) =>
typeof v === "number" ? Math.round(v * 100) / 100 : v
)
),
);
pandas DataFrame.pipe docs · pandas DataFrame.apply docs · tsb on GitHub