Repository: pyjanitor-devs/pyjanitor

Issue:
transform_column proposed improvement
`transform_column(df, col_name: str, function)` could be augmented to include a destination column. Example use case is when you want to perform a `log10` transformation, as in the docs example, yet you also want to preserve the non-transformed data for some other purpose.


Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -140,7 +140,7 @@ def _remove_special(col):
 
     # Store the original column names, if enabled by user
     if preserve_original_columns:
-        df.__dict__['original_columns'] = original_column_names
+        df.__dict__["original_columns"] = original_column_names
     return df
 
 
@@ -545,16 +545,12 @@ def fill_empty(df, columns, value):
         for col in columns:
             assert (
                 col in df.columns
-            ), "{col} missing from dataframe columns!".format(
-                col=col
-            )
+            ), "{col} missing from dataframe columns!".format(col=col)
             df[col] = df[col].fillna(value)
     else:
         assert (
             columns in df.columns
-        ), "{col} missing from dataframe columns!".format(
-            col=columns
-        )
+        ), "{col} missing from dataframe columns!".format(col=columns)
         df[columns] = df[columns].fillna(value)
 
     return df
@@ -1399,7 +1395,7 @@ def _round_to_fraction(number, denominator, digits=np.inf):
 
 
 @pf.register_dataframe_method
-def transform_column(df, col_name: str, function):
+def transform_column(df, col_name: str, function, dest_col_name: str = None):
     """
     Transforms the given column in-place using the provided function.
 
@@ -1430,9 +1426,14 @@ def transform_column(df, col_name: str, function):
     :param df: A pandas DataFrame.
     :param col_name: The column to transform.
     :param function: A function to apply on the column.
+    :param dest_col_name: The column name to store the transformation result
+        in. By default, replaces contents of original column.
     """
 
-    df[col_name] = df[col_name].apply(function)
+    if dest_col_name is None:
+        dest_col_name = col_name
+
+    df[dest_col_name] = df[col_name].apply(function)
     return df
 
 


Test patch:
diff --git a/tests/test_functions.py b/tests/test_functions.py
--- a/tests/test_functions.py
+++ b/tests/test_functions.py
@@ -665,7 +665,7 @@ def test_add_column_iterator_repeat(dataframe):
 def test_row_to_names(dataframe):
     df = dataframe.row_to_names(2)
     assert df.columns[0] == 3
-    assert df.columns[1] == 3.2346125
+    assert df.columns[1] == 3.234_612_5
     assert df.columns[2] == 3
     assert df.columns[3] == "lion"
     assert df.columns[4] == "Basel"
@@ -674,7 +674,7 @@ def test_row_to_names(dataframe):
 def test_row_to_names_delete_this_row(dataframe):
     df = dataframe.row_to_names(2, remove_row=True)
     assert df.iloc[2, 0] == 1
-    assert df.iloc[2, 1] == 1.23452345
+    assert df.iloc[2, 1] == 1.234_523_45
     assert df.iloc[2, 2] == 1
     assert df.iloc[2, 3] == "rabbit"
     assert df.iloc[2, 4] == "Cambridge"
@@ -683,7 +683,7 @@ def test_row_to_names_delete_this_row(dataframe):
 def test_row_to_names_delete_above(dataframe):
     df = dataframe.row_to_names(2, remove_rows_above=True)
     assert df.iloc[0, 0] == 3
-    assert df.iloc[0, 1] == 3.2346125
+    assert df.iloc[0, 1] == 3.234_612_5
     assert df.iloc[0, 2] == 3
     assert df.iloc[0, 3] == "lion"
     assert df.iloc[0, 4] == "Basel"
@@ -703,12 +703,26 @@ def test_round_to_nearest_half(dataframe):
 
 
 def test_transform_column(dataframe):
+    # replacing the data of the original column
+
     df = dataframe.transform_column("a", np.log10)
     expected = pd.Series(np.log10([1, 2, 3] * 3))
     expected.name = "a"
     pd.testing.assert_series_equal(df["a"], expected)
 
 
+def test_transform_column_with_dest(dataframe):
+    # creating a new destination column
+
+    expected_df = dataframe.assign(a_log10=np.log10(dataframe["a"]))
+
+    df = dataframe.copy().transform_column(
+        "a", np.log10, dest_col_name="a_log10"
+    )
+
+    pd.testing.assert_frame_equal(df, expected_df)
+
+
 def test_min_max_scale(dataframe):
     df = dataframe.min_max_scale(col_name="a")
     assert df["a"].min() == 0