Repository: feature-engine/feature_engine
Pull request #471 (2022-06-11 09:30:39)

Add inverse boxcox transform functionality to Box Cox Transformer
**Reference Issues/PRs**:
Issue : #449 (Add inverse Boxcox transform functionality)

**What does this implement/fix?**
Adds an inverse transform method to Boxcox transform

Please let me know if I missed something / did it incorrectly, ill fix it asap.

solegalli: Hi @SangamSwadiK 

The code looks great. Thank you!

We would have to add a test here to ensure that the inverse_transform returns the desired functionality:

https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/tests/test_transformation/test_boxcox_transformer.py#L8-L24

Would you be able to do that?

For inspiration you could look at how we do it for the LogTransformer here:
https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/tests/test_transformation/test_log_transformer.py#L8-L35
SangamSwadiK: > Hi @SangamSwadiK
> 
> The code looks great. Thank you!
> 
> We would have to expand this test to ensure that the inverse_transform returns the desired functionality:
> 
> https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/tests/test_transformation/test_boxcox_transformer.py#L8-L24
> 
> Would you be able to do that?
> 
> For inspiration you could look at how we do it for the LogTransformer here:
> 
> https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/tests/test_transformation/test_log_transformer.py#L8-L35

@solegalli  I've added test for inverse transform. Please let me know if more changes are required.
Thanks.
SangamSwadiK: > Hi @SangamSwadiK
> 
> thanks a lot for the quick turnaround!
> 
> This is almost ready. I should have mentioned before that we need to add the `inverse_transform` to the docstings of the class.
> 
> See for example how we bring the documentation forward for the log transformer using `{inverse_transform} ` here:
> 
> https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/feature_engine/transformation/log.py#L64-L73
> 
> which we import from our base docstrings like we do here:
> 
> https://github.com/feature-engine/feature_engine/blob/c16e989a933c03959a22131514e782d1b690af74/feature_engine/transformation/log.py#L10-L14
> 
> So if you could add the 2 lines of code above and also rephrase the sentence in the `inverse_transform` method that I point to in a following message, then, this is ready to go!!
> 
> Thank you so much!

@solegalli Sorry, I forgot to update the class docs, I've did the changes now.
Thanks.
solegalli: Thank you @SangamSwadiK ! 

Merging as we speak :)



Patch:
diff --git a/feature_engine/transformation/boxcox.py b/feature_engine/transformation/boxcox.py
index 60666d6..7638954 100644
--- a/feature_engine/transformation/boxcox.py
+++ b/feature_engine/transformation/boxcox.py
@@ -5,16 +5,20 @@ from typing import List, Optional, Union
 
 import pandas as pd
 import scipy.stats as stats
+from scipy.special import inv_boxcox
 
-from feature_engine.base_transformers import BaseNumericalTransformer
-from feature_engine._docstrings.methods import _fit_transform_docstring
+from feature_engine._docstrings.class_inputs import _variables_numerical_docstring
 from feature_engine._docstrings.fit_attributes import (
-    _variables_attribute_docstring,
     _feature_names_in_docstring,
     _n_features_in_docstring,
+    _variables_attribute_docstring,
+)
+from feature_engine._docstrings.methods import (
+    _fit_transform_docstring,
+    _inverse_transform_docstring,
 )
-from feature_engine._docstrings.class_inputs import _variables_numerical_docstring
 from feature_engine._docstrings.substitute import Substitution
+from feature_engine.base_transformers import BaseNumericalTransformer
 from feature_engine.tags import _return_tags
 from feature_engine.variable_manipulation import _check_input_parameter_variables
 
@@ -25,6 +29,7 @@ from feature_engine.variable_manipulation import _check_input_parameter_variable
     feature_names_in_=_feature_names_in_docstring,
     n_features_in_=_n_features_in_docstring,
     fit_transform=_fit_transform_docstring,
+    inverse_transform=_inverse_transform_docstring,
 )
 class BoxCoxTransformer(BaseNumericalTransformer):
     """
@@ -74,6 +79,8 @@ class BoxCoxTransformer(BaseNumericalTransformer):
 
     {fit_transform}
 
+    {inverse_transform}
+
     transform:
         Apply the BoxCox transformation.
 
@@ -138,6 +145,30 @@ class BoxCoxTransformer(BaseNumericalTransformer):
 
         return X
 
+    def inverse_transform(self, X: pd.DataFrame) -> pd.DataFrame:
+        """
+        Convert the data back to the original representation.
+
+        Parameters
+        ----------
+        X: Pandas DataFrame of shape = [n_samples, n_features]
+            The data to be inverse transformed.
+
+        Returns
+        -------
+        X_new: pandas dataframe
+            The dataframe with the original variables.
+        """
+
+        # check input dataframe and if class was fitted
+        X = super().transform(X)
+
+        # inverse transform
+        for feature in self.variables_:
+            X[feature] = inv_boxcox(X[feature], self.lambda_dict_[feature])
+
+        return X
+
     def _more_tags(self):
         tags_dict = _return_tags()
         tags_dict["variables"] = "numerical"


Test patch:
diff --git a/tests/test_transformation/test_boxcox_transformer.py b/tests/test_transformation/test_boxcox_transformer.py
index 03b1184..1681402 100644
--- a/tests/test_transformation/test_boxcox_transformer.py
+++ b/tests/test_transformation/test_boxcox_transformer.py
@@ -23,6 +23,16 @@ def test_automatically_finds_variables(df_vartypes):
     # test transform output
     pd.testing.assert_frame_equal(X, transf_df)
 
+    # test inverse_transform
+    Xit = transformer.inverse_transform(X)
+
+    # convert numbers to original format.
+    Xit["Age"] = Xit["Age"].round().astype("int64")
+    Xit["Marks"] = Xit["Marks"].round(1)
+
+    # test
+    pd.testing.assert_frame_equal(Xit, df_vartypes)
+
 
 def test_fit_raises_error_if_df_contains_na(df_na):
     # test case 2: when dataset contains na, fit method