Benchmarking¶
OversampleQA provides lightweight benchmarking utilities to compare oversamplers across datasets.
Quick comparison¶
from oversampleqa.benchmark import load_standard_datasets, run_benchmark
from imblearn.over_sampling import SMOTE, RandomOverSampler
datasets = load_standard_datasets()[:2]
oversamplers = [SMOTE(random_state=0), RandomOverSampler()]
df = run_benchmark(datasets, oversamplers, hidden_ratios=[0.1], n_runs=3)
print(df.head())
Advanced statistical benchmarking¶
from oversampleqa.advanced_benchmark import StatisticalBenchmark, DatasetRepository
from imblearn.over_sampling import SMOTE, RandomOverSampler
repo = DatasetRepository()
datasets = repo.create_synthetic_benchmark_suite(["easy", "hard"])
bench = StatisticalBenchmark(n_folds=3, n_repeats=2, random_state=0)
df = bench.run_comprehensive_benchmark(
datasets, [SMOTE(random_state=0), RandomOverSampler()], metrics=["hassanat"]
)
print(df[["dataset_name", "oversampler_name", "mean_error"]])
Reports¶
from oversampleqa.advanced_benchmark import create_benchmark_report
report_path = create_benchmark_report(df, output_path="benchmark_report.html")
print(report_path)
Reports include a run metadata block before the results table. It records the
package/runtime version context and the reproducibility identifiers present in
the result rows, such as dataset, oversampler, metric, hidden ratio, reference
mode, seeds, folds and repeats. Exported reports also write a
.metadata.json sidecar with the fuller machine-readable environment summary.
What the statistical benchmark's intervals cover¶
~oversampleqa.StatisticalBenchmark reports confidence intervals,
p-values and effect sizes. Be precise about what they describe.
Each observation in those statistics is one cross-validation fold.
_fold_records runs n_repeats × n_folds stratified splits and calls
~oversampleqa.validate_oversampling on each training fold. The
variance being summarised is therefore the variance of fold
composition — which rows land in each training fold — combined with
the oversampler's own randomness if it was constructed without a seed.
Since 0.3, the hold-out split inside each fold varies with the fold's seed. Before that it was pinned at 42 for every fold, so the reported spread excluded the single largest source of variance entirely.
ci_lower and ci_upper are a Student-t confidence interval for the
mean, at every sample size. It narrows as $\sqrt{n}$, and intervals
are comparable across configurations with different fold counts.
[!NOTE] Before 0.3 this switched formula at 30 observations: a t-interval for the mean below, and the 2.5th–97.5th percentiles of the observations at or above. Those are different quantities, written into the same two columns. On normally distributed values with σ = 0.05 the reported width jumped from 0.036 at n = 29 to 0.172 at n = 30 — a factor of 4.7 from one extra observation — and intervals could not be compared across configurations. Results produced before 0.3 carry that defect; results since do not.
Inspecting individual folds¶
The summary is one row per (dataset, oversampler, metric). That is enough to read a ranking and not enough to check one: it cannot be re-aggregated, plotted as a distribution, or given a different interval, and it does not say how many folds actually contributed.
~oversampleqa.StatisticalBenchmark.fold_results returns one row per
attempted fold:
bench = StatisticalBenchmark(n_folds=5, n_repeats=5)
summary = bench.run_comprehensive_benchmark(datasets, samplers)
folds = bench.fold_results()
with dataset_name, oversampler_name, metric, repeat, fold,
split_seed, hidden_ratio, error_rate, skipped and skip_reason.
Skipped folds are kept, with error_rate of nan and a stated
reason. This matters more than it sounds: a mean over three surviving
folds out of twenty-five is indistinguishable from a mean over
twenty-five once the skips are dropped. Count them before trusting an
interval:
contributing = (~folds["skipped"]).groupby(folds["oversampler_name"]).sum()
A combination whose folds all skip produces no summary row at all — only a warning. The fold frame still shows every attempt and why each one failed.
split_seed is the seed handed to the fold splitter for that repeat, so
a single repeat can be reproduced without rerunning the sweep.