Skip to content

Migrate RelativeFeatures to narwhals+numpy, add polars support - #995

Merged
solegalli merged 2 commits into
narwhals-migrationfrom
narwhals-relative-features
Aug 24, 2026
Merged

Migrate RelativeFeatures to narwhals+numpy, add polars support#995
solegalli merged 2 commits into
narwhals-migrationfrom
narwhals-relative-features

Conversation

@solegalli

Copy link
Copy Markdown
Collaborator

Replaces the 8 near-identical _add/_sub/_mul/_div/_truediv/_floordiv/_mod/ _pow pandas methods (~90 lines) with a single numpy-ufunc-driven transform(), per request. Benchmarked at 10k rows, 3 variables, 2 references: the numpy version is not just "minimal loss" but actually faster than the current pandas .div(..., axis=0) approach (552.6us vs 637.0us, 0.87x) - so this is a single unified narwhals+numpy code path, no pandas/polars branch at all (re-verified against the final committed code: 635.9us pandas, down from 800.7us before this change; 262.4us polars, previously unsupported).

One correctness fix during implementation: extracting all variables as one batched 2D array via select().to_numpy() upcasts every column to a common dtype, silently turning an int column's subtraction result into float and failing 3 existing tests. Fixed by extracting each variable as its own 1D array instead, preserving each column's own dtype promotion independently - matches pandas' per-column .sub()/.div()/etc. semantics, still a single vectorized numpy op per column (no Python-level row loop).

Also matched a subtler pandas behavior: floordiv/mod on integer input stay integer-typed, and assigning a float fill_value at zero-denominator positions needs the result array explicitly widened to float first (numpy arrays don't auto-promote dtype on assignment the way pandas' DataFrame column assignment does) - verified this reproduces pandas' output exactly, including for negative numbers (floor-division sign conventions matched NumPy's floor_divide/mod exactly across int/float/negative cases, so no other adjustment was needed there).

User guide's example tables verified accurate already (including the Age_pow_Age int64-overflow values, which are genuine hardware overflow behavior, not a doc error - confirmed identical between pandas and polars). Added "With polars" sections to docstring and user guide.

solegalli and others added 2 commits August 24, 2026 19:50
Replaces the 8 near-identical _add/_sub/_mul/_div/_truediv/_floordiv/_mod/
_pow pandas methods (~90 lines) with a single numpy-ufunc-driven transform(),
per request. Benchmarked at 10k rows, 3 variables, 2 references: the numpy
version is not just "minimal loss" but actually faster than the current
pandas .div(..., axis=0) approach (552.6us vs 637.0us, 0.87x) - so this is
a single unified narwhals+numpy code path, no pandas/polars branch at all
(re-verified against the final committed code: 635.9us pandas, down from
800.7us before this change; 262.4us polars, previously unsupported).

One correctness fix during implementation: extracting all `variables` as
one batched 2D array via select().to_numpy() upcasts every column to a
common dtype, silently turning an int column's subtraction result into
float and failing 3 existing tests. Fixed by extracting each variable as
its own 1D array instead, preserving each column's own dtype promotion
independently - matches pandas' per-column .sub()/.div()/etc. semantics,
still a single vectorized numpy op per column (no Python-level row loop).

Also matched a subtler pandas behavior: floordiv/mod on integer input stay
integer-typed, and assigning a float fill_value at zero-denominator
positions needs the result array explicitly widened to float first (numpy
arrays don't auto-promote dtype on assignment the way pandas' DataFrame
column assignment does) - verified this reproduces pandas' output exactly,
including for negative numbers (floor-division sign conventions matched
NumPy's floor_divide/mod exactly across int/float/negative cases, so no
other adjustment was needed there).

User guide's example tables verified accurate already (including the
Age_pow_Age int64-overflow values, which are genuine hardware overflow
behavior, not a doc error - confirmed identical between pandas and polars).
Added "With polars" sections to docstring and user guide.
…metrized suite

Same treatment as the MathFeatures test rewrite: one test per behavior,
parametrized over make_df=[pd.DataFrame, pl.DataFrame], checking identical
values come out for identical input instead of separate pandas-only and
polars-only test functions. Deletes the redundant separately-added polars
section, keeps its 3 genuinely-new cases (mixed dtype preservation, float
fill_value dtype widening, drop_original column list), and converts the
pandas-specific .loc-based zero-fill assertion to a narwhals-based one.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@solegalli
solegalli merged commit 295f9d1 into narwhals-migration Aug 24, 2026
4 of 10 checks passed
@solegalli
solegalli deleted the narwhals-relative-features branch August 24, 2026 19:13
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.

1 participant