Repository: pyjanitor-devs/pyjanitor

Issue:
[ENH] Add variant for multiple column renaming
Same thing as we've done before - `<func>_column` to `<func>_columns` list iteration.


Discussion:
I would like to work on this enhancement.  

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -21,6 +21,7 @@
     _replace_original_empty_string_with_none,
     _strip_underscores,
     check,
+    check_column,
     deprecated_alias,
 )
 
@@ -370,7 +371,7 @@ def get_features_targets(
 @pf.register_dataframe_method
 @deprecated_alias(old="old_column_name", new="new_column_name")
 def rename_column(
-    df: pd.DataFrame, old_column_name, new_column_name
+    df: pd.DataFrame, old_column_name: str, new_column_name: str
 ) -> pd.DataFrame:
     """
     Rename a column in place.
@@ -397,17 +398,48 @@ def rename_column(
     changing, then use the :py:meth:`pandas.DataFrame.rename` method.
 
     :param df: The pandas DataFrame object.
-    :param str old_column_name: The old column name.
-    :param str new_column_name: The new column name.
+    :param old_column_name: The old column name.
+    :param new_column_name: The new column name.
     :returns: A pandas DataFrame with renamed columns.
     """
-    if old_column_name not in df.columns:
-        raise ValueError(
-            f"{old_column_name} not present in dataframe columns!"
-        )
+    check_column(df, [old_column_name])
+
     return df.rename(columns={old_column_name: new_column_name})
 
 
+@pf.register_dataframe_method
+def rename_columns(df: pd.DataFrame, new_column_names: Dict) -> pd.DataFrame:
+    """
+    Rename columns in place.
+
+    Functional usage example:
+
+    .. code-block:: python
+
+        df = rename_columns({"old_column_name": "new_column_name"})
+
+    Method chaining example:
+
+    .. code-block:: python
+
+        import pandas as pd
+        import janitor
+        df = pd.DataFrame(...).rename_columns({"old_column_name":
+        "new_column_name"})  # noqa: E501
+
+    This is just syntactic sugar/a convenience function for renaming one column
+    at a time. If you are convinced that there are multiple columns in need of
+    changing, then use the :py:meth:`pandas.DataFrame.rename` method.
+
+    :param df: The pandas DataFrame object.
+    :param new_column_names: A dictionary of old and new column names.
+    :returns: A pandas DataFrame with renamed columns.
+    """
+    check_column(df, list(new_column_names.keys()))
+
+    return df.rename(columns=new_column_names)
+
+
 @pf.register_dataframe_method
 def reorder_columns(
     df: pd.DataFrame, column_order: Union[Iterable[str], pd.Index, Any]
diff --git a/janitor/utils.py b/janitor/utils.py
--- a/janitor/utils.py
+++ b/janitor/utils.py
@@ -2,7 +2,7 @@
 
 import functools
 import warnings
-from typing import Callable, Dict, Union
+from typing import Callable, Dict, List, Union
 
 import pandas as pd
 
@@ -247,3 +247,11 @@ def rename_kwargs(func_name: str, kwargs: Dict, aliases: Dict):
                 DeprecationWarning,
             )
             kwargs[new_alias] = kwargs.pop(old_alias)
+
+
+def check_column(df: pd.DataFrame, old_column_names: List):
+    for column_name in old_column_names:
+        if column_name not in df.columns:
+            raise ValueError(
+                f"{column_name} not present in dataframe columns!"
+            )


Test patch:
diff --git a/tests/functions/test_rename_columns.py b/tests/functions/test_rename_columns.py
new file mode 100644
--- /dev/null
+++ b/tests/functions/test_rename_columns.py
@@ -0,0 +1,25 @@
+import pytest
+from hypothesis import given
+
+
+@pytest.mark.functions
+def test_rename_columns(dataframe):
+    df = dataframe.clean_names().rename_columns(
+        {"a": "index", "bell_chart": "chart"}
+    )
+    assert set(df.columns) == set(
+        ["index", "chart", "decorated_elephant", "animals@#$%^", "cities"]
+    )
+    assert "a" not in set(df.columns)
+
+
+@pytest.mark.functions
+def test_rename_columns_absent_column(dataframe):
+    """
+    rename_column should raise an error if the column is absent.
+    """
+    df = dataframe.copy()
+    with pytest.raises(ValueError):
+        df.clean_names().rename_columns({"a": "index", "bb": "chart"})
+
+    assert set(df.columns) == set(dataframe.columns)
diff --git a/tests/utils/test_check_column.py b/tests/utils/test_check_column.py
new file mode 100644
--- /dev/null
+++ b/tests/utils/test_check_column.py
@@ -0,0 +1,21 @@
+import pytest
+from hypothesis import given
+
+from janitor.utils import check_column
+
+
+@pytest.mark.utils
+def test_check_column(dataframe):
+    """
+    rename_column should return true if column exist
+    """
+    assert check_column(dataframe, ["a"]) is None
+
+
+@pytest.mark.utils
+def test_check_column_absent_column(dataframe):
+    """
+    rename_column should raise an error if the column is absent.
+    """
+    with pytest.raises(ValueError):
+        check_column(dataframe, ["b"])