← tsb playground

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.

Why standalone? pandas chains operations via methods: 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.

API Summary

FunctionPandas equivalentDescription
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

pipe — functional pipeline

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

seriesApply — element-wise apply

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]

dataFrameApply — column/row aggregation

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]

dataFrameApplyMap — element-wise cell transform

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"]

dataFrameTransform — column-wise transform

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";
  });
});

dataFrameTransformRows — row-wise transform

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"]}`,
}));

Combining pipe + apply

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