Skip to content

Commit 8003f07

Browse files
solegalliclaude
andcommitted
Fix CategoricalMethodsMixin for narwhals-returning check_X
After the rebase onto narwhals-migration, check_X / check_X_y return a narwhals frame. The previous "Update base_encoder.py" left the method bodies referencing a local nw_X that no longer exists. - _encode / _check_nan_values_after_transformation: use the narwhals frame that is actually passed in (was NameError on nw_X). - _check_nan_values_after_transformation now assumes a narwhals frame (its only caller, _encode, hands it one); no nw.from_native round-trip. - _get_feature_names_in: single branch-free `list(X.columns)` (normalises a narwhals column list and a pandas Index alike). - _check_transform_input_and_state keeps the native X for the column-count check and returns the narwhals frame. - Drop now-unused narwhals imports; refresh docstrings. - test_categorical_method_mixin: pass a narwhals frame to the two direct _check_nan_values_after_transformation calls. The encoder subclasses still run pandas-only fit()/transform() code and are adapted in their own migration PRs. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
1 parent 5c3a55b commit 8003f07

2 files changed

Lines changed: 31 additions & 21 deletions

File tree

feature_engine/encoding/base_encoder.py

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import warnings
22
from typing import List, Union
33

4-
import narwhals as nw
5-
import narwhals.dependencies as nwd
64
from narwhals.typing import IntoDataFrame
75
from sklearn.base import BaseEstimator, TransformerMixin
86
from sklearn.utils.validation import check_is_fitted
@@ -163,42 +161,49 @@ def _check_or_select_variables(self, X: IntoDataFrame):
163161

164162
def _get_feature_names_in(self, X: IntoDataFrame):
165163
"""
166-
Returns attributes `featrure_names_in_` and `n_feature_names_in_`, which are
164+
Sets attributes `feature_names_in_` and `n_features_in_`, which are
167165
standard for all transformers in the library.
166+
167+
Parameters
168+
----------
169+
X: narwhals dataframe
170+
The dataframe returned by `check_X` / `check_X_y` at the start of `fit`.
168171
"""
169-
# save input features
170-
self.feature_names_in_ = X.columns
172+
# save input features. list() normalises both a narwhals `.columns`
173+
# (already a list) and a pandas `Index` to a plain list.
174+
self.feature_names_in_ = list(X.columns)
171175

172176
# save train set shape
173177
self.n_features_in_ = X.shape[1]
174178

175179
def _check_transform_input_and_state(self, X: IntoDataFrame) -> IntoDataFrame:
176180
"""
177181
Checks that the input is a dataframe and of the same size than the one used
178-
in the fit method. Checks absence of NA.
182+
in the fit method.
179183
180184
Parameters
181185
----------
182186
X: dataframe
187+
The dataframe entered by the user, in any library supported by narwhals.
183188
184189
Raises
185190
------
186191
TypeError
187192
If the input is not a dataframe
188193
ValueError
189-
- If the variable(s) contain null values.
190-
- If the df has different number of features than the df used in fit()
194+
If the df has a different number of features than the df used in fit()
191195
192196
Returns
193197
-------
194-
X: dataframe
195-
The same dataframe entered by the user.
198+
nw_X: narwhals dataframe
199+
The narwhalified version of the dataframe entered by the user.
196200
"""
197201

198202
# Check method fit has been called
199203
check_is_fitted(self)
200204

201-
# check that input is a dataframe
205+
# check that input is a dataframe. check_X returns a narwhals frame; the
206+
# original native X is kept for the column-count check below.
202207
nw_X = check_X(X)
203208

204209
# Check input data contains same number of columns as df used to fit
@@ -231,28 +236,32 @@ def transform(self, X: IntoDataFrame) -> IntoDataFrame:
231236
return X
232237

233238
def _encode(self, X: IntoDataFrame) -> IntoDataFrame:
239+
# X is the narwhals frame returned by _check_transform_input_and_state().
240+
# replace_strict() maps known categories and fills unseen/missing ones via
241+
# `default` in a single expression, and resolves to a plain numeric dtype
242+
# on both pandas and polars. get_column()/Series.replace_strict() (rather
243+
# than nw.col(), which only accepts string names) is what lets this handle
244+
# pandas integer column names too.
234245
default = self._unseen if self.unseen == "encode" else None
235246
new_series = [
236-
nw_X.get_column(feature).replace_strict(mapping, default=default)
247+
X.get_column(feature).replace_strict(mapping, default=default)
237248
for feature, mapping in self.encoder_dict_.items()
238249
]
239-
X = nw_X.with_columns(*new_series)
250+
X = X.with_columns(*new_series)
240251

241252
if self.unseen != "encode":
242253
# check if nan values were introduced by the transformation
243254
self._check_nan_values_after_transformation(X)
244-
245-
X = X.to_native()
246-
247-
return X
248255

249-
def _check_nan_values_after_transformation(self, X):
256+
return X.to_native()
250257

258+
def _check_nan_values_after_transformation(self, X: IntoDataFrame):
259+
# X is the encoded narwhals frame built by _encode().
251260
# check if NaN values were introduced by the encoding
252261
nan_columns = [
253262
feature
254263
for feature in self.encoder_dict_.keys()
255-
if nw_X.get_column(feature).null_count() > 0
264+
if X.get_column(feature).null_count() > 0
256265
]
257266

258267
if len(nan_columns) > 0:

tests/test_encoding/test_base_encoders/test_categorical_method_mixin.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import narwhals as nw
12
import numpy as np
23
import pandas as pd
34
import pytest
@@ -103,7 +104,7 @@ def test_raises_error_when_nan_introduced():
103104
msg = "During the encoding, NaN values were introduced in the feature(s) words."
104105

105106
with pytest.raises(ValueError) as record:
106-
enc._check_nan_values_after_transformation(output_df)
107+
enc._check_nan_values_after_transformation(nw.from_native(output_df))
107108
assert str(record.value) == msg
108109

109110
with pytest.raises(ValueError) as record:
@@ -122,7 +123,7 @@ def test_raises_warning_when_nan_introduced():
122123
assert record[0].message.args[0] == msg
123124

124125
with pytest.warns(UserWarning) as record:
125-
enc._check_nan_values_after_transformation(output_df)
126+
enc._check_nan_values_after_transformation(nw.from_native(output_df))
126127
assert record[0].message.args[0] == msg
127128

128129

0 commit comments

Comments
 (0)