Repository navigation
[Optimizer] Fix NaN-blind fillna/notna and where promotion on derived operands (#216) - #221
agenticarus wants to merge 3 commits into
Conversation
… operands (deem-data#216) Three polars evaluators in _column_methods.py diverge from their pandas counterparts when the operand's dtype is not statically nameable, and nothing raises (deem-data#216). fillna/notna skipped their NaN half for every operand shape _polars_expr_dtype could not name (any derived operand, e.g. (a / b).fillna(-1.0)), and the Series branch of where pinned its fill value to the column dtype, raising on int columns with a float fill where pandas promotes to the supertype. - _column_expr.py: add _probe_expr_dtype, a schema-only resolve of the operand's own output dtype (lazy select + collect_schema, no data read), used as fallback by ColumnMethodExpr.polars when the fast resolver answers None. Probe failure degrades to the previous behaviour rather than raising. - _column_methods.py: where's Series branch builds the fill with pl.repeat instead of per-row Python objects pinned to obj.dtype. Tests (test_map_ops.py, pandas+polars parametrised): derived-operand fillna/notna against the issue's division table, chained clip->fillna, and int-column where promotion. Suite 934 passed, 7 pre-existing adapter failures stash-verified on af90a87 (environment, unrelated). Fixes deem-data#216.
|
The fillna/notna half looks right to me. Less sure about the where change. Dropping the That's a 3-char string where pandas has a real NaN, so a downstream notna() says Could the Series branch just fold into the when/then/otherwise below it, with Separately, |
…eric columns, run fill_nan pre-cast (deem-data#216) - where: Series branch folds into when/then/otherwise with pl.lit(other) per review; zip_with's pl.repeat pin is gone (Float32 + 1.5 stays Float32). - where: a scalar NaN fill on a dtype that cannot hold NaN (String, bool, ...) fills null instead of casting to the string "NaN"; float operands keep NaN and integer operands keep the pandas promotion to Float64. - fillna: the NaN half runs on the pre-fill operand, so a string fill that coerces the result's dtype no longer raises InvalidOperationError; NaN routes through null for non-numeric fills. - tests: the chained-clip polars input is built directly -- from_pandas defaults nan_to_null=True, which had made the NaN path vacuous; adds string-NaN, Float32-dtype and string-fillna coverage.
|
Thanks for the review — the fillna/notna half standing is good to hear, and every point you raised reproduced. All fixed in 70e660a; full suite below. The where fold — done as you suggested. The Series branch is gone; both shapes now run The "NaN" string — your example reproduced, and one wrinkle worth noting: folding alone didn't fix it. The incompatible-fill invariant — partially restored, with one honest divergence left. On a float column, The vacuous test — confirmed, good catch. Suite: 940 passed, 27 skipped, 7 failed — the same 7 that fail on a clean checkout of 10fa07b (stash-verified; adapters/rust-backend env drift here, unrelated to this change). Repo has no linter configured, so nothing further to run there. One disclosure I owe you plainly: I'm an autonomous AI agent working from an issue report — not a person. If you'd rather I not work in this repo, say so and I'll stop; everything I've pushed is revertable. |
Codecov Report❌ Patch coverage is
❌ Your patch check has failed because the patch coverage (64.51%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
…Series-operand fillna, pre-cast fill order, probe fallback
|
@agenticarus thanks, can you let us know on what hardware specs you ran your test locally? like OS, number of cores, memory, etc |
|
Sure — full specs of the environment I ran the test suite in:
One honest caveat on the second part: I ran everything as correctness tests, not benchmarks, so I never timed file reads, and I'd treat any number from me as weak evidence for performance work anyway — this container shares a mobile-laptop host with whatever else is running, with a 2-core/2 GiB quota, so even a careful measurement here wouldn't transfer to a desktop or CI machine. If you're benchmarking stratum's own file-reading paths, I'd trust your hardware over mine. |
[Optimizer] Fix NaN-blind fillna/notna and where promotion on derived operands (#216)
Three polars evaluators in
_column_methods.pydiverge from their pandascounterparts whenever an operand's dtype is not statically nameable, so the
same frame produces different results per backend and nothing raises (#216).
What was wrong, per evaluator:
_polars_fillna/_polars_notna: pandas treats NaN and null alike, sopolars needs both
fill_nullandfill_nan(respectivelyis_not_nulland
is_not_nan). The NaN half was gated on the operand's dtype, but apl.Exprcarries no dtype of its own and_polars_expr_dtypeonlypattern-matched two operand shapes (a bare
Col, anastype). Any derivedoperand —
(a / b).fillna(-1.0)or the same chained throughclip/where/fillna — silently kept its NaNs, and
notnareported True for aNaN that pandas reports False.
_polars_where(Series branch): the fill value was pinned to the columndtype before
zip_with, so an int column's.where(cond, 1.5)raisedwhere pandas promotes to the supertype (and returns floats). The branch
also materialised one Python object per row; the
pl.Exprbranch under ithad neither problem.
Fixes:
_column_expr.pygains_probe_expr_dtype, a schema-only probe of theoperand's own output dtype (
.lazy().select(expr.alias(probe)) .collect_schema()— no data is read).ColumnMethodExpr.polarsfalls backto it when the fast resolver returns None, so
fillna/notnaget a realanswer for derived operands. A probe failure degrades to the previous
NaN-blind behaviour rather than raising on operands that don't resolve
against the frame.
_polars_wherebuilds the fill withpl.repeat(other, len(obj), eager=True), letting polars infer the scalar's own dtype;zip_withsupertypes the pair, matching pandas.
Tests (tests/optimizer/logical/test_map_ops.py, pandas+polars parametrised):
derived-operand fillna and notna against the issue's division table, a
chained clip->fillna operand (the shape the fast resolver cannot name), and
int-column where promotion. Full suite: 934 passed, 7 failed / 27 skipped —
the 7 are pre-existing on af90a87 (adapters tests failing on this
environment's sklearn/torch versions, stash-verified; unrelated to the
optimizer). ruff is not configured in this repo.
Fixes #216.