diff --git a/docs/user_guide/encoding/CountEncoder.rst b/docs/user_guide/encoding/CountEncoder.rst index 929507984..8616433f1 100644 --- a/docs/user_guide/encoding/CountEncoder.rst +++ b/docs/user_guide/encoding/CountEncoder.rst @@ -265,6 +265,55 @@ With the method `inverse_transform`, we can transform the encoded dataframes bac original representation, that is, we can replace the encoding with the original categorical values. +With polars +----------- + +:class:`CountEncoder()` works in the same way with a polars dataframe: + +.. code:: python + + import polars as pl + from feature_engine.encoding import CountEncoder + + df = pl.DataFrame({ + "cabin": ["M", "C", "M", "B", "M"], + "sex": ["male", "female", "male", "female", "male"], + "embarked": ["S", "C", "S", "S", "Q"], + }) + + encoder = CountEncoder( + encoding_method="count", + variables=["cabin", "sex", "embarked"], + ) + encoder.fit(df) + + print(encoder.encoder_dict_) + +.. code:: python + + {'cabin': {'M': 3, 'C': 1, 'B': 1}, 'sex': {'male': 3, 'female': 2}, 'embarked': {'S': 3, 'C': 1, 'Q': 1}} + +.. code:: python + + Xt = encoder.transform(df) + + print(Xt) + +.. code:: text + + shape: (5, 3) + ┌───────┬─────┬──────────┐ + │ cabin ┆ sex ┆ embarked │ + │ --- ┆ --- ┆ --- │ + │ i64 ┆ i64 ┆ i64 │ + ╞═══════╪═════╪══════════╡ + │ 3 ┆ 3 ┆ 3 │ + │ 1 ┆ 2 ┆ 1 │ + │ 3 ┆ 3 ┆ 3 │ + │ 1 ┆ 2 ┆ 3 │ + │ 3 ┆ 3 ┆ 1 │ + └───────┴─────┴──────────┘ + Additional resources -------------------- diff --git a/feature_engine/encoding/count_frequency.py b/feature_engine/encoding/count_frequency.py index 682c90680..d10316387 100644 --- a/feature_engine/encoding/count_frequency.py +++ b/feature_engine/encoding/count_frequency.py @@ -4,7 +4,7 @@ import warnings from typing import List, Optional, Union -import pandas as pd +from narwhals.typing import IntoDataFrame, IntoSeries from feature_engine._check_init_parameters.check_init_input_params import ( _check_return_empty_is_bool, @@ -157,6 +157,26 @@ class CountEncoder(CategoricalMethodsMixin, CategoricalInitMixinNA): 1 2 0.25 2 3 0.25 3 4 0.50 + + With polars + + >>> import polars as pl + >>> from feature_engine.encoding import CountEncoder + >>> X = pl.DataFrame(dict(x1 = [1,2,3,4], x2 = ["c", "a", "b", "c"])) + >>> cf = CountEncoder(encoding_method='count') + >>> cf.fit(X) + >>> cf.transform(X) + shape: (4, 2) + ┌─────┬─────┐ + │ x1 ┆ x2 │ + │ --- ┆ --- │ + │ i64 ┆ i64 │ + ╞═════╪═════╡ + │ 1 ┆ 2 │ + │ 2 ┆ 1 │ + │ 3 ┆ 1 │ + │ 4 ┆ 2 │ + └─────┴─────┘ """ def __init__( @@ -183,37 +203,44 @@ def __init__( self.unseen = unseen self.return_empty = return_empty - def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None): + def fit(self, X: IntoDataFrame, y: Optional[IntoSeries] = None): """ Learn the counts or frequencies which will be used to replace the categories. Parameters ---------- - X: pandas dataframe of shape = [n_samples, n_features] + X: dataframe of shape = [n_samples, n_features] The training dataset. Can be the entire dataframe, not just the variables to be transformed. - y: pandas Series, default = None + y: Series, default = None y is not needed in this encoder. You can pass y or None. """ - X = check_X(X) + nw_X = check_X(X) variables_ = self._check_or_select_variables(X) self._check_na(X, variables_) + if self.encoding_method not in ["count", "frequency"]: + raise ValueError( + "Unrecognized value for encoding_method. It should be 'count' or " + f"'frequency'. Got {self.encoding_method} instead." + ) + normalize = self.encoding_method == "frequency" + self.encoder_dict_ = {} - # learn encoding maps + # learn encoding maps. drop_nulls() before value_counts() mirrors + # pandas' value_counts(dropna=True) default - narwhals' value_counts() + # has no dropna param and keeps NaN as a countable category otherwise. + # sort=True matches pandas' own value_counts() default (descending + # by count), so encoder_dict_ has the same category order as before. for var in variables_: - if self.encoding_method == "count": - self.encoder_dict_[var] = X[var].value_counts().to_dict() - - elif self.encoding_method == "frequency": - self.encoder_dict_[var] = X[var].value_counts(normalize=True).to_dict() - else: - raise ValueError( - "Unrecognized value for encoding_method. It should be 'count' or " - f"'frequency'. Got {self.encoding_method} instead." - ) + counts = nw_X.get_column(var).drop_nulls().value_counts( + sort=True, normalize=normalize + ) + keys = counts.get_column(counts.columns[0]).to_list() + values = counts.get_column(counts.columns[1]).to_list() + self.encoder_dict_[var] = dict(zip(keys, values)) # unseen categories are replaced by 0 if self.unseen == "encode": diff --git a/tests/test_encoding/test_count_frequency_encoder.py b/tests/test_encoding/test_count_frequency_encoder.py index 88998755b..fcb0c293d 100644 --- a/tests/test_encoding/test_count_frequency_encoder.py +++ b/tests/test_encoding/test_count_frequency_encoder.py @@ -1,12 +1,46 @@ +import re import warnings +import narwhals as nw import pandas as pd +import polars as pl import pytest -from numpy import nan from sklearn.exceptions import NotFittedError from feature_engine.encoding import CountEncoder, CountFrequencyEncoder +DATA_ENC = { + "var_A": ["A"] * 6 + ["B"] * 10 + ["C"] * 4, + "var_B": ["A"] * 10 + ["B"] * 6 + ["C"] * 4, + "target": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], +} +DATA_ENC_RARE = { + "var_A": ["B"] * 9 + ["A"] * 6 + ["C"] * 4 + ["D"] * 1, + "var_B": ["A"] * 10 + ["B"] * 6 + ["C"] * 4, + "target": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], +} +DATA_ENC_NA = { + "var_A": [None] + ["B"] * 8 + ["A"] * 6 + ["C"] * 4 + ["D"] * 1, + "var_B": ["A"] * 10 + ["B"] * 6 + ["C"] * 4, + "target": [1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0], +} +DATA_VARTYPES = { + "Name": ["tom", "nick", "krish", "jack"], + "City": ["London", "Manchester", "Liverpool", "Bristol"], + "Age": [20, 21, 19, 18], + "Marks": [0.9, 0.8, 0.7, 0.6], + "dob": ["2020-02-24", "2020-02-25", "2020-02-26", "2020-02-27"], +} + + +def _to_pandas(X): + return nw.from_native(X, eager_only=True).to_pandas() + + +def _null_count(X): + nw_X = nw.from_native(X, eager_only=True) + return sum(nw_X.get_column(c).null_count() for c in nw_X.columns) + # init parameters @pytest.mark.parametrize("enc_method", ["arbitrary", False, 1]) @@ -40,35 +74,16 @@ def test_init_param_assignment(params): # fit and transform -def test_encode_1_variable_with_counts(df_enc): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_encode_1_variable_with_counts(make_df): # test case 1: 1 variable, counts + df_enc = make_df(DATA_ENC) encoder = CountEncoder(encoding_method="count", variables=["var_A"]) X = encoder.fit_transform(df_enc) # expected result - transf_df = df_enc.copy() - transf_df["var_A"] = [ - 6, - 6, - 6, - 6, - 6, - 6, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 4, - 4, - 4, - 4, - ] + transf_df = _to_pandas(df_enc) + transf_df["var_A"] = [6] * 6 + [10] * 10 + [4] * 4 # init params assert encoder.encoding_method == "count" @@ -78,60 +93,20 @@ def test_encode_1_variable_with_counts(df_enc): assert encoder.encoder_dict_ == {"var_A": {"A": 6, "B": 10, "C": 4}} assert encoder.n_features_in_ == 3 # transform params - pd.testing.assert_frame_equal(X, transf_df) + pd.testing.assert_frame_equal(_to_pandas(X), transf_df, check_dtype=False) -def test_automatically_select_variables_encode_with_frequency(df_enc): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_automatically_select_variables_encode_with_frequency(make_df): # test case 2: automatically select variables, frequency + df_enc = make_df(DATA_ENC) encoder = CountEncoder(encoding_method="frequency", variables=None) X = encoder.fit_transform(df_enc) # expected output - transf_df = df_enc.copy() - transf_df["var_A"] = [ - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.2, - 0.2, - 0.2, - 0.2, - ] - transf_df["var_B"] = [ - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.5, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.3, - 0.2, - 0.2, - 0.2, - 0.2, - ] + transf_df = _to_pandas(df_enc) + transf_df["var_A"] = [0.3] * 6 + [0.5] * 10 + [0.2] * 4 + transf_df["var_B"] = [0.5] * 10 + [0.3] * 6 + [0.2] * 4 # init params assert encoder.encoding_method == "frequency" @@ -144,12 +119,12 @@ def test_automatically_select_variables_encode_with_frequency(df_enc): } assert encoder.n_features_in_ == 3 # transform params - pd.testing.assert_frame_equal(X, transf_df) + pd.testing.assert_frame_equal(_to_pandas(X), transf_df, check_dtype=False) -def test_encoding_when_nan_in_fit_df(df_enc): - df = df_enc.copy() - df.loc[len(df)] = [nan, nan, nan] +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_encoding_when_nan_in_fit_df(make_df): + df_enc = make_df(DATA_ENC) encoder = CountEncoder( encoding_method="frequency", @@ -158,32 +133,37 @@ def test_encoding_when_nan_in_fit_df(df_enc): encoder.fit(df_enc) X = encoder.transform( - pd.DataFrame({"var_A": ["A", nan], "var_B": ["A", nan], "target": [1, 0]}) + make_df({"var_A": ["A", None], "var_B": ["A", None], "target": [1, 0]}) ) # transform params - pd.testing.assert_frame_equal( - X, - pd.DataFrame({"var_A": [0.3, nan], "var_B": [0.5, nan], "target": [1, 0]}), + result = _to_pandas(X) + expected = pd.DataFrame( + {"var_A": [0.3, None], "var_B": [0.5, None], "target": [1, 0]} ) + pd.testing.assert_frame_equal(result, expected, check_dtype=False) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) @pytest.mark.parametrize("enc_method", ["arbitrary", False, 1]) -def test_error_if_encoding_method_not_recognized_in_fit(enc_method, df_enc): +def test_error_if_encoding_method_not_recognized_in_fit(enc_method, make_df): + df_enc = make_df(DATA_ENC) enc = CountEncoder() enc.encoding_method = enc_method - with pytest.raises(ValueError) as record: - enc.fit(df_enc) msg = ( "Unrecognized value for encoding_method. It should be 'count' or " f"'frequency'. Got {enc_method} instead." ) - assert str(record.value) == msg + with pytest.raises(ValueError, match=re.escape(msg)): + enc.fit(df_enc) -def test_warning_when_df_contains_unseen_categories(df_enc, df_enc_rare): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_warning_when_df_contains_unseen_categories(make_df): # dataset to be transformed contains categories not present in # training dataset (unseen categories), unseen set to ignore. + df_enc = make_df(DATA_ENC) + df_enc_rare = make_df(DATA_ENC_RARE) msg = "During the encoding, NaN values were introduced in the feature(s) var_A." @@ -199,9 +179,12 @@ def test_warning_when_df_contains_unseen_categories(df_enc, df_enc_rare): assert record[0].message.args[0] == msg -def test_error_when_df_contains_unseen_categories(df_enc, df_enc_rare): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_error_when_df_contains_unseen_categories(make_df): # dataset to be transformed contains categories not present in # training dataset (unseen categories), unseen set to raise. + df_enc = make_df(DATA_ENC) + df_enc_rare = make_df(DATA_ENC_RARE) msg = "During the encoding, NaN values were introduced in the feature(s) var_A." @@ -209,12 +192,9 @@ def test_error_when_df_contains_unseen_categories(df_enc, df_enc_rare): encoder.fit(df_enc) # check for exception when unseen equals 'raise' - with pytest.raises(ValueError) as record: + with pytest.raises(ValueError, match=re.escape(msg)): encoder.transform(df_enc_rare) - # check that the error message matches - assert str(record.value) == msg - # check for no error and no warning when unseen equals 'encode' with warnings.catch_warnings(): warnings.simplefilter("error") @@ -223,11 +203,14 @@ def test_error_when_df_contains_unseen_categories(df_enc, df_enc_rare): encoder.transform(df_enc_rare) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) def test_no_error_triggered_when_df_contains_unseen_categories_and_unseen_is_encode( - df_enc, df_enc_rare + make_df, ): # dataset to be transformed contains categories not present in # training dataset (unseen categories). + df_enc = make_df(DATA_ENC) + df_enc_rare = make_df(DATA_ENC_RARE) # check for no error and no warning when unseen equals 'encode' warnings.simplefilter("error") @@ -237,41 +220,44 @@ def test_no_error_triggered_when_df_contains_unseen_categories_and_unseen_is_enc encoder.transform(df_enc_rare) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) @pytest.mark.parametrize("errors", ["raise", "ignore", "encode"]) -def test_fit_raises_error_if_df_contains_na(errors, df_enc_na): +def test_fit_raises_error_if_df_contains_na(errors, make_df): # test case 4: when dataset contains na, fit method + df_enc_na = make_df(DATA_ENC_NA) encoder = CountEncoder(unseen=errors) - with pytest.raises(ValueError) as record: - encoder.fit(df_enc_na) 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(df_enc_na) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) @pytest.mark.parametrize("errors", ["raise", "ignore", "encode"]) -def test_transform_raises_error_if_df_contains_na(errors, df_enc, df_enc_na): +def test_transform_raises_error_if_df_contains_na(errors, make_df): # test case 4: when dataset contains na, transform method + df_enc = make_df(DATA_ENC) + df_enc_na = make_df(DATA_ENC_NA) encoder = CountEncoder(unseen=errors) encoder.fit(df_enc) - with pytest.raises(ValueError) as record: - encoder.transform(df_enc_na) 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(df_enc_na) -def test_zero_encoding_for_new_categories(): - df_fit = pd.DataFrame( +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_zero_encoding_for_new_categories(make_df): + df_fit = make_df( {"col1": ["a", "a", "b", "a", "c"], "col2": ["1", "2", "3", "1", "2"]} ) - df_transf = pd.DataFrame( + df_transf = make_df( {"col1": ["a", "d", "b", "a", "c"], "col2": ["1", "2", "3", "1", "4"]} ) encoder = CountEncoder(unseen="encode").fit(df_fit) @@ -279,18 +265,21 @@ def test_zero_encoding_for_new_categories(): result = encoder.transform(df_transf) # check that no NaNs are added - assert pd.isnull(result).sum().sum() == 0 + assert _null_count(result) == 0 # check that the counts are correct for both new and old expected_result = pd.DataFrame({"col1": [3, 0, 1, 3, 1], "col2": [2, 2, 1, 2, 0]}) - pd.testing.assert_frame_equal(result, expected_result, check_dtype=False) + pd.testing.assert_frame_equal( + _to_pandas(result), expected_result, check_dtype=False + ) -def test_zero_encoding_for_unseen_categories_if_unseen_is_encode(): - df_fit = pd.DataFrame( +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_zero_encoding_for_unseen_categories_if_unseen_is_encode(make_df): + df_fit = make_df( {"col1": ["a", "a", "b", "a", "c"], "col2": ["1", "2", "3", "1", "2"]} ) - df_transform = pd.DataFrame( + df_transform = make_df( {"col1": ["a", "d", "b", "a", "c"], "col2": ["1", "2", "3", "1", "4"]} ) @@ -299,64 +288,71 @@ def test_zero_encoding_for_unseen_categories_if_unseen_is_encode(): result = encoder.transform(df_transform) # check that no NaNs are added - assert pd.isnull(result).sum().sum() == 0 + assert _null_count(result) == 0 # check that the counts are correct expected_result = pd.DataFrame({"col1": [3, 0, 1, 3, 1], "col2": [2, 2, 1, 2, 0]}) - pd.testing.assert_frame_equal(result, expected_result, check_dtype=False) + pd.testing.assert_frame_equal( + _to_pandas(result), expected_result, check_dtype=False + ) # with frequency - encoder = CountEncoder(encoding_method="frequency", unseen="encode").fit( - df_fit - ) + encoder = CountEncoder(encoding_method="frequency", unseen="encode").fit(df_fit) result = encoder.transform(df_transform) # check that no NaNs are added - assert pd.isnull(result).sum().sum() == 0 + assert _null_count(result) == 0 # check that the frequencies are correct expected_result = pd.DataFrame( {"col1": [0.6, 0, 0.2, 0.6, 0.2], "col2": [0.4, 0.4, 0.2, 0.4, 0]} ) - pd.testing.assert_frame_equal(result, expected_result) + pd.testing.assert_frame_equal( + _to_pandas(result), expected_result, check_dtype=False + ) -def test_nan_encoding_for_new_categories_if_unseen_is_ignore(): - df_fit = pd.DataFrame( +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_nan_encoding_for_new_categories_if_unseen_is_ignore(make_df): + df_fit = make_df( {"col1": ["a", "a", "b", "a", "c"], "col2": ["1", "2", "3", "1", "2"]} ) - df_transf = pd.DataFrame( + df_transf = make_df( {"col1": ["a", "d", "b", "a", "c"], "col2": ["1", "2", "3", "1", "4"]} ) encoder = CountEncoder(unseen="ignore").fit(df_fit) result = encoder.transform(df_transf) - # check that no NaNs are added - assert pd.isnull(result).sum().sum() == 2 + # check that 2 NaNs are added + assert _null_count(result) == 2 # check that the counts are correct for both new and old expected_result = pd.DataFrame( - {"col1": [3, nan, 1, 3, 1], "col2": [2, 2, 1, 2, nan]} + {"col1": [3, None, 1, 3, 1], "col2": [2, 2, 1, 2, None]} + ) + pd.testing.assert_frame_equal( + _to_pandas(result), expected_result, check_dtype=False ) - pd.testing.assert_frame_equal(result, expected_result) -def test_ignore_variable_format_with_frequency(df_vartypes): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_ignore_variable_format_with_frequency(make_df): + df_vartypes = make_df(DATA_VARTYPES) encoder = CountEncoder( encoding_method="frequency", variables=None, ignore_format=True ) X = encoder.fit_transform(df_vartypes) # expected output - transf_df = { - "Name": [0.25, 0.25, 0.25, 0.25], - "City": [0.25, 0.25, 0.25, 0.25], - "Age": [0.25, 0.25, 0.25, 0.25], - "Marks": [0.25, 0.25, 0.25, 0.25], - "dob": [0.25, 0.25, 0.25, 0.25], - } - - transf_df = pd.DataFrame(transf_df) + transf_df = pd.DataFrame( + { + "Name": [0.25, 0.25, 0.25, 0.25], + "City": [0.25, 0.25, 0.25, 0.25], + "Age": [0.25, 0.25, 0.25, 0.25], + "Marks": [0.25, 0.25, 0.25, 0.25], + "dob": [0.25, 0.25, 0.25, 0.25], + } + ) # init params assert encoder.encoding_method == "frequency" @@ -365,10 +361,11 @@ def test_ignore_variable_format_with_frequency(df_vartypes): assert encoder.variables_ == ["Name", "City", "Age", "Marks", "dob"] assert encoder.n_features_in_ == 5 # transform params - pd.testing.assert_frame_equal(X, transf_df) + pd.testing.assert_frame_equal(_to_pandas(X), transf_df, check_dtype=False) def test_column_names_are_numbers(df_numeric_columns): + # integer column names are not supported by polars - pandas only. encoder = CountEncoder( encoding_method="frequency", variables=[0, 1, 2, 3], ignore_format=True ) @@ -396,33 +393,13 @@ def test_column_names_are_numbers(df_numeric_columns): def test_variables_cast_as_category(df_enc_category_dtypes): + # pandas category dtype is not a polars concept - pandas only. encoder = CountEncoder(encoding_method="count", variables=["var_A"]) X = encoder.fit_transform(df_enc_category_dtypes) # expected result transf_df = df_enc_category_dtypes.copy() - transf_df["var_A"] = [ - 6, - 6, - 6, - 6, - 6, - 6, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 10, - 4, - 4, - 4, - 4, - ] + transf_df["var_A"] = [6] * 6 + [10] * 10 + [4] * 4 # transform params pd.testing.assert_frame_equal(X, transf_df, check_dtype=False) assert X["var_A"].dtypes == int @@ -432,55 +409,62 @@ def test_variables_cast_as_category(df_enc_category_dtypes): assert X["var_A"].dtypes == float -def test_inverse_transform_when_no_unseen(): - df = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_inverse_transform_when_no_unseen(make_df): + df = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) enc = CountEncoder() enc.fit(df) dft = enc.transform(df) - pd.testing.assert_frame_equal(enc.inverse_transform(dft), df) + pd.testing.assert_frame_equal( + _to_pandas(enc.inverse_transform(dft)), _to_pandas(df) + ) -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]}) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +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"]}) + df3 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", None]}) enc = CountEncoder(unseen="ignore") enc.fit(df1) dft = enc.transform(df2) - pd.testing.assert_frame_equal(enc.inverse_transform(dft), df3) + pd.testing.assert_frame_equal(_to_pandas(enc.inverse_transform(dft)), df3) -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"]}) - df3 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", nan]}) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +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"]}) + df3 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", None]}) enc = CountEncoder(unseen="encode") enc.fit(df1) dft = enc.transform(df2) - pd.testing.assert_frame_equal(enc.inverse_transform(dft), df3) + pd.testing.assert_frame_equal(_to_pandas(enc.inverse_transform(dft)), df3) -def test_inverse_transform_raises_non_fitted_error(): - df1 = pd.DataFrame({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_inverse_transform_raises_non_fitted_error(make_df): + df1 = make_df({"words": ["dog", "dog", "cat", "cat", "cat", "bird"]}) enc = CountEncoder() # 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) + enc.fit(df1_na) # Test when fit is not called prior to transform. with pytest.raises(NotFittedError): - enc.inverse_transform(df1) + enc.inverse_transform(df1_na) -def test_count_frequency_encoder_is_deprecated(): +@pytest.mark.parametrize("make_df", [pd.DataFrame, pl.DataFrame]) +def test_count_frequency_encoder_is_deprecated(make_df): """CountFrequencyEncoder should emit a FutureWarning and still work.""" - X = pd.DataFrame({"var_A": ["A"] * 6 + ["B"] * 2 + ["C"] * 2}) + X = make_df({"var_A": ["A"] * 6 + ["B"] * 2 + ["C"] * 2}) with pytest.warns(FutureWarning, match="CountFrequencyEncoder was deprecated"): enc = CountFrequencyEncoder(encoding_method="count") @@ -489,5 +473,5 @@ def test_count_frequency_encoder_is_deprecated(): enc_new = CountEncoder(encoding_method="count") pd.testing.assert_frame_equal( - enc.fit_transform(X), enc_new.fit_transform(X) + _to_pandas(enc.fit_transform(X)), _to_pandas(enc_new.fit_transform(X)) )