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
44 changes: 44 additions & 0 deletions benchmarks/pandas/bench_entropy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import numpy as np
import json
import time

N = 100
WARMUP = 5
ITERS = 50

p = np.arange(1, N + 1, dtype=float)
q = np.arange(N, 0, -1, dtype=float)

# Normalise
p_norm = p / p.sum()
q_norm = q / q.sum()


def entropy_fn(pk):
pk = pk / pk.sum()
return -np.sum(pk * np.log(pk + 1e-300))


def kl_divergence(pk, qk):
pk = pk / pk.sum()
qk = qk / qk.sum()
mask = pk > 0
return np.sum(pk[mask] * np.log(pk[mask] / (qk[mask] + 1e-300)))


for _ in range(WARMUP):
entropy_fn(p)
kl_divergence(p, q)

t0 = time.perf_counter()
for _ in range(ITERS):
entropy_fn(p)
kl_divergence(p, q)
total_ms = (time.perf_counter() - t0) * 1000

print(json.dumps({
"function": "entropy_klDivergence",
"mean_ms": total_ms / ITERS,
"iterations": ITERS,
"total_ms": total_ms,
}))
45 changes: 45 additions & 0 deletions benchmarks/pandas/bench_gaussianKDE.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Benchmark: Gaussian KDE evaluate on 1000-point dataset at 200 grid points.
Uses Scott bandwidth (numpy equivalent of Silverman for univariate data).
Pure numpy implementation to match pages workflow constraints.
"""
import json
import time
import numpy as np

N = 1_000
GRID = 200
WARMUP = 3
ITERATIONS = 20

# Create dataset: mix of two gaussians (same as TS benchmark)
i_vals = np.arange(N)
data = np.sin(i_vals * 0.01) * 2 + np.where(i_vals % 2 == 0, 0.0, 5.0)

xmin, xmax = -5.0, 10.0
grid = np.linspace(xmin, xmax, GRID)


def gaussian_kde_evaluate(data: np.ndarray, points: np.ndarray) -> np.ndarray:
"""Gaussian KDE with Silverman bandwidth, pure numpy."""
n = len(data)
std = np.std(data, ddof=1)
bw = (4.0 / (3.0 * n)) ** 0.2 * std # Silverman rule
diff = points[:, np.newaxis] - data[np.newaxis, :] # (GRID, N)
kernels = np.exp(-0.5 * (diff / bw) ** 2) / (bw * np.sqrt(2 * np.pi))
return kernels.mean(axis=1)


for _ in range(WARMUP):
gaussian_kde_evaluate(data, grid)

start = time.perf_counter()
for _ in range(ITERATIONS):
gaussian_kde_evaluate(data, grid)
total = (time.perf_counter() - start) * 1000

print(json.dumps({
"function": "gaussianKDE",
"mean_ms": total / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total,
}))
103 changes: 103 additions & 0 deletions benchmarks/pandas/bench_hypothesis_tests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
"""Benchmark: hypothesis_tests — scipy-style hypothesis tests vs pandas/scipy equivalents."""
import json
import math
import time

WARMUP = 3
ITERS = 20
N = 1000


def make_data(n: int, seed: int) -> list:
arr = []
x = seed
for _ in range(n):
x = (x * 1664525 + 1013904223) & 0xFFFFFFFF
arr.append((x & 0xFFFFFFFF) / 0x100000000)
return [v * 4 + 2 for v in arr]


a = make_data(N, 42)
b = make_data(N, 99)

# Pure-numpy/stdlib implementations matching what tsb does
try:
import numpy as np

def ttest1samp_np(x, popmean):
x = np.asarray(x)
n = len(x)
mean = x.mean()
se = x.std(ddof=1) / math.sqrt(n)
t = (mean - popmean) / se
return t

def ttestind_np(x, y):
x, y = np.asarray(x), np.asarray(y)
nx, ny = len(x), len(y)
vx, vy = x.var(ddof=1), y.var(ddof=1)
se = math.sqrt(vx / nx + vy / ny)
t = (x.mean() - y.mean()) / se
return t

def ttestrel_np(x, y):
d = np.asarray(x) - np.asarray(y)
n = len(d)
t = d.mean() / (d.std(ddof=1) / math.sqrt(n))
return t

def foneway_np(*groups):
grand = np.concatenate(groups)
grand_mean = grand.mean()
ss_between = sum(len(g) * (np.mean(g) - grand_mean) ** 2 for g in groups)
ss_within = sum(((np.asarray(g) - np.mean(g)) ** 2).sum() for g in groups)
df_between = len(groups) - 1
df_within = len(grand) - len(groups)
F = (ss_between / df_between) / (ss_within / df_within)
return F

def pearsonr_np(x, y):
x, y = np.asarray(x), np.asarray(y)
r = np.corrcoef(x, y)[0, 1]
return r

def spearmanr_np(x, y):
x, y = np.asarray(x), np.asarray(y)
rx = np.argsort(np.argsort(x)).astype(float)
ry = np.argsort(np.argsort(y)).astype(float)
r = np.corrcoef(rx, ry)[0, 1]
return r

def mannwhitneyu_np(x, y):
nx, ny = len(x), len(y)
all_vals = sorted([(v, 0) for v in x] + [(v, 1) for v in y], key=lambda t: t[0])
ranks = list(range(1, nx + ny + 1))
u1 = sum(ranks[i] for i, (_, g) in enumerate(all_vals) if g == 0)
u1 -= nx * (nx + 1) / 2
return u1

HAS_NP = True
except ImportError:
HAS_NP = False


def bench():
total = 0.0
for i in range(WARMUP + ITERS):
t0 = time.perf_counter()
if HAS_NP:
ttest1samp_np(a, 2.5)
ttestind_np(a, b)
ttestrel_np(a, b)
foneway_np(a, b)
pearsonr_np(a, b)
spearmanr_np(a, b)
mannwhitneyu_np(a, b)
elapsed = (time.perf_counter() - t0) * 1000
if i >= WARMUP:
total += elapsed
mean_ms = total / ITERS
print(json.dumps({"function": "hypothesis_tests", "mean_ms": mean_ms, "iterations": ITERS, "total_ms": total}))


bench()
43 changes: 43 additions & 0 deletions benchmarks/pandas/bench_linregress_polyfit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""Benchmark: linregress and polyfit — simple linear regression and polynomial fit.
Dataset: 10,000 points, 20 iterations.
"""
import json
import time
import numpy as np

