Skip to content

Commit 5dd1667

Browse files
Iteration 175: 5 new benchmark pairs (539 total, +5 vs best 534)
Add: str_contains, fillna_col_map, groupby_agg_no_index, crosstab_normalize, pct_change_periods Run: https://github.com/githubnext/tsessebe/actions/runs/24597203672 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 1cab883 commit 5dd1667

10 files changed

Lines changed: 395 additions & 0 deletions
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""Benchmark: pd.crosstab() with normalize options — proportions by row/col/all."""
2+
import json, time
3+
import pandas as pd
4+
import numpy as np
5+
6+
SIZE = 50_000
7+
WARMUP = 5
8+
ITERATIONS = 30
9+
10+
rng = np.random.default_rng(99)
11+
choices_a = ["north", "south", "east", "west"]
12+
choices_b = ["red", "green", "blue"]
13+
14+
a = pd.Series(np.array(choices_a)[rng.integers(0, 4, SIZE)])
15+
b = pd.Series(np.array(choices_b)[rng.integers(0, 3, SIZE)])
16+
17+
for _ in range(WARMUP):
18+
pd.crosstab(a, b, normalize=True)
19+
pd.crosstab(a, b, normalize="index")
20+
pd.crosstab(a, b, normalize="columns")
21+
22+
times = []
23+
for _ in range(ITERATIONS):
24+
t0 = time.perf_counter()
25+
pd.crosstab(a, b, normalize=True)
26+
pd.crosstab(a, b, normalize="index")
27+
pd.crosstab(a, b, normalize="columns")
28+
times.append((time.perf_counter() - t0) * 1000)
29+
30+
total_ms = sum(times)
31+
print(json.dumps({
32+
"function": "crosstab_normalize",
33+
"mean_ms": round(total_ms / ITERATIONS, 3),
34+
"iterations": ITERATIONS,
35+
"total_ms": round(total_ms, 3),
36+
}))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""Benchmark: DataFrame.fillna() with per-column fill dict."""
2+
import json, time, random
3+
import pandas as pd
4+
import numpy as np
5+
6+
ROWS = 50_000
7+
WARMUP = 5
8+
ITERATIONS = 30
9+
10+
random.seed(42)
11+
rng = np.random.default_rng(42)
12+
13+
col_a = np.where(rng.random(ROWS) < 0.2, np.nan, rng.random(ROWS) * 100)
14+
col_b = np.where(rng.random(ROWS) < 0.2, np.nan, rng.random(ROWS) * 50)
15+
col_c = np.where(rng.random(ROWS) < 0.2, np.nan, rng.random(ROWS) * 200)
16+
17+
df = pd.DataFrame({"a": col_a, "b": col_b, "c": col_c})
18+
fill_map = {"a": 0, "b": -1, "c": 99}
19+
20+
for _ in range(WARMUP):
21+
df.fillna(fill_map)
22+
23+
times = []
24+
for _ in range(ITERATIONS):
25+
t0 = time.perf_counter()
26+
df.fillna(fill_map)
27+
times.append((time.perf_counter() - t0) * 1000)
28+
29+
total_ms = sum(times)
30+
print(json.dumps({
31+
"function": "fillna_col_map",
32+
"mean_ms": round(total_ms / ITERATIONS, 3),
33+
"iterations": ITERATIONS,
34+
"total_ms": round(total_ms, 3),
35+
}))
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""Benchmark: DataFrameGroupBy.agg() with as_index=False — group key as column."""
2+
import json, time
3+
import pandas as pd
4+
import numpy as np
5+
6+
SIZE = 100_000
7+
WARMUP = 3
8+
ITERATIONS = 20
9+
10+
rng = np.random.default_rng(42)
11+
groups = np.array(["alpha", "beta", "gamma", "delta", "epsilon"])
12+
df = pd.DataFrame({
13+
"group": groups[rng.integers(0, 5, SIZE)],
14+
"x": rng.random(SIZE) * 100,
15+
"y": rng.random(SIZE) * 50,
16+
})
17+
18+
for _ in range(WARMUP):
19+
df.groupby("group", as_index=False).agg({"x": "mean", "y": "sum"})
20+
21+
times = []
22+
for _ in range(ITERATIONS):
23+
t0 = time.perf_counter()
24+
df.groupby("group", as_index=False).agg({"x": "mean", "y": "sum"})
25+
times.append((time.perf_counter() - t0) * 1000)
26+
27+
total_ms = sum(times)
28+
print(json.dumps({
29+
"function": "groupby_agg_no_index",
30+
"mean_ms": round(total_ms / ITERATIONS, 3),
31+
"iterations": ITERATIONS,
32+
"total_ms": round(total_ms, 3),
33+
}))
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"""Benchmark: Series.pct_change() / DataFrame.pct_change() with various periods."""
2+
import json, time
3+
import pandas as pd
4+
import numpy as np
5+
6+
ROWS = 100_000
7+
WARMUP = 5
8+
ITERATIONS = 30
9+
10+
rng = np.random.default_rng(7)
11+
data = rng.random(ROWS) * 100 + 10
12+
13+
series = pd.Series(data)
14+
df = pd.DataFrame({
15+
"a": data,
16+
"b": data * 1.5,
17+
"c": data * 0.8,
18+
})
19+
20+
for _ in range(WARMUP):
21+
series.pct_change(periods=1)
22+
series.pct_change(periods=7)
23+
df.pct_change(periods=5)
24+
25+
times = []
26+
for _ in range(ITERATIONS):
27+
t0 = time.perf_counter()
28+
series.pct_change(periods=1)
29+
series.pct_change(periods=7)
30+
df.pct_change(periods=5)
31+
times.append((time.perf_counter() - t0) * 1000)
32+
33+
total_ms = sum(times)
34+
print(json.dumps({
35+
"function": "pct_change_periods",
36+
"mean_ms": round(total_ms / ITERATIONS, 3),
37+
"iterations": ITERATIONS,
38+
"total_ms": round(total_ms, 3),
39+
}))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""Benchmark: pd.Series.str.contains() — regex and literal substring matching on 100k strings."""
2+
import json, time
3+
import pandas as pd
4+
5+
ROWS = 100_000
6+
WARMUP = 5
7+
ITERATIONS = 30
8+
9+
data = [f"item_{i % 500}_value_{i % 7}_end" for i in range(ROWS)]
10+
s = pd.Series(data)
11+
12+
for _ in range(WARMUP):
13+
s.str.contains("value", regex=False)
14+
s.str.contains(r"_[0-9]+_", regex=True)
15+
16+
times = []
17+
for _ in range(ITERATIONS):
18+
t0 = time.perf_counter()
19+
s.str.contains("value", regex=False)
20+
s.str.contains(r"_[0-9]+_", regex=True)
21+
times.append((time.perf_counter() - t0) * 1000)
22+
23+
total_ms = sum(times)
24+
print(json.dumps({
25+
"function": "str_contains",
26+
"mean_ms": round(total_ms / ITERATIONS, 3),
27+
"iterations": ITERATIONS,
28+
"total_ms": round(total_ms, 3),
29+
}))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Benchmark: crosstab() with normalize options — proportions by row/col/all.
3+
* Outputs JSON: {"function": "crosstab_normalize", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { Series, crosstab } from "../../src/index.ts";
6+
7+
const SIZE = 50_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 30;
10+
11+
let seed = 99;
12+
const rand = () => {
13+
seed = (seed * 1664525 + 1013904223) & 0x7fffffff;
14+
return seed;
15+
};
16+
17+
const choices_a = ["north", "south", "east", "west"];
18+
const choices_b = ["red", "green", "blue"];
19+
20+
const a = new Series({ data: Array.from({ length: SIZE }, () => choices_a[rand() % 4]) });
21+
const b = new Series({ data: Array.from({ length: SIZE }, () => choices_b[rand() % 3]) });
22+
23+
for (let i = 0; i < WARMUP; i++) {
24+
crosstab(a, b, { normalize: true });
25+
crosstab(a, b, { normalize: "index" });
26+
crosstab(a, b, { normalize: "columns" });
27+
}
28+
29+
const times: number[] = [];
30+
for (let i = 0; i < ITERATIONS; i++) {
31+
const t0 = performance.now();
32+
crosstab(a, b, { normalize: true });
33+
crosstab(a, b, { normalize: "index" });
34+
crosstab(a, b, { normalize: "columns" });
35+
times.push(performance.now() - t0);
36+
}
37+
38+
const total_ms = times.reduce((a, b) => a + b, 0);
39+
console.log(
40+
JSON.stringify({
41+
function: "crosstab_normalize",
42+
mean_ms: Math.round((total_ms / ITERATIONS) * 1000) / 1000,
43+
iterations: ITERATIONS,
44+
total_ms: Math.round(total_ms * 1000) / 1000,
45+
}),
46+
);
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Benchmark: fillnaDataFrame with ColumnFillMap — per-column fill values.
3+
* Outputs JSON: {"function": "fillna_col_map", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { DataFrame, Series, fillnaDataFrame } from "../../src/index.ts";
6+
7+
const ROWS = 50_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 30;
10+
11+
function seededRand(seed: number) {
12+
let s = seed;
13+
return () => {
14+
s = (s * 1664525 + 1013904223) & 0x7fffffff;
15+
return s / 0x7fffffff;
16+
};
17+
}
18+
19+
const rand = seededRand(42);
20+
21+
// Build a DataFrame with ~20% NaN in each column
22+
const colA = Array.from({ length: ROWS }, () => (rand() < 0.2 ? null : rand() * 100));
23+
const colB = Array.from({ length: ROWS }, () => (rand() < 0.2 ? null : rand() * 50));
24+
const colC = Array.from({ length: ROWS }, () => (rand() < 0.2 ? null : rand() * 200));
25+
26+
const df = new DataFrame({ a: colA, b: colB, c: colC });
27+
28+
// Per-column fill values
29+
const fillMap: Record<string, number> = { a: 0, b: -1, c: 99 };
30+
31+
for (let i = 0; i < WARMUP; i++) {
32+
fillnaDataFrame(df, { value: fillMap });
33+
}
34+
35+
const times: number[] = [];
36+
for (let i = 0; i < ITERATIONS; i++) {
37+
const t0 = performance.now();
38+
fillnaDataFrame(df, { value: fillMap });
39+
times.push(performance.now() - t0);
40+
}
41+
42+
const total_ms = times.reduce((a, b) => a + b, 0);
43+
console.log(
44+
JSON.stringify({
45+
function: "fillna_col_map",
46+
mean_ms: Math.round((total_ms / ITERATIONS) * 1000) / 1000,
47+
iterations: ITERATIONS,
48+
total_ms: Math.round(total_ms * 1000) / 1000,
49+
}),
50+
);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Benchmark: DataFrameGroupBy.agg() with asIndex=false — group key as column.
3+
* Outputs JSON: {"function": "groupby_agg_no_index", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { DataFrame } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 3;
9+
const ITERATIONS = 20;
10+
11+
let s = 42;
12+
const rand = () => {
13+
s = (s * 1664525 + 1013904223) & 0x7fffffff;
14+
return s / 0x7fffffff;
15+
};
16+
17+
const groups = ["alpha", "beta", "gamma", "delta", "epsilon"];
18+
const df = new DataFrame({
19+
group: Array.from({ length: SIZE }, () => groups[Math.floor(rand() * 5)]),
20+
x: Array.from({ length: SIZE }, () => rand() * 100),
21+
y: Array.from({ length: SIZE }, () => rand() * 50),
22+
});
23+
24+
for (let i = 0; i < WARMUP; i++) {
25+
df.groupby("group").agg({ x: "mean", y: "sum" }, false);
26+
}
27+
28+
const times: number[] = [];
29+
for (let i = 0; i < ITERATIONS; i++) {
30+
const t0 = performance.now();
31+
df.groupby("group").agg({ x: "mean", y: "sum" }, false);
32+
times.push(performance.now() - t0);
33+
}
34+
35+
const total_ms = times.reduce((a, b) => a + b, 0);
36+
console.log(
37+
JSON.stringify({
38+
function: "groupby_agg_no_index",
39+
mean_ms: Math.round((total_ms / ITERATIONS) * 1000) / 1000,
40+
iterations: ITERATIONS,
41+
total_ms: Math.round(total_ms * 1000) / 1000,
42+
}),
43+
);
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* Benchmark: pctChangeSeries / pctChangeDataFrame with various period values.
3+
* Outputs JSON: {"function": "pct_change_periods", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { Series, DataFrame, pctChangeSeries, pctChangeDataFrame } from "../../src/index.ts";
6+
7+
const ROWS = 100_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 30;
10+
11+
let s = 7;
12+
const rand = () => {
13+
s = (s * 1664525 + 1013904223) & 0x7fffffff;
14+
return s / 0x7fffffff;
15+
};
16+
17+
const data = Array.from({ length: ROWS }, () => rand() * 100 + 10);
18+
const series = new Series({ data });
19+
20+
const df = new DataFrame({
21+
a: data,
22+
b: data.map((v) => v * 1.5),
23+
c: data.map((v) => v * 0.8),
24+
});
25+
26+
for (let i = 0; i < WARMUP; i++) {
27+
pctChangeSeries(series, { periods: 1 });
28+
pctChangeSeries(series, { periods: 7 });
29+
pctChangeDataFrame(df, { periods: 5 });
30+
}
31+
32+
const times: number[] = [];
33+
for (let i = 0; i < ITERATIONS; i++) {
34+
const t0 = performance.now();
35+
pctChangeSeries(series, { periods: 1 });
36+
pctChangeSeries(series, { periods: 7 });
37+
pctChangeDataFrame(df, { periods: 5 });
38+
times.push(performance.now() - t0);
39+
}
40+
41+
const total_ms = times.reduce((a, b) => a + b, 0);
42+
console.log(
43+
JSON.stringify({
44+
function: "pct_change_periods",
45+
mean_ms: Math.round((total_ms / ITERATIONS) * 1000) / 1000,
46+
iterations: ITERATIONS,
47+
total_ms: Math.round(total_ms * 1000) / 1000,
48+
}),
49+
);

0 commit comments

Comments
 (0)