Repository: feature-engine/feature_engine
Pull request #341 (2021-12-16 06:30:16)

Add NaN detection to in discretizers transform method by creating a BaseDiscretizer class 
Started implementation of the `BaseDiscretizer` class which is a child class of the `BaseNumericalTransformer` class.

Morgan-Sell: Hi @solegalli, I incorporated some of the lessons learned from #338, e.g., add init params - `return_object` and `return_boundaries` - to the new parent class BaseDiscretiser.

Should we add the `rare_labels` parameter w/ the corresponding code raise an error if `self.rare_labels == "raise"`?

Also, I fixed the language to British English, i.e. discretizer --> discretiser ;)
Morgan-Sell: Hola @solegalli,

I'm still updating the `transform()` method in the BaseDiscretiser class. I need to add the error message. I also need to implement the tests for the 3 discretisers.

Morgan-Sell: Feliz fiestas, @solegalli!

I created/revised two tests - `test_error_if_input_df_contains_na_in_transform()` and `test_error_if_not_permitted_value_is_errors()` - for ArbirtraryDiscretiser, EqualDistanceDiscretiser, and EqualWidthDiscretiser.

I'm a bit uncertain if I used the correct dataframes - `df_vartypes` and `df_na[["Name", "City", "Age", "Marks", "dob"]]` - for `test_error_if_input_df_contains_na_in_transform()`.



Patch:
diff --git a/docs/user_guide/selection/DropHighPSIFeatures.rst b/docs/user_guide/selection/DropHighPSIFeatures.rst
index 3276bc8..65ccb78 100644
--- a/docs/user_guide/selection/DropHighPSIFeatures.rst
+++ b/docs/user_guide/selection/DropHighPSIFeatures.rst
@@ -505,6 +505,9 @@ will be done comparing the periods up to the French revolution and after.
     transformer = DropHighPSIFeatures(split_col='time', cut_off=date(1789, 7, 14))
     transformer.fit(X)
 
+**Important**: if the date variable is in pandas or NumPy datetime format, you may need
+to pass the cut_off value as `pd.to_datetime(1789-07-14)`.
+
 The PSI values shows the *century* variables in unstable as its value is above
 the 0.25 threshold.
 
@@ -847,3 +850,14 @@ and a non-shifted variable (for example *var_4*)
 
 
 .. image:: ../../images/PSI_distribution_case5.png
+
+
+More details
+^^^^^^^^^^^^
+
+In this notebook, we show how to use :class:`DropHighPSIFeatures` on a real dataset and
+give more detail about the underlying base and reference sub-dataframes used to
+determine the PSI.
+
+- `Jupyter notebook <https://nbviewer.org/github/feature-engine/feature-engine-examples/blob/main/selection/Drop-High-PSI-Features.ipynb>`_
+
diff --git a/feature_engine/datetime/datetime.py b/feature_engine/datetime/datetime.py
index de73f22..8e25bc8 100644
--- a/feature_engine/datetime/datetime.py
+++ b/feature_engine/datetime/datetime.py
@@ -39,11 +39,10 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
     - "quarter"
     - "semester"
     - "year"
-    - "week_of_the_month"
-    - "week_of_the_year"
-    - "day_of_the_week"
-    - "day_of_the_month"
-    - "day_of_the_year"
+    - "week"
+    - "day_of_week"
+    - "day_of_month"
+    - "day_of_year"
     - "weekend"
     - "month_start"
     - "month_end"
@@ -68,7 +67,7 @@ class DatetimeFeatures(BaseEstimator, TransformerMixin):
 
     features_to_extract: list, default=None
         The list of date features to extract. If None, the following features will be
-        extracted: "month", "year", "day_of_the_week", "day_of_the_month", "hour",
+        extracted: "month", "year", "day_of_week", "day_of_month", "hour",
         "minute" and "second". If "all", all supported features will be extracted.
         Alternatively, you can pass a list with the names of the supported features
         you want to extract.
diff --git a/feature_engine/datetime/datetime_constants.py b/feature_engine/datetime/datetime_constants.py
index 123f3d5..273247f 100644
--- a/feature_engine/datetime/datetime_constants.py
+++ b/feature_engine/datetime/datetime_constants.py
@@ -5,10 +5,10 @@ FEATURES_SUPPORTED = [
     "quarter",
     "semester",
     "year",
-    "week_of_the_year",
-    "day_of_the_week",
-    "day_of_the_month",
-    "day_of_the_year",
+    "week",
+    "day_of_week",
+    "day_of_month",
+    "day_of_year",
     "weekend",
     "month_start",
     "month_end",
@@ -28,10 +28,10 @@ FEATURES_SUFFIXES = {
     "quarter": "_quarter",
     "semester": "_semester",
     "year": "_year",
-    "week_of_the_year": "_woty",
-    "day_of_the_week": "_dotw",
-    "day_of_the_month": "_dotm",
-    "day_of_the_year": "_doty",
+    "week": "_week",
+    "day_of_week": "_day_of_week",
+    "day_of_month": "_day_of_month",
+    "day_of_year": "_day_of_year",
     "weekend": "_weekend",
     "month_start": "_month_start",
     "month_end": "_month_end",
@@ -51,10 +51,10 @@ FEATURES_FUNCTIONS = {
     "quarter": lambda x: x.dt.quarter,
     "semester": lambda x: np.where(x.dt.month <= 6, 1, 2).astype(np.int64),
     "year": lambda x: x.dt.year,
-    "week_of_the_year": lambda x: x.dt.isocalendar().week.astype(np.int64),
-    "day_of_the_week": lambda x: x.dt.dayofweek,
-    "day_of_the_month": lambda x: x.dt.day,
-    "day_of_the_year": lambda x: x.dt.dayofyear,
+    "week": lambda x: x.dt.isocalendar().week.astype(np.int64),
+    "day_of_week": lambda x: x.dt.dayofweek,
+    "day_of_month": lambda x: x.dt.day,
+    "day_of_year": lambda x: x.dt.dayofyear,
     "weekend": lambda x: np.where(x.dt.dayofweek <= 4, 0, 1).astype(np.int64),
     "month_start": lambda x: x.dt.is_month_start.astype(np.int64),
     "month_end": lambda x: x.dt.is_month_end.astype(np.int64),
@@ -72,8 +72,8 @@ FEATURES_FUNCTIONS = {
 FEATURES_DEFAULT = [
     "month",
     "year",
-    "day_of_the_week",
-    "day_of_the_month",
+    "day_of_week",
+    "day_of_month",
     "hour",
     "minute",
     "second",
diff --git a/feature_engine/discretisation/arbitrary.py b/feature_engine/discretisation/arbitrary.py
index 4a26dd2..02a2274 100644
--- a/feature_engine/discretisation/arbitrary.py
+++ b/feature_engine/discretisation/arbitrary.py
@@ -1,15 +1,16 @@
 # Authors: Soledad Galli <solegalli@protonmail.com>
 # License: BSD 3 clause
 
+import warnings
 from typing import Dict, List, Optional, Union
 
 import pandas as pd
 
-from feature_engine.base_transformers import BaseNumericalTransformer
+from feature_engine.discretisation.base_discretiser import BaseDiscretiser
 from feature_engine.validation import _return_tags
 
 
-class ArbitraryDiscretiser(BaseNumericalTransformer):
+class ArbitraryDiscretiser(BaseDiscretiser):
     """
     The ArbitraryDiscretiser() divides numerical variables into intervals which limits
     are determined by the user. Thus, it works only with numerical variables.
@@ -39,6 +40,12 @@ class ArbitraryDiscretiser(BaseNumericalTransformer):
         Whether the output, that is the bins, should be the interval boundaries. If
         True, it returns the interval boundaries. If False, it returns integers.
 
+    errors: string, default='ignore'
+        Indicates what to do when a value is outside the limits indicated in the
+        'binning_dict'. If 'raise', the transformation will raise an error.
+        If 'ignore', values outside the limits are returned as NaN
+        and a warning will be raised instead.
+
     Attributes
     ----------
     binner_dict_:
@@ -69,19 +76,25 @@ class ArbitraryDiscretiser(BaseNumericalTransformer):
         binning_dict: Dict[Union[str, int], List[Union[str, int]]],
         return_object: bool = False,
         return_boundaries: bool = False,
+        errors: str = "ignore",
     ) -> None:
 
         if not isinstance(binning_dict, dict):
             raise ValueError(
-                "Please provide at a dictionary with the interval limits per variable"
+                "binning_dict must be a dictionary with the interval limits per "
+                f"variable. Got {binning_dict} instead."
+            )
+
+        if errors not in ["ignore", "raise"]:
+            raise ValueError(
+                "errors only takes values 'ignore' and 'raise'. "
+                f"Got {errors} instead."
             )
 
-        if not isinstance(return_object, bool):
-            raise ValueError("return_object must be True or False")
+        super().__init__(return_object, return_boundaries)
 
         self.binning_dict = binning_dict
-        self.return_object = return_object
-        self.return_boundaries = return_boundaries
+        self.errors = errors
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
@@ -109,34 +122,42 @@ class ArbitraryDiscretiser(BaseNumericalTransformer):
     def transform(self, X: pd.DataFrame) -> pd.DataFrame:
         """Sort the variable values into the intervals.
 
-        Parameters
-        ----------
-        X: pandas dataframe of shape = [n_samples, n_features]
-            The dataframe to be transformed.
+       Parameters
+       ----------
+       X: pandas dataframe of shape = [n_samples, n_features]
+           The dataframe to be transformed.
 
-        Returns
-        -------
-        X_new: pandas dataframe of shape = [n_samples, n_features]
-            The transformed data with the discrete variables.
-        """
+       Returns
+       -------
+       X_new: pandas dataframe of shape = [n_samples, n_features]
+           The transformed data with the discrete variables.
+       """
 
-        # check input dataframe and if class was fitted
         X = super().transform(X)
+        # check if NaN values were introduced by the discretisation procedure.
+        if X[self.variables_].isnull().sum().sum() > 0:
 
-        # transform variables
-        if self.return_boundaries:
-            for feature in self.variables_:
-                X[feature] = pd.cut(X[feature], self.binner_dict_[feature])
+            # obtain the name(s) of the columns with null values
+            nan_columns = (
+                X[self.variables_].columns[X[self.variables_].isnull().any()].tolist()
+            )
 
-        else:
-            for feature in self.variables_:
-                X[feature] = pd.cut(
-                    X[feature], self.binner_dict_[feature], labels=False
+            if len(nan_columns) > 1:
+                nan_columns_str = ", ".join(nan_columns)
+            else:
+                nan_columns_str = nan_columns[0]
+
+            if self.errors == "ignore":
+                warnings.warn(
+                    f"During the discretisation, NaN values were introduced in "
+                    f"the feature(s) {nan_columns_str}."
                 )
 
-            # return object
-            if self.return_object:
-                X[self.variables_] = X[self.variables_].astype("O")
+            elif self.errors == "raise":
+                raise ValueError(
+                    "During the discretisation, NaN values were introduced in "
+                    f"the feature(s) {nan_columns_str}."
+                )
 
         return X
 
diff --git a/feature_engine/discretisation/base_discretiser.py b/feature_engine/discretisation/base_discretiser.py
new file mode 100644
index 0000000..4db2e0b
--- /dev/null
+++ b/feature_engine/discretisation/base_discretiser.py
@@ -0,0 +1,82 @@
+# Authors: Morgan Sell <morganpsell@gmail.com>
+# License: BSD 3 clause
+
+import pandas as pd
+
+from feature_engine.base_transformers import BaseNumericalTransformer
+
+
+class BaseDiscretiser(BaseNumericalTransformer):
+    """
+    Shared set-up checks and methods across numerical discretisers.
+
+    Parameters
+    ----------
+    return_object: bool, default=False
+        Whether the the discrete variable should be returned as numeric or as
+        object. If you would like to proceed with the engineering of the variable as if
+        it was categorical, use True. Alternatively, keep the default to False.
+
+    return_boundaries: bool, default=False
+        Whether the output should be the interval boundaries. If True, it returns
+        the interval boundaries. If False, it returns integers.
+
+    Methods
+    -------
+    transform:
+        Sort continuous variable values into the intervals.
+    """
+
+    def __init__(
+        self,
+        return_object: bool = False,
+        return_boundaries: bool = False,
+    ) -> None:
+
+        if not isinstance(return_object, bool):
+            raise ValueError(
+                "return_object must be True or False. " f"Got {return_object} instead."
+            )
+
+        if not isinstance(return_boundaries, bool):
+            raise ValueError(
+                "return_boundaries must be True or False. "
+                f"Got {return_boundaries} instead."
+            )
+
+        self.return_object = return_object
+        self.return_boundaries = return_boundaries
+
+    def transform(self, X: pd.DataFrame) -> pd.DataFrame:
+        """Sort the variable values into the intervals.
+
+        Parameters
+        ----------
+        X: pandas dataframe of shape = [n_samples, n_features]
+            The data to transform.
+
+        Returns
+        -------
+        X_new: pandas dataframe of shape = [n_samples, n_features]
+            The transformed data with the discrete variables.
+        """
+
+        # check input dataframe and if class was fitted
+        X = super().transform(X)
+
+        # transform variables
+        if self.return_boundaries:
+            for feature in self.variables_:
+                X[feature] = pd.cut(X[feature], self.binner_dict_[feature])
+
+        else:
+            for feature in self.variables_:
+                X[feature] = pd.cut(
+                    X[feature], self.binner_dict_[feature], labels=False
+                )
+
+            # return object
+            if self.return_object:
+                X[self.variables_] = X[self.variables_].astype("O")
+
+        return X
diff --git a/feature_engine/discretisation/equal_frequency.py b/feature_engine/discretisation/equal_frequency.py
index fe59fff..331cc87 100644
--- a/feature_engine/discretisation/equal_frequency.py
+++ b/feature_engine/discretisation/equal_frequency.py
@@ -5,11 +5,11 @@ from typing import List, Optional, Union
 
 import pandas as pd
 
-from feature_engine.base_transformers import BaseNumericalTransformer
+from feature_engine.discretisation.base_discretiser import BaseDiscretiser
 from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
-class EqualFrequencyDiscretiser(BaseNumericalTransformer):
+class EqualFrequencyDiscretiser(BaseDiscretiser):
     """
     The EqualFrequencyDiscretiser() divides continuous numerical variables
     into contiguous equal frequency intervals, that is, intervals that contain
@@ -86,18 +86,12 @@ class EqualFrequencyDiscretiser(BaseNumericalTransformer):
     ) -> None:
 
         if not isinstance(q, int):
-            raise ValueError("q must be an integer")
+            raise ValueError(f"q must be an integer. Got {q} instead.")
 
-        if not isinstance(return_object, bool):
-            raise ValueError("return_object must be True or False")
-
-        if not isinstance(return_boundaries, bool):
-            raise ValueError("return_boundaries must be True or False")
+        super().__init__(return_object, return_boundaries)
 
         self.q = q
         self.variables = _check_input_parameter_variables(variables)
-        self.return_object = return_object
-        self.return_boundaries = return_boundaries
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
@@ -129,37 +123,3 @@ class EqualFrequencyDiscretiser(BaseNumericalTransformer):
         self.n_features_in_ = X.shape[1]
 
         return self
-
-    def transform(self, X: pd.DataFrame) -> pd.DataFrame:
-        """Sort the variable values into the intervals.
-
-        Parameters
-        ----------
-        X: pandas dataframe of shape = [n_samples, n_features]
-            The data to transform.
-
-        Returns
-        -------
-        X_new: pandas dataframe of shape = [n_samples, n_features]
-            The transformed data with the discrete variables.
-        """
-
-        # check input dataframe and if class was fitted
-        X = super().transform(X)
-
-        # transform variables
-        if self.return_boundaries:
-            for feature in self.variables_:
-                X[feature] = pd.cut(X[feature], self.binner_dict_[feature])
-
-        else:
-            for feature in self.variables_:
-                X[feature] = pd.cut(
-                    X[feature], self.binner_dict_[feature], labels=False
-                )
-
-            # return object
-            if self.return_object:
-                X[self.variables_] = X[self.variables_].astype("O")
-
-        return X
diff --git a/feature_engine/discretisation/equal_width.py b/feature_engine/discretisation/equal_width.py
index fbc7bc6..6d6560e 100644
--- a/feature_engine/discretisation/equal_width.py
+++ b/feature_engine/discretisation/equal_width.py
@@ -5,11 +5,11 @@ from typing import List, Optional, Union
 
 import pandas as pd
 
-from feature_engine.base_transformers import BaseNumericalTransformer
+from feature_engine.discretisation.base_discretiser import BaseDiscretiser
 from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
-class EqualWidthDiscretiser(BaseNumericalTransformer):
+class EqualWidthDiscretiser(BaseDiscretiser):
     """
     The EqualWidthDiscretiser() divides continuous numerical variables into
     intervals of the same width, that is, equidistant intervals. Note that the
@@ -95,18 +95,12 @@ class EqualWidthDiscretiser(BaseNumericalTransformer):
     ) -> None:
 
         if not isinstance(bins, int):
-            raise ValueError("q must be an integer")
+            raise ValueError(f"bins must be an integer. Got {bins} instead.")
 
-        if not isinstance(return_object, bool):
-            raise ValueError("return_object must be True or False")
-
-        if not isinstance(return_boundaries, bool):
-            raise ValueError("return_boundaries must be True or False")
+        super().__init__(return_object, return_boundaries)
 
         self.bins = bins
         self.variables = _check_input_parameter_variables(variables)
-        self.return_object = return_object
-        self.return_boundaries = return_boundaries
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
@@ -142,38 +136,3 @@ class EqualWidthDiscretiser(BaseNumericalTransformer):
         self.n_features_in_ = X.shape[1]
 
         return self
-
-    def transform(self, X: pd.DataFrame) -> pd.DataFrame:
-        """
-        Sort the variable values into the intervals.
-
-        Parameters
-        ----------
-        X: pandas dataframe of shape = [n_samples, n_features]
-            The data to transform.
-
-        Returns
-        -------
-        X_new: pandas dataframe of shape = [n_samples, n_features]
-            The transformed data with the discrete variables.
-        """
-
-        # check input dataframe and if class was fitted
-        X = super().transform(X)
-
-        # transform variables
-        if self.return_boundaries:
-            for feature in self.variables_:
-                X[feature] = pd.cut(X[feature], self.binner_dict_[feature])
-
-        else:
-            for feature in self.variables_:
-                X[feature] = pd.cut(
-                    X[feature], self.binner_dict_[feature], labels=False
-                )
-
-            # return object
-            if self.return_object:
-                X[self.variables_] = X[self.variables_].astype("O")
-
-        return X
diff --git a/feature_engine/encoding/base_encoder.py b/feature_engine/encoding/base_encoder.py
index eb5b814..b117fc7 100644
--- a/feature_engine/encoding/base_encoder.py
+++ b/feature_engine/encoding/base_encoder.py
@@ -14,11 +14,43 @@ from feature_engine.validation import _return_tags
 from feature_engine.variable_manipulation import (
     _find_all_variables,
     _find_or_check_categorical_variables,
+    _check_input_parameter_variables,
 )
 
 
 class BaseCategoricalTransformer(BaseEstimator, TransformerMixin):
-    """shared set-up checks and methods across categorical transformers"""
+    """shared set-up checks and methods across categorical transformers
+
+    Parameters
+    ----------
+    variables: list, default=None
+        The list of categorical variables that will be encoded. If None, the
+        encoder will find and transform all variables of type object or categorical by
+        default. You can also make the transformer accept numerical variables, see the
+        next parameter.
+
+    ignore_format: bool, default=False
+        Whether the format in which the categorical variables are cast should be
+        ignored. If False, the encoder will automatically select variables of type
+        object or categorical, or check that the variables entered by the user are of
+        type object or categorical. If True, the encoder will select all variables or
+        accept all variables entered by the user, including those cast as numeric.
+    """
+
+    def __init__(
+        self,
+        variables: Union[None, int, str, List[Union[str, int]]] = None,
+        ignore_format: bool = False,
+    ) -> None:
+
+        if not isinstance(ignore_format, bool):
+            raise ValueError(
+                "ignore_format takes only booleans True and False. "
+                f"Got {ignore_format} instead."
+            )
+
+        self.variables = _check_input_parameter_variables(variables)
+        self.ignore_format = ignore_format
 
     def _check_fit_input_and_variables(self, X: pd.DataFrame) -> pd.DataFrame:
         """
@@ -144,15 +176,30 @@ class BaseCategoricalTransformer(BaseEstimator, TransformerMixin):
 
         # check if NaN values were introduced by the encoding
         if X[self.encoder_dict_.keys()].isnull().sum().sum() > 0:
-            warnings.warn(
-                "NaN values were introduced in the returned dataframe by the encoder."
-                "This means that some of the categories in the input dataframe were "
-                "not present in the training set used when the fit method was called. "
-                "Thus, mappings for those categories do not exist. Try using the "
-                "RareLabelCategoricalEncoder to remove infrequent categories before "
-                "calling this encoder."
+
+            # obtain the name(s) of the columns have null values
+            nan_columns = (
+                X[self.encoder_dict_.keys()]
+                .columns[X[self.encoder_dict_.keys()].isnull().any()]
+                .tolist()
             )
 
+            if len(nan_columns) > 1:
+                nan_columns_str = ", ".join(nan_columns)
+            else:
+                nan_columns_str = nan_columns[0]
+
+            if self.errors == "ignore":
+                warnings.warn(
+                    "During the encoding, NaN values were introduced in the feature(s) "
+                    f"{nan_columns_str}."
+                )
+            elif self.errors == "raise":
+                raise ValueError(
+                    "During the encoding, NaN values were introduced in the feature(s) "
+                    f"{nan_columns_str}."
+                )
+
         return X
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
@@ -186,3 +233,47 @@ class BaseCategoricalTransformer(BaseEstimator, TransformerMixin):
         # so we need to leave without this test
         tags_dict["_xfail_checks"]["check_estimators_nan_inf"] = "transformer allows NA"
         return tags_dict
+
+
+class BaseCategorical(BaseCategoricalTransformer):
+    """
+    BaseCategorical() is the parent class to some of the encoders.
+    It shares set-up checks of init parameters.
+
+    Parameters
+    ----------
+    variables: list, default=None
+        The list of categorical variables that will be encoded. If None, the
+        encoder will find and transform all variables of type object or categorical by
+        default. You can also make the transformer accept numerical variables, see the
+        next parameter.
+
+    ignore_format: bool, default=False
+        Whether the format in which the categorical variables are cast should be
+        ignored. If False, the encoder will automatically select variables of type
+        object or categorical, or check that the variables entered by the user are of
+        type object or categorical. If True, the encoder will select all variables or
+        accept all variables entered by the user, including those cast as numeric.
+
+    errors: string, default='ignore'
+        Indicates what to do, when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+    """
+
+    def __init__(
+        self,
+        variables: Union[None, int, str, List[Union[str, int]]] = None,
+        ignore_format: bool = False,
+        errors: str = "ignore",
+    ) -> None:
+
+        if errors not in ["raise", "ignore"]:
+            raise ValueError(
+                "errors takes only values 'raise' and 'ignore ."
+                f"Got {errors} instead."
+            )
+
+        super().__init__(variables, ignore_format)
+        self.errors = errors
diff --git a/feature_engine/encoding/count_frequency.py b/feature_engine/encoding/count_frequency.py
index 4cd43b7..e1f4cc9 100644
--- a/feature_engine/encoding/count_frequency.py
+++ b/feature_engine/encoding/count_frequency.py
@@ -5,11 +5,10 @@ from typing import List, Optional, Union
 
 import pandas as pd
 
-from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
-from feature_engine.variable_manipulation import _check_input_parameter_variables
+from feature_engine.encoding.base_encoder import BaseCategorical
 
 
-class CountFrequencyEncoder(BaseCategoricalTransformer):
+class CountFrequencyEncoder(BaseCategorical):
     """
     The CountFrequencyEncoder() replaces categories by either the count or the
     percentage of observations per category.
@@ -55,6 +54,12 @@ class CountFrequencyEncoder(BaseCategoricalTransformer):
         type object or categorical. If True, the encoder will select all variables or
         accept all variables entered by the user, including those cast as numeric.
 
+    errors: string, default='ignore'
+        Indicates what to do when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+
     Attributes
     ----------
     encoder_dict_:
@@ -97,18 +102,16 @@ class CountFrequencyEncoder(BaseCategoricalTransformer):
         encoding_method: str = "count",
         variables: Union[None, int, str, List[Union[str, int]]] = None,
         ignore_format: bool = False,
+        errors: str = "ignore"
     ) -> None:
 
         if encoding_method not in ["count", "frequency"]:
             raise ValueError(
                 "encoding_method takes only values 'count' and 'frequency'"
             )
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
+        super().__init__(variables, ignore_format, errors)
 
         self.encoding_method = encoding_method
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
@@ -149,11 +152,11 @@ class CountFrequencyEncoder(BaseCategoricalTransformer):
 
         return X
 
-    transform.__doc__ = BaseCategoricalTransformer.transform.__doc__
+    transform.__doc__ = BaseCategorical.transform.__doc__
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
         X = super().inverse_transform(X)
 
         return X
 
-    inverse_transform.__doc__ = BaseCategoricalTransformer.inverse_transform.__doc__
+    inverse_transform.__doc__ = BaseCategorical.inverse_transform.__doc__
diff --git a/feature_engine/encoding/decision_tree.py b/feature_engine/encoding/decision_tree.py
index 5a097e3..d5d288d 100644
--- a/feature_engine/encoding/decision_tree.py
+++ b/feature_engine/encoding/decision_tree.py
@@ -9,7 +9,6 @@ from sklearn.pipeline import Pipeline
 from feature_engine.discretisation import DecisionTreeDiscretiser
 from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
 from feature_engine.encoding.ordinal import OrdinalEncoder
-from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
 class DecisionTreeEncoder(BaseCategoricalTransformer):
@@ -139,14 +138,13 @@ class DecisionTreeEncoder(BaseCategoricalTransformer):
         ignore_format: bool = False,
     ) -> None:
 
+        super().__init__(variables, ignore_format)
         self.encoding_method = encoding_method
         self.cv = cv
         self.scoring = scoring
         self.regression = regression
         self.param_grid = param_grid
         self.random_state = random_state
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
@@ -176,6 +174,7 @@ class DecisionTreeEncoder(BaseCategoricalTransformer):
             encoding_method=self.encoding_method,
             variables=self.variables_,
             ignore_format=self.ignore_format,
+            errors="raise",
         )
 
         # initialize decision tree discretiser
diff --git a/feature_engine/encoding/mean_encoding.py b/feature_engine/encoding/mean_encoding.py
index ba99efd..83ef8c6 100644
--- a/feature_engine/encoding/mean_encoding.py
+++ b/feature_engine/encoding/mean_encoding.py
@@ -5,11 +5,10 @@ from typing import List, Union
 
 import pandas as pd
 
-from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
-from feature_engine.variable_manipulation import _check_input_parameter_variables
+from feature_engine.encoding.base_encoder import BaseCategorical
 
 
-class MeanEncoder(BaseCategoricalTransformer):
+class MeanEncoder(BaseCategorical):
     """
     The MeanEncoder() replaces categories by the mean value of the target for each
     category.
@@ -47,6 +46,12 @@ class MeanEncoder(BaseCategoricalTransformer):
         type object or categorical. If True, the encoder will select all variables or
         accept all variables entered by the user, including those cast as numeric.
 
+    errors: string, default='ignore'
+        Indicates what to do when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+
     Attributes
     ----------
     encoder_dict_:
@@ -95,13 +100,10 @@ class MeanEncoder(BaseCategoricalTransformer):
         self,
         variables: Union[None, int, str, List[Union[str, int]]] = None,
         ignore_format: bool = False,
+        errors: str = "ignore"
     ) -> None:
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
-
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
+        super().__init__(variables, ignore_format, errors)
 
     def fit(self, X: pd.DataFrame, y: pd.Series):
         """
@@ -142,11 +144,11 @@ class MeanEncoder(BaseCategoricalTransformer):
 
         return X
 
-    transform.__doc__ = BaseCategoricalTransformer.transform.__doc__
+    transform.__doc__ = BaseCategorical.transform.__doc__
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
         X = super().inverse_transform(X)
 
         return X
 
-    inverse_transform.__doc__ = BaseCategoricalTransformer.inverse_transform.__doc__
+    inverse_transform.__doc__ = BaseCategorical.inverse_transform.__doc__
diff --git a/feature_engine/encoding/one_hot.py b/feature_engine/encoding/one_hot.py
index eb8bfe4..6db7f34 100644
--- a/feature_engine/encoding/one_hot.py
+++ b/feature_engine/encoding/one_hot.py
@@ -7,7 +7,6 @@ import numpy as np
 import pandas as pd
 
 from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
-from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
 class OneHotEncoder(BaseCategoricalTransformer):
@@ -146,14 +145,10 @@ class OneHotEncoder(BaseCategoricalTransformer):
         if not isinstance(drop_last_binary, bool):
             raise ValueError("drop_last_binary takes only True or False")
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
-
+        super().__init__(variables, ignore_format)
         self.top_categories = top_categories
         self.drop_last = drop_last
         self.drop_last_binary = drop_last_binary
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
diff --git a/feature_engine/encoding/ordinal.py b/feature_engine/encoding/ordinal.py
index b1dd6d7..29ff95d 100644
--- a/feature_engine/encoding/ordinal.py
+++ b/feature_engine/encoding/ordinal.py
@@ -5,11 +5,10 @@ from typing import List, Optional, Union
 
 import pandas as pd
 
-from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
-from feature_engine.variable_manipulation import _check_input_parameter_variables
+from feature_engine.encoding.base_encoder import BaseCategorical
 
 
-class OrdinalEncoder(BaseCategoricalTransformer):
+class OrdinalEncoder(BaseCategorical):
     """
     The OrdinalCategoricalEncoder() replaces categories by ordinal numbers
     (0, 1, 2, 3, etc). The numbers can be ordered based on the mean of the target
@@ -52,6 +51,12 @@ class OrdinalEncoder(BaseCategoricalTransformer):
         type object or categorical. If True, the encoder will select all variables or
         accept all variables entered by the user, including those cast as numeric.
 
+    errors: string, default='ignore'
+        Indicates what to do when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+
     Attributes
     ----------
     encoder_dict_:
@@ -102,6 +107,7 @@ class OrdinalEncoder(BaseCategoricalTransformer):
         encoding_method: str = "ordered",
         variables: Union[None, int, str, List[Union[str, int]]] = None,
         ignore_format: bool = False,
+        errors: str = "ignore"
     ) -> None:
 
         if encoding_method not in ["ordered", "arbitrary"]:
@@ -109,12 +115,9 @@ class OrdinalEncoder(BaseCategoricalTransformer):
                 "encoding_method takes only values 'ordered' and 'arbitrary'"
             )
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
+        super().__init__(variables, ignore_format, errors)
 
         self.encoding_method = encoding_method
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """Learn the numbers to be used to replace the categories in each
@@ -174,11 +177,11 @@ class OrdinalEncoder(BaseCategoricalTransformer):
 
         return X
 
-    transform.__doc__ = BaseCategoricalTransformer.transform.__doc__
+    transform.__doc__ = BaseCategorical.transform.__doc__
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
         X = super().inverse_transform(X)
 
         return X
 
-    inverse_transform.__doc__ = BaseCategoricalTransformer.inverse_transform.__doc__
+    inverse_transform.__doc__ = BaseCategorical.inverse_transform.__doc__
diff --git a/feature_engine/encoding/probability_ratio.py b/feature_engine/encoding/probability_ratio.py
index c4e3d8f..24483e8 100644
--- a/feature_engine/encoding/probability_ratio.py
+++ b/feature_engine/encoding/probability_ratio.py
@@ -6,12 +6,11 @@ from typing import List, Union
 import numpy as np
 import pandas as pd
 
-from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
+from feature_engine.encoding.base_encoder import BaseCategorical
 from feature_engine.validation import _return_tags
-from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
-class PRatioEncoder(BaseCategoricalTransformer):
+class PRatioEncoder(BaseCategorical):
     """
     The PRatioEncoder() replaces categories by the ratio of the probability of the
     target = 1 and the probability of the target = 0.
@@ -72,6 +71,12 @@ class PRatioEncoder(BaseCategoricalTransformer):
         type object or categorical. If True, the encoder will select all variables or
         accept all variables entered by the user, including those cast as numeric.
 
+    errors: string, default='ignore'
+        Indicates what to do when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+
     Attributes
     ----------
     encoder_dict_:
@@ -110,6 +115,7 @@ class PRatioEncoder(BaseCategoricalTransformer):
         encoding_method: str = "ratio",
         variables: Union[None, int, str, List[Union[str, int]]] = None,
         ignore_format: bool = False,
+        errors: str = "ignore"
     ) -> None:
 
         if encoding_method not in ["ratio", "log_ratio"]:
@@ -117,12 +123,9 @@ class PRatioEncoder(BaseCategoricalTransformer):
                 "encoding_method takes only values 'ratio' and 'log_ratio'"
             )
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
+        super().__init__(variables, ignore_format, errors)
 
         self.encoding_method = encoding_method
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: pd.Series):
         """
@@ -198,14 +201,14 @@ class PRatioEncoder(BaseCategoricalTransformer):
 
         return X
 
-    transform.__doc__ = BaseCategoricalTransformer.transform.__doc__
+    transform.__doc__ = BaseCategorical.transform.__doc__
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
         X = super().inverse_transform(X)
 
         return X
 
-    inverse_transform.__doc__ = BaseCategoricalTransformer.inverse_transform.__doc__
+    inverse_transform.__doc__ = BaseCategorical.inverse_transform.__doc__
 
     def _more_tags(self):
         tags_dict = _return_tags()
diff --git a/feature_engine/encoding/rare_label.py b/feature_engine/encoding/rare_label.py
index 2e882dc..22bbced 100644
--- a/feature_engine/encoding/rare_label.py
+++ b/feature_engine/encoding/rare_label.py
@@ -8,7 +8,6 @@ import numpy as np
 import pandas as pd
 
 from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
-from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
 class RareLabelEncoder(BaseCategoricalTransformer):
@@ -117,15 +116,11 @@ class RareLabelEncoder(BaseCategoricalTransformer):
             if max_n_categories < 0 or not isinstance(max_n_categories, int):
                 raise ValueError("max_n_categories takes only positive integer numbers")
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
-
+        super().__init__(variables, ignore_format)
         self.tol = tol
         self.n_categories = n_categories
         self.max_n_categories = max_n_categories
         self.replace_with = replace_with
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
 
     def fit(self, X: pd.DataFrame, y: Optional[pd.Series] = None):
         """
diff --git a/feature_engine/encoding/woe.py b/feature_engine/encoding/woe.py
index 1186af8..1d319ab 100644
--- a/feature_engine/encoding/woe.py
+++ b/feature_engine/encoding/woe.py
@@ -6,12 +6,11 @@ from typing import List, Union
 import numpy as np
 import pandas as pd
 
-from feature_engine.encoding.base_encoder import BaseCategoricalTransformer
+from feature_engine.encoding.base_encoder import BaseCategorical
 from feature_engine.validation import _return_tags
-from feature_engine.variable_manipulation import _check_input_parameter_variables
 
 
-class WoEEncoder(BaseCategoricalTransformer):
+class WoEEncoder(BaseCategorical):
     """
     The WoEEncoder() replaces categories by the weight of evidence
     (WoE). The WoE was used primarily in the financial sector to create credit risk
@@ -55,6 +54,12 @@ class WoEEncoder(BaseCategoricalTransformer):
         type object or categorical. If True, the encoder will select all variables or
         accept all variables entered by the user, including those cast as numeric.
 
+    errors: string, default='ignore'
+        Indicates what to do when categories not present in the train set are
+        encountered during transform. If 'raise', then rare categories will raise an
+        error. If 'ignore', then rare categories will be set as NaN and a warning will
+        be raised instead.
+
     Attributes
     ----------
     encoder_dict_:
@@ -100,13 +105,10 @@ class WoEEncoder(BaseCategoricalTransformer):
         self,
         variables: Union[None, int, str, List[Union[str, int]]] = None,
         ignore_format: bool = False,
+        errors: str = "ignore"
     ) -> None:
 
-        if not isinstance(ignore_format, bool):
-            raise ValueError("ignore_format takes only booleans True and False")
-
-        self.variables = _check_input_parameter_variables(variables)
-        self.ignore_format = ignore_format
+        super().__init__(variables, ignore_format, errors)
 
     def fit(self, X: pd.DataFrame, y: pd.Series):
         """
@@ -178,14 +180,14 @@ class WoEEncoder(BaseCategoricalTransformer):
 
         return X
 
-    transform.__doc__ = BaseCategoricalTransformer.transform.__doc__
+    transform.__doc__ = BaseCategorical.transform.__doc__
 
     def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
         X = super().inverse_transform(X)
 
         return X
 
-    inverse_transform.__doc__ = BaseCategoricalTransformer.inverse_transform.__doc__
+    inverse_transform.__doc__ = BaseCategorical.inverse_transform.__doc__
 
     def _more_tags(self):
         tags_dict = _return_tags()


Test patch:
diff --git a/tests/test_datetime/conftest.py b/tests/test_datetime/conftest.py
index 1498a9f..8a49b32 100644
--- a/tests/test_datetime/conftest.py
+++ b/tests/test_datetime/conftest.py
@@ -10,10 +10,10 @@ def df_datetime_transformed(df_datetime):
         "datetime_range_quarter": [1, 1, 1, 1],
         "datetime_range_semester": [1, 1, 1, 1],
         "datetime_range_year": [2020, 2020, 2020, 2020],
-        "datetime_range_woty": [9, 9, 9, 9],
-        "datetime_range_dotw": [0, 1, 2, 3],
-        "datetime_range_dotm": [24, 25, 26, 27],
-        "datetime_range_doty": [55, 56, 57, 58],
+        "datetime_range_week": [9, 9, 9, 9],
+        "datetime_range_day_of_week": [0, 1, 2, 3],
+        "datetime_range_day_of_month": [24, 25, 26, 27],
+        "datetime_range_day_of_year": [55, 56, 57, 58],
         "datetime_range_weekend": [0, 0, 0, 0],
         "datetime_range_month_start": [0, 0, 0, 0],
         "datetime_range_month_end": [0, 0, 0, 0],
@@ -30,10 +30,10 @@ def df_datetime_transformed(df_datetime):
         "date_obj1_quarter": [1, 1, 2, 2],
         "date_obj1_semester": [1, 1, 1, 1],
         "date_obj1_year": [2010, 1945, 2100, 1999],
-        "date_obj1_woty": [53, 8, 24, 20],
-        "date_obj1_dotw": [4, 5, 0, 0],
-        "date_obj1_dotm": [1, 24, 14, 17],
-        "date_obj1_doty": [1, 55, 165, 137],
+        "date_obj1_week": [53, 8, 24, 20],
+        "date_obj1_day_of_week": [4, 5, 0, 0],
+        "date_obj1_day_of_month": [1, 24, 14, 17],
+        "date_obj1_day_of_year": [1, 55, 165, 137],
         "date_obj1_weekend": [0, 1, 0, 0],
         "date_obj1_month_start": [1, 0, 0, 0],
         "date_obj1_month_end": [0, 0, 0, 0],
@@ -50,10 +50,10 @@ def df_datetime_transformed(df_datetime):
         "date_obj2_quarter": [4, 4, 2, 1],
         "date_obj2_semester": [2, 2, 1, 1],
         "date_obj2_year": [2012, 2009, 1995, 2004],
-        "date_obj2_woty": [41, 53, 26, 12],
-        "date_obj2_dotw": [3, 3, 4, 2],
-        "date_obj2_dotm": [11, 31, 30, 17],
-        "date_obj2_doty": [285, 365, 181, 77],
+        "date_obj2_week": [41, 53, 26, 12],
+        "date_obj2_day_of_week": [3, 3, 4, 2],
+        "date_obj2_day_of_month": [11, 31, 30, 17],
+        "date_obj2_day_of_year": [285, 365, 181, 77],
         "date_obj2_weekend": [0, 0, 0, 0],
         "date_obj2_month_start": [0, 0, 0, 0],
         "date_obj2_month_end": [0, 1, 1, 0],
@@ -70,10 +70,10 @@ def df_datetime_transformed(df_datetime):
         "time_obj_quarter": [today.quarter] * 4,
         "time_obj_semester": [1 if today.month <= 6 else 2] * 4,
         "time_obj_year": [today.year] * 4,
-        "time_obj_woty": [today.week] * 4,
-        "time_obj_dotw": [today.dayofweek] * 4,
-        "time_obj_dotm": [today.day] * 4,
-        "time_obj_doty": [today.dayofyear] * 4,
+        "time_obj_week": [today.week] * 4,
+        "time_obj_day_of_week": [today.dayofweek] * 4,
+        "time_obj_day_of_month": [today.day] * 4,
+        "time_obj_day_of_year": [today.dayofyear] * 4,
         "time_obj_weekend": [1 if today.dayofweek > 4 else 0] * 4,
         "time_obj_month_start": [int(today.is_month_start)] * 4,
         "time_obj_month_end": [int(today.is_month_end)] * 4,
diff --git a/tests/test_datetime/test_datetime_features.py b/tests/test_datetime/test_datetime_features.py
index d497a80..97a89c3 100644
--- a/tests/test_datetime/test_datetime_features.py
+++ b/tests/test_datetime/test_datetime_features.py
@@ -226,25 +226,25 @@ def test_extract_all_datetime_features(df_datetime, df_datetime_transformed):
 
 def test_extract_specified_datetime_features(df_datetime, df_datetime_transformed):
     X = DatetimeFeatures(
-        features_to_extract=["semester", "week_of_the_year"]
+        features_to_extract=["semester", "week"]
     ).fit_transform(df_datetime)
     pd.testing.assert_frame_equal(
         X,
         df_datetime_transformed[
             vars_non_dt
-            + [var + "_" + feat for var in vars_dt for feat in ["semester", "woty"]]
+            + [var + "_" + feat for var in vars_dt for feat in ["semester", "week"]]
         ],
     )
 
     # different order than they appear in the glossary
-    X = DatetimeFeatures(features_to_extract=["hour", "day_of_the_week"]).fit_transform(
+    X = DatetimeFeatures(features_to_extract=["hour", "day_of_week"]).fit_transform(
         df_datetime
     )
     pd.testing.assert_frame_equal(
         X,
         df_datetime_transformed[
             vars_non_dt
-            + [var + "_" + feat for var in vars_dt for feat in ["hour", "dotw"]]
+            + [var + "_" + feat for var in vars_dt for feat in ["hour", "day_of_week"]]
         ],
     )
 
@@ -343,7 +343,7 @@ def test_extract_features_without_dropping_original_variables(
 ):
     X = DatetimeFeatures(
         variables=["datetime_range", "date_obj2"],
-        features_to_extract=["week_of_the_year", "quarter"],
+        features_to_extract=["week", "quarter"],
         drop_original=False,
     ).fit_transform(df_datetime)
 
@@ -357,7 +357,7 @@ def test_extract_features_without_dropping_original_variables(
                 for feat in [
                     var + "_" + feat
                     for var in ["datetime_range", "date_obj2"]
-                    for feat in ["woty", "quarter"]
+                    for feat in ["week", "quarter"]
                 ]
             ],
             axis=1,
@@ -377,11 +377,11 @@ def test_extract_features_from_variables_containing_nans():
 
 def test_extract_features_with_different_datetime_parsing_options(df_datetime):
     X = DatetimeFeatures(
-        features_to_extract=["day_of_the_month"], dayfirst=True
+        features_to_extract=["day_of_month"], dayfirst=True
     ).fit_transform(df_datetime[["date_obj2"]])
     pd.testing.assert_frame_equal(
         X,
-        pd.DataFrame({"date_obj2_dotm": [10, 31, 30, 17]}),
+        pd.DataFrame({"date_obj2_day_of_month": [10, 31, 30, 17]}),
     )
 
     X = DatetimeFeatures(features_to_extract=["year"], yearfirst=True).fit_transform(
diff --git a/tests/test_discretisation/test_arbitrary_discretiser.py b/tests/test_discretisation/test_arbitrary_discretiser.py
index f100167..d721878 100644
--- a/tests/test_discretisation/test_arbitrary_discretiser.py
+++ b/tests/test_discretisation/test_arbitrary_discretiser.py
@@ -1,19 +1,28 @@
 import numpy as np
 import pandas as pd
-from sklearn.datasets import load_boston
+import pytest
+from numpy.random import default_rng
+from scipy.stats import skewnorm
+from sklearn.datasets import fetch_california_housing
 
 from feature_engine.discretisation import ArbitraryDiscretiser
 
 
 def test_arbitrary_discretiser():
-    boston_dataset = load_boston()
-    data = pd.DataFrame(boston_dataset.data, columns=boston_dataset.feature_names)
-    user_dict = {"LSTAT": [0, 10, 20, 30, np.Inf]}
+    california_dataset = fetch_california_housing()
+    data = pd.DataFrame(
+        california_dataset.data, columns=california_dataset.feature_names
+    )
+    user_dict = {"HouseAge": [0, 20, 40, 60, np.Inf]}
 
     data_t1 = data.copy()
     data_t2 = data.copy()
-    data_t1["LSTAT"] = pd.cut(data["LSTAT"], bins=[0, 10, 20, 30, np.Inf])
-    data_t2["LSTAT"] = pd.cut(data["LSTAT"], bins=[0, 10, 20, 30, np.Inf], labels=False)
+
+    # HouseAge is the median house age in the block group.
+    data_t1["HouseAge"] = pd.cut(data["HouseAge"], bins=[0, 20, 40, 60, np.Inf])
+    data_t2["HouseAge"] = pd.cut(
+        data["HouseAge"], bins=[0, 20, 40, 60, np.Inf], labels=False
+    )
 
     transformer = ArbitraryDiscretiser(
         binning_dict=user_dict, return_object=False, return_boundaries=False
@@ -24,7 +33,7 @@ def test_arbitrary_discretiser():
     assert transformer.return_object is False
     assert transformer.return_boundaries is False
     # fit params
-    assert transformer.variables_ == ["LSTAT"]
+    assert transformer.variables_ == ["HouseAge"]
     assert transformer.binner_dict_ == user_dict
     # transform params
     pd.testing.assert_frame_equal(X, data_t2)
@@ -34,3 +43,76 @@ def test_arbitrary_discretiser():
     )
     X = transformer.fit_transform(data)
     pd.testing.assert_frame_equal(X, data_t1)
+
+
+def test_error_if_input_df_contains_na_in_transform(df_vartypes, df_na):
+    # test case 1: when dataset contains na, transform method
+    age_dict = {"Age": [0, 10, 20, 30, np.Inf]}
+
+    with pytest.raises(ValueError):
+        transformer = ArbitraryDiscretiser(binning_dict=age_dict)
+        transformer.fit(df_vartypes)
+        transformer.transform(df_na[["Name", "City", "Age", "Marks", "dob"]])
+
+
+def test_error_when_nan_introduced_during_transform():
+    # test error when NA are introduced during the discretisation.
+    rng = default_rng()
+
+    # create dataframe with 2 variables, 1 normal and 1 skewed
+    random = skewnorm.rvs(a=-50, loc=4, size=100)
+    random = random - min(random)  # Shift so the minimum value is equal to zero.
+
+    train = pd.concat(
+        [
+            pd.Series(rng.standard_normal(100)),
+            pd.Series(random),
+        ],
+        axis=1,
+    )
+
+    train.columns = ["var_a", "var_b"]
+
+    # create a dataframe with 2 variables normally distributed
+    test = pd.concat(
+        [
+            pd.Series(rng.standard_normal(100)),
+            pd.Series(rng.standard_normal(100)),
+        ],
+        axis=1,
+    )
+
+    test.columns = ["var_a", "var_b"]
+
+    msg = (
+        "During the discretisation, NaN values were introduced "
+        "in the feature(s) var_b."
+    )
+
+    limits_dict = {"var_a": [-5, -2, 0, 2, 5], "var_b": [0, 2, 5]}
+
+    # check for warning when errors equals 'ignore'
+    with pytest.warns(UserWarning) as record:
+        transformer = ArbitraryDiscretiser(binning_dict=limits_dict, errors="ignore")
+        transformer.fit(train)
+        transformer.transform(test)
+
+    # check that only one warning was returned
+    assert len(record) == 1
+    # check that message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when errors equals 'raise'
+    with pytest.raises(ValueError) as record:
+        transformer = ArbitraryDiscretiser(binning_dict=limits_dict, errors="raise")
+        transformer.fit(train)
+        transformer.transform(test)
+
+    # check that error message matches
+    assert str(record.value) == msg
+
+
+def test_error_if_not_permitted_value_is_errors():
+    age_dict = {"Age": [0, 10, 20, 30, np.Inf]}
+    with pytest.raises(ValueError):
+        ArbitraryDiscretiser(binning_dict=age_dict, errors="medialuna")
diff --git a/tests/test_encoding/check_estimator_encoders.py b/tests/test_encoding/test_check_estimator_encoders.py
similarity index 100%
rename from tests/test_encoding/check_estimator_encoders.py
rename to tests/test_encoding/test_check_estimator_encoders.py
diff --git a/tests/test_encoding/test_count_frequency_encoder.py b/tests/test_encoding/test_count_frequency_encoder.py
index e8ef049..cb101ae 100644
--- a/tests/test_encoding/test_count_frequency_encoder.py
+++ b/tests/test_encoding/test_count_frequency_encoder.py
@@ -122,11 +122,30 @@ def test_error_if_input_df_contains_categories_not_present_in_fit_df(
 ):
     # test case 3: when dataset to be transformed contains categories not present in
     # training dataset
-    with pytest.warns(UserWarning):
-        encoder = CountFrequencyEncoder()
+
+    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 = CountFrequencyEncoder(errors="ignore")
+        encoder.fit(df_enc)
+        encoder.transform(df_enc_rare)
+
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.raises(ValueError) as record:
+        encoder = CountFrequencyEncoder(errors="raise")
+
         encoder.fit(df_enc)
         encoder.transform(df_enc_rare)
 
+    # check that the error message matches
+    assert str(record.value) == msg
+
 
 def test_fit_raises_error_if_df_contains_na(df_enc_na):
     # test case 4: when dataset contains na, fit method
@@ -238,3 +257,8 @@ def test_variables_cast_as_category(df_enc_category_dtypes):
     encoder = CountFrequencyEncoder(encoding_method="frequency", variables=["var_A"])
     X = encoder.fit_transform(df_enc_category_dtypes)
     assert X["var_A"].dtypes == float
+
+
+def test_error_if_rare_labels_not_permitted_value():
+    with pytest.raises(ValueError):
+        CountFrequencyEncoder(errors="empanada")
diff --git a/tests/test_encoding/test_mean_encoder.py b/tests/test_encoding/test_mean_encoder.py
index 706997a..144883d 100644
--- a/tests/test_encoding/test_mean_encoder.py
+++ b/tests/test_encoding/test_mean_encoder.py
@@ -126,11 +126,29 @@ def test_warning_if_transform_df_contains_categories_not_present_in_fit_df(
 ):
     # test case 4: when dataset to be transformed contains categories not present
     # in training dataset
-    with pytest.warns(UserWarning):
-        encoder = MeanEncoder()
+
+    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(errors="ignore")
         encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
         encoder.transform(df_enc_rare[["var_A", "var_B"]])
 
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.raises(ValueError) as record:
+        encoder = MeanEncoder(errors="raise")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that the error message matches
+    assert str(record.value) == msg
+
 
 def test_fit_raises_error_if_df_contains_na(df_enc_na):
     # test case 4: when dataset contains na, fit method
@@ -293,3 +311,8 @@ def test_variables_cast_as_category(df_enc_category_dtypes):
 
     pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]], check_dtype=False)
     assert X["var_A"].dtypes == float
+
+
+def test_error_if_rare_labels_not_permitted_value():
+    with pytest.raises(ValueError):
+        MeanEncoder(errors="empanada")
diff --git a/tests/test_encoding/test_ordinal_encoder.py b/tests/test_encoding/test_ordinal_encoder.py
index c1ab2e7..394cbb9 100644
--- a/tests/test_encoding/test_ordinal_encoder.py
+++ b/tests/test_encoding/test_ordinal_encoder.py
@@ -67,10 +67,27 @@ def test_error_if_input_df_contains_categories_not_present_in_training_df(
 ):
     # test case 4: when dataset to be transformed contains categories not present
     # in training dataset
-    with pytest.warns(UserWarning):
-        encoder = OrdinalEncoder(encoding_method="arbitrary")
-        encoder.fit(df_enc)
-        encoder.transform(df_enc_rare)
+    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 = OrdinalEncoder(errors="ignore")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.raises(ValueError) as record:
+        encoder = OrdinalEncoder(errors="raise")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that the error message matches
+    assert str(record.value) == msg
 
 
 def test_non_fitted_error(df_enc):
@@ -156,3 +173,8 @@ def test_variables_cast_as_category(df_enc_category_dtypes):
     # test transform output
     pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]], check_dtype=False)
     assert X["var_A"].dtypes == int
+
+
+def test_error_if_rare_labels_not_permitted_value():
+    with pytest.raises(ValueError):
+        OrdinalEncoder(errors="empanada")
diff --git a/tests/test_encoding/test_probability_ratio_encoder.py b/tests/test_encoding/test_probability_ratio_encoder.py
index fb98fa2..bee9bec 100644
--- a/tests/test_encoding/test_probability_ratio_encoder.py
+++ b/tests/test_encoding/test_probability_ratio_encoder.py
@@ -253,3 +253,34 @@ def test_logratio_on_numerical_variables(df_enc_numeric):
     # test error raise
     with pytest.raises(ValueError):
         PRatioEncoder(encoding_method="other")
+
+
+def test_warn_if_transform_df_contains_categories_not_seen_in_fit(df_enc, df_enc_rare):
+    # test case 3: when dataset to be transformed contains categories not present
+    # in training dataset
+    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 = PRatioEncoder(errors="ignore")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.raises(ValueError) as record:
+        encoder = PRatioEncoder(errors="raise")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that the error message matches
+    assert str(record.value) == msg
+
+
+def test_error_if_rare_labels_not_permitted_value():
+    with pytest.raises(ValueError):
+        PRatioEncoder(errors="empanada")
diff --git a/tests/test_encoding/test_woe_encoder.py b/tests/test_encoding/test_woe_encoder.py
index 535dfc2..d2df729 100644
--- a/tests/test_encoding/test_woe_encoder.py
+++ b/tests/test_encoding/test_woe_encoder.py
@@ -90,11 +90,28 @@ def test_error_target_is_not_passed(df_enc):
 def test_warn_if_transform_df_contains_categories_not_seen_in_fit(df_enc, df_enc_rare):
     # test case 3: when dataset to be transformed contains categories not present
     # in training dataset
-    encoder = WoEEncoder(variables=None)
-    with pytest.warns(UserWarning):
+    msg = "During the encoding, NaN values were introduced in the feature(s) var_A."
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.warns(UserWarning) as record:
+        encoder = WoEEncoder(errors="ignore")
+        encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
+        encoder.transform(df_enc_rare[["var_A", "var_B"]])
+
+    # check that only one warning was raised
+    assert len(record) == 1
+    # check that the message matches
+    assert record[0].message.args[0] == msg
+
+    # check for error when rare_labels equals 'raise'
+    with pytest.raises(ValueError) as record:
+        encoder = WoEEncoder(errors="raise")
         encoder.fit(df_enc[["var_A", "var_B"]], df_enc["target"])
         encoder.transform(df_enc_rare[["var_A", "var_B"]])
 
+    # check that the error message matches
+    assert str(record.value) == msg
+
 
 def test_error_if_target_not_binary():
     # test case 4: the target is not binary
@@ -292,3 +309,8 @@ def test_variables_cast_as_category(df_enc_category_dtypes):
 
     pd.testing.assert_frame_equal(X, transf_df[["var_A", "var_B"]], check_dtype=False)
     assert X["var_A"].dtypes == float
+
+
+def test_error_if_rare_labels_not_permitted_value():
+    with pytest.raises(ValueError):
+        WoEEncoder(errors="empanada")