N = 10_000
WARMUP = 3
ITERATIONS = 20

x = np.arange(N, dtype=float) / N
y = 2.5 * x + 1.0 + np.sin(np.arange(N) * 0.01) * 0.1


def linregress_numpy(xs, ys):
"""Simple OLS linear regression matching scipy.stats.linregress."""
n = len(xs)
sx = xs.sum()
sy = ys.sum()
sxx = (xs * xs).sum()
sxy = (xs * ys).sum()
slope = (n * sxy - sx * sy) / (n * sxx - sx * sx)
intercept = (sy - slope * sx) / n
return slope, intercept


for _ in range(WARMUP):
linregress_numpy(x, y)
np.polyfit(x, y, 2)

start = time.perf_counter()
for _ in range(ITERATIONS):
linregress_numpy(x, y)
np.polyfit(x, y, 2)
total = (time.perf_counter() - start) * 1000

print(json.dumps({
"function": "linregress_polyfit",
"mean_ms": total / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total,
}))
37 changes: 37 additions & 0 deletions benchmarks/pandas/bench_lreshape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Benchmark: lreshape — wide-to-long reshape using named column groups.
Dataset: 10,000 rows with 4 value columns (v1..v4), 50 iterations.
"""
import json
import time
import numpy as np
import pandas as pd

N = 10_000
WARMUP = 3
ITERATIONS = 50

ids = np.arange(N)
data = {
"id": ids,
"v1": ids * 1.0,
"v2": ids * 2.0,
"v3": ids * 3.0,
"v4": ids * 4.0,
}
df = pd.DataFrame(data)
groups = {"value": ["v1", "v2", "v3", "v4"]}

for _ in range(WARMUP):
pd.lreshape(df, groups)

start = time.perf_counter()
for _ in range(ITERATIONS):
pd.lreshape(df, groups)
total = (time.perf_counter() - start) * 1000

print(json.dumps({
"function": "lreshape",
"mean_ms": total / ITERATIONS,
"iterations": ITERATIONS,
"total_ms": total,
}))
68 changes: 68 additions & 0 deletions benchmarks/pandas/bench_mutual_information.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import numpy as np
import json
import time

N = 1000
WARMUP = 5
ITERS = 50
CATS = 10

# Same paired observations as the TS benchmark
xs = np.array([i % CATS for i in range(N)])
ys = np.array([(i % CATS) + (i // CATS) % 3 for i in range(N)])


def mutual_information(xs, ys):
"""Compute mutual information I(X;Y) from paired observations."""
n = len(xs)
ux, cx = np.unique(xs, return_counts=True)
uy, cy = np.unique(ys, return_counts=True)
px = cx / n
py = cy / n

# Joint counts
joint_counts = {}
for x, y in zip(xs, ys):
key = (int(x), int(y))
joint_counts[key] = joint_counts.get(key, 0) + 1

mi = 0.0
for (xi, yi), cnt in joint_counts.items():
pxy = cnt / n
pxi = px[np.searchsorted(ux, xi)]
pyi = py[np.searchsorted(uy, yi)]
if pxy > 0:
mi += pxy * np.log(pxy / (pxi * pyi + 1e-300))
return mi


def normalized_mi(xs, ys):
"""Normalized mutual information (arithmetic normalization)."""
mi = mutual_information(xs, ys)
n = len(xs)
_, cx = np.unique(xs, return_counts=True)
_, cy = np.unique(ys, return_counts=True)
px = cx / n
py = cy / n
hx = -np.sum(px * np.log(px + 1e-300))
hy = -np.sum(py * np.log(py + 1e-300))
denom = (hx + hy) / 2
return mi / denom if denom > 0 else 0.0


for _ in range(WARMUP):
mutual_information(xs, ys)
normalized_mi(xs, ys)

t0 = time.perf_counter()
for _ in range(ITERS):
mutual_information(xs, ys)
normalized_mi(xs, ys)
total_ms = (time.perf_counter() - t0) * 1000

print(json.dumps({
"function": "mutual_information",
"mean_ms": total_ms / ITERS,
"iterations": ITERS,
"total_ms": total_ms,
}))
31 changes: 31 additions & 0 deletions benchmarks/tsb/bench_entropy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { entropy, klDivergence } from "../../src/index.js";

const N = 100;
const WARMUP = 5;
const ITERS = 50;

// Build two probability distributions of length N
const p: number[] = Array.from({ length: N }, (_, i) => i + 1);
const q: number[] = Array.from({ length: N }, (_, i) => N - i);

let t0 = performance.now();
for (let i = 0; i < WARMUP; i++) {
entropy(p);
klDivergence(p, q);
}
t0 = performance.now();

for (let i = 0; i < ITERS; i++) {
entropy(p);
klDivergence(p, q);
}
const total_ms = performance.now() - t0;

console.log(
JSON.stringify({
function: "entropy_klDivergence",
mean_ms: total_ms / ITERS,
iterations: ITERS,
total_ms,
}),
);
Loading
Loading