Skip to content

Fix three bugs in area_of_applicability - #320

Open
ljwolf wants to merge 1 commit into
nanophyto:mainfrom
ljwolf:aoa-bugfixes
Open

ljwolf wants to merge 1 commit into
nanophyto:mainfrom
ljwolf:aoa-bugfixes

Conversation

@ljwolf

@ljwolf ljwolf commented May 23, 2026 •

Copy link
Copy Markdown
Collaborator

sorry! Reworking through this code an i caught two bugs in using numpy.percentile (caused by the difference between it and pandas) and one train/test confusion that didn’t affect validity

  1. Tukey threshold percentile scale (0.25, 0.75) -> (25, 75) numpy.percentile expects values in [0, 100], not [0, 1]. The old calls were essentially returning the minimum of di_train for both hinges, so IQR collapsed to ~0 and the Tukey cutoff was always the 75th-percentile-ish value of di_train -- then the next line (cutpoint = numpy.maximum(cutpoint, di_train.max())) clamped it up to the max anyway, hiding the bug.

  2. Float-threshold percentile scale: numpy.percentile(di_train, t) where t in (0, 1). Same scale issue. Documentation says the user passes a quantile in (0, 1); convert to percentile via threshold * 100.

  3. CV-split unpack order: sklearn CrossValidator.split() yields (train_idx, test_idx). The previous loop unpacked them as (test_ix, train_ix), so:

  • d_mins[test_ix] = ... was actually d_mins[train_ix_real], overwritten (k-1) times per index (one per fold the point appeared in as training data) instead of filled exactly once for each held-out point.
  • hold_to_seen_d was train->test distance, not the intended holdout->in-fold distance.

it’s important to note this is symmetric in Euclidean so the distance values weren't wrong per se, but the rows fed to d_mins[test_ix] = ...min(axis=1) corresponded to the wrong points. Restores the documented semantics: each held-out point's minimum distance to its training fold, recorded once per point.

Further we weren’t using the tukey cutoff, but it’s useful to correct just in case.

1. Tukey threshold percentile scale (0.25, 0.75) -> (25, 75)
   numpy.percentile expects values in [0, 100], not [0, 1]. The old
   calls were essentially returning the minimum of di_train for both
   hinges, so IQR collapsed to ~0 and the Tukey cutoff was always
   the 75th-percentile-ish value of di_train -- then the next line
   (cutpoint = numpy.maximum(cutpoint, di_train.max())) clamped it
   up to the max anyway, hiding the bug.

2. Float-threshold percentile scale: numpy.percentile(di_train, t)
   where t in (0, 1). Same scale issue. Documentation says the
   user passes a quantile in (0, 1); convert to percentile via
   threshold * 100.

3. CV-split unpack order: sklearn CrossValidator.split() yields
   (train_idx, test_idx). The previous loop unpacked them as
   (test_ix, train_ix), so:
     - d_mins[test_ix] = ... was actually d_mins[train_ix_real],
       overwritten (k-1) times per index (one per fold the point
       appeared in as training data) instead of filled exactly
       once for each held-out point.
     - hold_to_seen_d was train->test distance, not the intended
       holdout->in-fold distance. Symmetric in Euclidean so the
       distance values weren't wrong per se, but the rows fed to
       d_mins[test_ix] = ...min(axis=1) corresponded to the wrong
       points.
   Restores the documented semantics: each held-out point's
   minimum distance to its training fold, recorded once per point.
@nanophyto
nanophyto self-requested a review May 28, 2026 08:55

@nanophyto nanophyto left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch thanks for fixing it.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants