Skip to content

Commit 6609544

Browse files
Iteration 167: Add 5 new benchmark pairs
Added benchmarks for: - nan_sum_mean_std (nansum/nanmean/nanstd vs np.nansum/np.nanmean/np.nanstd) - nan_var_min_max (nanvar/nanmin/nanmax vs np.nanvar/np.nanmin/np.nanmax) - sample_weights (sampleSeries/sampleDataFrame with weights option) - histogram_bin_edges (histogram with custom binEdges vs np.histogram with bins array) - pivot_table_aggfunc_variants (pivotTable with sum/count/min/max aggfuncs) Metric: 539 (previous best: 513, delta: +26) Run: https://github.com/githubnext/tsessebe/actions/runs/24588989692 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent da7e31e commit 6609544

10 files changed

Lines changed: 345 additions & 0 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Benchmark: np.histogram with custom bin edges on 100k-element array.
3+
Outputs JSON: {"function": "histogram_bin_edges", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import numpy as np
8+
9+
SIZE = 100_000
10+
WARMUP = 5
11+
ITERATIONS = 50
12+
13+
data = np.array([(i % 1000) * 0.1 for i in range(SIZE)])
14+
bin_edges = np.array([i * 5.0 for i in range(21)]) # 20 bins covering [0, 100)
15+
16+
for _ in range(WARMUP):
17+
np.histogram(data, bins=bin_edges)
18+
np.histogram(data, bins=20)
19+
20+
start = time.perf_counter()
21+
for _ in range(ITERATIONS):
22+
np.histogram(data, bins=bin_edges)
23+
np.histogram(data, bins=20)
24+
total = (time.perf_counter() - start) * 1000
25+
26+
print(json.dumps({"function": "histogram_bin_edges", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"""
2+
Benchmark: np.nansum / np.nanmean / np.nanstd — nan-ignoring aggregates on 100k-element arrays.
3+
Outputs JSON: {"function": "nan_sum_mean_std", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import math
7+
import time
8+
import numpy as np
9+
10+
SIZE = 100_000
11+
WARMUP = 5
12+
ITERATIONS = 50
13+
14+
# Array with ~10% NaN values
15+
data = np.array([float("nan") if i % 10 == 0 else math.sin(i * 0.01) * 100 + 50 for i in range(SIZE)])
16+
17+
for _ in range(WARMUP):
18+
np.nansum(data)
19+
np.nanmean(data)
20+
np.nanstd(data)
21+
22+
start = time.perf_counter()
23+
for _ in range(ITERATIONS):
24+
np.nansum(data)
25+
np.nanmean(data)
26+
np.nanstd(data)
27+
total = (time.perf_counter() - start) * 1000
28+
29+
print(json.dumps({"function": "nan_sum_mean_std", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""
2+
Benchmark: np.nanvar / np.nanmin / np.nanmax — nan-ignoring aggregates on 100k-element arrays.
3+
Outputs JSON: {"function": "nan_var_min_max", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import numpy as np
8+
9+
SIZE = 100_000
10+
WARMUP = 5
11+
ITERATIONS = 50
12+
13+
# Array with ~10% NaN values
14+
data = np.array([float("nan") if i % 10 == 0 else (i % 1000) * 0.1 - 50 for i in range(SIZE)])
15+
16+
for _ in range(WARMUP):
17+
np.nanvar(data)
18+
np.nanmin(data)
19+
np.nanmax(data)
20+
21+
start = time.perf_counter()
22+
for _ in range(ITERATIONS):
23+
np.nanvar(data)
24+
np.nanmin(data)
25+
np.nanmax(data)
26+
total = (time.perf_counter() - start) * 1000
27+
28+
print(json.dumps({"function": "nan_var_min_max", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
"""
2+
Benchmark: pd.pivot_table with multiple aggfuncs (sum, count, min, max) on 50k-row DataFrame.
3+
Outputs JSON: {"function": "pivot_table_aggfunc_variants", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import time
7+
import pandas as pd
8+
9+
ROWS = 50_000
10+
WARMUP = 3
11+
ITERATIONS = 20
12+
13+
regions = ["North", "South", "East", "West"]
14+
categories = ["A", "B", "C", "D", "E"]
15+
16+
df = pd.DataFrame({
17+
"region": [regions[i % len(regions)] for i in range(ROWS)],
18+
"category": [categories[i % len(categories)] for i in range(ROWS)],
19+
"sales": [(i % 1000) * 1.5 + 10 for i in range(ROWS)],
20+
})
21+
22+
for _ in range(WARMUP):
23+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="sum")
24+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="count")
25+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="min")
26+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="max")
27+
28+
start = time.perf_counter()
29+
for _ in range(ITERATIONS):
30+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="sum")
31+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="count")
32+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="min")
33+
pd.pivot_table(df, values="sales", index="region", columns="category", aggfunc="max")
34+
total = (time.perf_counter() - start) * 1000
35+
36+
print(json.dumps({"function": "pivot_table_aggfunc_variants", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""
2+
Benchmark: DataFrame.sample / Series.sample with weights option on 100k rows.
3+
Outputs JSON: {"function": "sample_weights", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
"""
5+
import json
6+
import math
7+
import time
8+
import pandas as pd
9+
import numpy as np
10+
11+
SIZE = 100_000
12+
WARMUP = 3
13+
ITERATIONS = 20
14+
15+
data = list(range(SIZE))
16+
weights = np.array([math.exp((i / SIZE) * 3) for i in range(SIZE)])
17+
weights_normalized = weights / weights.sum()
18+
19+
s = pd.Series(data)
20+
df = pd.DataFrame({"a": data, "b": [i * 2.0 for i in range(SIZE)], "c": [i * 3.0 for i in range(SIZE)]})
21+
22+
for _ in range(WARMUP):
23+
s.sample(n=1000, weights=weights_normalized)
24+
df.sample(n=1000, weights=weights_normalized)
25+
26+
start = time.perf_counter()
27+
for _ in range(ITERATIONS):
28+
s.sample(n=1000, weights=weights_normalized)
29+
df.sample(n=1000, weights=weights_normalized)
30+
total = (time.perf_counter() - start) * 1000
31+
32+
print(json.dumps({"function": "sample_weights", "mean_ms": total / ITERATIONS, "iterations": ITERATIONS, "total_ms": total}))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* Benchmark: histogram with custom binEdges option on 100k-element array.
3+
* Outputs JSON: {"function": "histogram_bin_edges", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { histogram } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 50;
10+
11+
const data = Array.from({ length: SIZE }, (_, i) => (i % 1000) * 0.1);
12+
13+
// Custom bin edges: 20 edges covering [0, 100) in equal-width steps of 5
14+
const binEdges: number[] = Array.from({ length: 21 }, (_, i) => i * 5);
15+
16+
for (let i = 0; i < WARMUP; i++) {
17+
histogram(data, { binEdges });
18+
histogram(data, { bins: 20 });
19+
}
20+
21+
const start = performance.now();
22+
for (let i = 0; i < ITERATIONS; i++) {
23+
histogram(data, { binEdges });
24+
histogram(data, { bins: 20 });
25+
}
26+
const total = performance.now() - start;
27+
28+
console.log(
29+
JSON.stringify({
30+
function: "histogram_bin_edges",
31+
mean_ms: total / ITERATIONS,
32+
iterations: ITERATIONS,
33+
total_ms: total,
34+
}),
35+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Benchmark: nansum / nanmean / nanstd — nan-ignoring aggregates on 100k-element arrays.
3+
* Outputs JSON: {"function": "nan_sum_mean_std", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { nansum, nanmean, nanstd } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 50;
10+
11+
// Array with ~10% null values
12+
const data: (number | null)[] = Array.from({ length: SIZE }, (_, i) =>
13+
i % 10 === 0 ? null : Math.sin(i * 0.01) * 100 + 50,
14+
);
15+
16+
for (let i = 0; i < WARMUP; i++) {
17+
nansum(data);
18+
nanmean(data);
19+
nanstd(data);
20+
}
21+
22+
const start = performance.now();
23+
for (let i = 0; i < ITERATIONS; i++) {
24+
nansum(data);
25+
nanmean(data);
26+
nanstd(data);
27+
}
28+
const total = performance.now() - start;
29+
30+
console.log(
31+
JSON.stringify({
32+
function: "nan_sum_mean_std",
33+
mean_ms: total / ITERATIONS,
34+
iterations: ITERATIONS,
35+
total_ms: total,
36+
}),
37+
);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* Benchmark: nanvar / nanmin / nanmax — nan-ignoring aggregates on 100k-element arrays.
3+
* Outputs JSON: {"function": "nan_var_min_max", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { nanvar, nanmin, nanmax } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 5;
9+
const ITERATIONS = 50;
10+
11+
// Array with ~10% null values
12+
const data: (number | null)[] = Array.from({ length: SIZE }, (_, i) =>
13+
i % 10 === 0 ? null : (i % 1000) * 0.1 - 50,
14+
);
15+
16+
for (let i = 0; i < WARMUP; i++) {
17+
nanvar(data);
18+
nanmin(data);
19+
nanmax(data);
20+
}
21+
22+
const start = performance.now();
23+
for (let i = 0; i < ITERATIONS; i++) {
24+
nanvar(data);
25+
nanmin(data);
26+
nanmax(data);
27+
}
28+
const total = performance.now() - start;
29+
30+
console.log(
31+
JSON.stringify({
32+
function: "nan_var_min_max",
33+
mean_ms: total / ITERATIONS,
34+
iterations: ITERATIONS,
35+
total_ms: total,
36+
}),
37+
);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* Benchmark: pivotTable with multiple aggfuncs (sum, count, min, max) on 50k-row DataFrame.
3+
* Outputs JSON: {"function": "pivot_table_aggfunc_variants", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { DataFrame, pivotTable } from "../../src/index.ts";
6+
7+
const ROWS = 50_000;
8+
const WARMUP = 3;
9+
const ITERATIONS = 20;
10+
11+
const regions = ["North", "South", "East", "West"];
12+
const categories = ["A", "B", "C", "D", "E"];
13+
14+
const region = Array.from({ length: ROWS }, (_, i) => regions[i % regions.length] as string);
15+
const category = Array.from({ length: ROWS }, (_, i) => categories[i % categories.length] as string);
16+
const sales = Array.from({ length: ROWS }, (_, i) => (i % 1000) * 1.5 + 10);
17+
18+
const df = DataFrame.fromColumns({ region, category, sales });
19+
20+
for (let i = 0; i < WARMUP; i++) {
21+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "sum" });
22+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "count" });
23+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "min" });
24+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "max" });
25+
}
26+
27+
const start = performance.now();
28+
for (let i = 0; i < ITERATIONS; i++) {
29+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "sum" });
30+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "count" });
31+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "min" });
32+
pivotTable(df, { values: "sales", index: "region", columns: "category", aggfunc: "max" });
33+
}
34+
const total = performance.now() - start;
35+
36+
console.log(
37+
JSON.stringify({
38+
function: "pivot_table_aggfunc_variants",
39+
mean_ms: total / ITERATIONS,
40+
iterations: ITERATIONS,
41+
total_ms: total,
42+
}),
43+
);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* Benchmark: sampleSeries / sampleDataFrame with weights option on 100k rows.
3+
* Outputs JSON: {"function": "sample_weights", "mean_ms": ..., "iterations": ..., "total_ms": ...}
4+
*/
5+
import { Series, DataFrame, sampleSeries, sampleDataFrame } from "../../src/index.ts";
6+
7+
const SIZE = 100_000;
8+
const WARMUP = 3;
9+
const ITERATIONS = 20;
10+
11+
const data = Array.from({ length: SIZE }, (_, i) => i * 1.0);
12+
// Exponentially increasing weights so later rows are more likely to be picked
13+
const weights = Array.from({ length: SIZE }, (_, i) => Math.exp((i / SIZE) * 3));
14+
15+
const s = new Series({ data });
16+
17+
const df = DataFrame.fromColumns({
18+
a: data,
19+
b: Array.from({ length: SIZE }, (_, i) => i * 2.0),
20+
c: Array.from({ length: SIZE }, (_, i) => i * 3.0),
21+
});
22+
23+
for (let i = 0; i < WARMUP; i++) {
24+
sampleSeries(s, { n: 1000, weights });
25+
sampleDataFrame(df, { n: 1000, weights });
26+
}
27+
28+
const start = performance.now();
29+
for (let i = 0; i < ITERATIONS; i++) {
30+
sampleSeries(s, { n: 1000, weights });
31+
sampleDataFrame(df, { n: 1000, weights });
32+
}
33+
const total = performance.now() - start;
34+
35+
console.log(
36+
JSON.stringify({
37+
function: "sample_weights",
38+
mean_ms: total / ITERATIONS,
39+
iterations: ITERATIONS,
40+
total_ms: total,
41+
}),
42+
);

0 commit comments

Comments
 (0)