Skip to content

Commit e9271d9

Browse files
Iteration 201: add combineFirstSeries and isNamedAggSpec benchmarks
Run: https://github.com/githubnext/tsessebe/actions/runs/24612917385 Add 2 new benchmark pairs (benchmarked_functions: 534 → 536): - bench_combine_first_series_fn: standalone combineFirstSeries fn (Series/fill-missing) - bench_is_named_agg_spec: isNamedAggSpec type-guard for NamedAgg spec dicts
1 parent 2073b71 commit e9271d9

4 files changed

Lines changed: 181 additions & 0 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""
2+
Benchmark: Series.combine_first() — fill NaN values from another Series (union of indexes).
3+
Mirrors tsb bench_combine_first_series_fn.ts (standalone combineFirstSeries fn).
4+
Outputs JSON: {"function": "combine_first_series_fn", "mean_ms": ..., "iterations": ..., "total_ms": ...}
5+
"""
6+
import json
7+
import time
8+
import numpy as np
9+
import pandas as pd
10+
11+
SIZE = 100_000
12+
WARMUP = 5
13+
ITERATIONS = 30
14+
15+
rng = np.random.default_rng(42)
16+
raw = rng.uniform(0, 10, SIZE)
17+
mask = rng.integers(0, 4, SIZE) == 0 # ~25% nulls
18+
d1 = pd.array(raw, dtype="Float64")
19+
for idx in range(SIZE):
20+
if mask[idx]:
21+
d1[idx] = pd.NA
22+
23+
s1 = pd.Series(d1, dtype="Float64")
24+
s2 = pd.Series(rng.uniform(0, 10, SIZE))
25+
26+
for _ in range(WARMUP):
27+
s1.combine_first(s2)
28+
29+
times = []
30+
for _ in range(ITERATIONS):
31+
t0 = time.perf_counter()
32+
s1.combine_first(s2)
33+
times.append((time.perf_counter() - t0) * 1000)
34+
35+
total_ms = sum(times)
36+
print(json.dumps({
37+
"function": "combine_first_series_fn",
38+
"mean_ms": round(total_ms / ITERATIONS, 3),
39+
"iterations": ITERATIONS,
40+
"total_ms": round(total_ms, 3),
41+
}))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"""
2+
Benchmark: is_named_agg_spec equivalent — check whether all values in a dict
3+
are of a given type (mirrors tsb's isNamedAggSpec guard).
4+
In pandas the equivalent is isinstance-checking NamedAgg namedtuples.
5+
Outputs JSON: {"function": "is_named_agg_spec", "mean_ms": ..., "iterations": ..., "total_ms": ...}
6+
"""
7+
import json
8+
import time
9+
from collections import namedtuple
10+
11+
WARMUP = 5
12+
ITERATIONS = 100
13+
14+
NamedAgg = namedtuple("NamedAgg", ["column", "aggfunc"])
15+
16+
17+
def is_named_agg_spec(spec: dict) -> bool:
18+
"""Return True if every value is a NamedAgg instance."""
19+
return all(isinstance(v, NamedAgg) for v in spec.values())
20+
21+
22+
# A valid spec — all NamedAgg instances (200 entries).
23+
valid_spec = {f"col_{i}": NamedAgg(f"src_{i % 10}", "sum") for i in range(200)}
24+
25+
# An invalid spec — plain string values.
26+
invalid_spec = {f"col_{i}": "sum" for i in range(200)}
27+
28+
for _ in range(WARMUP):
29+
is_named_agg_spec(valid_spec)
30+
is_named_agg_spec(invalid_spec)
31+
32+
times = []
33+
for _ in range(ITERATIONS):
34+
t0 = time.perf_counter()
35+
for _ in range(500):
36+
is_named_agg_spec(valid_spec)
37+
is_named_agg_spec(invalid_spec)
38+
times.append((time.perf_counter() - t0) * 1000)
39+
40+
total_ms = sum(times)
41+
print(json.dumps({
42+
"function": "is_named_agg_spec",
43+
"mean_ms": round(total_ms / ITERATIONS, 3),
44+
"iterations": ITERATIONS,
45+
"total_ms": round(total_ms, 3),
46+
}))
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Benchmark: combineFirstSeries (standalone fn) — fill NaN values from another Series (union of indexes).
3+
* Uses the exported `combineFirstSeries` function rather than the `Series.combineFirst()` method.
4+
* Mirrors bench_combine_first.ts but exercises the standalone export.
5+
* Outputs JSON: {"function": "combine_first_series_fn", "mean_ms": ..., "iterations": ..., "total_ms": ...}
6+
*/
7+
import { Series, combineFirstSeries } from "../../src/index.ts";
8+
9+
const SIZE = 100_000;
10+
const WARMUP = 5;
11+
const ITERATIONS = 30;
12+
13+
const rng = (seed: number) => {
14+
let s = seed;
15+
return () => {
16+
s = (s * 1664525 + 1013904223) & 0xffffffff;
17+
return ((s >>> 0) / 0xffffffff) * 10;
18+
};
19+
};
20+
const rand = rng(42);
21+
22+
const d1: (number | null)[] = Array.from({ length: SIZE }, (_, i) => (i % 4 === 0 ? null : rand()));
23+
const d2 = Array.from({ length: SIZE }, () => rand());
24+
const s1 = new Series(d1);
25+
const s2 = new Series(d2);
26+
27+
for (let i = 0; i < WARMUP; i++) {
28+
combineFirstSeries(s1, s2);
29+
}
30+
31+
const times: number[] = [];
32+
for (let i = 0; i < ITERATIONS; i++) {
33+
const t0 = performance.now();
34+
combineFirstSeries(s1, s2);
35+
times.push(performance.now() - t0);
36+
}
37+
38+
const total = times.reduce((a, b) => a + b, 0);
39+
console.log(
40+
JSON.stringify({
41+
function: "combine_first_series_fn",
42+
mean_ms: Math.round((total / ITERATIONS) * 1000) / 1000,
43+
iterations: ITERATIONS,
44+
total_ms: Math.round(total * 1000) / 1000,
45+
}),
46+
);
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/**
2+
* Benchmark: isNamedAggSpec — type-guard that checks whether a spec object
3+
* consists entirely of NamedAgg instances. Used by DataFrameGroupBy.agg()
4+
* to distinguish NamedAggSpec from plain AggSpec dicts.
5+
* Outputs JSON: {"function": "is_named_agg_spec", "mean_ms": ..., "iterations": ..., "total_ms": ...}
6+
*/
7+
import { isNamedAggSpec, namedAgg } from "../../src/index.ts";
8+
9+
const WARMUP = 5;
10+
const ITERATIONS = 100;
11+
12+
// A large spec dict that IS a NamedAggSpec — all values are NamedAgg instances.
13+
const validSpec = Object.fromEntries(
14+
Array.from({ length: 200 }, (_, i) => [
15+
`col_${i}`,
16+
namedAgg(`src_${i % 10}`, "sum"),
17+
]),
18+
);
19+
20+
// A dict that is NOT a NamedAggSpec — plain string values.
21+
const invalidSpec: Record<string, string> = Object.fromEntries(
22+
Array.from({ length: 200 }, (_, i) => [`col_${i}`, "sum"]),
23+
);
24+
25+
for (let i = 0; i < WARMUP; i++) {
26+
isNamedAggSpec(validSpec);
27+
isNamedAggSpec(invalidSpec);
28+
}
29+
30+
const times: number[] = [];
31+
for (let i = 0; i < ITERATIONS; i++) {
32+
const t0 = performance.now();
33+
for (let j = 0; j < 500; j++) {
34+
isNamedAggSpec(validSpec);
35+
isNamedAggSpec(invalidSpec);
36+
}
37+
times.push(performance.now() - t0);
38+
}
39+
40+
const total = times.reduce((a, b) => a + b, 0);
41+
console.log(
42+
JSON.stringify({
43+
function: "is_named_agg_spec",
44+
mean_ms: Math.round((total / ITERATIONS) * 1000) / 1000,
45+
iterations: ITERATIONS,
46+
total_ms: Math.round(total * 1000) / 1000,
47+
}),
48+
);

0 commit comments

Comments
 (0)