Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions benchmarks/pandas/bench_boolean_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
41 changes: 41 additions & 0 deletions benchmarks/pandas/bench_datetime_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
45 changes: 45 additions & 0 deletions benchmarks/pandas/bench_first_last_valid_index.py
Original file line number Diff line number Diff line change
@@ -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,
}))
48 changes: 48 additions & 0 deletions benchmarks/pandas/bench_gaussian_kde.py
Original file line number Diff line number Diff line change
@@ -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,
}))
41 changes: 41 additions & 0 deletions benchmarks/pandas/bench_string_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
58 changes: 58 additions & 0 deletions benchmarks/pandas/bench_timedelta_array.py
Original file line number Diff line number Diff line change
@@ -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,
}))
46 changes: 46 additions & 0 deletions benchmarks/tsb/bench_boolean_array.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
);
41 changes: 41 additions & 0 deletions benchmarks/tsb/bench_datetime_array.ts
Original file line number Diff line number Diff line change
@@ -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,
}),
);
Loading
Loading