Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
2e09d0e
[Autoloop: perf-comparison] Iteration 418: firstValidIndex/lastValidI…
github-actions[bot] Jul 23, 2026
3dc825d
ci: trigger checks
github-actions[bot] Jul 23, 2026
402ebd6
[Autoloop: perf-comparison] Iteration 419: BooleanArray benchmark
github-actions[bot] Jul 24, 2026
e55e590
ci: trigger checks
github-actions[bot] Jul 24, 2026
8d70dac
[Autoloop: perf-comparison] Iteration 420: StringArray benchmark
github-actions[bot] Jul 24, 2026
1b0a1f8
ci: trigger checks
github-actions[bot] Jul 24, 2026
7500ea9
[Autoloop: perf-comparison] Iteration 421: DatetimeArray benchmark
github-actions[bot] Jul 25, 2026
744a1a5
ci: trigger checks
github-actions[bot] Jul 25, 2026
744e7b2
[Autoloop: perf-comparison] Iteration 422: TimedeltaArray benchmark
github-actions[bot] Jul 25, 2026
b1bc5cc
ci: trigger checks
github-actions[bot] Jul 25, 2026
dfda6b1
[Autoloop: perf-comparison] Iteration 423: gaussianKDE benchmark
github-actions[bot] Jul 26, 2026
25e7105
ci: trigger checks
github-actions[bot] Jul 26, 2026
66c5b82
[Autoloop: perf-comparison] Iteration 424: add mode benchmark
github-actions[bot] Jul 26, 2026
f419b5d
ci: trigger checks
github-actions[bot] Jul 26, 2026
6fdb111
[Autoloop: perf-comparison] Iteration 425: add renyiEntropy/tsallisEn…
github-actions[bot] Jul 27, 2026
1a4b041
ci: trigger checks
github-actions[bot] Jul 27, 2026
320e47d
[Autoloop: perf-comparison] Iteration 426: add jointEntropy/condition…
github-actions[bot] Jul 27, 2026
503cb2a
ci: trigger checks
github-actions[bot] Jul 27, 2026
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,
}))
61 changes: 61 additions & 0 deletions benchmarks/pandas/bench_joint_cond_entropy.py
Original file line number Diff line number Diff line change
@@ -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,
}))
27 changes: 27 additions & 0 deletions benchmarks/pandas/bench_mode.py
Original file line number Diff line number Diff line change
@@ -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,
}))
69 changes: 69 additions & 0 deletions benchmarks/pandas/bench_renyi_tsallis_entropy.py
Original file line number Diff line number Diff line change
@@ -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,
}))
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,
}))
Loading
Loading