oversampleqa.validator¶
oversampleqa.validator
¶
Oversampling validation utilities.
ValidationSplit
¶
Bases: NamedTuple
Training data and reference sets for one validation run.
Shared by :func:validate_oversampling, MemoryEfficientValidator and
TypedValidator so the three cannot drift apart on what the error rate
measures.
Source code in src/oversampleqa/validator.py
59 60 61 62 63 64 65 66 67 68 69 70 71 72 | |
infer_minority_label(y)
¶
Return the least frequent label in y.
Benchmark catalogs used to hardcode minority_label=1, which is right for
a make_classification dataset built with descending weights and wrong
for real data: load_breast_cancer has 212 malignant against 357 benign,
so its minority is class 0 and the catalog declared 1 -- the whole benchmark
was oversampling the majority.
Truncation makes a fixed answer impossible rather than merely wrong. The
first 200 rows of load_breast_cancer are 104 class-0 against 96 class-1,
so max_samples inverts which class is rarer. Deriving the label from the
data actually returned is the only answer correct at every size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
y
|
NDArray[integer]
|
Label array. |
required |
Returns:
| Type | Description |
|---|---|
int
|
The least frequent label. Ties resolve to the smaller label, which |
int
|
keeps the result deterministic; a perfectly balanced dataset has no |
int
|
minority and the caller should not be asking. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/oversampleqa/validator.py
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | |
prepare_validation_split(X, y, minority_label, majority_label, hidden_ratio, *, reference='hidden_minority', minority_hidden_ratio=None, min_hidden=5, random_state=42, stratify_by=None)
¶
Build the train/hidden split defining the validation estimand.
See :func:validate_oversampling for the meaning of reference,
random_state and stratify_by.
Raises¶
ValueError
If the held-out minority would contain fewer than min_hidden
points, or if stratify_by is not aligned with the majority class.
Source code in src/oversampleqa/validator.py
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 | |
score_nearest_distances(nearest_hidden, nearest_min)
¶
Count errors and ties from nearest-neighbour distances.
Returns (n_errors, n_ties). The comparison is strict: a point exactly
equidistant from both reference sets is not evidence of a majority-like
artefact, and counting ties as errors is a one-directional bias on
discrete or quantised features.
Source code in src/oversampleqa/validator.py
219 220 221 222 223 224 225 226 227 228 229 230 231 232 | |
warn_reference_bias(reference, stacklevel=3)
¶
Emit the train_minority bias warning.
Source code in src/oversampleqa/validator.py
235 236 237 238 239 240 241 242 243 244 245 246 247 | |
extract_synthetic_samples(X_original, X_resampled, y_resampled, minority_label)
¶
Return synthetic minority samples from a resampled dataset.
Parameters¶
X_original : ndarray
Original feature matrix used for fitting the oversampler.
X_resampled : ndarray
Feature matrix returned by oversampler.fit_resample.
y_resampled : ndarray
Corresponding labels for X_resampled.
minority_label : int
Label of the minority class that was oversampled.
Returns¶
ndarray Array containing only the synthetic minority samples.
Raises¶
ValueError If the oversampler did not preserve the original samples as a prefix of its output, so synthetic rows cannot be identified positionally.
Notes¶
Synthetic samples are identified positionally: everything after the
original rows is assumed to be new. That holds for the SMOTE family and
RandomOverSampler, which append. It does not hold for combined
over/under-samplers such as SMOTEENN and SMOTETomek, which delete
original rows. A length check alone does not catch this -- SMOTEENN
can still return more rows than it was given while having removed some of
the originals -- so the prefix is compared element-wise.
Source code in src/oversampleqa/validator.py
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 | |
require_prefix_preserved(X_original, X_resampled, caller)
¶
Raise unless the resampler left the original rows as an unchanged prefix.
Synthetic rows are identified positionally, so a resampler that deletes or
reorders originals makes that slice meaningless. A length check alone does
not catch it -- SMOTEENN can return more rows than it was given while
having removed some originals -- so the prefix is compared element-wise.
Shared by the binary and multiclass paths. The multiclass path had no such check and would return plausible-looking numbers from a misaligned slice.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
X_original
|
NDArray[floating]
|
Matrix passed to |
required |
X_resampled
|
NDArray[floating]
|
Matrix returned by it. |
required |
caller
|
str
|
Function name, for the message. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the originals are not an unchanged prefix. |
Source code in src/oversampleqa/validator.py
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 | |
validate_oversampling(X, y, minority_label, oversampler, hidden_ratio=0.1, metric='hassanat', metric_kwargs=None, return_details=False, *, reference='hidden_minority', minority_hidden_ratio=None, min_hidden=5, duplication_warn_threshold=0.5, random_state=42, stratify_by=None, n_repeats=1, reseed_oversampler=False)
¶
Validate oversampling using the hidden majority approach.
A fraction of the majority class is held out. The oversampler is fitted on what remains, and each synthetic minority point is scored by comparing its nearest-neighbour distance to the hidden majority against its nearest-neighbour distance to a minority reference set. A synthetic point strictly closer to the hidden majority counts as an error.
Parameters¶
X, y : ndarray
Input data and labels.
minority_label : int
Label of the minority class.
oversampler : BaseOverSampler
Instance of an imbalanced-learn oversampler.
hidden_ratio : float, default=0.1
Fraction of majority samples to hide during validation.
metric : str, default="hassanat"
Distance metric to use.
metric_kwargs : dict, optional
Additional keyword arguments passed to :func:distance_matrix.
return_details : bool, default=False
If True return a :class:~oversampleqa.types.ValidationDetails
instead of the bare error rate.
reference : {"hidden_minority", "train_minority"}, default="hidden_minority"
Which minority set the synthetic points are compared against.
``"hidden_minority"`` also holds out part of the minority class and
compares against those held-out points. Both sides of the comparison
are then unseen, which is the same estimand
:func:`validate_multiclass_oversampling` uses.
``"train_minority"`` compares against the full minority class -- the
very data the oversampler interpolated from. This is the historical
behaviour, retained so old numbers can be reproduced. It biases the
result toward "no error" by an amount that depends on minority
density rather than on oversampler quality, and emits a
``FutureWarning``.
minority_hidden_ratio : float, optional
Fraction of the minority class to hide when
reference="hidden_minority". Defaults to hidden_ratio.
min_hidden : int, default=5
Minimum number of held-out minority points required. A
nearest-neighbour comparison against fewer than a handful of points is
not meaningful, so this raises rather than warns.
duplication_warn_threshold : float, default=0.5
Emit a UserWarning when this fraction of synthetic points coincide
with a real point.
random_state : int, Generator, SeedSequence or None, default=42
Seeds the hold-out split. Which points get hidden is the single
largest driver of the error rate, so varying this varies the result.
The default of 42 reproduces previously documented numbers; None
draws fresh entropy and is not reproducible.
stratify_by : ndarray, optional
Group labels aligned with y. When given, the majority hold-out
takes hidden_ratio within each group, so a hold-out cannot miss a
cluster entirely. Strata are never inferred automatically.
n_repeats : int, default=1
Number of independent hold-out splits. Above 1, the returned details
carry the per-repeat vector and its dispersion. Repeat streams are
spawned from a SeedSequence rather than derived as seed + i,
which would correlate them.
reseed_oversampler : bool, default=False
Give the oversampler a fresh seed on each repeat. This changes what the
dispersion covers -- see Notes.
Returns¶
float or ValidationDetails
Error rate by default, nan if no synthetic samples were produced.
With n_repeats > 1 the bare return is the mean across repeats.
Raises¶
ValueError
If the labels are not binary, if minority_label is absent, if the
held-out minority would be smaller than min_hidden, or if the
oversampler does not preserve the original samples as a prefix.
Notes¶
The error rate is a relative quantity. Its scale depends on
hidden_ratio, on the density of the data, and on dimensionality, so
values are not comparable across datasets. See :doc:/concepts.
What the repeat interval covers. With n_repeats > 1 the reported
interval is a percentile bootstrap over the per-repeat error rates. It
describes the variability of the hold-out split, conditional on this
dataset and on the oversampler's own seed. It is not a confidence
interval for a population quantity, and with reseed_oversampler=False
it does not include the oversampler's own randomness at all. Setting
reseed_oversampler=True clones the sampler with a fresh seed per
repeat, so the dispersion then covers both sources together -- a different,
wider decomposition.
Synthetic points generated from shared parent points are not independent, so a binomial interval on the error rate would be too narrow. Nothing here claims more than the repeat-level bootstrap supports.
Source code in src/oversampleqa/validator.py
329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 | |
validate_multiclass_oversampling(X, y, oversampler, hidden_ratio=0.1, metric='hassanat', metric_kwargs=None, return_matrix=False, *, min_hidden=5, random_state=42)
¶
Validate oversampling for multi-class datasets.
For each class label, a portion of samples is hidden and the remainder
is used for training the oversampler along with all other visible
classes. The nearest hidden class to each synthetic sample determines
the error attribution. The function returns the per-class error rates
and optionally the full error matrix where matrix[i, j] counts how
many synthetic samples generated for class i are closest to hidden
samples from class j.
Parameters¶
X, y : ndarray
Input data and labels.
oversampler : BaseOverSampler
Instance of an imbalanced-learn oversampler supporting
multi-class data.
hidden_ratio : float, default=0.1
Fraction of each class to hide during validation.
metric : str, default="hassanat"
Distance metric to use.
metric_kwargs : dict, optional
Additional keyword arguments passed to :func:distance_matrix.
return_matrix : bool, default=False
If True also return the error matrix.
min_hidden : int, default=5
Minimum held-out points per class. Every class is a reference for
every other, so a class that cannot supply a usable hidden set makes
attribution unreliable for all of them.
random_state : int, Generator, SeedSequence or None, default=42
Seeds the per-class hold-out. Defaults to 42, which reproduces
previously documented numbers.
Returns¶
dict or tuple
Mapping of class_label -> error_rate. If return_matrix is
True the second element is the error matrix.
A class for which the sampler generated no synthetic points maps to
``nan``, not ``0.0``: nothing was measured, and ``0.0`` is the score of
a perfect result. Use :func:`macro_error_rate` to summarise, or
``np.nanmean`` over the values -- a plain mean propagates the ``nan``.
Raises¶
ValueError
If hidden_ratio is out of range, if any class would hide fewer than
min_hidden points, or if the oversampler did not preserve the
original rows as a prefix of its output.
Notes¶
A synthetic point equidistant from its own class and another is attributed
to its own class, matching :func:score_nearest_distances. Ties previously
went to whichever class appeared first in label order, which biased results
toward low-numbered classes for reasons unrelated to the data.
Source code in src/oversampleqa/validator.py
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 | |
macro_error_rate(error_rates)
¶
Average per-class error rates over the classes actually measured.
:func:validate_multiclass_oversampling reports nan for a class the
sampler generated nothing for. A plain mean propagates that to the summary,
turning "one class was not measured" into "no result at all"; counting the
nan as zero would be worse still, since it would read as a perfect score
for the class that was never evaluated.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
error_rates
|
Mapping[int, float]
|
Mapping of class label to error rate. |
required |
Returns:
| Type | Description |
|---|---|
float
|
Mean over the classes with a measurement, or |
Source code in src/oversampleqa/validator.py
875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 | |