From 2e09d0eb907f77108d44ceb7e65359be832f3537 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 23 Jul 2026 13:33:06 +0000 Subject: [PATCH 01/61] [Autoloop: perf-comparison] Iteration 418: firstValidIndex/lastValidIndex benchmark Run: https://github.com/githubnext/tsb/actions/runs/30010910150 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_first_last_valid_index.py | 45 ++++++++++++++++ .../tsb/bench_first_last_valid_index.ts | 52 +++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 benchmarks/pandas/bench_first_last_valid_index.py create mode 100644 benchmarks/tsb/bench_first_last_valid_index.ts diff --git a/benchmarks/pandas/bench_first_last_valid_index.py b/benchmarks/pandas/bench_first_last_valid_index.py new file mode 100644 index 00000000..674aab50 --- /dev/null +++ b/benchmarks/pandas/bench_first_last_valid_index.py @@ -0,0 +1,45 @@ +""" +Benchmark: first_valid_index / last_valid_index +Outputs JSON: {"function": "first_last_valid_index", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +import pandas as pd + +N = 100_000 + +# Series where first valid is near the start (a few NaN at beginning) +data_start = np.where(np.arange(N) < 10, np.nan, np.arange(N, dtype=float)) +series_start = pd.Series(data_start) + +# Series where last valid is near the end (a few NaN at the end) +data_end = np.where(np.arange(N) >= N - 10, np.nan, np.arange(N, dtype=float)) +series_end = pd.Series(data_end) + +# Series with NaN scattered throughout +data_mixed = np.where(np.arange(N) % 7 == 0, np.nan, np.arange(N, dtype=float)) +series_mixed = pd.Series(data_mixed) + +# Warm-up +for _ in range(20): + series_start.first_valid_index() + series_end.last_valid_index() + series_mixed.first_valid_index() + series_mixed.last_valid_index() + +iterations = 500 +start = time.perf_counter() +for _ in range(iterations): + series_start.first_valid_index() + series_end.last_valid_index() + series_mixed.first_valid_index() + series_mixed.last_valid_index() +total_ms = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "first_last_valid_index", + "mean_ms": total_ms / iterations, + "iterations": iterations, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_first_last_valid_index.ts b/benchmarks/tsb/bench_first_last_valid_index.ts new file mode 100644 index 00000000..33d04869 --- /dev/null +++ b/benchmarks/tsb/bench_first_last_valid_index.ts @@ -0,0 +1,52 @@ +/** + * Benchmark: firstValidIndex / lastValidIndex + * Outputs JSON: {"function": "first_last_valid_index", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { Series, firstValidIndex, lastValidIndex } from "../../src/index.ts"; + +const N = 100_000; + +// Series where first valid is near the start (a few NaN/null at beginning) +const dataStart = Float64Array.from({ length: N }, (_, i) => + i < 10 ? NaN : i, +); +const seriesStart = new Series({ data: dataStart }); + +// Series where last valid is near the end (a few NaN/null at the end) +const dataEnd = Float64Array.from({ length: N }, (_, i) => + i >= N - 10 ? NaN : i, +); +const seriesEnd = new Series({ data: dataEnd }); + +// Series with NaN scattered throughout (worst-case scan) +const dataMixed = Float64Array.from({ length: N }, (_, i) => + i % 7 === 0 ? NaN : i, +); +const seriesMixed = new Series({ data: dataMixed }); + +// Warm-up +for (let w = 0; w < 20; w++) { + firstValidIndex(seriesStart); + lastValidIndex(seriesEnd); + firstValidIndex(seriesMixed); + lastValidIndex(seriesMixed); +} + +const iterations = 500; +const start = performance.now(); +for (let i = 0; i < iterations; i++) { + firstValidIndex(seriesStart); + lastValidIndex(seriesEnd); + firstValidIndex(seriesMixed); + lastValidIndex(seriesMixed); +} +const total_ms = performance.now() - start; + +console.log( + JSON.stringify({ + function: "first_last_valid_index", + mean_ms: total_ms / iterations, + iterations, + total_ms, + }), +); From 3dc825db15d933035463639253588e2b104b3704 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 23 Jul 2026 13:33:10 +0000 Subject: [PATCH 02/61] ci: trigger checks From 402ebd60e22e5fa960c32897b16b6bfc6906e568 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 01:28:41 +0000 Subject: [PATCH 03/61] [Autoloop: perf-comparison] Iteration 419: BooleanArray benchmark Benchmark BooleanArray extension array (100k elements, ~10% nulls, 50 iters): from/any/all/sum/and/or/not/fillna. TS: arrays.BooleanArray from tsb; Python: pd.array(dtype='boolean'). Run: https://github.com/githubnext/tsb/actions/runs/30058807058 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_boolean_array.py | 43 ++++++++++++++++++++++ benchmarks/tsb/bench_boolean_array.ts | 46 ++++++++++++++++++++++++ 2 files changed, 89 insertions(+) create mode 100644 benchmarks/pandas/bench_boolean_array.py create mode 100644 benchmarks/tsb/bench_boolean_array.ts diff --git a/benchmarks/pandas/bench_boolean_array.py b/benchmarks/pandas/bench_boolean_array.py new file mode 100644 index 00000000..8a16b8d8 --- /dev/null +++ b/benchmarks/pandas/bench_boolean_array.py @@ -0,0 +1,43 @@ +"""Benchmark: BooleanArray — nullable boolean extension array operations. +N=100_000 elements with ~10% nulls using pandas BooleanArray. +Tests: array creation, any, all, sum, and, or, invert, fillna. +""" +import json +import time +import pandas as pd + +N = 100_000 +WARMUP = 5 +ITERATIONS = 50 + +# Same pattern as TS version (~10% nulls) +raw = [(None if i % 10 == 0 else bool(i % 3 != 0)) for i in range(N)] +raw2 = [(None if i % 7 == 0 else bool(i % 2 == 0)) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="boolean") + b = pd.array(raw2, dtype="boolean") + _ = a.any(skipna=True) + _ = a.all(skipna=True) + _ = a.sum(skipna=True) + _ = a & b + _ = a | b + _ = ~a + _ = a.fillna(False) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "boolean_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_boolean_array.ts b/benchmarks/tsb/bench_boolean_array.ts new file mode 100644 index 00000000..9d00600e --- /dev/null +++ b/benchmarks/tsb/bench_boolean_array.ts @@ -0,0 +1,46 @@ +/** + * Benchmark: BooleanArray — nullable boolean extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/any/all/sum/and/or/not/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 5; +const ITERATIONS = 50; + +// Build input with ~10% nulls (same pattern across TS and Python) +const raw: (boolean | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : i % 3 !== 0, +); + +// Build a second array for bitwise ops +const raw2: (boolean | null)[] = Array.from({ length: N }, (_, i) => + i % 7 === 0 ? null : i % 2 === 0, +); + +function run(): void { + const a = arrays.BooleanArray.from(raw); + const b = arrays.BooleanArray.from(raw2); + a.any(); + a.all(); + a.sum(); + a.and(b); + a.or(b); + a.not(); + a.fillna(false); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "boolean_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From e55e59067378eacb16dbafa78d0b4fefa2030f8b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 01:30:56 +0000 Subject: [PATCH 04/61] ci: trigger checks From 8d70dac506d156f176e6be57ef35c1b535f34623 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 13:28:16 +0000 Subject: [PATCH 05/61] [Autoloop: perf-comparison] Iteration 420: StringArray benchmark Run: https://github.com/githubnext/tsb/actions/runs/30096576504 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_string_array.py | 41 +++++++++++++++++++++++++ benchmarks/tsb/bench_string_array.ts | 40 ++++++++++++++++++++++++ 2 files changed, 81 insertions(+) create mode 100644 benchmarks/pandas/bench_string_array.py create mode 100644 benchmarks/tsb/bench_string_array.ts diff --git a/benchmarks/pandas/bench_string_array.py b/benchmarks/pandas/bench_string_array.py new file mode 100644 index 00000000..fd9bcda2 --- /dev/null +++ b/benchmarks/pandas/bench_string_array.py @@ -0,0 +1,41 @@ +"""Benchmark: StringArray — nullable string extension array operations. +N=100_000 elements with ~10% nulls using pandas StringDtype. +Tests: from_sequence, upper, lower, strip, contains, len, fillna. +""" +import json +import time +import pandas as pd + +N = 100_000 +WARMUP = 3 +ITERATIONS = 50 + +WORDS = ["hello", "world", " foo ", "bar", "baz", " qux ", "quux", "corge", "grault", "garply"] + +raw = [(None if i % 10 == 0 else WORDS[i % len(WORDS)]) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="string") + _ = a.str.upper() + _ = a.str.lower() + _ = a.str.strip() + _ = a.str.contains("oo", na=False) + _ = a.str.len() + _ = a.fillna("NA") + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "string_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_string_array.ts b/benchmarks/tsb/bench_string_array.ts new file mode 100644 index 00000000..6902f524 --- /dev/null +++ b/benchmarks/tsb/bench_string_array.ts @@ -0,0 +1,40 @@ +/** + * Benchmark: StringArray — nullable string extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/upper/lower/strip/contains/len/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 50; + +const WORDS = ["hello", "world", " foo ", "bar", "baz", " qux ", "quux", "corge", "grault", "garply"]; + +const raw: (string | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : WORDS[i % WORDS.length], +); + +function run(): void { + const a = arrays.StringArray.from(raw); + a.upper(); + a.lower(); + a.strip(); + a.contains("oo"); + a.len(); + a.fillna("NA"); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "string_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 1b0a1f83240a24eb10a499aaa8a206b6a851ea92 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Jul 2026 13:36:13 +0000 Subject: [PATCH 06/61] ci: trigger checks From 7500ea9ee9a1d1bb718bbbb1edc6d4b0eb9afc6f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 01:28:16 +0000 Subject: [PATCH 07/61] [Autoloop: perf-comparison] Iteration 421: DatetimeArray benchmark Add bench_datetime_array.ts and bench_datetime_array.py benchmarking nullable datetime extension array: from/year/month/day/isna/notna/fillna. 100k elements with ~10% nulls, 50 iterations. Run: https://github.com/githubnext/tsb/actions/runs/30138360760 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_datetime_array.py | 41 +++++++++++++++++++++++ benchmarks/tsb/bench_datetime_array.ts | 41 +++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 benchmarks/pandas/bench_datetime_array.py create mode 100644 benchmarks/tsb/bench_datetime_array.ts diff --git a/benchmarks/pandas/bench_datetime_array.py b/benchmarks/pandas/bench_datetime_array.py new file mode 100644 index 00000000..139917d6 --- /dev/null +++ b/benchmarks/pandas/bench_datetime_array.py @@ -0,0 +1,41 @@ +"""Benchmark: DatetimeArray — nullable datetime extension array operations. +N=100_000 elements with ~10% nulls using pandas DatetimeArray. +Tests: from_sequence, year, month, day, isna, notna, fillna. +""" +import json +import time +import pandas as pd +import numpy as np + +N = 100_000 +WARMUP = 3 +ITERATIONS = 50 + +base = pd.Timestamp("2020-01-01") +raw = [(None if i % 10 == 0 else base + pd.Timedelta(days=i)) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="datetime64[ns]") + _ = a.year + _ = a.month + _ = a.day + _ = pd.isna(a) + _ = ~pd.isna(a) + _ = a.fillna(pd.Timestamp("2000-01-01")) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "datetime_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_datetime_array.ts b/benchmarks/tsb/bench_datetime_array.ts new file mode 100644 index 00000000..db6de09e --- /dev/null +++ b/benchmarks/tsb/bench_datetime_array.ts @@ -0,0 +1,41 @@ +/** + * Benchmark: DatetimeArray — nullable datetime extension array operations. + * N=100_000 elements with ~10% nulls. Tests from/year/month/day/isna/notna/fillna. + */ +import { arrays, Timestamp } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 50; + +const BASE_MS = new Date("2020-01-01").getTime(); +const raw: (string | null)[] = Array.from({ length: N }, (_, i) => { + if (i % 10 === 0) return null; + const ms = BASE_MS + i * 86_400_000; // 1 day per element + return new Date(ms).toISOString().slice(0, 10); +}); + +function run(): void { + const a = arrays.DatetimeArray.from(raw); + a.year; + a.month; + a.day; + a.isna(); + a.notna(); + a.fillna(new Timestamp("2000-01-01")); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "datetime_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 744a1a52352272fe21e678a83b943594d218304c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 01:30:38 +0000 Subject: [PATCH 08/61] ci: trigger checks From 744e7b264b15c754b9f0b5d855b5f2287ff3fd64 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 13:30:13 +0000 Subject: [PATCH 09/61] [Autoloop: perf-comparison] Iteration 422: TimedeltaArray benchmark Add bench_timedelta_array.ts and bench_timedelta_array.py benchmarks for TimedeltaArray (100k elements, ~10% nulls, 50 iters): from/days/hours/ totalSeconds/isna/notna/sum/min/max/fillna. Run: https://github.com/githubnext/tsb/actions/runs/30159549081 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_timedelta_array.py | 58 ++++++++++++++++++++++ benchmarks/tsb/bench_timedelta_array.ts | 52 +++++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 benchmarks/pandas/bench_timedelta_array.py create mode 100644 benchmarks/tsb/bench_timedelta_array.ts diff --git a/benchmarks/pandas/bench_timedelta_array.py b/benchmarks/pandas/bench_timedelta_array.py new file mode 100644 index 00000000..fb64110f --- /dev/null +++ b/benchmarks/pandas/bench_timedelta_array.py @@ -0,0 +1,58 @@ +""" +Benchmark: pd.arrays.TimedeltaArray — create and operate on nullable timedelta arrays. +Outputs JSON: {"function": "timedelta_array", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +import pandas as pd + +SIZE = 100_000 +WARMUP = 5 +ITERATIONS = 50 + +# Build values: ~10% NaT, small durations (i seconds) to avoid overflow +values = np.array( + [None if i % 10 == 0 else i * 1_000_000_000 for i in range(SIZE)], # nanoseconds (1 ns/unit) + dtype=object, +) +td_values = pd.to_timedelta(values, unit="ns") +fill_value = pd.Timedelta(0) + + +def run(): + arr = pd.array(td_values, dtype="timedelta64[ns]") + + # Component access + _ = arr.days + _ = arr.seconds + _ = arr.total_seconds() + + # Null checks + _ = arr.isna() + _ = ~arr.isna() + + # Aggregation (via numpy) + valid = td_values[~pd.isna(td_values)] + _ = valid.sum() + _ = valid.min() + _ = valid.max() + + # Fill + _ = arr.fillna(fill_value) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total_ms = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "timedelta_array", + "mean_ms": total_ms / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_timedelta_array.ts b/benchmarks/tsb/bench_timedelta_array.ts new file mode 100644 index 00000000..b810adef --- /dev/null +++ b/benchmarks/tsb/bench_timedelta_array.ts @@ -0,0 +1,52 @@ +/** + * Benchmark: TimedeltaArray — create and operate on nullable timedelta arrays. + * Outputs JSON: {"function": "timedelta_array", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { TimedeltaArray, Timedelta } from "../../src/index.js"; + +const SIZE = 100_000; +const WARMUP = 5; +const ITERATIONS = 50; + +// Build raw values: ~10% null +const values: (number | null)[] = Array.from({ length: SIZE }, (_, i) => + i % 10 === 0 ? null : i * 60_000, +); + +const fillValue = Timedelta.fromMilliseconds(0); + +function run(): void { + const arr = TimedeltaArray.from(values); + + // Component access + void arr.days; + void arr.hours; + void arr.totalSeconds; + + // Null checks + void arr.isna(); + void arr.notna(); + + // Aggregation + void arr.sum(); + void arr.min(); + void arr.max(); + + // Fill + void arr.fillna(fillValue); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "timedelta_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From b1bc5cca9a41c1a0f21d9a96d321c1d84a6663b4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 25 Jul 2026 13:32:50 +0000 Subject: [PATCH 10/61] ci: trigger checks From dfda6b122f5a057b7277aca5d84fb0daab6fc60d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 01:32:31 +0000 Subject: [PATCH 11/61] [Autoloop: perf-comparison] Iteration 423: gaussianKDE benchmark Add matched TypeScript + Python benchmarks for Gaussian KDE (evaluate + integrate) on a bimodal 10k-point dataset with 200 evaluation points, 20 iterations. Run: https://github.com/githubnext/tsb/actions/runs/30182788398 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_gaussian_kde.py | 48 +++++++++++++++++++++++++ benchmarks/tsb/bench_gaussian_kde.ts | 40 +++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 benchmarks/pandas/bench_gaussian_kde.py create mode 100644 benchmarks/tsb/bench_gaussian_kde.ts diff --git a/benchmarks/pandas/bench_gaussian_kde.py b/benchmarks/pandas/bench_gaussian_kde.py new file mode 100644 index 00000000..5d012c82 --- /dev/null +++ b/benchmarks/pandas/bench_gaussian_kde.py @@ -0,0 +1,48 @@ +"""Benchmark: Gaussian KDE on 10k data points — evaluate, integrate (pure numpy)""" +import json, time +import numpy as np + +N = 10_000 +EVAL_POINTS = 200 +WARMUP = 3 +ITERATIONS = 20 + +# Generate data from a bimodal distribution +indices = np.arange(N, dtype=np.float64) +t = indices / N +data = np.where(t < 0.5, np.sin(indices * 0.05) * 2 + 3, np.cos(indices * 0.03) * 2 - 3) + +eval_pts = np.linspace(-6, -6 + (EVAL_POINTS - 1) * 0.06, EVAL_POINTS) + +# Silverman bandwidth (matches tsb default) +std = np.std(data, ddof=1) +bw = (4.0 / (3.0 * N)) ** 0.2 * std + +SQRT_2PI = np.sqrt(2.0 * np.pi) + +def kde_evaluate(data, eval_pts, bw): + # shape: (n_eval, n_data) + z = (eval_pts[:, None] - data[None, :]) / bw + return np.exp(-0.5 * z * z).sum(axis=1) / (N * bw * SQRT_2PI) + +def kde_integrate(data, a, b, bw, n=200): + xs = np.linspace(a, b, n) + ys = kde_evaluate(data, xs, bw) + return np.trapz(ys, xs) + +for _ in range(WARMUP): + kde_evaluate(data, eval_pts, bw) + kde_integrate(data, -2, 2, bw) + +start = time.perf_counter() +for _ in range(ITERATIONS): + kde_evaluate(data, eval_pts, bw) + kde_integrate(data, -2, 2, bw) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "gaussian_kde", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_gaussian_kde.ts b/benchmarks/tsb/bench_gaussian_kde.ts new file mode 100644 index 00000000..a0c639a5 --- /dev/null +++ b/benchmarks/tsb/bench_gaussian_kde.ts @@ -0,0 +1,40 @@ +/** + * Benchmark: Gaussian KDE on 10k data points — evaluate, pdf, integrate + */ +import { gaussianKDE } from "../../src/index.js"; + +const N = 10_000; +const EVAL_POINTS = 200; +const WARMUP = 3; +const ITERATIONS = 20; + +// Generate data from a bimodal distribution +const data: number[] = Array.from({ length: N }, (_, i) => { + const t = i / N; + return t < 0.5 ? Math.sin(i * 0.05) * 2 + 3 : Math.cos(i * 0.03) * 2 - 3; +}); + +const evalPoints: number[] = Array.from({ length: EVAL_POINTS }, (_, i) => -6 + i * 0.06); + +const kde = gaussianKDE(data); + +for (let i = 0; i < WARMUP; i++) { + kde.evaluate(evalPoints); + kde.integrate(-2, 2); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + kde.evaluate(evalPoints); + kde.integrate(-2, 2); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "gaussian_kde", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 25e71058f00b1703e72c22d95bf96c1d9a3998dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 01:49:55 +0000 Subject: [PATCH 12/61] ci: trigger checks From 66c5b8250a98b4eaf817b04561b442e19cddf82d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 13:26:46 +0000 Subject: [PATCH 13/61] [Autoloop: perf-comparison] Iteration 424: add mode benchmark Run: https://github.com/githubnext/tsb/actions/runs/30203838065 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_mode.py | 27 +++++++++++++++++++++++++++ benchmarks/tsb/bench_mode.ts | 31 +++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 benchmarks/pandas/bench_mode.py create mode 100644 benchmarks/tsb/bench_mode.ts diff --git a/benchmarks/pandas/bench_mode.py b/benchmarks/pandas/bench_mode.py new file mode 100644 index 00000000..cf585756 --- /dev/null +++ b/benchmarks/pandas/bench_mode.py @@ -0,0 +1,27 @@ +"""Benchmark: mode on 100k-element Series (mixed numeric with repeats)""" +import json, time +import numpy as np +import pandas as pd + +ROWS = 100_000 +WARMUP = 3 +ITERATIONS = 10 + +# Same data: values 0..9 cycling so mode is meaningful +data = np.arange(ROWS) % 10 +s = pd.Series(data, dtype="float64") + +for _ in range(WARMUP): + s.mode() + +start = time.perf_counter() +for _ in range(ITERATIONS): + s.mode() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "mode", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_mode.ts b/benchmarks/tsb/bench_mode.ts new file mode 100644 index 00000000..c83d8326 --- /dev/null +++ b/benchmarks/tsb/bench_mode.ts @@ -0,0 +1,31 @@ +/** + * Benchmark: mode on 100k-element Series (mixed numeric with repeats) + */ +import { Series, modeSeries } from "../../src/index.js"; + +const ROWS = 100_000; +const WARMUP = 3; +const ITERATIONS = 10; + +// Create data with ~10 distinct values so mode is meaningful +const data = Float64Array.from({ length: ROWS }, (_, i) => i % 10); +const s = new Series(data); + +for (let i = 0; i < WARMUP; i++) { + modeSeries(s); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + modeSeries(s); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "mode", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From f419b5da605494006605d8cdc9a886960f40a5f5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Jul 2026 13:29:05 +0000 Subject: [PATCH 14/61] ci: trigger checks From 6fdb1118f8a2306c9928313a5788dffb6bfe1772 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Jul 2026 08:11:28 +0000 Subject: [PATCH 15/61] [Autoloop: perf-comparison] Iteration 425: add renyiEntropy/tsallisEntropy/jsDivergence/jsDistance/crossEntropy benchmark Run: https://github.com/githubnext/tsb/actions/runs/30248425219 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_renyi_tsallis_entropy.py | 69 +++++++++++++++++++ benchmarks/tsb/bench_renyi_tsallis_entropy.ts | 36 ++++++++++ 2 files changed, 105 insertions(+) create mode 100644 benchmarks/pandas/bench_renyi_tsallis_entropy.py create mode 100644 benchmarks/tsb/bench_renyi_tsallis_entropy.ts diff --git a/benchmarks/pandas/bench_renyi_tsallis_entropy.py b/benchmarks/pandas/bench_renyi_tsallis_entropy.py new file mode 100644 index 00000000..e59eaac6 --- /dev/null +++ b/benchmarks/pandas/bench_renyi_tsallis_entropy.py @@ -0,0 +1,69 @@ +import numpy as np +import json +import time + +N = 200 +WARMUP = 5 +ITERS = 50 + +p = np.arange(1, N + 1, dtype=float) +q = np.arange(N, 0, -1, dtype=float) + + +def renyi_entropy(pk, alpha=2): + pk = pk / pk.sum() + if abs(alpha - 1) < 1e-10: + return -np.sum(pk * np.log(pk + 1e-300)) + sum_pow = np.sum(pk ** alpha) + return np.log(sum_pow) / (1 - alpha) + + +def tsallis_entropy(pk, q_param=2): + pk = pk / pk.sum() + if abs(q_param - 1) < 1e-10: + return -np.sum(pk * np.log(pk + 1e-300)) + sum_pow = np.sum(pk ** q_param) + return (1 - sum_pow) / (q_param - 1) + + +def js_divergence(pk, qk): + pk = pk / pk.sum() + qk = qk / qk.sum() + m = 0.5 * (pk + qk) + kl_pm = np.sum(pk * np.log((pk + 1e-300) / (m + 1e-300))) + kl_qm = np.sum(qk * np.log((qk + 1e-300) / (m + 1e-300))) + return 0.5 * kl_pm + 0.5 * kl_qm + + +def js_distance(pk, qk): + return np.sqrt(js_divergence(pk, qk)) + + +def cross_entropy(pk, qk): + pk = pk / pk.sum() + qk = qk / qk.sum() + return -np.sum(pk * np.log(qk + 1e-300)) + + +for _ in range(WARMUP): + renyi_entropy(p) + tsallis_entropy(p) + js_divergence(p, q) + js_distance(p, q) + cross_entropy(p, q) + +t0 = time.perf_counter() +for _ in range(ITERS): + renyi_entropy(p) + tsallis_entropy(p) + js_divergence(p, q) + js_distance(p, q) + cross_entropy(p, q) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "renyi_tsallis_entropy", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_renyi_tsallis_entropy.ts b/benchmarks/tsb/bench_renyi_tsallis_entropy.ts new file mode 100644 index 00000000..1ef6751d --- /dev/null +++ b/benchmarks/tsb/bench_renyi_tsallis_entropy.ts @@ -0,0 +1,36 @@ +import { renyiEntropy, tsallisEntropy, jsDivergence, jsDistance, crossEntropy } from "../../src/index.js"; + +const N = 200; +const WARMUP = 5; +const ITERS = 50; + +// 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); + +for (let i = 0; i < WARMUP; i++) { + renyiEntropy(p, 2); + tsallisEntropy(p, 2); + jsDivergence(p, q); + jsDistance(p, q); + crossEntropy(p, q); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERS; i++) { + renyiEntropy(p, 2); + tsallisEntropy(p, 2); + jsDivergence(p, q); + jsDistance(p, q); + crossEntropy(p, q); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "renyi_tsallis_entropy", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 1a4b0413094e6c2c949be78a7539055e70e74082 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Jul 2026 08:17:28 +0000 Subject: [PATCH 16/61] ci: trigger checks From 320e47da6a88432814e17bb250b9d73365a2c13d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Jul 2026 19:30:28 +0000 Subject: [PATCH 17/61] [Autoloop: perf-comparison] Iteration 426: add jointEntropy/conditionalEntropy/variationOfInformation benchmark 1000-element paired observations, 10 categories, 50 iters each. Python: pure-numpy implementations matching tsb semantics. Run: https://github.com/githubnext/tsb/actions/runs/30297867311 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_joint_cond_entropy.py | 61 +++++++++++++++++++ benchmarks/tsb/bench_joint_cond_entropy.ts | 36 +++++++++++ 2 files changed, 97 insertions(+) create mode 100644 benchmarks/pandas/bench_joint_cond_entropy.py create mode 100644 benchmarks/tsb/bench_joint_cond_entropy.ts diff --git a/benchmarks/pandas/bench_joint_cond_entropy.py b/benchmarks/pandas/bench_joint_cond_entropy.py new file mode 100644 index 00000000..e76a5c79 --- /dev/null +++ b/benchmarks/pandas/bench_joint_cond_entropy.py @@ -0,0 +1,61 @@ +import json +import time +import numpy as np + +N = 1000 +WARMUP = 5 +ITERS = 50 + +# Build paired observations: two correlated categorical variables (10 categories each) +CATS = 10 +x = np.array([i % CATS for i in range(N)]) +y = np.array([(i % CATS + (i // CATS) % 3) % CATS for i in range(N)]) + + +def joint_entropy(x, y): + """H(X, Y) from paired observations.""" + pairs, counts = np.unique(np.stack([x, y], axis=1), axis=0, return_counts=True) + p = counts / counts.sum() + return -np.sum(p * np.log(p + 1e-300)) + + +def conditional_entropy(x, y): + """H(X|Y) = H(X,Y) - H(Y).""" + _, y_counts = np.unique(y, return_counts=True) + p_y = y_counts / y_counts.sum() + h_y = -np.sum(p_y * np.log(p_y + 1e-300)) + h_xy = joint_entropy(x, y) + return max(0.0, h_xy - h_y) + + +def variation_of_information(x, y): + """VI(X,Y) = H(X|Y) + H(Y|X).""" + _, x_counts = np.unique(x, return_counts=True) + _, y_counts = np.unique(y, return_counts=True) + p_x = x_counts / x_counts.sum() + p_y = y_counts / y_counts.sum() + h_x = -np.sum(p_x * np.log(p_x + 1e-300)) + h_y = -np.sum(p_y * np.log(p_y + 1e-300)) + h_xy = joint_entropy(x, y) + mi = max(0.0, h_x + h_y - h_xy) + return max(0.0, h_x + h_y - 2 * mi) + + +for _ in range(WARMUP): + joint_entropy(x, y) + conditional_entropy(x, y) + variation_of_information(x, y) + +t0 = time.perf_counter() +for _ in range(ITERS): + joint_entropy(x, y) + conditional_entropy(x, y) + variation_of_information(x, y) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "joint_cond_entropy", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_joint_cond_entropy.ts b/benchmarks/tsb/bench_joint_cond_entropy.ts new file mode 100644 index 00000000..f69a20d1 --- /dev/null +++ b/benchmarks/tsb/bench_joint_cond_entropy.ts @@ -0,0 +1,36 @@ +import { jointEntropy, conditionalEntropy, variationOfInformation } 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) % CATS, +]); + +let t0 = performance.now(); +for (let i = 0; i < WARMUP; i++) { + jointEntropy(pairs); + conditionalEntropy(pairs); + variationOfInformation(pairs); +} +t0 = performance.now(); + +for (let i = 0; i < ITERS; i++) { + jointEntropy(pairs); + conditionalEntropy(pairs); + variationOfInformation(pairs); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "joint_cond_entropy", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 503cb2a10186e8ce8805d00e47d76ad05dee0c27 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 Jul 2026 19:36:54 +0000 Subject: [PATCH 18/61] ci: trigger checks From 1b98a766a0f61f6845b356f3193e3d36431fd389 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 08:01:26 +0000 Subject: [PATCH 19/61] [Autoloop: perf-comparison] Iteration 427: normalizedMI benchmark (4 NMI methods, 1000 pairs, 50 iters) Run: https://github.com/githubnext/tsb/actions/runs/30339666515 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_normalized_mi.py | 86 ++++++++++++++++++++++++ benchmarks/tsb/bench_normalized_mi.ts | 42 ++++++++++++ 2 files changed, 128 insertions(+) create mode 100644 benchmarks/pandas/bench_normalized_mi.py create mode 100644 benchmarks/tsb/bench_normalized_mi.ts diff --git a/benchmarks/pandas/bench_normalized_mi.py b/benchmarks/pandas/bench_normalized_mi.py new file mode 100644 index 00000000..81f3d51e --- /dev/null +++ b/benchmarks/pandas/bench_normalized_mi.py @@ -0,0 +1,86 @@ +""" +Benchmark: normalized mutual information with four normalization methods +— arithmetic, geometric, min, max — +on 1000 paired categorical observations (10 categories each). + +Pure-numpy implementation mirroring tsb's normalizedMI. +""" + +import time +import json +import math +from collections import Counter + +N = 1000 +WARMUP = 5 +ITERS = 50 +CATS = 10 + + +def _entropy_from_counts(counts, n): + """Shannon entropy (nats) from a Counter of counts.""" + h = 0.0 + for c in counts.values(): + p = c / n + if p > 0: + h -= p * math.log(p) + return h + + +def normalized_mi(pairs, method="arithmetic"): + """ + Normalized Mutual Information between X and Y. + + pairs: list of (x, y) tuples + method: 'arithmetic' | 'geometric' | 'min' | 'max' + """ + n = len(pairs) + if n == 0: + return 0.0 + + x_counts = Counter(x for x, _ in pairs) + y_counts = Counter(y for _, y in pairs) + joint_counts = Counter(pairs) + + hX = _entropy_from_counts(x_counts, n) + hY = _entropy_from_counts(y_counts, n) + hXY = _entropy_from_counts(joint_counts, n) + + mi = max(0.0, hX + hY - hXY) + + if method == "arithmetic": + denom = 0.5 * (hX + hY) + elif method == "geometric": + denom = math.sqrt(hX * hY) if hX > 0 and hY > 0 else 0.0 + elif method == "min": + denom = min(hX, hY) + elif method == "max": + denom = max(hX, hY) + else: + denom = 0.5 * (hX + hY) + + return mi / denom if denom > 0 else 0.0 + + +pairs = [(i % CATS, (i % CATS + (i // CATS) % 3) % CATS) for i in range(N)] + +for _ in range(WARMUP): + normalized_mi(pairs, "arithmetic") + normalized_mi(pairs, "geometric") + normalized_mi(pairs, "min") + normalized_mi(pairs, "max") + +t0 = time.perf_counter() +for _ in range(ITERS): + normalized_mi(pairs, "arithmetic") + normalized_mi(pairs, "geometric") + normalized_mi(pairs, "min") + normalized_mi(pairs, "max") +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "normalized_mi", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_normalized_mi.ts b/benchmarks/tsb/bench_normalized_mi.ts new file mode 100644 index 00000000..8ec044da --- /dev/null +++ b/benchmarks/tsb/bench_normalized_mi.ts @@ -0,0 +1,42 @@ +/** + * Benchmark: normalizedMI with all four normalization methods + * — arithmetic, geometric, min, max — + * on 1000 paired categorical observations (10 categories each). + */ +import { normalizedMI } from "../../src/index.js"; + +const N = 1000; +const WARMUP = 5; +const ITERS = 50; +const CATS = 10; + +const pairs: [number, number][] = Array.from({ length: N }, (_, i) => [ + i % CATS, + (i % CATS + Math.floor(i / CATS) % 3) % CATS, +]); + +let t0 = performance.now(); +for (let i = 0; i < WARMUP; i++) { + normalizedMI(pairs, "arithmetic"); + normalizedMI(pairs, "geometric"); + normalizedMI(pairs, "min"); + normalizedMI(pairs, "max"); +} +t0 = performance.now(); + +for (let i = 0; i < ITERS; i++) { + normalizedMI(pairs, "arithmetic"); + normalizedMI(pairs, "geometric"); + normalizedMI(pairs, "min"); + normalizedMI(pairs, "max"); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "normalized_mi", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From accb8f4d07ac80b2351eb94012f9f5d53466548d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 08:04:08 +0000 Subject: [PATCH 20/61] ci: trigger checks From a1c17f7dcb64039b27aace61a8d55a5cd432f110 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 19:29:04 +0000 Subject: [PATCH 21/61] [Autoloop: perf-comparison] Iteration 428: polyval benchmark Add TypeScript and Python benchmarks for polyval (degree-5 polynomial evaluated at 100k points, 50 iterations). Python uses numpy.polyval for the equivalent comparison. Run: https://github.com/githubnext/tsb/actions/runs/30391517527 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_polyval.py | 30 ++++++++++++++++++++++++++++ benchmarks/tsb/bench_polyval.ts | 32 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 benchmarks/pandas/bench_polyval.py create mode 100644 benchmarks/tsb/bench_polyval.ts diff --git a/benchmarks/pandas/bench_polyval.py b/benchmarks/pandas/bench_polyval.py new file mode 100644 index 00000000..472ca416 --- /dev/null +++ b/benchmarks/pandas/bench_polyval.py @@ -0,0 +1,30 @@ +""" +Benchmark: polyval — evaluate a polynomial with given coefficients. +Dataset: degree-5 polynomial evaluated at 100,000 points, 50 iterations. +""" +import json +import time +import numpy as np + +N = 100_000 +WARMUP = 5 +ITERATIONS = 50 + +# Degree-5 polynomial coefficients [a5, a4, a3, a2, a1, a0] +coefs = [1.5, -2.3, 0.7, 4.1, -0.9, 3.0] +xs = np.linspace(-5.0, 5.0, N) + +for _ in range(WARMUP): + np.polyval(coefs, xs) + +start = time.perf_counter() +for _ in range(ITERATIONS): + np.polyval(coefs, xs) +total = (time.perf_counter() - start) * 1000 # ms + +print(json.dumps({ + "function": "polyval", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_polyval.ts b/benchmarks/tsb/bench_polyval.ts new file mode 100644 index 00000000..2021209d --- /dev/null +++ b/benchmarks/tsb/bench_polyval.ts @@ -0,0 +1,32 @@ +/** + * Benchmark: polyval — evaluate a polynomial with given coefficients. + * Dataset: degree-5 polynomial evaluated at 100,000 points, 50 iterations. + */ +import { polyval } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 5; +const ITERATIONS = 50; + +// Degree-5 polynomial coefficients [a5, a4, a3, a2, a1, a0] +const coefs = [1.5, -2.3, 0.7, 4.1, -0.9, 3.0]; +const xs = Array.from({ length: N }, (_, i) => (i / N) * 10 - 5); + +for (let i = 0; i < WARMUP; i++) { + polyval(coefs, xs); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + polyval(coefs, xs); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "polyval", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 3581d25c28c005f2ae7596de15f07fd6f2fc7cd7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 28 Jul 2026 19:32:33 +0000 Subject: [PATCH 22/61] ci: trigger checks From fb4cd09f65f136d766d21c4409623a68f78d989a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 08:04:59 +0000 Subject: [PATCH 23/61] [Autoloop: perf-comparison] Iteration 429: holiday observance benchmarks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add benchmark pair for pandas.tseries.holiday observance functions: nearestWorkday, nextMonday, nextMondayOrTuesday, previousFriday, previousWorkday, sundayToMonday (tsb) vs nearest_workday, next_monday, next_monday_or_tuesday, previous_friday, previous_workday, sunday_to_monday (pandas.tseries.holiday). 1000 dates, 50 iterations each. Metric: 766 → 767 Run: https://github.com/githubnext/tsb/actions/runs/30433042744 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_holiday_observances.py | 56 ++++++++++++++++++ benchmarks/tsb/bench_holiday_observances.ts | 57 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 benchmarks/pandas/bench_holiday_observances.py create mode 100644 benchmarks/tsb/bench_holiday_observances.ts diff --git a/benchmarks/pandas/bench_holiday_observances.py b/benchmarks/pandas/bench_holiday_observances.py new file mode 100644 index 00000000..baffb9cf --- /dev/null +++ b/benchmarks/pandas/bench_holiday_observances.py @@ -0,0 +1,56 @@ +""" +Benchmark: Holiday observance functions from pandas.tseries.holiday. + +Compares against tsb (TypeScript) bench_holiday_observances.ts. +""" +import datetime +import json +import time + +from pandas.tseries.holiday import ( + nearest_workday, + next_monday, + next_monday_or_tuesday, + previous_friday, + previous_workday, + sunday_to_monday, +) + +N = 1_000 +WARMUP = 5 +ITERS = 50 + +base = datetime.date(2000, 1, 1) +dates = [base + datetime.timedelta(days=i) for i in range(N)] + +# warm-up +for _ in range(WARMUP): + for d in dates: + nearest_workday(d) + next_monday(d) + next_monday_or_tuesday(d) + previous_friday(d) + previous_workday(d) + sunday_to_monday(d) + +t0 = time.perf_counter() +for _ in range(ITERS): + for d in dates: + nearest_workday(d) + next_monday(d) + next_monday_or_tuesday(d) + previous_friday(d) + previous_workday(d) + sunday_to_monday(d) +total_ms = (time.perf_counter() - t0) * 1000 + +print( + json.dumps( + { + "function": "holiday_observances", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, + } + ) +) diff --git a/benchmarks/tsb/bench_holiday_observances.ts b/benchmarks/tsb/bench_holiday_observances.ts new file mode 100644 index 00000000..6d24ecdb --- /dev/null +++ b/benchmarks/tsb/bench_holiday_observances.ts @@ -0,0 +1,57 @@ +/** + * Benchmark: Holiday observance functions (nearestWorkday, nextMonday, previousFriday, etc.) + * + * Mirrors pandas.tseries.holiday observance helpers: + * nearest_workday, next_monday, previous_friday, sunday_to_monday, etc. + */ +import { + nearestWorkday, + nextMonday, + nextMondayOrTuesday, + previousFriday, + previousWorkday, + sundayToMonday, +} from "../../src/index.js"; + +const N = 1_000; +const WARMUP = 5; +const ITERS = 50; + +// Create N dates spread across multiple years +const BASE = new Date("2000-01-01").getTime(); +const MS_PER_DAY = 86_400_000; +const dates: Date[] = Array.from({ length: N }, (_, i) => new Date(BASE + i * MS_PER_DAY)); + +let t0 = performance.now(); +for (let i = 0; i < WARMUP; i++) { + for (const d of dates) { + nearestWorkday(d); + nextMonday(d); + nextMondayOrTuesday(d); + previousFriday(d); + previousWorkday(d); + sundayToMonday(d); + } +} +t0 = performance.now(); + +for (let i = 0; i < ITERS; i++) { + for (const d of dates) { + nearestWorkday(d); + nextMonday(d); + nextMondayOrTuesday(d); + previousFriday(d); + previousWorkday(d); + sundayToMonday(d); + } +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "holiday_observances", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 9a39d856e3820afe113119f6b830e598c667de0a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 08:11:45 +0000 Subject: [PATCH 24/61] ci: trigger checks From 5bdd9460b902bb53515247aaef7e2522e4fba884 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 19:32:47 +0000 Subject: [PATCH 25/61] [Autoloop: perf-comparison] Iteration 430: information_extended benchmarks (jsDivergence/jsDistance/crossEntropy/renyiEntropy/tsallisEntropy/jointEntropy/conditionalEntropy/variationOfInformation) Run: https://github.com/githubnext/tsb/actions/runs/30483937580 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_information_extended.py | 77 +++++++++++++++++++ benchmarks/tsb/bench_information_extended.ts | 65 ++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 benchmarks/pandas/bench_information_extended.py create mode 100644 benchmarks/tsb/bench_information_extended.ts diff --git a/benchmarks/pandas/bench_information_extended.py b/benchmarks/pandas/bench_information_extended.py new file mode 100644 index 00000000..1e9bf2e2 --- /dev/null +++ b/benchmarks/pandas/bench_information_extended.py @@ -0,0 +1,77 @@ +import scipy.stats +import numpy as np +import json +import time + +N = 1000 +WARMUP = 5 +ITERS = 50 + +BINS = 100 +pk = np.arange(1, BINS + 1, dtype=float) +pk /= pk.sum() +qk = pk[::-1].copy() + +CATS = 10 +xs = np.array([i % CATS for i in range(N)]) +ys = np.array([(i % CATS + i // CATS) % CATS for i in range(N)]) + +def js_divergence(p, q): + m = 0.5 * (p + q) + return 0.5 * scipy.stats.entropy(p, m) + 0.5 * scipy.stats.entropy(q, m) + +def js_distance(p, q): + return js_divergence(p, q) ** 0.5 + +def cross_entropy(p, q): + return -np.sum(p * np.log(q + 1e-300)) + +def renyi_entropy(p, alpha): + return np.log(np.sum(p ** alpha)) / (1 - alpha) + +def tsallis_entropy(p, q_param): + return (1 - np.sum(p ** q_param)) / (q_param - 1) + +def joint_entropy(x, y): + pairs, counts = np.unique(np.stack([x, y], axis=1), axis=0, return_counts=True) + probs = counts / counts.sum() + return -np.sum(probs * np.log(probs + 1e-300)) + +def conditional_entropy(x, y): + return joint_entropy(x, y) - scipy.stats.entropy(np.unique(x, return_counts=True)[1]) + +def variation_of_information(x, y): + hx = scipy.stats.entropy(np.unique(x, return_counts=True)[1] / len(x)) + hy = scipy.stats.entropy(np.unique(y, return_counts=True)[1] / len(y)) + hxy = joint_entropy(x, y) + return hx + hy - 2 * hxy + +# Warm up +for _ in range(WARMUP): + js_divergence(pk, qk) + js_distance(pk, qk) + cross_entropy(pk, qk) + renyi_entropy(pk, 2) + tsallis_entropy(pk, 2) + joint_entropy(xs, ys) + conditional_entropy(xs, ys) + variation_of_information(xs, ys) + +t0 = time.perf_counter() +for _ in range(ITERS): + js_divergence(pk, qk) + js_distance(pk, qk) + cross_entropy(pk, qk) + renyi_entropy(pk, 2) + tsallis_entropy(pk, 2) + joint_entropy(xs, ys) + conditional_entropy(xs, ys) + variation_of_information(xs, ys) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "information_extended", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_information_extended.ts b/benchmarks/tsb/bench_information_extended.ts new file mode 100644 index 00000000..1c0ecd80 --- /dev/null +++ b/benchmarks/tsb/bench_information_extended.ts @@ -0,0 +1,65 @@ +import { + jsDivergence, + jsDistance, + crossEntropy, + renyiEntropy, + tsallisEntropy, + jointEntropy, + conditionalEntropy, + variationOfInformation, +} from "../../src/index.js"; + +const N = 1000; +const WARMUP = 5; +const ITERS = 50; + +// Build a simple probability mass function (PMF) of length 100 +const BINS = 100; +const pk: number[] = Array.from({ length: BINS }, (_, i) => i + 1); +const total = pk.reduce((a, b) => a + b, 0); +const pkNorm = pk.map((v) => v / total); +const qkNorm = pk + .slice() + .reverse() + .map((v) => v / total); + +// Paired observations for joint/conditional entropy +const CATS = 10; +const pairs: [number, number][] = Array.from({ length: N }, (_, i) => [ + i % CATS, + (i % CATS + Math.floor(i / CATS)) % CATS, +]); + +// Warm up +for (let i = 0; i < WARMUP; i++) { + jsDivergence(pkNorm, qkNorm); + jsDistance(pkNorm, qkNorm); + crossEntropy(pkNorm, qkNorm); + renyiEntropy(pkNorm, 2); + tsallisEntropy(pkNorm, 2); + jointEntropy(pairs); + conditionalEntropy(pairs); + variationOfInformation(pairs); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERS; i++) { + jsDivergence(pkNorm, qkNorm); + jsDistance(pkNorm, qkNorm); + crossEntropy(pkNorm, qkNorm); + renyiEntropy(pkNorm, 2); + tsallisEntropy(pkNorm, 2); + jointEntropy(pairs); + conditionalEntropy(pairs); + variationOfInformation(pairs); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "information_extended", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From cf04ae718cb3f4f0a3ebbb91f442965e5880b583 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 29 Jul 2026 19:42:30 +0000 Subject: [PATCH 26/61] ci: trigger checks From c52156c05bde2bdfd4abf5b001677d5dd34e7f5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Jul 2026 07:50:20 +0000 Subject: [PATCH 27/61] [Autoloop: perf-comparison] Iteration 431: stata benchmark (readStata/toStata round-trip, 500 rows, 20 iters) Run: https://github.com/githubnext/tsb/actions/runs/30523821551 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_stata.py | 70 ++++++++++++++++++++++++++++++++ benchmarks/tsb/bench_stata.ts | 63 ++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 benchmarks/pandas/bench_stata.py create mode 100644 benchmarks/tsb/bench_stata.ts diff --git a/benchmarks/pandas/bench_stata.py b/benchmarks/pandas/bench_stata.py new file mode 100644 index 00000000..aed1a8ba --- /dev/null +++ b/benchmarks/pandas/bench_stata.py @@ -0,0 +1,70 @@ +"""Benchmark: read_stata / to_stata — Stata .dta file I/O round-trip + +Creates a 500-row DataFrame with mixed columns (int, float, string), +then benchmarks: + - df.to_stata (DataFrame → .dta buffer) + - pd.read_stata (buffer → DataFrame) +Dataset: 500 rows × 4 columns; 3 warm-up + 20 measured iterations each. +Outputs JSON: {"function": "stata", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import io +import numpy as np +import pandas as pd + +ROWS = 500 +WARMUP = 3 +ITERATIONS = 20 + +df = pd.DataFrame( + { + "id": np.arange(ROWS, dtype=np.int32), + "value": np.arange(ROWS, dtype=np.float64) * 1.1, + "score": (np.arange(ROWS) % 100) * 0.5, + "label": [f"cat_{i % 20}" for i in range(ROWS)], + } +) + + +def to_buf(df): + buf = io.BytesIO() + df.to_stata(buf, write_index=False) + buf.seek(0) + return buf + + +# Warm up +for _ in range(WARMUP): + buf = to_buf(df) + pd.read_stata(buf) + +# Benchmark to_stata +t0 = time.perf_counter() +for _ in range(ITERATIONS): + to_buf(df) +write_total = (time.perf_counter() - t0) * 1000 + +# Pre-generate buffer for read_stata benchmark +stata_buf = to_buf(df).read() + +# Benchmark read_stata +t1 = time.perf_counter() +for _ in range(ITERATIONS): + pd.read_stata(io.BytesIO(stata_buf)) +read_total = (time.perf_counter() - t1) * 1000 + +total = write_total + read_total + +print( + json.dumps( + { + "function": "stata", + "mean_ms": total / (ITERATIONS * 2), + "iterations": ITERATIONS * 2, + "total_ms": total, + "write_mean_ms": write_total / ITERATIONS, + "read_mean_ms": read_total / ITERATIONS, + } + ) +) diff --git a/benchmarks/tsb/bench_stata.ts b/benchmarks/tsb/bench_stata.ts new file mode 100644 index 00000000..a13346c1 --- /dev/null +++ b/benchmarks/tsb/bench_stata.ts @@ -0,0 +1,63 @@ +/** + * Benchmark: readStata / toStata — Stata .dta file I/O round-trip + * + * Creates a 500-row DataFrame with mixed columns (int, float, string), + * then benchmarks: + * - toStata (DataFrame → Uint8Array .dta buffer) + * - readStata (Uint8Array .dta buffer → DataFrame) + * Dataset: 500 rows × 4 columns; 3 warm-up + 20 measured iterations each. + * Outputs JSON: {"function": "stata", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { readStata, toStata, DataFrame, Series } from "../../src/index.js"; + +const ROWS = 500; +const WARMUP = 3; +const ITERATIONS = 20; + +const ids = Array.from({ length: ROWS }, (_, i) => i); +const values = Array.from({ length: ROWS }, (_, i) => i * 1.1); +const scores = Array.from({ length: ROWS }, (_, i) => (i % 100) * 0.5); +const labels = Array.from({ length: ROWS }, (_, i) => `cat_${i % 20}`); + +const df = new DataFrame({ + id: new Series(ids), + value: new Series(values), + score: new Series(scores), + label: new Series(labels), +}); + +// Warm up +for (let i = 0; i < WARMUP; i++) { + const buf = toStata(df); + readStata(buf); +} + +// Benchmark toStata +const t0 = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + toStata(df); +} +const writeTotal = performance.now() - t0; + +// Pre-generate buffer for readStata benchmark +const buf = toStata(df); + +// Benchmark readStata +const t1 = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + readStata(buf); +} +const readTotal = performance.now() - t1; + +const total = writeTotal + readTotal; + +console.log( + JSON.stringify({ + function: "stata", + mean_ms: total / (ITERATIONS * 2), + iterations: ITERATIONS * 2, + total_ms: total, + write_mean_ms: writeTotal / ITERATIONS, + read_mean_ms: readTotal / ITERATIONS, + }), +); From ea706dbdcfde612bf90d22a7b336ae2dda921d72 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Jul 2026 07:53:25 +0000 Subject: [PATCH 28/61] ci: trigger checks From c837e00e9894a73225d5c421441174ae79938dd2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Jul 2026 19:35:34 +0000 Subject: [PATCH 29/61] [Autoloop: perf-comparison] Iteration 432: SparseArray arithmetic/utility ops benchmark Add bench_sparse_array_ops.ts and bench_sparse_array_ops.py benchmarking SparseArray.add(), mul(), fillna(), slice(), toCoo(), std(), min(), max() on a 100k-element sparse array at 5% density (30 iterations). Python: pd.arrays.SparseArray equivalents. Run: https://github.com/githubnext/tsb/actions/runs/30574434717 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_sparse_array_ops.py | 53 +++++++++++++++++++++ benchmarks/tsb/bench_sparse_array_ops.ts | 53 +++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 benchmarks/pandas/bench_sparse_array_ops.py create mode 100644 benchmarks/tsb/bench_sparse_array_ops.ts diff --git a/benchmarks/pandas/bench_sparse_array_ops.py b/benchmarks/pandas/bench_sparse_array_ops.py new file mode 100644 index 00000000..c59a9c72 --- /dev/null +++ b/benchmarks/pandas/bench_sparse_array_ops.py @@ -0,0 +1,53 @@ +""" +Benchmark: pandas SparseArray arithmetic and utility operations. + +Covers: __add__(scalar), __mul__(scalar), fillna(value), and COO conversion +(toCoo has no direct pandas equivalent — scipy.sparse is used instead), +std(), min(), max(). + +Dataset: 100k-element SparseArray at ~5% density. +Outputs JSON: {"function": "sparse_array_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import math +import numpy as np +import pandas as pd + +N = 100_000 +WARMUP = 5 +ITERATIONS = 30 + +# Build matching sparse dataset (~5% density, fill_value=0) +dense = np.zeros(N) +for i in range(0, N, 20): + dense[i] = math.sin(i * 0.001) * 100 + 1 + +sparse = pd.arrays.SparseArray(dense, fill_value=0.0) + +def run_ops(): + _ = sparse + 5 + _ = sparse * 2 + _ = sparse.fillna(0) + _ = sparse[1000:50000] + # toCoo equivalent: extract sp_values and sp_index + _ = sparse.sp_values + _ = sparse.sp_index + _ = sparse.std() + _ = sparse.min() + _ = sparse.max() + +for _ in range(WARMUP): + run_ops() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run_ops() +total_ms = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "sparse_array_ops", + "mean_ms": total_ms / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_sparse_array_ops.ts b/benchmarks/tsb/bench_sparse_array_ops.ts new file mode 100644 index 00000000..e70671fd --- /dev/null +++ b/benchmarks/tsb/bench_sparse_array_ops.ts @@ -0,0 +1,53 @@ +/** + * Benchmark: SparseArray arithmetic and utility operations. + * + * Covers: add(scalar), mul(scalar), fillna(value), slice(start, end), + * toCoo(), std(), min(), max() + * Dataset: 100k-element sparse array at ~5% density. + * Outputs JSON: {"function": "sparse_array_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { SparseArray } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 5; +const ITERATIONS = 30; + +const dense: number[] = new Array(N).fill(0); +for (let i = 0; i < N; i += 20) { + dense[i] = Math.sin(i * 0.001) * 100 + 1; +} + +const sparse = SparseArray.fromDense(dense, 0, "float64"); + +for (let i = 0; i < WARMUP; i++) { + sparse.add(5); + sparse.mul(2); + sparse.fillna(0); + sparse.slice(1000, 50000); + sparse.toCoo(); + sparse.std(); + sparse.min(); + sparse.max(); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + sparse.add(5); + sparse.mul(2); + sparse.fillna(0); + sparse.slice(1000, 50000); + sparse.toCoo(); + sparse.std(); + sparse.min(); + sparse.max(); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "sparse_array_ops", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 95b89db4da080ac12ba6e8b9380107f8619c0368 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 30 Jul 2026 19:38:31 +0000 Subject: [PATCH 30/61] ci: trigger checks From cc1788848171efc3e451674b4c2afaf384c1c5ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 31 Jul 2026 08:04:25 +0000 Subject: [PATCH 31/61] [Autoloop: perf-comparison] Iteration 433: IntegerArray arithmetic ops benchmark Add bench_integer_array_ops.ts and bench_integer_array_ops.py benchmarking IntegerArray.sub(), floordiv(), mod(), pow(), astype(), count() on a 100k-element Int32 array with 10% nulls (20 iterations). Python: pd.array arithmetic operators and astype/count equivalents. Run: https://github.com/githubnext/tsb/actions/runs/30614182745 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_integer_array_ops.py | 48 +++++++++++++++++++ benchmarks/tsb/bench_integer_array_ops.ts | 49 ++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 benchmarks/pandas/bench_integer_array_ops.py create mode 100644 benchmarks/tsb/bench_integer_array_ops.ts diff --git a/benchmarks/pandas/bench_integer_array_ops.py b/benchmarks/pandas/bench_integer_array_ops.py new file mode 100644 index 00000000..879688c5 --- /dev/null +++ b/benchmarks/pandas/bench_integer_array_ops.py @@ -0,0 +1,48 @@ +"""Benchmark: IntegerArray arithmetic extensions — sub, floordiv, mod, pow, astype, count. + +Covers pandas IntegerArray operations not in bench_integer_array: + - arr - 10 → sub(scalar) + - arr // 7 → floor division + - arr % 13 → modulo + - arr ** 2 → power + - arr.astype("Int64") + - arr.count() → non-null count + +Dataset: 100,000 Int32 elements with ~10% nulls (same as bench_integer_array). +""" +import json +import time + +import pandas as pd + +N = 100_000 +WARMUP = 3 +ITERATIONS = 20 + +raw = [(None if i % 10 == 0 else int((i % 1000) - 500)) for i in range(N)] +a = pd.array(raw, dtype="Int32") + + +def run(): + _ = a - 10 + _ = a // 7 + _ = a % 13 + _ = a ** 2 + _ = a.astype("Int64") + _ = a.count() + + +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_ops", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_integer_array_ops.ts b/benchmarks/tsb/bench_integer_array_ops.ts new file mode 100644 index 00000000..8a51d2c9 --- /dev/null +++ b/benchmarks/tsb/bench_integer_array_ops.ts @@ -0,0 +1,49 @@ +/** + * Benchmark: IntegerArray arithmetic extensions — sub, floordiv, mod, pow, astype, count. + * + * Covers IntegerArray operations not in bench_integer_array: + * - sub(scalar) → pandas IntegerArray subtraction + * - floordiv(scalar) → pandas IntegerArray floor division + * - mod(scalar) → pandas IntegerArray modulo + * - pow(scalar) → pandas IntegerArray power + * - astype(dtype) → pandas IntegerArray.astype("Int64") + * - count() → pandas IntegerArray count (non-null elements) + * + * Dataset: 100,000 Int32 elements with ~10% nulls (same as bench_integer_array). + * Outputs JSON: {"function": "integer_array_ops", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const raw: (number | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : (i % 1000) - 500, +); + +const a = arrays.IntegerArray.from(raw, "Int32"); + +function run(): void { + a.sub(10); + a.floordiv(7); + a.mod(13); + a.pow(2); + a.astype("Int64"); + a.count(); +} + +for (let i = 0; i < WARMUP; i++) run(); + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) run(); +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "integer_array_ops", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 8c7373eb593add72b319b1dd6637945809f5e545 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 31 Jul 2026 08:10:28 +0000 Subject: [PATCH 32/61] ci: trigger checks From 842c4ad210e0919e733c404c432bf7f442d907d8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Aug 2026 01:40:58 +0000 Subject: [PATCH 33/61] [Autoloop: perf-comparison] Iteration 434: applymap benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds element-wise DataFrame cell mapping benchmark (applymap/df.map), 50k rows × 4 float columns, 30 iterations, Python uses df.map/df.applymap with pandas >= 2.1 compatibility. Run: https://github.com/githubnext/tsb/actions/runs/30677780854 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_applymap.py | 40 +++++++++++++++++++++++++++++ benchmarks/tsb/bench_applymap.ts | 39 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 benchmarks/pandas/bench_applymap.py create mode 100644 benchmarks/tsb/bench_applymap.ts diff --git a/benchmarks/pandas/bench_applymap.py b/benchmarks/pandas/bench_applymap.py new file mode 100644 index 00000000..dd8826d4 --- /dev/null +++ b/benchmarks/pandas/bench_applymap.py @@ -0,0 +1,40 @@ +""" +Benchmark: DataFrame.map (applymap) — element-wise function on every cell. +Mirrors pandas DataFrame.map / DataFrame.applymap. +Dataset: 50,000 rows × 4 columns of float64. +""" +import json +import time +import numpy as np +import pandas as pd + +ROWS = 50_000 +WARMUP = 5 +ITERATIONS = 30 + +df = pd.DataFrame({ + "a": np.arange(ROWS, dtype=np.float64) * 0.5, + "b": np.arange(ROWS, dtype=np.float64) * 1.1, + "c": np.arange(ROWS, dtype=np.float64) * 2.3, + "d": np.arange(ROWS, dtype=np.float64) * 0.7, +}) + +fn = lambda v: v * 2.0 + 1.0 + +# pandas >= 2.1 uses df.map(); older versions use df.applymap() +_map = df.map if hasattr(df, "map") else df.applymap + +for _ in range(WARMUP): + _map(fn) + +start = time.perf_counter() +for _ in range(ITERATIONS): + _map(fn) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "applymap", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_applymap.ts b/benchmarks/tsb/bench_applymap.ts new file mode 100644 index 00000000..1826a533 --- /dev/null +++ b/benchmarks/tsb/bench_applymap.ts @@ -0,0 +1,39 @@ +/** + * Benchmark: applymap — element-wise function applied to every cell of a DataFrame. + * Mirrors pandas DataFrame.map (formerly DataFrame.applymap). + * Dataset: 50,000 rows × 4 columns of float64. + */ +import { DataFrame, applymap } from "../../src/index.js"; +import type { Scalar } from "../../src/types.js"; + +const ROWS = 50_000; +const WARMUP = 5; +const ITERATIONS = 30; + +const df = new DataFrame({ + a: Array.from({ length: ROWS }, (_, i) => i * 0.5), + b: Array.from({ length: ROWS }, (_, i) => i * 1.1), + c: Array.from({ length: ROWS }, (_, i) => i * 2.3), + d: Array.from({ length: ROWS }, (_, i) => i * 0.7), +}); + +const fn = (v: Scalar): Scalar => (v as number) * 2.0 + 1.0; + +for (let i = 0; i < WARMUP; i++) { + applymap(df, fn); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + applymap(df, fn); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "applymap", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From d9d29115cf1703715d5fd8c648b789b11a11b39e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Aug 2026 01:50:28 +0000 Subject: [PATCH 34/61] ci: trigger checks From 26400e7366910b43976845099f75aca98b7d68be Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Aug 2026 13:30:47 +0000 Subject: [PATCH 35/61] [Autoloop: perf-comparison] Iteration 435: string_accessor benchmark (split/replace/extract/join, 100k strings, 15 iters) Run: https://github.com/githubnext/tsb/actions/runs/30701520139 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_string_accessor.py | 35 +++++++++++++++++++ benchmarks/tsb/bench_string_accessor.ts | 40 ++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 benchmarks/pandas/bench_string_accessor.py create mode 100644 benchmarks/tsb/bench_string_accessor.ts diff --git a/benchmarks/pandas/bench_string_accessor.py b/benchmarks/pandas/bench_string_accessor.py new file mode 100644 index 00000000..f331ed27 --- /dev/null +++ b/benchmarks/pandas/bench_string_accessor.py @@ -0,0 +1,35 @@ +"""Benchmark: string_accessor — Series.str.split / replace / extract / join on 100k strings""" +import json, time +import pandas as pd + +ROWS = 100_000 +WARMUP = 3 +ITERATIONS = 15 + +words = ["apple", "banana", "cherry", "date", "elderberry"] +data = [f"{words[i % 5]}-{i % 100}-suffix" for i in range(ROWS)] +s = pd.Series(data) + +# pre-split series for join benchmark +split = s.str.split("-") + +for _ in range(WARMUP): + s.str.split("-") + s.str.replace("suffix", "end", regex=False) + s.str.extract(r"([a-z]+)-") + split.str.join("_") + +start = time.perf_counter() +for _ in range(ITERATIONS): + s.str.split("-") + s.str.replace("suffix", "end", regex=False) + s.str.extract(r"([a-z]+)-") + split.str.join("_") +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "string_accessor", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_string_accessor.ts b/benchmarks/tsb/bench_string_accessor.ts new file mode 100644 index 00000000..78617e3d --- /dev/null +++ b/benchmarks/tsb/bench_string_accessor.ts @@ -0,0 +1,40 @@ +/** + * Benchmark: string_accessor — Series.str.split / replace / extract / join on 100k strings + */ +import { Series } from "../../src/index.js"; + +const ROWS = 100_000; +const WARMUP = 3; +const ITERATIONS = 15; + +const words = ["apple", "banana", "cherry", "date", "elderberry"]; +const data = Array.from({ length: ROWS }, (_, i) => `${words[i % 5]}-${i % 100}-suffix`); +const s = new Series({ data }); + +// pre-split series for join benchmark +const split = s.str.split("-"); + +for (let i = 0; i < WARMUP; i++) { + s.str.split("-"); + s.str.replace("suffix", "end"); + s.str.extract("([a-z]+)-"); + split.str.join("_"); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + s.str.split("-"); + s.str.replace("suffix", "end"); + s.str.extract("([a-z]+)-"); + split.str.join("_"); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "string_accessor", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 77d1ef95725d2b06cc1a6c7ec2c176dc301baf09 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 1 Aug 2026 13:38:25 +0000 Subject: [PATCH 36/61] ci: trigger checks From 032397ef23f8f389bcc9709b9d5fbf976510d9bd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 2 Aug 2026 01:35:18 +0000 Subject: [PATCH 37/61] [Autoloop: perf-comparison] Iteration 436: clip_with_bounds benchmark Add TypeScript and Python benchmarks for clipSeriesWithBounds / clipDataFrameWithBounds, benchmarking per-element clipping with Series/array bounds (100k rows, 4-col DataFrame). Run: https://github.com/githubnext/tsb/actions/runs/30726948175 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_clip_with_bounds.py | 36 +++++++++++++++++++ benchmarks/tsb/bench_clip_with_bounds.ts | 38 +++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 benchmarks/pandas/bench_clip_with_bounds.py create mode 100644 benchmarks/tsb/bench_clip_with_bounds.ts diff --git a/benchmarks/pandas/bench_clip_with_bounds.py b/benchmarks/pandas/bench_clip_with_bounds.py new file mode 100644 index 00000000..9dacf2f7 --- /dev/null +++ b/benchmarks/pandas/bench_clip_with_bounds.py @@ -0,0 +1,36 @@ +""" +Benchmark: pandas Series.clip / DataFrame.clip with array/Series bounds. +Outputs JSON: {"function": "clip_with_bounds", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +import pandas as pd + +ROWS = 100_000 +WARMUP = 5 +ITERATIONS = 20 + +data = np.array([(i % 200) - 100 for i in range(ROWS)], dtype=float) +lower_arr = np.full(ROWS, -30.0) +upper_arr = np.full(ROWS, 30.0) +s = pd.Series(data) +lower_s = pd.Series(lower_arr) +upper_s = pd.Series(upper_arr) + +df_cols = {f"col{c}": [(i + c * 10) % 200 - 100 for i in range(ROWS)] for c in range(4)} +df = pd.DataFrame(df_cols, dtype=float) +df_lower = pd.Series(np.full(ROWS, -30.0)) +df_upper = pd.Series(np.full(ROWS, 30.0)) + +for _ in range(WARMUP): + s.clip(lower=lower_s, upper=upper_s) + df.clip(lower=df_lower, upper=df_upper, axis=0) + +start = time.perf_counter() +for _ in range(ITERATIONS): + s.clip(lower=lower_s, upper=upper_s) + df.clip(lower=df_lower, upper=df_upper, axis=0) +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({"function": "clip_with_bounds", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total})) diff --git a/benchmarks/tsb/bench_clip_with_bounds.ts b/benchmarks/tsb/bench_clip_with_bounds.ts new file mode 100644 index 00000000..defcf8b0 --- /dev/null +++ b/benchmarks/tsb/bench_clip_with_bounds.ts @@ -0,0 +1,38 @@ +/** + * Benchmark: clipSeriesWithBounds / clipDataFrameWithBounds — per-element clipping with Series/array bounds. + * Outputs JSON: {"function": "clip_with_bounds", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { Series, DataFrame, clipSeriesWithBounds, clipDataFrameWithBounds } from "../../src/index.ts"; + +const ROWS = 100_000; +const WARMUP = 5; +const ITERATIONS = 20; + +const data = Float64Array.from({ length: ROWS }, (_, i) => (i % 200) - 100); +const lowerArr = Array.from({ length: ROWS }, () => -30); +const upperArr = Array.from({ length: ROWS }, () => 30); +const s = new Series(data); +const lowerSeries = new Series(lowerArr); +const upperSeries = new Series(upperArr); + +const dfCols: Record = {}; +for (let c = 0; c < 4; c++) { + dfCols[`col${c}`] = Array.from({ length: ROWS }, (_, i) => (i + c * 10) % 200 - 100); +} +const df = new DataFrame(dfCols); +const dfLower = new Series(Array.from({ length: ROWS }, () => -30)); +const dfUpper = new Series(Array.from({ length: ROWS }, () => 30)); + +for (let i = 0; i < WARMUP; i++) { + clipSeriesWithBounds(s, { lower: lowerSeries, upper: upperSeries }); + clipDataFrameWithBounds(df, { lower: dfLower, upper: dfUpper, axis: 0 }); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + clipSeriesWithBounds(s, { lower: lowerSeries, upper: upperSeries }); + clipDataFrameWithBounds(df, { lower: dfLower, upper: dfUpper, axis: 0 }); +} +const total = performance.now() - start; + +console.log(JSON.stringify({ function: "clip_with_bounds", mean_ms: total / ITERATIONS, iterations: ITERATIONS, total_ms: total })); From 44838d01e99dff4f01eb661196d9252ae76c1a85 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 2 Aug 2026 01:42:26 +0000 Subject: [PATCH 38/61] ci: trigger checks From 9fdcff1510af4905492a78a341be89d6fae2cd10 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 2 Aug 2026 13:32:43 +0000 Subject: [PATCH 39/61] [Autoloop: perf-comparison] Iteration 437: swaplevel_dataframe benchmark MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add bench_swaplevel_dataframe.ts and bench_swaplevel_dataframe.py benchmarking swapLevelDataFrame and reorderLevelsDataFrame on a 50k-row × 3-column DataFrame with a 3-level MultiIndex row index (30 iterations, warm-up 5). Run: https://github.com/githubnext/tsb/actions/runs/30749712022 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_swaplevel_dataframe.py | 57 +++++++++++++++++++ benchmarks/tsb/bench_swaplevel_dataframe.ts | 56 ++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 benchmarks/pandas/bench_swaplevel_dataframe.py create mode 100644 benchmarks/tsb/bench_swaplevel_dataframe.ts diff --git a/benchmarks/pandas/bench_swaplevel_dataframe.py b/benchmarks/pandas/bench_swaplevel_dataframe.py new file mode 100644 index 00000000..ccf241a4 --- /dev/null +++ b/benchmarks/pandas/bench_swaplevel_dataframe.py @@ -0,0 +1,57 @@ +""" +Benchmark: DataFrame.swaplevel / DataFrame.reorder_levels on 50k-row MultiIndex DataFrame. + +Mirrors tsb bench_swaplevel_dataframe.ts. + +Dataset: 50 000-row × 3-column DataFrame with a 3-level MultiIndex row index. + +Outputs JSON: {"function": "swaplevel_dataframe", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time + +import numpy as np +import pandas as pd + +N = 50_000 +WARMUP = 5 +ITERATIONS = 30 + +lev_a = [f"a{i % 100}" for i in range(N)] +lev_b = [i % 500 for i in range(N)] +lev_c = [i % 10 for i in range(N)] + +idx = pd.MultiIndex.from_arrays([lev_a, lev_b, lev_c], names=["L0", "L1", "L2"]) +df = pd.DataFrame( + { + "x": np.arange(N, dtype=float), + "y": np.arange(N, dtype=float) * 2.0, + "z": np.arange(N, dtype=float) * 3.0, + }, + index=idx, +) + +for _ in range(WARMUP): + df.swaplevel(0, 1, axis=0) + df.swaplevel(0, 2, axis=0) + df.reorder_levels([2, 0, 1], axis=0) + df.reorder_levels([1, 2, 0], axis=0) + +start = time.perf_counter() +for _ in range(ITERATIONS): + df.swaplevel(0, 1, axis=0) + df.swaplevel(0, 2, axis=0) + df.reorder_levels([2, 0, 1], axis=0) + df.reorder_levels([1, 2, 0], axis=0) +total_ms = (time.perf_counter() - start) * 1000 + +print( + json.dumps( + { + "function": "swaplevel_dataframe", + "mean_ms": total_ms / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total_ms, + } + ) +) diff --git a/benchmarks/tsb/bench_swaplevel_dataframe.ts b/benchmarks/tsb/bench_swaplevel_dataframe.ts new file mode 100644 index 00000000..3c05ab73 --- /dev/null +++ b/benchmarks/tsb/bench_swaplevel_dataframe.ts @@ -0,0 +1,56 @@ +/** + * Benchmark: swapLevelDataFrame / reorderLevelsDataFrame on 50k-row MultiIndex DataFrame. + * + * Mirrors pandas: + * - DataFrame.swaplevel(i, j, axis=0) → swapLevelDataFrame + * - DataFrame.reorder_levels(order, axis=0) → reorderLevelsDataFrame + * + * Dataset: 50 000-row × 3-column DataFrame with a 3-level MultiIndex row index. + * + * Outputs JSON: {"function": "swaplevel_dataframe", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { DataFrame, MultiIndex, swapLevelDataFrame, reorderLevelsDataFrame } from "../../src/index.ts"; + +const N = 50_000; +const WARMUP = 5; +const ITERATIONS = 30; + +const levA = Array.from({ length: N }, (_, i) => `a${i % 100}`); +const levB = Array.from({ length: N }, (_, i) => i % 500); +const levC = Array.from({ length: N }, (_, i) => i % 10); +const tuples: [string, number, number][] = levA.map((v, i) => [v, levB[i], levC[i]]); +const idx = new MultiIndex({ tuples }); + +const df = DataFrame.fromColumns( + { + x: Array.from({ length: N }, (_, i) => i * 1.0), + y: Array.from({ length: N }, (_, i) => i * 2.0), + z: Array.from({ length: N }, (_, i) => i * 3.0), + }, + { index: idx }, +); + +for (let i = 0; i < WARMUP; i++) { + swapLevelDataFrame(df, 0, 1); + swapLevelDataFrame(df, 0, 2); + reorderLevelsDataFrame(df, [2, 0, 1]); + reorderLevelsDataFrame(df, [1, 2, 0]); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + swapLevelDataFrame(df, 0, 1); + swapLevelDataFrame(df, 0, 2); + reorderLevelsDataFrame(df, [2, 0, 1]); + reorderLevelsDataFrame(df, [1, 2, 0]); +} +const total_ms = performance.now() - start; + +console.log( + JSON.stringify({ + function: "swaplevel_dataframe", + mean_ms: total_ms / ITERATIONS, + iterations: ITERATIONS, + total_ms, + }), +); From 5f1924bed6e3d58b4f84234be592f7ea99bce061 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 2 Aug 2026 13:36:01 +0000 Subject: [PATCH 40/61] ci: trigger checks From 646ec8f52747c27603386b2d7ad27f7847251c1a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 01:33:54 +0000 Subject: [PATCH 41/61] [Autoloop: perf-comparison] Iteration 438: information_advanced benchmark Add bench_information_advanced.ts and bench_information_advanced.py covering 9 advanced information-theory functions: jsDivergence, jsDistance, crossEntropy, renyiEntropy, tsallisEntropy, jointEntropy, conditionalEntropy, normalizedMI, variationOfInformation. Dataset: N=200 PMF + 1000 paired observations, 50 iters. Run: https://github.com/githubnext/tsb/actions/runs/30776731199 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../pandas/bench_information_advanced.py | 142 ++++++++++++++++++ benchmarks/tsb/bench_information_advanced.ts | 67 +++++++++ 2 files changed, 209 insertions(+) create mode 100644 benchmarks/pandas/bench_information_advanced.py create mode 100644 benchmarks/tsb/bench_information_advanced.ts diff --git a/benchmarks/pandas/bench_information_advanced.py b/benchmarks/pandas/bench_information_advanced.py new file mode 100644 index 00000000..acd58ce5 --- /dev/null +++ b/benchmarks/pandas/bench_information_advanced.py @@ -0,0 +1,142 @@ +""" +Benchmark: advanced information theory functions. +Mirrors the TypeScript bench_information_advanced.ts benchmark. +Uses pure numpy (no scipy) for JS divergence, Rényi entropy, Tsallis entropy, +joint entropy, conditional entropy, NMI, and variation of information. +Outputs JSON: {"function": "...", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" + +import json +import time +from collections import Counter +import numpy as np + +N = 200 +WARMUP = 5 +ITERS = 50 + +p_raw = np.arange(1, N + 1, dtype=float) +q_raw = np.arange(N, 0, -1, dtype=float) + +# Paired observations for joint/conditional entropy +obs_x = [f"c{i % 5}" for i in range(1000)] +obs_y = [f"d{i % 4}" for i in range(1000)] + + +def _norm(arr: np.ndarray) -> np.ndarray: + s = arr.sum() + return arr / s if s > 0 else arr + + +def js_divergence(p: np.ndarray, q: np.ndarray) -> float: + p_n = _norm(p[p > 0]) + q_n = _norm(q[q > 0]) + # Rebuild full-length normalized arrays for mixture + pf = _norm(p) + qf = _norm(q) + m = (pf + qf) / 2.0 + kl_pm = np.sum(pf[pf > 0] * np.log(pf[pf > 0] / m[pf > 0])) + kl_qm = np.sum(qf[qf > 0] * np.log(qf[qf > 0] / m[qf > 0])) + return 0.5 * kl_pm + 0.5 * kl_qm + + +def js_distance(p: np.ndarray, q: np.ndarray) -> float: + return float(np.sqrt(max(0.0, js_divergence(p, q)))) + + +def cross_entropy(p: np.ndarray, q: np.ndarray) -> float: + pn = _norm(p) + qn = _norm(q) + mask = (pn > 0) & (qn > 0) + return float(-np.sum(pn[mask] * np.log(qn[mask]))) + + +def renyi_entropy(p: np.ndarray, alpha: float) -> float: + pn = _norm(p[p > 0]) + if abs(alpha - 1.0) < 1e-10: + return float(-np.sum(pn * np.log(pn))) + return float(np.log(np.sum(pn**alpha)) / (1.0 - alpha)) + + +def tsallis_entropy(p: np.ndarray, q_param: float) -> float: + pn = _norm(p[p > 0]) + if abs(q_param - 1.0) < 1e-10: + return float(-np.sum(pn * np.log(pn))) + return float((1.0 - np.sum(pn**q_param)) / (q_param - 1.0)) + + +def joint_entropy(xs, ys) -> float: + counts = Counter(zip(xs, ys)) + total = len(xs) + probs = np.array([c / total for c in counts.values()]) + return float(-np.sum(probs * np.log(probs))) + + +def conditional_entropy(xs, ys) -> float: + joint = Counter(zip(xs, ys)) + y_counts = Counter(ys) + total = len(xs) + h = 0.0 + for (x, y), c in joint.items(): + p_xy = c / total + p_y = y_counts[y] / total + h -= p_xy * np.log(p_xy / p_y) + return h + + +def marginal_entropy(xs) -> float: + counts = Counter(xs) + total = len(xs) + probs = np.array([c / total for c in counts.values()]) + return float(-np.sum(probs * np.log(probs))) + + +def mutual_information(xs, ys) -> float: + return marginal_entropy(xs) + marginal_entropy(ys) - joint_entropy(xs, ys) + + +def normalized_mi(xs, ys) -> float: + hx = marginal_entropy(xs) + hy = marginal_entropy(ys) + mi = mutual_information(xs, ys) + denom = 0.5 * (hx + hy) + return mi / denom if denom > 0 else 0.0 + + +def variation_of_information(xs, ys) -> float: + hx = marginal_entropy(xs) + hy = marginal_entropy(ys) + mi = mutual_information(xs, ys) + return hx + hy - 2.0 * mi + + +for _ in range(WARMUP): + js_divergence(p_raw, q_raw) + js_distance(p_raw, q_raw) + cross_entropy(p_raw, q_raw) + renyi_entropy(p_raw, 0.5) + tsallis_entropy(p_raw, 2) + joint_entropy(obs_x, obs_y) + conditional_entropy(obs_x, obs_y) + normalized_mi(obs_x, obs_y) + variation_of_information(obs_x, obs_y) + +t0 = time.perf_counter() +for _ in range(ITERS): + js_divergence(p_raw, q_raw) + js_distance(p_raw, q_raw) + cross_entropy(p_raw, q_raw) + renyi_entropy(p_raw, 0.5) + tsallis_entropy(p_raw, 2) + joint_entropy(obs_x, obs_y) + conditional_entropy(obs_x, obs_y) + normalized_mi(obs_x, obs_y) + variation_of_information(obs_x, obs_y) +total_ms = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "information_advanced", + "mean_ms": total_ms / ITERS, + "iterations": ITERS, + "total_ms": total_ms, +})) diff --git a/benchmarks/tsb/bench_information_advanced.ts b/benchmarks/tsb/bench_information_advanced.ts new file mode 100644 index 00000000..f7abac6d --- /dev/null +++ b/benchmarks/tsb/bench_information_advanced.ts @@ -0,0 +1,67 @@ +/** + * Benchmark: advanced information theory functions. + * Covers: jsDivergence, jsDistance, crossEntropy, renyiEntropy, tsallisEntropy, + * jointEntropy, conditionalEntropy, normalizedMI, variationOfInformation. + * Mirrors the corresponding scipy.stats / custom-numpy Python benchmarks. + * Outputs JSON: {"function": "...", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { + jsDivergence, + jsDistance, + crossEntropy, + renyiEntropy, + tsallisEntropy, + jointEntropy, + conditionalEntropy, + normalizedMI, + variationOfInformation, +} from "../../src/index.js"; + +const N = 200; +const WARMUP = 5; +const ITERS = 50; + +// 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); + +// Paired observations for joint/conditional entropy functions +const obs: [string, string][] = Array.from({ length: 1000 }, (_, i) => [ + `c${i % 5}`, + `d${i % 4}`, +]); + +for (let i = 0; i < WARMUP; i++) { + jsDivergence(p, q); + jsDistance(p, q); + crossEntropy(p, q); + renyiEntropy(p, 0.5); + tsallisEntropy(p, 2); + jointEntropy(obs); + conditionalEntropy(obs); + normalizedMI(obs); + variationOfInformation(obs); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERS; i++) { + jsDivergence(p, q); + jsDistance(p, q); + crossEntropy(p, q); + renyiEntropy(p, 0.5); + tsallisEntropy(p, 2); + jointEntropy(obs); + conditionalEntropy(obs); + normalizedMI(obs); + variationOfInformation(obs); +} +const total_ms = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "information_advanced", + mean_ms: total_ms / ITERS, + iterations: ITERS, + total_ms, + }), +); From 513f585d7303456efac5b97fff34b3273839af20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 01:37:38 +0000 Subject: [PATCH 42/61] ci: trigger checks From bd734772a7beed7d7ecde833fd054f789b97ec56 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 13:58:35 +0000 Subject: [PATCH 43/61] [Autoloop: perf-comparison] Iteration 439: format_table benchmark (toMarkdown/toLaTeX/seriesToMarkdown, 1000-row DataFrame, 20 iters) Run: https://github.com/githubnext/tsb/actions/runs/30819531253 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_format_table.py | 39 +++++++++++++++++++++++++ benchmarks/tsb/bench_format_table.ts | 39 +++++++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 benchmarks/pandas/bench_format_table.py create mode 100644 benchmarks/tsb/bench_format_table.ts diff --git a/benchmarks/pandas/bench_format_table.py b/benchmarks/pandas/bench_format_table.py new file mode 100644 index 00000000..c01d4977 --- /dev/null +++ b/benchmarks/pandas/bench_format_table.py @@ -0,0 +1,39 @@ +"""Benchmark: to_markdown / to_latex / Series.to_markdown on a 1000-row DataFrame""" +import json, time, subprocess, sys +try: + import tabulate # noqa: F401 +except ImportError: + subprocess.run([sys.executable, "-m", "pip", "install", "tabulate", "--quiet"], check=False) +import numpy as np +import pandas as pd + +ROWS = 1_000 +WARMUP = 3 +ITERATIONS = 20 + +data = { + "a": np.arange(ROWS) * 1.1, + "b": np.sin(np.arange(ROWS)) * 100, + "c": np.arange(ROWS) % 7, +} +df = pd.DataFrame(data) +s = pd.Series(np.arange(ROWS) * 2.5, name="x") + +for _ in range(WARMUP): + df.to_markdown() + df.to_latex() + s.to_markdown() + +start = time.perf_counter() +for _ in range(ITERATIONS): + df.to_markdown() + df.to_latex() + s.to_markdown() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "format_table", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_format_table.ts b/benchmarks/tsb/bench_format_table.ts new file mode 100644 index 00000000..4a9470b9 --- /dev/null +++ b/benchmarks/tsb/bench_format_table.ts @@ -0,0 +1,39 @@ +/** + * Benchmark: toMarkdown / toLaTeX / seriesToMarkdown on a 1000-row DataFrame + */ +import { DataFrame, Series, toMarkdown, toLaTeX, seriesToMarkdown } from "../../src/index.js"; + +const ROWS = 1_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const data: Record = { + a: Array.from({ length: ROWS }, (_, i) => i * 1.1), + b: Array.from({ length: ROWS }, (_, i) => Math.sin(i) * 100), + c: Array.from({ length: ROWS }, (_, i) => i % 7), +}; +const df = new DataFrame(data); +const s = new Series(Array.from({ length: ROWS }, (_, i) => i * 2.5), { name: "x" }); + +for (let i = 0; i < WARMUP; i++) { + toMarkdown(df); + toLaTeX(df); + seriesToMarkdown(s); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + toMarkdown(df); + toLaTeX(df); + seriesToMarkdown(s); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "format_table", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 81285c862167fd1d578b540f11ec9ebf5219c326 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 3 Aug 2026 14:06:20 +0000 Subject: [PATCH 44/61] ci: trigger checks From 2863f1f131929ab4a6bfb2d48dd67581dfe142a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 01:34:05 +0000 Subject: [PATCH 45/61] [Autoloop: perf-comparison] Iteration 440: add masked_array and frequencies benchmarks Run: https://github.com/githubnext/tsb/actions/runs/30868541111 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_frequencies.py | 36 +++++++++++++++++++++ benchmarks/pandas/bench_masked_array.py | 40 +++++++++++++++++++++++ benchmarks/tsb/bench_frequencies.ts | 40 +++++++++++++++++++++++ benchmarks/tsb/bench_masked_array.ts | 43 +++++++++++++++++++++++++ 4 files changed, 159 insertions(+) create mode 100644 benchmarks/pandas/bench_frequencies.py create mode 100644 benchmarks/pandas/bench_masked_array.py create mode 100644 benchmarks/tsb/bench_frequencies.ts create mode 100644 benchmarks/tsb/bench_masked_array.ts diff --git a/benchmarks/pandas/bench_frequencies.py b/benchmarks/pandas/bench_frequencies.py new file mode 100644 index 00000000..78ab1f59 --- /dev/null +++ b/benchmarks/pandas/bench_frequencies.py @@ -0,0 +1,36 @@ +"""Benchmark: frequencies — to_offset and infer_freq. +Tests parsing frequency strings and inferring frequency from date arrays. +""" +import json +import time +import pandas as pd + +WARMUP = 5 +ITERATIONS = 50 + +FREQ_STRINGS = ["D", "h", "min", "s", "ME", "MS", "YE", "YS", "W", "3ME", "2h", "QE", "QS"] + +# Build a regularly-spaced daily date index for infer_freq +daily_index = pd.date_range("2020-01-01", periods=365, freq="D") + + +def run(): + for freq in FREQ_STRINGS: + pd.tseries.frequencies.to_offset(freq) + pd.infer_freq(daily_index) + + +for _ in range(WARMUP): + run() + +start = time.perf_counter() +for _ in range(ITERATIONS): + run() +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "frequencies", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/pandas/bench_masked_array.py b/benchmarks/pandas/bench_masked_array.py new file mode 100644 index 00000000..31ec917e --- /dev/null +++ b/benchmarks/pandas/bench_masked_array.py @@ -0,0 +1,40 @@ +"""Benchmark: MaskedArray — base nullable array operations via pandas IntegerArray. +N=100_000 elements with ~10% nulls. Tests isna/notna/any-na/getitem/to_numpy/dropna/fillna. +""" +import json +import time +import pandas as pd +import numpy as np + +N = 100_000 +WARMUP = 3 +ITERATIONS = 20 + +raw = [(None if i % 10 == 0 else int((i % 1000) - 500)) for i in range(N)] + + +def run(): + a = pd.array(raw, dtype="Int32") + _ = pd.isna(a) + _ = pd.notna(a) + _ = bool(pd.isna(a).any()) + _ = a[42] + _ = np.asarray(a, dtype=object) + _ = a.dropna() + _ = 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": "masked_array", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_frequencies.ts b/benchmarks/tsb/bench_frequencies.ts new file mode 100644 index 00000000..799ac5e4 --- /dev/null +++ b/benchmarks/tsb/bench_frequencies.ts @@ -0,0 +1,40 @@ +/** + * Benchmark: frequencies — toOffset and inferFreq. + * Tests parsing frequency strings and inferring frequency from date arrays. + */ +import { toOffset, inferFreq } from "../../src/index.js"; + +const WARMUP = 5; +const ITERATIONS = 50; + +const FREQ_STRINGS = ["D", "h", "min", "s", "ME", "MS", "YE", "YS", "W", "3ME", "2h", "QE", "QS"]; + +// Build a regularly-spaced daily date array for inferFreq +const BASE = new Date(Date.UTC(2020, 0, 1)); +const DAILY_DATES = Array.from({ length: 365 }, (_, i) => new Date(BASE.getTime() + i * 86_400_000)); + +function run() { + for (const freq of FREQ_STRINGS) { + toOffset(freq); + } + inferFreq(DAILY_DATES); +} + +for (let i = 0; i < WARMUP; i++) { + run(); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + run(); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "frequencies", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); diff --git a/benchmarks/tsb/bench_masked_array.ts b/benchmarks/tsb/bench_masked_array.ts new file mode 100644 index 00000000..7d9c38ae --- /dev/null +++ b/benchmarks/tsb/bench_masked_array.ts @@ -0,0 +1,43 @@ +/** + * Benchmark: MaskedArray — base nullable array operations via IntegerArray. + * N=100_000 elements with ~10% nulls. Tests isna/notna/hasNa/at/toArray/dropna/fillna. + */ +import { arrays } from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const raw: (number | null)[] = Array.from({ length: N }, (_, i) => + i % 10 === 0 ? null : (i % 1000) - 500, +); + +function run() { + const a = arrays.IntegerArray.from(raw, "Int32"); + a.isna(); + a.notna(); + a.hasNa(); + a.at(42); + a.toArray(); + a.dropna(); + a.fillna(0); +} + +for (let i = 0; i < WARMUP; i++) { + run(); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + run(); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "masked_array", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 71d9caa76280f5c6d78f99413c8b726f45954ce5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 01:36:49 +0000 Subject: [PATCH 46/61] ci: trigger checks From 1923387c4d12a2c48020019e6ef088289f8d8a2b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 13:47:40 +0000 Subject: [PATCH 47/61] [Autoloop: perf-comparison] Iteration 441: add chi2_contingency and kstest_jarquebera benchmarks Run: https://github.com/githubnext/tsb/actions/runs/30913778633 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_chi2_contingency.py | 47 +++++++++++++++++++ benchmarks/pandas/bench_kstest_jarquebera.py | 31 +++++++++++++ benchmarks/tsb/bench_chi2_contingency.ts | 47 +++++++++++++++++++ benchmarks/tsb/bench_kstest_jarquebera.ts | 48 ++++++++++++++++++++ 4 files changed, 173 insertions(+) create mode 100644 benchmarks/pandas/bench_chi2_contingency.py create mode 100644 benchmarks/pandas/bench_kstest_jarquebera.py create mode 100644 benchmarks/tsb/bench_chi2_contingency.ts create mode 100644 benchmarks/tsb/bench_kstest_jarquebera.ts diff --git a/benchmarks/pandas/bench_chi2_contingency.py b/benchmarks/pandas/bench_chi2_contingency.py new file mode 100644 index 00000000..79372401 --- /dev/null +++ b/benchmarks/pandas/bench_chi2_contingency.py @@ -0,0 +1,47 @@ +""" +Benchmark: chi2Contingency — chi-squared test of independence on contingency tables. +Mirrors tsb bench_chi2_contingency.ts. +Dataset: 500 iterations over 4×4, 5×3, and 3×5 contingency tables. +Outputs JSON: {"function": "chi2_contingency", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +from scipy.stats import chi2_contingency + +WARMUP = 10 +ITERATIONS = 500 + +table4x4 = np.array([ + [10, 20, 30, 15], + [25, 35, 10, 20], + [15, 10, 25, 30], + [20, 15, 35, 10], +], dtype=float) +table5x3 = np.array([ + [50, 30, 20], + [40, 45, 15], + [35, 25, 40], + [20, 50, 30], + [55, 10, 35], +], dtype=float) +table3x5 = np.array([ + [10, 20, 15, 25, 30], + [30, 15, 25, 10, 20], + [20, 30, 10, 35, 5], +], dtype=float) + +for _ in range(WARMUP): + chi2_contingency(table4x4) + chi2_contingency(table5x3) + chi2_contingency(table3x5) + +t0 = time.perf_counter() +for _ in range(ITERATIONS): + chi2_contingency(table4x4) + chi2_contingency(table5x3) + chi2_contingency(table3x5) +total_ms = (time.perf_counter() - t0) * 1000 +mean_ms = total_ms / ITERATIONS + +print(json.dumps({"function": "chi2_contingency", "mean_ms": mean_ms, "iterations": ITERATIONS, "total_ms": total_ms})) diff --git a/benchmarks/pandas/bench_kstest_jarquebera.py b/benchmarks/pandas/bench_kstest_jarquebera.py new file mode 100644 index 00000000..8e051e83 --- /dev/null +++ b/benchmarks/pandas/bench_kstest_jarquebera.py @@ -0,0 +1,31 @@ +""" +Benchmark: kstest + jarqueBera — Kolmogorov-Smirnov test and Jarque-Bera normality test. +Mirrors tsb bench_kstest_jarquebera.ts. +Dataset: 1,000 samples; 200 measured iterations. +Outputs JSON: {"function": "kstest_jarquebera", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import numpy as np +from scipy.stats import kstest, jarque_bera + +WARMUP = 10 +ITERATIONS = 200 +N = 1_000 + +rng = np.random.default_rng(42) +# Match tsb: uniform in [-3, 3] +data = rng.uniform(-3, 3, N).tolist() + +for _ in range(WARMUP): + kstest(data, "norm") + jarque_bera(data) + +t0 = time.perf_counter() +for _ in range(ITERATIONS): + kstest(data, "norm") + jarque_bera(data) +total_ms = (time.perf_counter() - t0) * 1000 +mean_ms = total_ms / ITERATIONS + +print(json.dumps({"function": "kstest_jarquebera", "mean_ms": mean_ms, "iterations": ITERATIONS, "total_ms": total_ms})) diff --git a/benchmarks/tsb/bench_chi2_contingency.ts b/benchmarks/tsb/bench_chi2_contingency.ts new file mode 100644 index 00000000..1e014209 --- /dev/null +++ b/benchmarks/tsb/bench_chi2_contingency.ts @@ -0,0 +1,47 @@ +/** + * Benchmark: chi2Contingency — chi-squared test of independence on contingency tables. + * Mirrors scipy.stats.chi2_contingency (pandas users typically call scipy via pandas workflows). + * Dataset: 500 iterations over a 4×4, 5×3, and 3×5 contingency table. + * Outputs JSON: {"function": "chi2_contingency", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { chi2Contingency } from "../../src/index.ts"; + +const WARMUP = 10; +const ITERATIONS = 500; + +// Four representative contingency tables +const table4x4: readonly (readonly number[])[] = [ + [10, 20, 30, 15], + [25, 35, 10, 20], + [15, 10, 25, 30], + [20, 15, 35, 10], +]; +const table5x3: readonly (readonly number[])[] = [ + [50, 30, 20], + [40, 45, 15], + [35, 25, 40], + [20, 50, 30], + [55, 10, 35], +]; +const table3x5: readonly (readonly number[])[] = [ + [10, 20, 15, 25, 30], + [30, 15, 25, 10, 20], + [20, 30, 10, 35, 5], +]; + +for (let i = 0; i < WARMUP; i++) { + chi2Contingency(table4x4); + chi2Contingency(table5x3); + chi2Contingency(table3x5); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + chi2Contingency(table4x4); + chi2Contingency(table5x3); + chi2Contingency(table3x5); +} +const total_ms = performance.now() - t0; +const mean_ms = total_ms / ITERATIONS; + +console.log(JSON.stringify({ function: "chi2_contingency", mean_ms, iterations: ITERATIONS, total_ms })); diff --git a/benchmarks/tsb/bench_kstest_jarquebera.ts b/benchmarks/tsb/bench_kstest_jarquebera.ts new file mode 100644 index 00000000..2519e77f --- /dev/null +++ b/benchmarks/tsb/bench_kstest_jarquebera.ts @@ -0,0 +1,48 @@ +/** + * Benchmark: kstest + jarqueBera — Kolmogorov-Smirnov test and Jarque-Bera normality test. + * Mirrors scipy.stats.kstest and scipy.stats.jarque_bera. + * Dataset: 1,000 samples; 200 measured iterations. + * Outputs JSON: {"function": "kstest_jarquebera", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { kstest, jarqueBera } from "../../src/index.ts"; + +const WARMUP = 10; +const ITERATIONS = 200; +const N = 1_000; + +// Generate pseudo-random normal-ish data using LCG +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) * 6 - 3); // uniform in [-3, 3] + } + return arr; +} + +const data = makeData(N, 42); + +// Standard normal CDF approximation (Abramowitz & Stegun) +function normalCdf(x: number): number { + const t = 1 / (1 + 0.2316419 * Math.abs(x)); + const poly = + t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429)))); + const p = 1 - (1 / Math.sqrt(2 * Math.PI)) * Math.exp(-0.5 * x * x) * poly; + return x >= 0 ? p : 1 - p; +} + +for (let i = 0; i < WARMUP; i++) { + kstest(data, normalCdf); + jarqueBera(data); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + kstest(data, normalCdf); + jarqueBera(data); +} +const total_ms = performance.now() - t0; +const mean_ms = total_ms / ITERATIONS; + +console.log(JSON.stringify({ function: "kstest_jarquebera", mean_ms, iterations: ITERATIONS, total_ms })); From f4032c43272d92b40b839a8564a57c7e686e5d62 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 4 Aug 2026 13:56:21 +0000 Subject: [PATCH 48/61] ci: trigger checks From 0e5f374a48f82c1464ed0a1f67eacc71e885d10b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Aug 2026 01:29:46 +0000 Subject: [PATCH 49/61] [Autoloop: perf-comparison] Iteration 442: add numeric_extended benchmarks Add TypeScript and Python benchmarks for digitize, histogram, linspace, arange, zscore, minMaxNormalize, and percentileOfScore from numeric_extended. Run: https://github.com/githubnext/tsb/actions/runs/30966192541 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_numeric_extended.py | 58 +++++++++++++++++++++ benchmarks/tsb/bench_numeric_extended.ts | 50 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 benchmarks/pandas/bench_numeric_extended.py create mode 100644 benchmarks/tsb/bench_numeric_extended.ts diff --git a/benchmarks/pandas/bench_numeric_extended.py b/benchmarks/pandas/bench_numeric_extended.py new file mode 100644 index 00000000..2fd7fd33 --- /dev/null +++ b/benchmarks/pandas/bench_numeric_extended.py @@ -0,0 +1,58 @@ +""" +Benchmark: numeric_extended (digitize, histogram, linspace, arange, zscore, +minmax normalization, percentileofscore) using numpy/scipy. +""" +import json +import time +import numpy as np +try: + from scipy.stats import zscore as sp_zscore, percentileofscore +except ImportError: + import subprocess, sys + subprocess.check_call([sys.executable, "-m", "pip", "install", "scipy", "-q"]) + from scipy.stats import zscore as sp_zscore, percentileofscore + +N = 100_000 +WARMUP = 3 +ITERS = 20 + +# Same deterministic dataset as TypeScript version +data = np.array([((i * 2654435761) % 1_000_000) / 10_000 for i in range(N)], dtype=float) +bins20 = np.linspace(0, 100, 21) # 0,5,...,100 + +def bench(fn): + for _ in range(WARMUP): + fn() + t0 = time.perf_counter() + for _ in range(ITERS): + fn() + return (time.perf_counter() - t0) / ITERS * 1000 # ms + +digitize_ms = bench(lambda: np.digitize(data, bins20)) +histogram_ms = bench(lambda: np.histogram(data, bins=20)) +linspace_ms = bench(lambda: np.linspace(0, 100, N)) +arange_ms = bench(lambda: np.arange(0, 100, 0.001)) +zscore_ms = bench(lambda: sp_zscore(data, ddof=1)) +minmax_ms = bench(lambda: (data - data.min()) / (data.max() - data.min())) +percentile_ms = bench(lambda: percentileofscore(data, 50.0)) + +mean_ms = ( + digitize_ms + histogram_ms + linspace_ms + arange_ms + + zscore_ms + minmax_ms + percentile_ms +) / 7 + +print(json.dumps({ + "function": "numeric_extended", + "mean_ms": round(mean_ms, 4), + "iterations": ITERS, + "total_ms": round(mean_ms * ITERS, 4), + "details": { + "digitize_ms": round(digitize_ms, 4), + "histogram_ms": round(histogram_ms, 4), + "linspace_ms": round(linspace_ms, 4), + "arange_ms": round(arange_ms, 4), + "zscore_ms": round(zscore_ms, 4), + "minmax_ms": round(minmax_ms, 4), + "percentile_ms": round(percentile_ms, 4), + } +})) diff --git a/benchmarks/tsb/bench_numeric_extended.ts b/benchmarks/tsb/bench_numeric_extended.ts new file mode 100644 index 00000000..91ae1daa --- /dev/null +++ b/benchmarks/tsb/bench_numeric_extended.ts @@ -0,0 +1,50 @@ +/** + * Benchmark: numeric_extended — digitize, histogram, linspace, arange, zscore, + * minMaxNormalize, percentileOfScore on 100k-element arrays. + */ +import { + digitize, + histogram, + linspace, + arange, + zscore, + minMaxNormalize, + percentileOfScore, + Series, +} from "../../src/index.js"; + +const N = 100_000; +const WARMUP = 3; +const ITERATIONS = 10; + +// Deterministic dataset in [0, 100) +const data: number[] = Array.from({ length: N }, (_, i) => ((i * 2654435761) % 1_000_000) / 10_000); +const bins20: number[] = Array.from({ length: 21 }, (_, i) => i * 5); +const series = new Series({ data }); + +function bench(fn: () => void): number { + for (let i = 0; i < WARMUP; i++) fn(); + const t0 = performance.now(); + for (let i = 0; i < ITERATIONS; i++) fn(); + return (performance.now() - t0) / ITERATIONS; +} + +const digitize_ms = bench(() => digitize(data, bins20)); +const histogram_ms = bench(() => histogram(data, { bins: 20 })); +const linspace_ms = bench(() => linspace(0, 100, N)); +const arange_ms = bench(() => arange(0, 100, 0.001)); +const zscore_ms = bench(() => zscore(series)); +const minmax_ms = bench(() => minMaxNormalize(series)); +const percentile_ms = bench(() => percentileOfScore(data, 50.0)); + +const mean_ms = + (digitize_ms + histogram_ms + linspace_ms + arange_ms + zscore_ms + minmax_ms + percentile_ms) / 7; + +console.log( + JSON.stringify({ + function: "numeric_extended", + mean_ms: parseFloat(mean_ms.toFixed(4)), + iterations: ITERATIONS, + total_ms: parseFloat((mean_ms * ITERATIONS).toFixed(4)), + }), +); From 4ad24aca564eb669443fcdf8958032d4dfe52db5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Aug 2026 01:37:44 +0000 Subject: [PATCH 50/61] ci: trigger checks From 806b80b5100259ed879b9bb73aa7db87c6261817 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Aug 2026 13:43:38 +0000 Subject: [PATCH 51/61] [Autoloop: perf-comparison] Iteration 443: add sort_index_columns benchmark (sortIndexDataFrame axis=1, 50-col DataFrame, 30 iters) Run: https://github.com/githubnext/tsb/actions/runs/31010097715 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_sort_index_columns.py | 45 +++++++++++++++++ benchmarks/tsb/bench_sort_index_columns.ts | 49 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 benchmarks/pandas/bench_sort_index_columns.py create mode 100644 benchmarks/tsb/bench_sort_index_columns.ts diff --git a/benchmarks/pandas/bench_sort_index_columns.py b/benchmarks/pandas/bench_sort_index_columns.py new file mode 100644 index 00000000..a5bce9d5 --- /dev/null +++ b/benchmarks/pandas/bench_sort_index_columns.py @@ -0,0 +1,45 @@ +""" +Benchmark: DataFrame.sort_index(axis=1) — sort column labels on a 100k-row +DataFrame with many shuffled columns. + +Exercises the column-sort code path (axis=1), which is distinct from the +default row-index sort (axis=0) benchmarked elsewhere. + +Outputs JSON: {"function": "sort_index_columns", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +import pandas as pd +import numpy as np + +ROWS = 100_000 +N_COLS = 50 +WARMUP = 5 +ITERATIONS = 30 + +# Build column names that are intentionally shuffled (z-first alphabetical order) +col_names = [f"col_{str(N_COLS - 1 - i).zfill(3)}" for i in range(N_COLS)] + +rng = np.random.default_rng(42) +data = {name: rng.random(ROWS) for name in col_names} +df = pd.DataFrame(data) + +# Warm up +for _ in range(WARMUP): + df.sort_index(axis=1, ascending=True) + df.sort_index(axis=1, ascending=False) + +start = time.perf_counter() +for _ in range(ITERATIONS): + df.sort_index(axis=1, ascending=True) + df.sort_index(axis=1, ascending=False) +total_s = time.perf_counter() - start +total_ms = total_s * 1000 +mean_ms = total_ms / ITERATIONS + +print(json.dumps({ + "function": "sort_index_columns", + "mean_ms": round(mean_ms, 4), + "iterations": ITERATIONS, + "total_ms": round(total_ms, 4), +})) diff --git a/benchmarks/tsb/bench_sort_index_columns.ts b/benchmarks/tsb/bench_sort_index_columns.ts new file mode 100644 index 00000000..5d7ec89a --- /dev/null +++ b/benchmarks/tsb/bench_sort_index_columns.ts @@ -0,0 +1,49 @@ +/** + * Benchmark: sortIndexDataFrame with axis=1 — sort column labels on a 100k-row + * DataFrame with many shuffled columns. + * + * Exercises the column-sort code path (axis=1) of sortIndexDataFrame, which + * is distinct from the default row-index sort (axis=0) benchmarked elsewhere. + * + * Outputs JSON: {"function": "sort_index_columns", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { DataFrame, sortIndexDataFrame } from "../../src/index.ts"; + +const ROWS = 100_000; +const N_COLS = 50; +const WARMUP = 5; +const ITERATIONS = 30; + +// Build column names that are intentionally shuffled (z-first alphabetical order) +const colNames: string[] = Array.from({ length: N_COLS }, (_, i) => { + const suffix = String(N_COLS - 1 - i).padStart(3, "0"); + return `col_${suffix}`; +}); + +const cols: Record = {}; +for (let ci = 0; ci < N_COLS; ci++) { + cols[colNames[ci]] = Array.from({ length: ROWS }, (_, r) => r * (ci + 1) * 0.001); +} +const df = DataFrame.fromColumns(cols); + +// Warm up +for (let i = 0; i < WARMUP; i++) { + sortIndexDataFrame(df, { axis: 1, ascending: true }); + sortIndexDataFrame(df, { axis: 1, ascending: false }); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + sortIndexDataFrame(df, { axis: 1, ascending: true }); + sortIndexDataFrame(df, { axis: 1, ascending: false }); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "sort_index_columns", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 60eaabd1a815a8a55c43628e2f20e97a247427db Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 5 Aug 2026 13:46:54 +0000 Subject: [PATCH 52/61] ci: trigger checks From 3fd006ab7e40e45c3e5e1f289e73472c9b4ff7c0 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Aug 2026 01:27:02 +0000 Subject: [PATCH 53/61] [Autoloop: perf-comparison] Iteration 444: bench_datetime_tz (tz_localize + tz_convert) Run: https://github.com/githubnext/tsb/actions/runs/31062534316 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_datetime_tz.py | 30 ++++++++++++++++++++++ benchmarks/tsb/bench_datetime_tz.ts | 35 ++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 benchmarks/pandas/bench_datetime_tz.py create mode 100644 benchmarks/tsb/bench_datetime_tz.ts diff --git a/benchmarks/pandas/bench_datetime_tz.py b/benchmarks/pandas/bench_datetime_tz.py new file mode 100644 index 00000000..cd693bf9 --- /dev/null +++ b/benchmarks/pandas/bench_datetime_tz.py @@ -0,0 +1,30 @@ +"""Benchmark: tz_localize and tz_convert on DatetimeIndex (pandas equivalent).""" +import json +import time +import pandas as pd + +SIZE = 10_000 +WARMUP = 5 +ITERATIONS = 50 + +naive = pd.date_range(start="2024-01-01", periods=SIZE, freq="h") + +# Warm-up +for _ in range(WARMUP): + ny = naive.tz_localize("America/New_York") + ny.tz_convert("UTC") + ny.tz_convert("Europe/London") + +start = time.perf_counter() +for _ in range(ITERATIONS): + ny = naive.tz_localize("America/New_York") + ny.tz_convert("UTC") + ny.tz_convert("Europe/London") +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "datetime_tz", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_datetime_tz.ts b/benchmarks/tsb/bench_datetime_tz.ts new file mode 100644 index 00000000..23aff512 --- /dev/null +++ b/benchmarks/tsb/bench_datetime_tz.ts @@ -0,0 +1,35 @@ +/** + * Benchmark: datetime_tz — tz_localize and tz_convert on DatetimeIndex. + * Outputs JSON: {"function": "datetime_tz", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { date_range, tz_localize, tz_convert } from "../../src/index.js"; + +const SIZE = 10_000; +const WARMUP = 5; +const ITERATIONS = 50; + +const naive = date_range({ start: "2024-01-01", periods: SIZE, freq: "H" }); + +// Warm-up +for (let i = 0; i < WARMUP; i++) { + const ny = tz_localize(naive, "America/New_York"); + tz_convert(ny, "UTC"); + tz_convert(ny, "Europe/London"); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const ny = tz_localize(naive, "America/New_York"); + tz_convert(ny, "UTC"); + tz_convert(ny, "Europe/London"); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "datetime_tz", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From cf72bc2c0a4ff6e383ddd12ec02ccfe7668888fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Aug 2026 01:32:12 +0000 Subject: [PATCH 54/61] ci: trigger checks From 8ba6bb09d68ebc8aa98089de5505e132f3bcd744 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Aug 2026 13:36:44 +0000 Subject: [PATCH 55/61] [Autoloop: perf-comparison] Iteration 445: Add HDF5 round-trip benchmarks (readHdf/toHdf) Run: https://github.com/githubnext/tsb/actions/runs/31105723794 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_hdf.py | 49 ++++++++++++++++++++++++++++++++++ benchmarks/tsb/bench_hdf.ts | 36 +++++++++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 benchmarks/pandas/bench_hdf.py create mode 100644 benchmarks/tsb/bench_hdf.ts diff --git a/benchmarks/pandas/bench_hdf.py b/benchmarks/pandas/bench_hdf.py new file mode 100644 index 00000000..776cf46a --- /dev/null +++ b/benchmarks/pandas/bench_hdf.py @@ -0,0 +1,49 @@ +""" +Benchmark: read_hdf / to_hdf — HDF5 round-trip on 5k rows. +DataFrame with int, float, and string columns. +""" +import json +import time +import tempfile +import os +import pandas as pd +import numpy as np + +ROWS = 5_000 +WARMUP = 3 +ITERATIONS = 20 + +rng = np.random.default_rng(42) +df = pd.DataFrame({ + "id": np.arange(ROWS, dtype=np.int64), + "value": np.arange(ROWS, dtype=np.float64) * 1.1, + "label": [f"cat_{i % 50}" for i in range(ROWS)], +}) + +def run_hdf_roundtrip(path: str) -> None: + df.to_hdf(path, key="df", mode="w") + pd.read_hdf(path, key="df") + +# Use a temp file since pandas to_hdf requires a file path (not BytesIO for HDF5) +with tempfile.NamedTemporaryFile(suffix=".h5", delete=False) as f: + tmp_path = f.name + +try: + # Warm up + for _ in range(WARMUP): + run_hdf_roundtrip(tmp_path) + + # Measure round-trip + start = time.perf_counter() + for _ in range(ITERATIONS): + run_hdf_roundtrip(tmp_path) + total = (time.perf_counter() - start) * 1000 +finally: + os.unlink(tmp_path) + +print(json.dumps({ + "function": "hdf", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_hdf.ts b/benchmarks/tsb/bench_hdf.ts new file mode 100644 index 00000000..62b47287 --- /dev/null +++ b/benchmarks/tsb/bench_hdf.ts @@ -0,0 +1,36 @@ +/** + * Benchmark: readHdf / toHdf — HDF5 round-trip on 5k rows. + * DataFrame with int, float, and string columns. + */ +import { DataFrame, toHdf, readHdf } from "../../src/index.js"; + +const ROWS = 5_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const ids = Array.from({ length: ROWS }, (_, i) => i); +const values = Array.from({ length: ROWS }, (_, i) => i * 1.1); +const labels = Array.from({ length: ROWS }, (_, i) => `cat_${i % 50}`); + +const df = new DataFrame({ id: ids, value: values, label: labels }); + +for (let i = 0; i < WARMUP; i++) { + const buf = toHdf(df); + readHdf(buf); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const buf = toHdf(df); + readHdf(buf); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "hdf", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From ac3f375c4ae8381234872f5ebaede446def46eb6 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 6 Aug 2026 13:39:34 +0000 Subject: [PATCH 56/61] ci: trigger checks From aa6a24ec340bb26f27e52f9a855fc9044ddeed59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 7 Aug 2026 07:32:10 +0000 Subject: [PATCH 57/61] [Autoloop: perf-comparison] Iteration 446: Add Parquet round-trip benchmarks (readParquet/toParquet) Run: https://github.com/githubnext/tsb/actions/runs/31157223454 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_parquet.py | 37 ++++++++++++++++++++++++++++++ benchmarks/tsb/bench_parquet.ts | 37 ++++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 benchmarks/pandas/bench_parquet.py create mode 100644 benchmarks/tsb/bench_parquet.ts diff --git a/benchmarks/pandas/bench_parquet.py b/benchmarks/pandas/bench_parquet.py new file mode 100644 index 00000000..09ab3e71 --- /dev/null +++ b/benchmarks/pandas/bench_parquet.py @@ -0,0 +1,37 @@ +"""Benchmark: read_parquet / to_parquet — Parquet round-trip on 10k rows.""" +import json, time, io +import pandas as pd +import numpy as np + +ROWS = 10_000 +WARMUP = 3 +ITERATIONS = 20 + +rng = np.random.default_rng(42) +df = pd.DataFrame({ + "id": np.arange(ROWS, dtype=np.int64), + "value": np.arange(ROWS, dtype=np.float64) * 1.1, + "flag": np.array([(i % 2 == 0) for i in range(ROWS)], dtype=bool), + "label": [f"item_{i % 100}" for i in range(ROWS)], +}) + +for _ in range(WARMUP): + buf = io.BytesIO() + df.to_parquet(buf, engine="pyarrow") + buf.seek(0) + pd.read_parquet(buf, engine="pyarrow") + +start = time.perf_counter() +for _ in range(ITERATIONS): + buf = io.BytesIO() + df.to_parquet(buf, engine="pyarrow") + buf.seek(0) + pd.read_parquet(buf, engine="pyarrow") +total = (time.perf_counter() - start) * 1000 + +print(json.dumps({ + "function": "parquet", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_parquet.ts b/benchmarks/tsb/bench_parquet.ts new file mode 100644 index 00000000..2d8d930f --- /dev/null +++ b/benchmarks/tsb/bench_parquet.ts @@ -0,0 +1,37 @@ +/** + * Benchmark: readParquet / toParquet — Parquet round-trip on 10k rows. + * DataFrame with int, float, boolean, and string columns; 20 measured iterations. + */ +import { DataFrame, toParquet, readParquet } from "../../src/index.js"; + +const ROWS = 10_000; +const WARMUP = 3; +const ITERATIONS = 20; + +const ids = Array.from({ length: ROWS }, (_, i) => i); +const values = Array.from({ length: ROWS }, (_, i) => i * 1.1); +const flags = Array.from({ length: ROWS }, (_, i) => i % 2 === 0); +const labels = Array.from({ length: ROWS }, (_, i) => `item_${i % 100}`); + +const df = new DataFrame({ id: ids, value: values, flag: flags, label: labels }); + +for (let i = 0; i < WARMUP; i++) { + const buf = toParquet(df); + readParquet(buf); +} + +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const buf = toParquet(df); + readParquet(buf); +} +const total = performance.now() - start; + +console.log( + JSON.stringify({ + function: "parquet", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From 1aee12ae9491a5d4d2dc32e2d6b32d530a9da0c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 7 Aug 2026 07:34:28 +0000 Subject: [PATCH 58/61] ci: trigger checks From 5ca398015c9cd6c23ad3f9674e1f47d284c339c7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 7 Aug 2026 19:31:18 +0000 Subject: [PATCH 59/61] =?UTF-8?q?[Autoloop:=20perf-comparison]=20Iteration?= =?UTF-8?q?=20447:=20Add=20bench=5Fholiday=5Fcalendar=20=E2=80=94=20Abstra?= =?UTF-8?q?ctHolidayCalendar,=20Holiday,=20register=5Fcalendar,=20get=5Fca?= =?UTF-8?q?lendar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run: https://github.com/githubnext/tsb/actions/runs/31210523668 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_holiday_calendar.py | 55 +++++++++++++++++++ benchmarks/tsb/bench_holiday_calendar.ts | 61 +++++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 benchmarks/pandas/bench_holiday_calendar.py create mode 100644 benchmarks/tsb/bench_holiday_calendar.ts diff --git a/benchmarks/pandas/bench_holiday_calendar.py b/benchmarks/pandas/bench_holiday_calendar.py new file mode 100644 index 00000000..a9a8c8bb --- /dev/null +++ b/benchmarks/pandas/bench_holiday_calendar.py @@ -0,0 +1,55 @@ +""" +Benchmark: pandas custom AbstractHolidayCalendar — custom calendar definition, +holiday generation, and calendar registry (get_calendar / register). + +Creates a small custom calendar with 5 fixed-date rules and measures how long +it takes to compute the observed holiday dates for a 20-year range. + +Outputs JSON: {"function": "holiday_calendar", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import time +from datetime import datetime + +import pandas as pd +from pandas.tseries.holiday import AbstractHolidayCalendar, Holiday, register + +WARMUP = 5 +ITERATIONS = 50 + +# ── Custom calendar with 5 fixed-date holidays ──────────────────────────────── + +class CustomCalendar(AbstractHolidayCalendar): + name = "BenchCustomCalendar" + rules = [ + Holiday("New Year's Day", month=1, day=1), + Holiday("Valentine's Day", month=2, day=14), + Holiday("May Day", month=5, day=1), + Holiday("Midsummer", month=6, day=24), + Holiday("Christmas Day", month=12, day=25), + ] + +register(CustomCalendar) + +start_date = datetime(2000, 1, 1) +end_date = datetime(2019, 12, 31) + +# Warm up +for _ in range(WARMUP): + cal = CustomCalendar() + cal.holidays(start=start_date, end=end_date) + AbstractHolidayCalendar.get_calendar("BenchCustomCalendar") + +t0 = time.perf_counter() +for _ in range(ITERATIONS): + cal = CustomCalendar() + cal.holidays(start=start_date, end=end_date) + AbstractHolidayCalendar.get_calendar("BenchCustomCalendar") +total = (time.perf_counter() - t0) * 1000 + +print(json.dumps({ + "function": "holiday_calendar", + "mean_ms": total / ITERATIONS, + "iterations": ITERATIONS, + "total_ms": total, +})) diff --git a/benchmarks/tsb/bench_holiday_calendar.ts b/benchmarks/tsb/bench_holiday_calendar.ts new file mode 100644 index 00000000..85d75f97 --- /dev/null +++ b/benchmarks/tsb/bench_holiday_calendar.ts @@ -0,0 +1,61 @@ +/** + * Benchmark: AbstractHolidayCalendar — custom calendar definition, holiday + * generation, and calendar registry (get_calendar / register_calendar). + * + * Creates a small custom calendar with 5 fixed-date rules and measures how + * long it takes to compute the observed holiday dates for a 20-year range. + * Also benchmarks the registry lookup overhead. + * + * Outputs JSON: {"function": "holiday_calendar", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { + AbstractHolidayCalendar, + Holiday, + register_calendar, + get_calendar, +} from "../../src/index.js"; + +const WARMUP = 5; +const ITERATIONS = 50; + +// ── Custom calendar with 5 fixed-date holidays ──────────────────────────────── + +class CustomCalendar extends AbstractHolidayCalendar { + readonly name = "BenchCustomCalendar"; + readonly rules: readonly Holiday[] = [ + new Holiday("New Year's Day", { month: 1, day: 1 }), + new Holiday("Valentine's Day", { month: 2, day: 14 }), + new Holiday("May Day", { month: 5, day: 1 }), + new Holiday("Midsummer", { month: 6, day: 24 }), + new Holiday("Christmas Day", { month: 12, day: 25 }), + ]; +} + +register_calendar("BenchCustomCalendar", () => new CustomCalendar()); + +const startDate = new Date("2000-01-01"); +const endDate = new Date("2019-12-31"); + +// Warm up +for (let i = 0; i < WARMUP; i++) { + const cal = new CustomCalendar(); + cal.holidays(startDate, endDate); + get_calendar("BenchCustomCalendar"); +} + +const t0 = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + const cal = new CustomCalendar(); + cal.holidays(startDate, endDate); + get_calendar("BenchCustomCalendar"); +} +const total = performance.now() - t0; + +console.log( + JSON.stringify({ + function: "holiday_calendar", + mean_ms: total / ITERATIONS, + iterations: ITERATIONS, + total_ms: total, + }), +); From f083b6e991a7772a378bf819516260edef159945 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 7 Aug 2026 19:34:51 +0000 Subject: [PATCH 60/61] ci: trigger checks From 15edd35f639e97273b087e737ef3abab7a773aa7 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 8 Aug 2026 07:38:43 +0000 Subject: [PATCH 61/61] =?UTF-8?q?[Autoloop:=20perf-comparison]=20Iteration?= =?UTF-8?q?=20448:=20benchmark=20readSqlTable=20=E2=80=94=20read=20an=20en?= =?UTF-8?q?tire=20named=20table=20into=20a=20DataFrame?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Run: https://github.com/githubnext/tsb/actions/runs/31245670724 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- benchmarks/pandas/bench_read_sql_table.py | 50 ++++++++++++++++++++ benchmarks/tsb/bench_read_sql_table.ts | 57 +++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 benchmarks/pandas/bench_read_sql_table.py create mode 100644 benchmarks/tsb/bench_read_sql_table.ts diff --git a/benchmarks/pandas/bench_read_sql_table.py b/benchmarks/pandas/bench_read_sql_table.py new file mode 100644 index 00000000..d0c5ad2a --- /dev/null +++ b/benchmarks/pandas/bench_read_sql_table.py @@ -0,0 +1,50 @@ +"""Benchmark: read_sql_table equivalent — read an entire named table into a DataFrame. + +Uses an in-memory SQLite database to mirror the tsb readSqlTable benchmark. +`pandas.read_sql_query("SELECT * FROM sensors", con)` is used as the functional +equivalent — both read all rows from a named table via a SQL connection. + +Dataset: 10k rows, 3 columns (id int, score float, category str). +Outputs JSON: {"function": "read_sql_table", "mean_ms": ..., "iterations": ..., "total_ms": ...} +""" +import json +import math +import sqlite3 +import time + +import pandas as pd + +ROWS = 10_000 +WARMUP = 5 +ITERATIONS = 30 + +# ── Build matching dataset ──────────────────────────────────────────────────── +data = { + "id": list(range(ROWS)), + "score": [math.sin(i * 0.01) * 100 for i in range(ROWS)], + "category": [f"cat_{i % 50}" for i in range(ROWS)], +} +df_src = pd.DataFrame(data) + +# ── SQLite in-memory database ───────────────────────────────────────────────── +con = sqlite3.connect(":memory:") +df_src.to_sql("sensors", con, index=False, if_exists="replace") + +# ── Warm-up ─────────────────────────────────────────────────────────────────── +for _ in range(WARMUP): + pd.read_sql_query("SELECT * FROM sensors", con) + +# ── Benchmark ──────────────────────────────────────────────────────────────── +start = time.perf_counter() +for _ in range(ITERATIONS): + pd.read_sql_query("SELECT * FROM sensors", con) +total_ms = (time.perf_counter() - start) * 1000 + +con.close() + +print(json.dumps({ + "function": "read_sql_table", + "mean_ms": round(total_ms / ITERATIONS, 4), + "iterations": ITERATIONS, + "total_ms": round(total_ms, 4), +})) diff --git a/benchmarks/tsb/bench_read_sql_table.ts b/benchmarks/tsb/bench_read_sql_table.ts new file mode 100644 index 00000000..351cd92c --- /dev/null +++ b/benchmarks/tsb/bench_read_sql_table.ts @@ -0,0 +1,57 @@ +/** + * Benchmark: readSqlTable — read an entire named table into a DataFrame. + * + * Uses a mock SqlConnection adapter that returns a 10k-row, 3-column result set. + * Covers the `readSqlTable` path (table-name validation via `listTables()` + + * `SELECT * FROM ` query dispatch). + * + * Outputs JSON: {"function": "read_sql_table", "mean_ms": ..., "iterations": ..., "total_ms": ...} + */ +import { readSqlTable } from "../../src/index.js"; +import type { SqlConnection, SqlResult, SqlRow, SqlValue } from "../../src/index.js"; + +const ROWS = 10_000; +const WARMUP = 5; +const ITERATIONS = 30; + +// ── Shared result set ───────────────────────────────────────────────────────── +const columns: string[] = ["id", "score", "category"]; +const rows: SqlRow[] = Array.from({ length: ROWS }, (_, i) => ({ + id: i, + score: Math.sin(i * 0.01) * 100, + category: `cat_${i % 50}`, +})); + +// ── Mock adapter ────────────────────────────────────────────────────────────── +class TableAdapter implements SqlConnection { + query(_sql: string, _params?: readonly SqlValue[]): SqlResult { + return { columns, rows }; + } + listTables(): readonly string[] { + return ["sensors", "events", "logs"]; + } +} + +const conn = new TableAdapter(); + +// ── Warm-up ─────────────────────────────────────────────────────────────────── +for (let i = 0; i < WARMUP; i++) { + readSqlTable("sensors", conn); +} + +// ── Benchmark ──────────────────────────────────────────────────────────────── +const start = performance.now(); +for (let i = 0; i < ITERATIONS; i++) { + readSqlTable("sensors", conn); +} +const total_ms = performance.now() - start; +const mean_ms = total_ms / ITERATIONS; + +console.log( + JSON.stringify({ + function: "read_sql_table", + mean_ms: parseFloat(mean_ms.toFixed(4)), + iterations: ITERATIONS, + total_ms: parseFloat(total_ms.toFixed(4)), + }), +);