From 702935b6cf44dadb7ecb2ae284573535ea1400c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 13:39:40 +0000 Subject: [PATCH 01/16] [Autoloop: perf-comparison] Iteration 392: gaussianKDE benchmark pair MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add bench_gaussianKDE.ts and bench_gaussianKDE.py — benchmarks Gaussian KDE evaluate on 1000-point dataset at 200 grid points using Silverman bandwidth. Metric: 728 → 729. Run: https://github.com/githubnext/tsb/actions/runs/29096370299 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_gaussianKDE.py | 45 ++++++++++++++++++++++++++ benchmarks/tsb/bench_gaussianKDE.ts | 44 +++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 benchmarks/pandas/bench_gaussianKDE.py create mode 100644 benchmarks/tsb/bench_gaussianKDE.ts diff --git a/benchmarks/pandas/bench_gaussianKDE.py b/benchmarks/pandas/bench_gaussianKDE.py new file mode 100644 index 00000000..353cb996 --- /dev/null +++ b/benchmarks/pandas/bench_gaussianKDE.py @@ -0,0 +1,45 @@ +"""Benchmark: Gaussian KDE evaluate on 1000-point dataset at 200 grid points. +Uses Scott bandwidth (numpy equivalent of Silverman for univariate data). +Pure numpy implementation to match pages workflow constraints. +""" +import json +import time +import numpy as np + +N = 1_000 +GRID = 200 +WARMUP = 3 +ITERATIONS = 20 + +# Create dataset: mix of two gaussians (same as TS benchmark) +i_vals = np.arange(N) +data = np.sin(i_vals * 0.01) * 2 + np.where(i_vals % 2 == 0, 0.0, 5.0) + +xmin, xmax = -5.0, 10.0 +grid = np.linspace(xmin, xmax, GRID) + + +def gaussian_kde_evaluate(data: np.ndarray, points: np.ndarray) -> np.ndarray: + """Gaussian KDE with Silverman bandwidth, pure numpy.""" + n = len(data) + std = np.std(data, ddof=1) + bw = (4.0 / (3.0 * n)) ** 0.2 * std # Silverman rule + diff = points[:, np.newaxis] - data[np.newaxis, :] # (GRID, N) + kernels = np.exp(-0.5 * (diff / bw) ** 2) / (bw * np.sqrt(2 * np.pi)) + return kernels.mean(axis=1) + + +for _ in range(WARMUP): + gaussian_kde_evaluate(data, grid) + +start = time.perf_counter() +for _ in range(ITERATIONS): + gaussian_kde_evaluate(data, grid) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "gaussianKDE", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_gaussianKDE.ts b/benchmarks/tsb/bench_gaussianKDE.ts new file mode 100644 index 00000000..9e3f4de8 --- /dev/null +++ b/benchmarks/tsb/bench_gaussianKDE.ts @@ -0,0 +1,44 @@ +/** + * Benchmark: Gaussian KDE evaluate on 1000-point dataset at 200 grid points. + * Uses Silverman bandwidth (default). + */ +import { gaussianKDE } from "../../src/index.js"; + +const N = 1_000; +const GRID = 200; +const WARMUP = 3; +const ITERATIONS = 20; + +// Create dataset: mix of two gaussians +const data: number[] = []; +for (let i = 0; i < N; i++) { + const x = Math.sin(i * 0.01) * 2 + (i % 2 === 0 ? 0 : 5); + data.push(x); +} + +// Grid points to evaluate KDE at +const xmin = -5; +const xmax = 10; +const grid: number[] = Array.from({ length: GRID }, (_, i) => xmin + (i / (GRID - 1)) * (xmax - xmin)); + +// Warm-up +for (let i = 0; i < WARMUP; i++) { + const kde = gaussianKDE(data); + kde.evaluate(grid); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const kde = gaussianKDE(data); + kde.evaluate(grid); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "gaussianKDE", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 9184f785d147c9b7980c62f394d37a9719e610c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Jul 2026 13:39:44 +0000 Subject: [PATCH 02/16] ci: trigger checks From 4d9c9c85f415a22b6c69945a90dc904efe9b0af2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Jul 2026 01:28:38 +0000 Subject: [PATCH 03/16] [Autoloop: perf-comparison] Iteration 393: hypothesis_tests benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add bench_hypothesis_tests.ts and bench_hypothesis_tests.py benchmarking ttest1samp, ttestInd, ttestRel, fOneway, pearsonr, spearmanr, mannWhitneyU over N=1000 samples, 20 measured iterations. metric 729→730. Run: https://github.com/githubnext/tsb/actions/runs/29134514272 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_hypothesis_tests.py | 103 ++++++++++++++++++++ benchmarks/tsb/bench_hypothesis_tests.ts | 41 ++++++++ 2 files changed, 144 insertions(+) create mode 100644 benchmarks/pandas/bench_hypothesis_tests.py create mode 100644 benchmarks/tsb/bench_hypothesis_tests.ts diff --git a/benchmarks/pandas/bench_hypothesis_tests.py b/benchmarks/pandas/bench_hypothesis_tests.py new file mode 100644 index 00000000..ee6863b6 --- /dev/null +++ b/benchmarks/pandas/bench_hypothesis_tests.py @@ -0,0 +1,103 @@ +"""Benchmark: hypothesis_tests — scipy-style hypothesis tests vs pandas/scipy equivalents.""" +import json +import math +import time + +WARMUP = 3 +ITERS = 20 +N = 1000 + + +def make_data(n: int, seed: int) -> list: + arr = [] + x = seed + for _ in range(n): + x = (x * 1664525 + 1013904223) & 0xFFFFFFFF + arr.append((x & 0xFFFFFFFF) / 0x100000000) + return [v * 4 + 2 for v in arr] + + +a = make_data(N, 42) +b = make_data(N, 99) + +# Pure-numpy/stdlib implementations matching what tsb does +try: + import numpy as np + + def ttest1samp_np(x, popmean): + x = np.asarray(x) + n = len(x) + mean = x.mean() + se = x.std(ddof=1) / math.sqrt(n) + t = (mean - popmean) / se + return t + + def ttestind_np(x, y): + x, y = np.asarray(x), np.asarray(y) + nx, ny = len(x), len(y) + vx, vy = x.var(ddof=1), y.var(ddof=1) + se = math.sqrt(vx / nx + vy / ny) + t = (x.mean() - y.mean()) / se + return t + + def ttestrel_np(x, y): + d = np.asarray(x) - np.asarray(y) + n = len(d) + t = d.mean() / (d.std(ddof=1) / math.sqrt(n)) + return t + + def foneway_np(*groups): + grand = np.concatenate(groups) + grand_mean = grand.mean() + ss_between = sum(len(g) * (np.mean(g) - grand_mean) ** 2 for g in groups) + ss_within = sum(((np.asarray(g) - np.mean(g)) ** 2).sum() for g in groups) + df_between = len(groups) - 1 + df_within = len(grand) - len(groups) + F = (ss_between / df_between) / (ss_within / df_within) + return F + + def pearsonr_np(x, y): + x, y = np.asarray(x), np.asarray(y) + r = np.corrcoef(x, y)[0, 1] + return r + + def spearmanr_np(x, y): + x, y = np.asarray(x), np.asarray(y) + rx = np.argsort(np.argsort(x)).astype(float) + ry = np.argsort(np.argsort(y)).astype(float) + r = np.corrcoef(rx, ry)[0, 1] + return r + + def mannwhitneyu_np(x, y): + nx, ny = len(x), len(y) + all_vals = sorted([(v, 0) for v in x] + [(v, 1) for v in y], key=lambda t: t[0]) + ranks = list(range(1, nx + ny + 1)) + u1 = sum(ranks[i] for i, (_, g) in enumerate(all_vals) if g == 0) + u1 -= nx * (nx + 1) / 2 + return u1 + + HAS_NP = True +except ImportError: + HAS_NP = False + + +def bench(): + total = 0.0 + for i in range(WARMUP + ITERS): + t0 = time.perf_counter() + if HAS_NP: + ttest1samp_np(a, 2.5) + ttestind_np(a, b) + ttestrel_np(a, b) + foneway_np(a, b) + pearsonr_np(a, b) + spearmanr_np(a, b) + mannwhitneyu_np(a, b) + elapsed = (time.perf_counter() - t0) * 1000 + if i >= WARMUP: + total += elapsed + mean_ms = total / ITERS + print(json.dumps({"function": "hypothesis_tests", "mean_ms": mean_ms, "iterations": ITERS, "total_ms": total})) + + +bench() diff --git a/benchmarks/tsb/bench_hypothesis_tests.ts b/benchmarks/tsb/bench_hypothesis_tests.ts new file mode 100644 index 00000000..05d36cb7 --- /dev/null +++ b/benchmarks/tsb/bench_hypothesis_tests.ts @@ -0,0 +1,41 @@ +import { ttest1samp, ttestInd, ttestRel, fOneway, pearsonr, spearmanr, mannWhitneyU } from "../../src/index.js"; + +const WARMUP = 3; +const ITERS = 20; +const N = 1000; + +// Seeded deterministic data +function makeData(n: number, seed: number): number[] { + const arr: number[] = []; + let x = seed; + for (let i = 0; i < n; i++) { + x = (x * 1664525 + 1013904223) & 0xffffffff; + arr.push((x >>> 0) / 0x100000000); + } + return arr; +} + +const a = makeData(N, 42).map((v) => v * 4 + 2); +const b = makeData(N, 99).map((v) => v * 4 + 2.5); + +function bench(): void { + let total = 0; + for (let i = 0; i < WARMUP + ITERS; i++) { + const t0 = performance.now(); + ttest1samp(a, 2.5); + ttestInd(a, b); + ttestRel(a, b); + fOneway([a, b]); + pearsonr(a, b); + spearmanr(a, b); + mannWhitneyU(a, b); + const elapsed = performance.now() - t0; + if (i >= WARMUP) total += elapsed; + } + const mean_ms = total / ITERS; + console.log( + JSON.stringify({ function: "hypothesis_tests", mean_ms, iterations: ITERS, total_ms: total }), + ); +} + +bench(); From dee50e0a75a7fcce819a451d9234b9094cc355df Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Jul 2026 01:31:02 +0000 Subject: [PATCH 04/16] ci: trigger checks From 00591a98a708b7b3302c70b1731c1a78324409b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Jul 2026 13:25:10 +0000 Subject: [PATCH 05/16] [Autoloop: perf-comparison] Iteration 394: entropy/klDivergence benchmarks Run: https://github.com/githubnext/tsb/actions/runs/29154137340 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_entropy.py | 44 ++++++++++++++++++++++++++++++ benchmarks/tsb/bench_entropy.ts | 31 +++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 benchmarks/pandas/bench_entropy.py create mode 100644 benchmarks/tsb/bench_entropy.ts diff --git a/benchmarks/pandas/bench_entropy.py b/benchmarks/pandas/bench_entropy.py new file mode 100644 index 00000000..5c3f3f05 --- /dev/null +++ b/benchmarks/pandas/bench_entropy.py @@ -0,0 +1,44 @@ +import numpy as np +import json +import time + +N = 100 +WARMUP = 5 +ITERS = 50 + +p = np.arange(1, N + 1, dtype=float) +q = np.arange(N, 0, -1, dtype=float) + +# Normalise +p_norm = p / p.sum() +q_norm = q / q.sum() + + +def entropy_fn(pk): + pk = pk / pk.sum() + return -np.sum(pk * np.log(pk + 1e-300)) + + +def kl_divergence(pk, qk): + pk = pk / pk.sum() + qk = qk / qk.sum() + mask = pk > 0 + return np.sum(pk[mask] * np.log(pk[mask] / (qk[mask] + 1e-300))) + + +for _ in range(WARMUP): + entropy_fn(p) + kl_divergence(p, q) + +t0 = time.perf_counter() +for _ in range(ITERS): + entropy_fn(p) + kl_divergence(p, q) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "entropy_klDivergence", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_entropy.ts b/benchmarks/tsb/bench_entropy.ts new file mode 100644 index 00000000..f4783389 --- /dev/null +++ b/benchmarks/tsb/bench_entropy.ts @@ -0,0 +1,31 @@ +import { entropy, klDivergence } from "../../src/index.js"; + +const N = 100; +const WARMUP = 5; +const ITERS = 50; + +// Build two probability distributions of length N +const p: number[] = Array.from({ length: N }, (_, i) => i + 1); +const q: number[] = Array.from({ length: N }, (_, i) => N - i); + +let t0 = performance.now(); +for (let i = 0; i < WARMUP; i++) { + entropy(p); + klDivergence(p, q); +} +t0 = performance.now(); + +for (let i = 0; i < ITERS; i++) { + entropy(p); + klDivergence(p, q); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "entropy_klDivergence", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 208bf941229bc0c90ebf8ab67c67b9e85d2d667e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 11 Jul 2026 13:28:05 +0000 Subject: [PATCH 06/16] ci: trigger checks From 1931df846322fa1ab4d2c8f0df4108e3f1c86068 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 12 Jul 2026 01:32:18 +0000 Subject: [PATCH 07/16] [Autoloop: perf-comparison] Iteration 395: mutualInformation/normalizedMI benchmarks Run: https://github.com/githubnext/tsb/actions/runs/29175171519 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_mutual_information.py | 68 +++++++++++++++++++ benchmarks/tsb/bench_mutual_information.ts | 34 ++++++++++ 2 files changed, 102 insertions(+) create mode 100644 benchmarks/pandas/bench_mutual_information.py create mode 100644 benchmarks/tsb/bench_mutual_information.ts diff --git a/benchmarks/pandas/bench_mutual_information.py b/benchmarks/pandas/bench_mutual_information.py new file mode 100644 index 00000000..2d0bafe1 --- /dev/null +++ b/benchmarks/pandas/bench_mutual_information.py @@ -0,0 +1,68 @@ +import numpy as np +import json +import time + +N = 1000 +WARMUP = 5 +ITERS = 50 +CATS = 10 + +# Same paired observations as the TS benchmark +xs = np.array([i % CATS for i in range(N)]) +ys = np.array([(i % CATS) + (i // CATS) % 3 for i in range(N)]) + + +def mutual_information(xs, ys): + """Compute mutual information I(X;Y) from paired observations.""" + n = len(xs) + ux, cx = np.unique(xs, return_counts=True) + uy, cy = np.unique(ys, return_counts=True) + px = cx / n + py = cy / n + + # Joint counts + joint_counts = {} + for x, y in zip(xs, ys): + key = (int(x), int(y)) + joint_counts[key] = joint_counts.get(key, 0) + 1 + + mi = 0.0 + for (xi, yi), cnt in joint_counts.items(): + pxy = cnt / n + pxi = px[np.searchsorted(ux, xi)] + pyi = py[np.searchsorted(uy, yi)] + if pxy > 0: + mi += pxy * np.log(pxy / (pxi * pyi + 1e-300)) + return mi + + +def normalized_mi(xs, ys): + """Normalized mutual information (arithmetic normalization).""" + mi = mutual_information(xs, ys) + n = len(xs) + _, cx = np.unique(xs, return_counts=True) + _, cy = np.unique(ys, return_counts=True) + px = cx / n + py = cy / n + hx = -np.sum(px * np.log(px + 1e-300)) + hy = -np.sum(py * np.log(py + 1e-300)) + denom = (hx + hy) / 2 + return mi / denom if denom > 0 else 0.0 + + +for _ in range(WARMUP): + mutual_information(xs, ys) + normalized_mi(xs, ys) + +t0 = time.perf_counter() +for _ in range(ITERS): + mutual_information(xs, ys) + normalized_mi(xs, ys) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "mutual_information", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_mutual_information.ts b/benchmarks/tsb/bench_mutual_information.ts new file mode 100644 index 00000000..0f8df611 --- /dev/null +++ b/benchmarks/tsb/bench_mutual_information.ts @@ -0,0 +1,34 @@ +import { mutualInformation, normalizedMI } from "../../src/index.js"; + +const N = 1000; +const WARMUP = 5; +const ITERS = 50; + +// Build paired observations: two correlated categorical variables (10 categories each) +const CATS = 10; +const pairs: [number, number][] = Array.from({ length: N }, (_, i) => [ + i % CATS, + (i % CATS) + Math.floor(i / CATS) % 3, +]); + +let t0 = performance.now(); +for (let i = 0; i < WARMUP; i++) { + mutualInformation(pairs); + normalizedMI(pairs, "arithmetic"); +} +t0 = performance.now(); + +for (let i = 0; i < ITERS; i++) { + mutualInformation(pairs); + normalizedMI(pairs, "arithmetic"); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "mutual_information", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 5a7556989a8616bae30cb00155a2418950450ede Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 12 Jul 2026 01:36:24 +0000 Subject: [PATCH 08/16] ci: trigger checks From 53d0b77458db90d89cef920a28fb41eb8c3a6e72 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 12 Jul 2026 13:25:13 +0000 Subject: [PATCH 09/16] [Autoloop: perf-comparison] Iteration 396: lreshape + linregress/polyfit benchmarks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add benchmark pairs for lreshape (wide-to-long reshape) and linregress/polyfit (linear and polynomial regression). Metric: 732→734. Run: https://github.com/githubnext/tsb/actions/runs/29194216373 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_linregress_polyfit.py | 43 +++++++++++++++++++ benchmarks/pandas/bench_lreshape.py | 37 ++++++++++++++++ benchmarks/tsb/bench_linregress_polyfit.ts | 33 ++++++++++++++ benchmarks/tsb/bench_lreshape.ts | 37 ++++++++++++++++ 4 files changed, 150 insertions(+) create mode 100644 benchmarks/pandas/bench_linregress_polyfit.py create mode 100644 benchmarks/pandas/bench_lreshape.py create mode 100644 benchmarks/tsb/bench_linregress_polyfit.ts create mode 100644 benchmarks/tsb/bench_lreshape.ts diff --git a/benchmarks/pandas/bench_linregress_polyfit.py b/benchmarks/pandas/bench_linregress_polyfit.py new file mode 100644 index 00000000..c4c08a97 --- /dev/null +++ b/benchmarks/pandas/bench_linregress_polyfit.py @@ -0,0 +1,43 @@ +"""Benchmark: linregress and polyfit — simple linear regression and polynomial fit. +Dataset: 10,000 points, 20 iterations. +""" +import json +import time +import numpy as np + +N = 10_000 +WARMUP = 3 +ITERATIONS = 20 + +x = np.arange(N, dtype=float) / N +y = 2.5 * x + 1.0 + np.sin(np.arange(N) * 0.01) * 0.1 + + +def linregress_numpy(xs, ys): + """Simple OLS linear regression matching scipy.stats.linregress.""" + n = len(xs) + sx = xs.sum() + sy = ys.sum() + sxx = (xs * xs).sum() + sxy = (xs * ys).sum() + slope = (n * sxy - sx * sy) / (n * sxx - sx * sx) + intercept = (sy - slope * sx) / n + return slope, intercept + + +for _ in range(WARMUP): + linregress_numpy(x, y) + np.polyfit(x, y, 2) + +start = time.perf_counter() +for _ in range(ITERATIONS): + linregress_numpy(x, y) + np.polyfit(x, y, 2) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "linregress_polyfit", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/pandas/bench_lreshape.py b/benchmarks/pandas/bench_lreshape.py new file mode 100644 index 00000000..96b02a3e --- /dev/null +++ b/benchmarks/pandas/bench_lreshape.py @@ -0,0 +1,37 @@ +"""Benchmark: lreshape — wide-to-long reshape using named column groups. +Dataset: 10,000 rows with 4 value columns (v1..v4), 50 iterations. +""" +import json +import time +import numpy as np +import pandas as pd + +N = 10_000 +WARMUP = 3 +ITERATIONS = 50 + +ids = np.arange(N) +data = { + "id": ids, + "v1": ids * 1.0, + "v2": ids * 2.0, + "v3": ids * 3.0, + "v4": ids * 4.0, +} +df = pd.DataFrame(data) +groups = {"value": ["v1", "v2", "v3", "v4"]} + +for _ in range(WARMUP): + pd.lreshape(df, groups) + +start = time.perf_counter() +for _ in range(ITERATIONS): + pd.lreshape(df, groups) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "lreshape", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_linregress_polyfit.ts b/benchmarks/tsb/bench_linregress_polyfit.ts new file mode 100644 index 00000000..b1f100be --- /dev/null +++ b/benchmarks/tsb/bench_linregress_polyfit.ts @@ -0,0 +1,33 @@ +/** + * Benchmark: linregress and polyfit — simple linear regression and polynomial fit. + * Dataset: 10,000 points, 20 iterations. + */ +import { linregress, polyfit, polyval } from "../../src/index.js"; + +const N = 10_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const x = Array.from({ length: N }, (_, i) => i / N); +const y = Array.from({ length: N }, (_, i) => 2.5 * (i / N) + 1.0 + Math.sin(i * 0.01) * 0.1); + +for (let i = 0; i < WARMUP; i++) { + linregress(x, y); + polyfit(x, y, 2); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + linregress(x, y); + polyfit(x, y, 2); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "linregress_polyfit", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); diff --git a/benchmarks/tsb/bench_lreshape.ts b/benchmarks/tsb/bench_lreshape.ts new file mode 100644 index 00000000..58f94459 --- /dev/null +++ b/benchmarks/tsb/bench_lreshape.ts @@ -0,0 +1,37 @@ +/** + * Benchmark: lreshape — wide-to-long reshape using named column groups. + * Dataset: 10,000 rows with 4 value columns (v1..v4), 50 iterations. + */ +import { DataFrame, lreshape } from "../../src/index.js"; + +const N = 10_000; +const WARMUP = 3; +const ITERATIONS = 50; + +const ids = Array.from({ length: N }, (_, i) => i); +const v1 = Array.from({ length: N }, (_, i) => i * 1.0); +const v2 = Array.from({ length: N }, (_, i) => i * 2.0); +const v3 = Array.from({ length: N }, (_, i) => i * 3.0); +const v4 = Array.from({ length: N }, (_, i) => i * 4.0); + +const df = DataFrame.fromColumns({ id: ids, v1, v2, v3, v4 }); +const groups = { value: ["v1", "v2", "v3", "v4"] }; + +for (let i = 0; i < WARMUP; i++) { + lreshape(df, groups); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + lreshape(df, groups); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "lreshape", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 5222e782d756e7ce577dde1c321580f3121b97bb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 12 Jul 2026 13:27:24 +0000 Subject: [PATCH 10/16] ci: trigger checks From c460e76a19a84f768ccce104660c605a69465fb5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Jul 2026 01:30:01 +0000 Subject: [PATCH 11/16] [Autoloop: perf-comparison] Iteration 397: contingency benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add expectedFreq/relativeRisk/oddsRatio/association (Cramér's V) benchmark. TypeScript: src/stats/contingency.ts functions on 4×4 and 2×2 tables, 50 iters. Python: pure-numpy equivalents (no scipy). Run: https://github.com/githubnext/tsb/actions/runs/29217286269 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_contingency.py | 78 ++++++++++++++++++++++++++ benchmarks/tsb/bench_contingency.ts | 50 +++++++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 benchmarks/pandas/bench_contingency.py create mode 100644 benchmarks/tsb/bench_contingency.ts diff --git a/benchmarks/pandas/bench_contingency.py b/benchmarks/pandas/bench_contingency.py new file mode 100644 index 00000000..431f58cb --- /dev/null +++ b/benchmarks/pandas/bench_contingency.py @@ -0,0 +1,78 @@ +""" +Benchmark: contingency — expected_freq, relative_risk, odds_ratio, association +Pure-numpy equivalents (no scipy) matching the TypeScript benchmark. +Dataset: same 4x4 table as the TypeScript benchmark. +""" +import json +import time +import numpy as np + +WARMUP = 10 +ITERS = 50 + +observed = np.array([ + [120, 80, 40, 60], + [90, 110, 70, 30], + [50, 60, 100, 90], + [40, 50, 90, 120], +], dtype=float) + +two_by_two = np.array([ + [60.0, 40.0], + [30.0, 70.0], +]) + + +def expected_freq(obs): + row_sums = obs.sum(axis=1, keepdims=True) + col_sums = obs.sum(axis=0, keepdims=True) + total = obs.sum() + return row_sums * col_sums / total + + +def relative_risk(obs): + a, b = obs[0, 0], obs[0, 1] + c, d = obs[1, 0], obs[1, 1] + risk0 = a / (a + b) + risk1 = c / (c + d) + return risk0 / risk1 + + +def odds_ratio(obs): + a, b = obs[0, 0], obs[0, 1] + c, d = obs[1, 0], obs[1, 1] + return (a * d) / (b * c) + + +def association_cramer(obs): + exp = expected_freq(obs) + chi2 = np.sum((obs - exp) ** 2 / exp) + n = obs.sum() + r, c = obs.shape + return np.sqrt(chi2 / n / min(r - 1, c - 1)) + + +# Warm up +for _ in range(WARMUP): + expected_freq(observed) + relative_risk(two_by_two) + odds_ratio(two_by_two) + association_cramer(observed) + +# Measure +start = time.perf_counter() +for _ in range(ITERS): + expected_freq(observed) + relative_risk(two_by_two) + odds_ratio(two_by_two) + association_cramer(observed) +total_s = time.perf_counter() - start +total_ms = total_s * 1000 +mean_ms = total_ms / ITERS + +print(json.dumps({ + "function": "contingency", + "mean_ms": round(mean_ms, 4), + "iterations": ITERS, + "total_ms": round(total_ms, 4), +})) diff --git a/benchmarks/tsb/bench_contingency.ts b/benchmarks/tsb/bench_contingency.ts new file mode 100644 index 00000000..609776c4 --- /dev/null +++ b/benchmarks/tsb/bench_contingency.ts @@ -0,0 +1,50 @@ +/** + * Benchmark: contingency — expectedFreq, relativeRisk, oddsRatio, association + * Dataset: 4×4 contingency table built from 100,000 categorised observations. + */ +import { expectedFreq, relativeRisk, oddsRatio, association } from "../../src/index.js"; + +const WARMUP = 10; +const ITERS = 50; + +// Build a 4×4 observed count table +const observed: readonly (readonly number[])[] = [ + [120, 80, 40, 60], + [90, 110, 70, 30], + [50, 60, 100, 90], + [40, 50, 90, 120], +]; + +// 2×2 table for relativeRisk / oddsRatio (requires exactly 2 rows × 2 cols) +const twoByTwo: readonly (readonly number[])[] = [ + [60, 40], + [30, 70], +]; + +// Warm up +for (let i = 0; i < WARMUP; i++) { + expectedFreq(observed); + relativeRisk(twoByTwo); + oddsRatio(twoByTwo); + association(observed, "cramer"); +} + +// Measure +const start = performance.now(); +for (let i = 0; i < ITERS; i++) { + expectedFreq(observed); + relativeRisk(twoByTwo); + oddsRatio(twoByTwo); + association(observed, "cramer"); +} +const total_ms = performance.now() - start; +const mean_ms = total_ms / ITERS; + +console.log( + JSON.stringify({ + function: "contingency", + mean_ms: parseFloat(mean_ms.toFixed(4)), + iterations: ITERS, + total_ms: parseFloat(total_ms.toFixed(4)), + }), +); From 5d1b2cab5afee22b6a0912716c6ef489e5e95016 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Jul 2026 01:32:04 +0000 Subject: [PATCH 12/16] ci: trigger checks From fb08d7ac70d0b63b6b0e81f449a04b5395d518a7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Jul 2026 13:48:04 +0000 Subject: [PATCH 13/16] [Autoloop: perf-comparison] Iteration 398: multivariate benchmark (mahalanobis, covMatrix, PCA) Run: https://github.com/githubnext/tsb/actions/runs/29254325811 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_multivariate.py | 53 +++++++++++++++++++++++++ benchmarks/tsb/bench_multivariate.ts | 49 +++++++++++++++++++++++ 2 files changed, 102 insertions(+) create mode 100644 benchmarks/pandas/bench_multivariate.py create mode 100644 benchmarks/tsb/bench_multivariate.ts diff --git a/benchmarks/pandas/bench_multivariate.py b/benchmarks/pandas/bench_multivariate.py new file mode 100644 index 00000000..4922a1d5 --- /dev/null +++ b/benchmarks/pandas/bench_multivariate.py @@ -0,0 +1,53 @@ +"""Benchmark: multivariate statistics — Mahalanobis distance, covariance matrix, PCA +Dataset: 500 observations x 5 features (matching the TypeScript benchmark) +""" +import json +import time +import numpy as np + +N = 500 +P = 5 +WARMUP = 3 +ITERATIONS = 20 + +# Generate deterministic dataset matching TS version +i_idx = np.arange(N) +j_idx = np.arange(P) +X = np.sin(i_idx[:, None] * 0.1 + j_idx[None, :]) * 10 + j_idx[None, :] * 2 # (N, P) + +u = X[0] +v = X[1] + +# Pre-compute covariance and diagonal inverse covariance +cov = np.cov(X, rowvar=False) # (P, P) sample covariance +diag_inv_cov = np.diag(1.0 / np.maximum(np.diag(cov), 1e-10)) # diagonal approx of VI + + +def run_iteration(X, u, v, diag_inv_cov): + # Mahalanobis distance with pre-computed VI + diff = u - v + dist = np.sqrt(diff @ diag_inv_cov @ diff) + # Covariance matrix + cov_m = np.cov(X, rowvar=False) + # PCA via SVD (3 components) + X_centered = X - X.mean(axis=0) + _, _, Vt = np.linalg.svd(X_centered, full_matrices=False) + components = Vt[:3] + scores = X_centered @ components.T + return dist, cov_m, scores + + +for _ in range(WARMUP): + run_iteration(X, u, v, diag_inv_cov) + +start = time.perf_counter() +for _ in range(ITERATIONS): + run_iteration(X, u, v, diag_inv_cov) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "multivariate", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_multivariate.ts b/benchmarks/tsb/bench_multivariate.ts new file mode 100644 index 00000000..199ec9e7 --- /dev/null +++ b/benchmarks/tsb/bench_multivariate.ts @@ -0,0 +1,49 @@ +/** + * Benchmark: multivariate statistics — mahalanobis distance, covMatrix, PCA + * Dataset: 500 observations × 5 features (realistic small-to-medium size) + */ +import { mahalanobis, covMatrix, PCA } from "../../src/index.js"; + +const N = 500; +const P = 5; +const WARMUP = 3; +const ITERATIONS = 20; + +// Generate a deterministic dataset +const X: number[][] = Array.from({ length: N }, (_, i) => + Array.from({ length: P }, (_, j) => Math.sin(i * 0.1 + j) * 10 + j * 2), +); + +const u = X[0]!; +const v = X[1]!; + +// Pre-compute inverse covariance for mahalanobis +const cov = covMatrix(X); +// Simple diagonal approximation for VI (invertMatrix is tested via mahalanobis internals) +const VI: number[][] = Array.from({ length: P }, (_, i) => + Array.from({ length: P }, (_, j) => (i === j ? 1 / Math.max(cov[i]![i]!, 1e-10) : 0)), +); + +// Warm up +for (let i = 0; i < WARMUP; i++) { + mahalanobis(u, v, VI); + covMatrix(X); + new PCA({ n_components: 3 }).fit(X); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + mahalanobis(u, v, VI); + covMatrix(X); + new PCA({ n_components: 3 }).fit(X); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "multivariate", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From b768b7d171c897d40a74866c2ca7bc678879c7c4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 13 Jul 2026 13:56:37 +0000 Subject: [PATCH 14/16] ci: trigger checks From 1dce402c207a91365acc4f8f97bd488748d848f7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Jul 2026 01:28:53 +0000 Subject: [PATCH 15/16] [Autoloop: perf-comparison] Iteration 399: IntegerArray/FloatingArray benchmark pair Add bench_integer_array.ts and bench_integer_array.py benchmarking nullable integer extension arrays (N=100_000, ~10% nulls): from/sum/mean/min/max/add/fillna. metric: 737 (was 736, +1) Run: https://github.com/githubnext/tsb/actions/runs/29298380978 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_integer_array.py | 41 +++++++++++++++++++++ benchmarks/tsb/bench_integer_array.ts | 45 ++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 benchmarks/pandas/bench_integer_array.py create mode 100644 benchmarks/tsb/bench_integer_array.ts diff --git a/benchmarks/pandas/bench_integer_array.py b/benchmarks/pandas/bench_integer_array.py new file mode 100644 index 00000000..fb481445 --- /dev/null +++ b/benchmarks/pandas/bench_integer_array.py @@ -0,0 +1,41 @@ +"""Benchmark: IntegerArray — nullable integer extension array operations. +N=100_000 elements with ~10% nulls using pandas IntegerArray. +Tests: from_sequence, sum, mean, min, max, add scalar, fillna. +""" +import json +import time +import numpy as np +import pandas as pd + +N = 100_000 +WARMUP = 3 +ITERATIONS = 20 + +# Build input with ~10% nulls (same pattern as TS version) +raw = [(None if i % 10 == 0 else int((i % 1000) - 500)) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="Int32") + _ = a.sum(skipna=True) + _ = a.mean(skipna=True) + _ = a.min(skipna=True) + _ = a.max(skipna=True) + _ = a + 1 + _ = a.fillna(0) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "integer_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_integer_array.ts b/benchmarks/tsb/bench_integer_array.ts new file mode 100644 index 00000000..a2eaea3d --- /dev/null +++ b/benchmarks/tsb/bench_integer_array.ts @@ -0,0 +1,45 @@ +/** + * Benchmark: IntegerArray — nullable integer extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/sum/mean/min/max/add/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 20; + +// Build input with ~10% nulls +const raw: (number | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : (i % 1000) - 500, +); + +for (let i = 0; i < WARMUP; i++) { + const a = arrays.IntegerArray.from(raw, "Int32"); + a.sum(); + a.mean(); + a.min(); + a.max(); + a.add(1); + a.fillna(0); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const a = arrays.IntegerArray.from(raw, "Int32"); + a.sum(); + a.mean(); + a.min(); + a.max(); + a.add(1); + a.fillna(0); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "integer_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From a4e669012ef6eba944977bf1e791648af3b525dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 14 Jul 2026 01:31:47 +0000 Subject: [PATCH 16/16] ci: trigger checks