diff --git a/docs/user_guide/encoding/MeanEncoder.rst b/docs/user_guide/encoding/MeanEncoder.rst index 086eef00f..2419e8b74 100644 --- a/docs/user_guide/encoding/MeanEncoder.rst +++ b/docs/user_guide/encoding/MeanEncoder.rst @@ -364,6 +364,62 @@ After encoding the features we can use the data sets to train machine learning a encoded variable. Hence, this encoding method is suitable for predictive modelling that uses models that are sensitive to the size of the feature space. +With polars +~~~~~~~~~~~ + +:class:`MeanEncoder()` works the same way with a polars dataframe. Let's create a toy dataset: + +.. code:: python + + import polars as pl + from feature_engine.encoding import MeanEncoder + + X = pl.DataFrame({ + "city": ["London", "Manchester", "Liverpool", "London", "Manchester", "Liverpool"], + "price": [500, 300, 250, 520, 310, 260], + }) + y = pl.Series("target", [1, 0, 0, 1, 0, 1]) + +Let's set up :class:`MeanEncoder()` to encode `city` with the target mean, and fit it to the data: + +.. code:: python + + encoder = MeanEncoder(variables=["city"]) + encoder.fit(X, y) + + encoder.encoder_dict_ + +We see the resulting mappings from category to target mean: + +.. code:: python + + {'city': {'London': 1.0, 'Liverpool': 0.5, 'Manchester': 0.0}} + +Now let's transform the data: + +.. code:: python + + encoder.transform(X) + +We obtain a polars dataframe with the categories in `city` replaced by the target mean: + +.. code:: text + + shape: (6, 2) + ┌──────┬───────┐ + │ city ┆ price │ + │ --- ┆ --- │ + │ f64 ┆ i64 │ + ╞══════╪═══════╡ + │ 1.0 ┆ 500 │ + │ 0.0 ┆ 300 │ + │ 0.5 ┆ 250 │ + │ 1.0 ┆ 520 │ + │ 0.0 ┆ 310 │ + │ 0.5 ┆ 260 │ + └──────┴───────┘ + + Additional resources -------------------- diff --git a/feature_engine/encoding/mean_encoding.py b/feature_engine/encoding/mean_encoding.py index 145aa6394..71aead003 100644 --- a/feature_engine/encoding/mean_encoding.py +++ b/feature_engine/encoding/mean_encoding.py @@ -2,7 +2,9 @@ # License: BSD 3 clause from typing import List, Union -import pandas as pd +import narwhals as nw +import narwhals.dependencies as nwd +from narwhals.typing import IntoDataFrame, IntoSeries from feature_engine._check_init_parameters.check_init_input_params import ( _check_return_empty_is_bool, @@ -203,64 +205,96 @@ def __init__( check_parameter_unseen(unseen, ["ignore", "raise", "encode"]) self.unseen = unseen - def fit(self, X: pd.DataFrame, y: pd.Series): + def fit(self, X: IntoDataFrame, y: IntoSeries): """ Learn the mean value of the target for each category of the variable. Parameters ---------- - X: pandas dataframe of shape = [n_samples, n_features] + X: dataframe of shape = [n_samples, n_features] The training input samples. Can be the entire dataframe, not just the variables to be encoded. - y: pandas series + y: Series The target. """ - X, y = check_X_y(X, y) + nw_X, y = check_X_y(X, y) variables_ = self._check_or_select_variables(X) self._check_na(X, variables_) self.encoder_dict_ = {} - y_prior = y.mean() + # pair y with X by position, so list, array and series targets all work + target_name = "__feature_engine_mean_target__" + if nwd.is_into_series(y): + y_nw = nw.from_native(y, series_only=True).alias(target_name) + else: + y_nw = nw.new_series( + name=target_name, values=y, backend=nw_X.implementation + ) + nw_Xy = nw_X.with_columns(y_nw) + + y_prior = y_nw.mean() if self.unseen == "encode": self._unseen = y_prior if self.smoothing == "auto": - y_var = y.var(ddof=0) - for var in variables_: - if self.smoothing == "auto": - damping = y.groupby(X[var]).var(ddof=0) / y_var - else: - damping = self.smoothing - counts = X[var].value_counts() - counts.index = counts.index.infer_objects() - _lambda = counts / (counts + damping) - self.encoder_dict_[var] = ( - _lambda * y.groupby(X[var], observed=False).mean() - + (1.0 - _lambda) * y_prior - ).to_dict() + y_var = y_nw.var(ddof=0) + + # pandas is faster than narwhals. + if nwd.is_pandas_dataframe(X): + # pandas series with the index of X + y = nw_Xy[target_name].to_native() + for var in variables_: + if self.smoothing == "auto": + damping = y.groupby(X[var]).var(ddof=0) / y_var + else: + damping = self.smoothing + counts = X[var].value_counts() + counts.index = counts.index.infer_objects() + _lambda = counts / (counts + damping) + self.encoder_dict_[var] = ( + _lambda * y.groupby(X[var], observed=False).mean() + + (1.0 - _lambda) * y_prior + ).to_dict() + else: + for var in variables_: + stats = nw_Xy.group_by(var, drop_null_keys=True).agg( + nw.col(target_name).mean().alias("__mean__"), + nw.col(target_name).len().alias("__count__"), + nw.col(target_name).var(ddof=0).alias("__var__"), + ) + if self.smoothing == "auto": + damping = nw.col("__var__") / y_var + else: + damping = self.smoothing + _lambda = nw.col("__count__") / (nw.col("__count__") + damping) + encoding = _lambda * nw.col("__mean__") + (1.0 - _lambda) * y_prior + stats = stats.select(var, encoding.alias("__encoding__")) + self.encoder_dict_[var] = dict( + zip(stats[var].to_list(), stats["__encoding__"].to_list()) + ) # assign underscore parameters at the end in case code above fails self.variables_ = variables_ self._get_feature_names_in(X) return self - def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame: + def inverse_transform(self, X: IntoDataFrame) -> IntoDataFrame: """Convert the encoded variable back to the original values. Note that if unseen was set to 'encode', then this method is not implemented. Parameters ---------- - X: pandas dataframe of shape = [n_samples, n_features]. + X: dataframe of shape = [n_samples, n_features]. The transformed dataframe. Returns ------- - X_tr: pandas dataframe of shape = [n_samples, n_features]. + X_tr: dataframe of shape = [n_samples, n_features]. The un-transformed dataframe, with the categorical variables containing the original values. """ diff --git a/tests/test_encoding/test_mean_encoder.py b/tests/test_encoding/test_mean_encoder.py index a13d0e5bf..43237d4bb 100644 --- a/tests/test_encoding/test_mean_encoder.py +++ b/tests/test_encoding/test_mean_encoder.py @@ -1,9 +1,15 @@ +import re + +import numpy as np import pandas as pd import pytest -from numpy import nan from sklearn.exceptions import NotFittedError from feature_engine.encoding import MeanEncoder +from tests.backend_helpers import make_series, frame_to_dict + +ENC_DICT_VAR_A = {"A": 0.3333333333333333, "B": 0.2, "C": 0.5} +ENC_DICT_VAR_B = {"A": 0.2, "B": 0.3333333333333333, "C": 0.5} # test init params @@ -32,308 +38,192 @@ def test_raises_error_when_not_allowed_smoothing_param_in_init(smoothing): # fit and transform -def test_user_enters_1_variable(df_enc): +def test_user_enters_1_variable(make_df, data_enc): # test case 1: 1 variable + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + encoder = MeanEncoder(variables=["var_A"]) - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - X = encoder.transform(df_enc[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) - # expected output - transf_df = df_enc.copy() - transf_df["var_A"] = [ - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.5, - 0.5, - 0.5, - 0.5, - ] - - # test init params - assert encoder.variables == ["var_A"] # test fit attr assert encoder.variables_ == ["var_A"] - assert encoder.encoder_dict_ == { - "var_A": {"A": 0.3333333333333333, "B": 0.2, "C": 0.5} - } + assert encoder.encoder_dict_ == {"var_A": ENC_DICT_VAR_A} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [ENC_DICT_VAR_A[v] for v in data_enc["var_A"]], + "var_B": data_enc["var_B"], + } -def test_automatically_find_variables(df_enc): +def test_automatically_find_variables(make_df, data_enc): # test case 2: automatically select variables + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + encoder = MeanEncoder(variables=None) - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - X = encoder.transform(df_enc[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) - # expected output - transf_df = df_enc.copy() - transf_df["var_A"] = [ - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.5, - 0.5, - 0.5, - 0.5, - ] - transf_df["var_B"] = [ - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.5, - 0.5, - 0.5, - 0.5, - ] - - # test init params - assert encoder.variables is None # test fit attr assert encoder.variables_ == ["var_A", "var_B"] - assert encoder.encoder_dict_ == { - "var_A": {"A": 0.3333333333333333, "B": 0.2, "C": 0.5}, - "var_B": {"A": 0.2, "B": 0.3333333333333333, "C": 0.5}, - } + assert encoder.encoder_dict_ == {"var_A": ENC_DICT_VAR_A, "var_B": ENC_DICT_VAR_B} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [ENC_DICT_VAR_A[v] for v in data_enc["var_A"]], + "var_B": [ENC_DICT_VAR_B[v] for v in data_enc["var_B"]], + } + +@pytest.mark.parametrize("to_target", [list, np.array]) +def test_target_as_list_or_array(make_df, data_enc, to_target): + # a list or numpy array target takes a different code path than a Series + X = make_df(data_enc)[["var_A", "var_B"]] + y = to_target(data_enc["target"]) + + encoder = MeanEncoder() + encoder.fit(X, y) + Xt = encoder.transform(X) + + assert encoder.encoder_dict_ == {"var_A": ENC_DICT_VAR_A, "var_B": ENC_DICT_VAR_B} + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [ENC_DICT_VAR_A[v] for v in data_enc["var_A"]], + "var_B": [ENC_DICT_VAR_B[v] for v in data_enc["var_B"]], + } -def test_encoding_when_nan_in_fit_df(df_enc): - df = df_enc.copy() - df.loc[len(df)] = [nan, nan, 0] + +def test_encoding_when_nan_in_fit_df(make_df, data_enc): + data = { + "var_A": data_enc["var_A"] + [None], + "var_B": data_enc["var_B"] + [None], + "target": data_enc["target"] + [0], + } + X = make_df(data)[["var_A", "var_B"]] + y = make_series(make_df, data["target"]) encoder = MeanEncoder(missing_values="ignore") - encoder.fit(df[["var_A", "var_B"]], df["target"]) + encoder.fit(X, y) - X = encoder.transform( - pd.DataFrame( - { - "var_A": ["A", nan], - "var_B": ["A", nan], - } - ) - ) + Xt = encoder.transform(make_df({"var_A": ["A", None], "var_B": ["A", None]})) - # transform params - pd.testing.assert_frame_equal( - X, - pd.DataFrame( - { - "var_A": [0.3333333333333333, nan], - "var_B": [0.2, nan], - } - ), - ) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [0.3333333333333333, None], + "var_B": [0.2, None], + } -def test_warning_if_transform_df_contains_categories_not_present_in_fit_df( - df_enc, df_enc_rare +def test_raises_if_transform_df_contains_categories_not_present_in_fit_df( + make_df, data_enc, data_enc_rare ): # test case 4: when dataset to be transformed contains categories not present # in training dataset + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + X_rare = make_df(data_enc_rare)[["var_A", "var_B"]] msg = "During the encoding, NaN values were introduced in the feature(s) var_A." - # check for warning when rare_labels equals 'ignore' - with pytest.warns(UserWarning) as record: - encoder = MeanEncoder(unseen="ignore") - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - encoder.transform(df_enc_rare[["var_A", "var_B"]]) - - # check that at least one warning was raised (Pandas 3 may emit additional - # deprecation warnings) - assert len(record) >= 1 - # check that the message matches - assert any(r.message.args[0] == msg for r in record) - - # check for error when rare_labels equals 'raise' - with pytest.raises(ValueError) as record: - encoder = MeanEncoder(unseen="raise") - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - encoder.transform(df_enc_rare[["var_A", "var_B"]]) + # check for warning when unseen equals 'ignore' + encoder = MeanEncoder(unseen="ignore") + encoder.fit(X, y) + with pytest.warns(UserWarning, match=re.escape(msg)): + encoder.transform(X_rare) - # check that the error message matches - assert str(record.value) == msg + # check for error when unseen equals 'raise' + encoder = MeanEncoder(unseen="raise") + encoder.fit(X, y) + with pytest.raises(ValueError, match=re.escape(msg)): + encoder.transform(X_rare) -def test_fit_raises_error_if_df_contains_na(df_enc_na): +def test_fit_raises_error_if_df_contains_na(make_df, data_enc_na): # test case 4: when dataset contains na, fit method + X = make_df(data_enc_na)[["var_A", "var_B"]] + y = make_series(make_df, data_enc_na["target"]) + encoder = MeanEncoder() - with pytest.raises(ValueError) as record: - encoder.fit(df_enc_na[["var_A", "var_B"]], df_enc_na["target"]) msg = ( "Some of the variables in the dataset contain NaN. Check and " "remove those before using this transformer or set the parameter " "`missing_values='ignore'` when initialising this transformer." ) - assert str(record.value) == msg + with pytest.raises(ValueError, match=re.escape(msg)): + encoder.fit(X, y) -def test_transform_raises_error_if_df_contains_na(df_enc, df_enc_na): +def test_transform_raises_error_if_df_contains_na(make_df, data_enc, data_enc_na): # test case 4: when dataset contains na, transform method + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + X_na = make_df(data_enc_na)[["var_A", "var_B"]] + encoder = MeanEncoder() - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - with pytest.raises(ValueError) as record: - encoder.transform(df_enc_na[["var_A", "var_B"]]) + encoder.fit(X, y) msg = ( "Some of the variables in the dataset contain NaN. Check and " "remove those before using this transformer or set the parameter " "`missing_values='ignore'` when initialising this transformer." ) - assert str(record.value) == msg + with pytest.raises(ValueError, match=re.escape(msg)): + encoder.transform(X_na) -def test_user_enters_1_variable_ignore_format(df_enc_numeric): +def test_user_enters_1_variable_ignore_format(make_df, data_enc_numeric): # test case 1: 1 variable + X = make_df(data_enc_numeric)[["var_A", "var_B"]] + y = make_series(make_df, data_enc_numeric["target"]) + encoder = MeanEncoder(variables=["var_A"], ignore_format=True) - encoder.fit(df_enc_numeric[["var_A", "var_B"]], df_enc_numeric["target"]) - X = encoder.transform(df_enc_numeric[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) + + enc_dict_var_a = {1: 0.3333333333333333, 2: 0.2, 3: 0.5} - # expected output - transf_df = df_enc_numeric.copy() - transf_df["var_A"] = [ - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.5, - 0.5, - 0.5, - 0.5, - ] - - # test init params - assert encoder.variables == ["var_A"] # test fit attr assert encoder.variables_ == ["var_A"] - assert encoder.encoder_dict_ == {"var_A": {1: 0.3333333333333333, 2: 0.2, 3: 0.5}} + assert encoder.encoder_dict_ == {"var_A": enc_dict_var_a} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [enc_dict_var_a[v] for v in data_enc_numeric["var_A"]], + "var_B": data_enc_numeric["var_B"], + } -def test_automatically_find_variables_ignore_format(df_enc_numeric): +def test_automatically_find_variables_ignore_format(make_df, data_enc_numeric): # test case 2: automatically select variables + X = make_df(data_enc_numeric)[["var_A", "var_B"]] + y = make_series(make_df, data_enc_numeric["target"]) + encoder = MeanEncoder(variables=None, ignore_format=True) - encoder.fit(df_enc_numeric[["var_A", "var_B"]], df_enc_numeric["target"]) - X = encoder.transform(df_enc_numeric[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) + + enc_dict_var_a = {1: 0.3333333333333333, 2: 0.2, 3: 0.5} + enc_dict_var_b = {1: 0.2, 2: 0.3333333333333333, 3: 0.5} - # expected output - transf_df = df_enc_numeric.copy() - transf_df["var_A"] = [ - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.5, - 0.5, - 0.5, - 0.5, - ] - transf_df["var_B"] = [ - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.5, - 0.5, - 0.5, - 0.5, - ] - - # test init params - assert encoder.variables is None # test fit attr assert encoder.variables_ == ["var_A", "var_B"] - assert encoder.encoder_dict_ == { - "var_A": {1: 0.3333333333333333, 2: 0.2, 3: 0.5}, - "var_B": {1: 0.2, 2: 0.3333333333333333, 3: 0.5}, - } + assert encoder.encoder_dict_ == {"var_A": enc_dict_var_a, "var_B": enc_dict_var_b} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [enc_dict_var_a[v] for v in data_enc_numeric["var_A"]], + "var_B": [enc_dict_var_b[v] for v in data_enc_numeric["var_B"]], + } def test_variables_cast_as_category(df_enc_category_dtypes): + # pandas-only. df = df_enc_category_dtypes.copy() encoder = MeanEncoder(variables=["var_A"]) encoder.fit(df[["var_A", "var_B"]], df["target"]) @@ -341,40 +231,21 @@ def test_variables_cast_as_category(df_enc_category_dtypes): # expected output transf_df = df.copy() - transf_df["var_A"] = [ - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.3333333333333333, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.2, - 0.5, - 0.5, - 0.5, - 0.5, - ] + transf_df["var_A"] = [0.3333333333333333] * 6 + [0.2] * 10 + [0.5] * 4 pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]], check_dtype=False) assert X["var_A"].dtypes.name == "float64" -def test_auto_smoothing(df_enc): +def test_auto_smoothing(make_df, data_enc): + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + encoder = MeanEncoder(smoothing="auto") - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - X = encoder.transform(df_enc[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) # expected output - transf_df = df_enc.copy() var_A_dict = { "A": 0.328335832083958, "B": 0.20707964601769913, @@ -385,29 +256,28 @@ def test_auto_smoothing(df_enc): "B": 0.328335832083958, "C": 0.4541284403669725, } - transf_df["var_A"] = transf_df["var_A"].map(var_A_dict) - transf_df["var_B"] = transf_df["var_B"].map(var_B_dict) - # test init params - assert encoder.variables is None # test fit attr assert encoder.variables_ == ["var_A", "var_B"] - assert encoder.encoder_dict_ == { - "var_A": var_A_dict, - "var_B": var_B_dict, - } + assert encoder.encoder_dict_ == {"var_A": var_A_dict, "var_B": var_B_dict} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [var_A_dict[v] for v in data_enc["var_A"]], + "var_B": [var_B_dict[v] for v in data_enc["var_B"]], + } -def test_value_smoothing(df_enc): +def test_value_smoothing(make_df, data_enc): + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + encoder = MeanEncoder(smoothing=100) - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - X = encoder.transform(df_enc[["var_A", "var_B"]]) + encoder.fit(X, y) + Xt = encoder.transform(X) # expected output - transf_df = df_enc.copy() var_A_dict = { "A": 0.3018867924528302, "B": 0.2909090909090909, @@ -418,80 +288,86 @@ def test_value_smoothing(df_enc): "B": 0.3018867924528302, "C": 0.30769230769230765, } - transf_df["var_A"] = transf_df["var_A"].map(var_A_dict) - transf_df["var_B"] = transf_df["var_B"].map(var_B_dict) - # test init params - assert encoder.variables is None # test fit attr assert encoder.variables_ == ["var_A", "var_B"] - assert encoder.encoder_dict_ == { - "var_A": var_A_dict, - "var_B": var_B_dict, - } + assert encoder.encoder_dict_ == {"var_A": var_A_dict, "var_B": var_B_dict} assert encoder.n_features_in_ == 2 # test transform output - pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == { + "var_A": [var_A_dict[v] for v in data_enc["var_A"]], + "var_B": [var_B_dict[v] for v in data_enc["var_B"]], + } -def test_encoding_new_categories(df_enc): - df_unseen = pd.DataFrame({"var_A": ["D"], "var_B": ["D"]}) +def test_encoding_new_categories(make_df, data_enc): + X = make_df(data_enc)[["var_A", "var_B"]] + y = make_series(make_df, data_enc["target"]) + df_unseen = make_df({"var_A": ["D"], "var_B": ["D"]}) + encoder = MeanEncoder(unseen="encode") - encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"]) - df_transformed = encoder.transform(df_unseen) - assert (df_transformed == df_enc["target"].mean()).all(axis=None) + encoder.fit(X, y) + Xt = encoder.transform(df_unseen) + + target_mean = sum(data_enc["target"]) / len(data_enc["target"]) + assert isinstance(Xt, make_df) + assert frame_to_dict(Xt) == {"var_A": [target_mean], "var_B": [target_mean]} -def test_inverse_transform_when_no_unseen(): - df = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) - y = [1, 0, 1, 0, 1, 0] +def test_inverse_transform_when_no_unseen(make_df): + words = ["dog", "dog", "cat", "cat", "cat", "bird"] + df = make_df({"words": words}) + y = make_series(make_df, [1, 0, 1, 0, 1, 0]) enc = MeanEncoder() enc.fit(df, y) dft = enc.transform(df) - pd.testing.assert_frame_equal(enc.inverse_transform(dft), df) + Xi = enc.inverse_transform(dft) + assert isinstance(Xi, make_df) + assert frame_to_dict(Xi) == {"words": words} -def test_inverse_transform_when_ignore_unseen(): - df1 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) - df2 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "frog"]}) - df3 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", nan]}) - y = [1, 0, 1, 0, 1, 0] +def test_inverse_transform_when_ignore_unseen(make_df): + df1 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) + df2 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "frog"]}) + y = make_series(make_df, [1, 0, 1, 0, 1, 0]) enc = MeanEncoder(unseen="ignore") enc.fit(df1, y) dft = enc.transform(df2) - pd.testing.assert_frame_equal(enc.inverse_transform(dft), df3) + Xi = enc.inverse_transform(dft) + assert isinstance(Xi, make_df) + assert frame_to_dict(Xi) == {"words": ["dog", "dog", "cat", "cat", "cat", None]} -def test_inverse_transform_when_encode_unseen(): - df1 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) - df2 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "frog"]}) - y = [1, 0, 1, 0, 1, 0] +def test_inverse_transform_when_encode_unseen(make_df): + df1 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) + df2 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "frog"]}) + y = make_series(make_df, [1, 0, 1, 0, 1, 0]) enc = MeanEncoder(unseen="encode") enc.fit(df1, y) dft = enc.transform(df2) - with pytest.raises(NotImplementedError) as record: - enc.inverse_transform(dft) msg = ( "inverse_transform is not implemented for this transformer when " "`unseen='encode'`." ) - assert str(record.value) == msg + with pytest.raises(NotImplementedError, match=re.escape(msg)): + enc.inverse_transform(dft) -def test_inverse_transform_raises_non_fitted_error(): - df1 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) - y = [1, 0, 1, 0, 1, 0] +def test_inverse_transform_raises_non_fitted_error(make_df): + df1 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) + y = make_series(make_df, [1, 0, 1, 0, 1, 0]) enc = MeanEncoder() # Test when fit is not called prior to transform. with pytest.raises(NotFittedError): enc.inverse_transform(df1) - df1.loc[len(df1) - 1] = nan + df1_na = make_df({"words": ["dog", "dog", "cat", "cat", "cat", None]}) with pytest.raises(ValueError): - enc.fit(df1, y) + enc.fit(df1_na, y) # Test when fit is not called prior to transform. with pytest.raises(NotFittedError): - enc.inverse_transform(df1) + enc.inverse_transform(df1_na)