Skip to content

Fix KNNImputer wrong donors for multi-NaN rows and f64 crash - #364

Merged
josevalim merged 5 commits into
elixir-nx:mainfrom
RicardoSantos-99:fix-knn-imputer-multi-nan-distance
Aug 27, 2026
Merged

Fix KNNImputer wrong donors for multi-NaN rows and f64 crash#364
josevalim merged 5 commits into
elixir-nx:mainfrom
RicardoSantos-99:fix-knn-imputer-multi-nan-distance

Conversation

@RicardoSantos-99

@RicardoSantos-99 RicardoSantos-99 commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

KNNImputer computes distance to a candidate donor by zeroing out only the single column currently being filled. Any other missing value in that same row is left as NaN, so it poisons the distance sum for every donor. Every candidate ends up at infinity, and picking "nearest" from a constant array just returns the first num_neighbors rows in the tensor. So any row with two or more missing values gets imputed from the first rows in the data instead of its real nearest neighbors, silently.

Fixed by excluding any column where either row has NaN, not just the one being filled, matching the present-features weighting sklearn.impute.KNNImputer's nan_euclidean_distances uses. Verified against it directly: on a case where the true nearest donors sit far from the ones the old code always picked, the old code returns [1.05, 2.05, 5.0], this returns [55.0, 85.0, 5.0], matching sklearn exactly.

Also fixed a separate bug found while testing: the distance accumulator and the NaN placeholder both hardcoded f32, so an f64 tensor crashed as soon as a value needed imputing. Both now take their type from the input.

nan_euclidean only zeroed nan_col (the single value currently being
filled) before comparing a row to a candidate donor. Any OTHER missing
value in that same row was left as NaN, so it poisoned the squared
euclidean sum with NaN for every donor, regardless of how close that
donor actually was. The result: every candidate distance came out as
infinity, and top_k on a constant array just returns the first
num_neighbors indices - so any row with two or more missing values was
imputed from the first rows in the tensor instead of its real nearest
neighbors.

Rewrote the usable-columns mask to exclude any column where either the
target row or the donor has NaN, not just nan_col, matching the
present-features weighting sklearn's nan_euclidean_distances uses. The
donor-missing-nan_col guard is unchanged and still correctly rules out
the row itself as its own neighbor, since a row's own nan_col value is
NaN by construction.

Verified against sklearn.impute.KNNImputter directly, matches exactly
on an adversarial case where the true nearest donors are far apart in
row index from the ones the old code always picked.
Updated the two existing tests: their expected values encoded the bug
(a row with 3 missing values was imputed from the first num_neighbors
rows in the tensor). Recomputed against sklearn.impute.KNNImputter on
the exact same matrix. Confirmed all 3 fail against the code they
replace and pass here.

Added a dedicated adversarial case where the true nearest donors sit
at high row indices and the ones the old code always picked sit at
low indices with wildly different values, so the two can't coincide
by accident.
row_distances and the NaN placeholder used to hardcode type: {:f, 32}
regardless of the input's actual type. An f64 tensor crashed inside
the while loop as soon as a value needed imputing, since the distance
returned by nan_euclidean carried the input's real type while the loop
accumulator was fixed at f32. Both now take their type from the input
tensor.
Confirmed this fails with a CompileError against the code it replaces
and passes here, with the result checked bit-exact against a plain
f64 average (not just close within tolerance).
@RicardoSantos-99 RicardoSantos-99 changed the title Fix knn imputer multi nan distance Fix KNNImputer wrong donors for multi-NaN rows and f64 crash Aug 27, 2026
@josevalim
josevalim merged commit e4590a0 into elixir-nx:main Aug 27, 2026
2 checks passed
@josevalim

Copy link
Copy Markdown
Contributor

💚 💙 💜 💛 ❤️

@RicardoSantos-99
RicardoSantos-99 deleted the fix-knn-imputer-multi-nan-distance branch August 27, 2026 10:58
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