Repository: pyjanitor-devs/pyjanitor

Issue:
[BUG] Warning message in `coalesce`
# Brief Description

`bfill` and `ffill`  are used in `coalesce` and in the future, will be keyword-only;

# System Information

<!-- System information helps us. To keep things simple, just let us know the OS and Python version first.
You can provide the optional information later. -->

- Operating system: Linux
- Python version (required): 3.9

# Minimally Reproducible Code

```python
df.coalesce(column_names=...)
/home/sam/miniforge3/envs/dev/lib/python3.9/site-packages/janitor/functions.py:1057: FutureWarning: In a future version of pandas all arguments of DataFrame.bfill will be keyword-only
  outcome = df.filter(column_names).bfill(1).ffill(1).iloc[:, 0]
/home/sam/miniforge3/envs/dev/lib/python3.9/site-packages/janitor/functions.py:1057: FutureWarning: In a future version of pandas all arguments of DataFrame.ffill will be keyword-only
  outcome = df.filter(column_names).bfill(1).ffill(1).iloc[:, 0]
```



Discussion:

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -1008,7 +1008,7 @@ def reorder_columns(
 @deprecated_alias(columns="column_names", new_column_name="target_column_name")
 def coalesce(
     df: pd.DataFrame,
-    column_names: Iterable[Hashable],
+    *column_names,
     target_column_name: Optional[str] = None,
     default_value: Optional[Union[int, float, str]] = None,
 ) -> pd.DataFrame:
@@ -1035,7 +1035,7 @@ def coalesce(
         1  2.0  10.0  10
         2  NaN   NaN   7
 
-        df.coalesce(column_names = ['A', 'B', 'C'],
+        df.coalesce('A', 'B', 'C',
                     target_column_name = 'D')
 
             A     B   C    D
@@ -1046,7 +1046,7 @@ def coalesce(
     If no target column is provided, then the first column is updated,
     with the null values removed::
 
-        df.coalesce(column_names = ['A', 'B', 'C'])
+        df.coalesce('A', 'B', 'C')
 
             A     B   C
         0  1.0   NaN   5
@@ -1065,7 +1065,7 @@ def coalesce(
         3  9.0  9.0
         4  9.0  9.0
 
-        df.coalesce(column_names = ['s1', 's2'],
+        df.coalesce('s1', 's2',
                     target_column_name = 's3',
                     default_value = 0)
 
@@ -1081,7 +1081,7 @@ def coalesce(
 
     .. code-block:: python
 
-        df = coalesce(df, columns=['col1', 'col2'], 'col3')
+        df = coalesce(df, 'col1', 'col2', target_column_name ='col3')
 
     Method chaining syntax:
 
@@ -1089,7 +1089,7 @@ def coalesce(
 
         import pandas as pd
         import janitor
-        df = pd.DataFrame(...).coalesce(['col1', 'col2'])
+        df = pd.DataFrame(...).coalesce('col1', 'col2')
 
     The first example will create a new column called 'col3' with values from
     'col2' inserted where values from 'col1' are NaN.
@@ -1112,12 +1112,6 @@ def coalesce(
     :raises ValueError: if length of `column_names` is less than 2.
     """
 
-    check("column_names", column_names, [list])
-    if target_column_name:
-        check("target_column_name", target_column_name, [str])
-    if default_value:
-        check("default_value", default_value, [int, float, str])
-
     if not column_names:
         return df
 
@@ -1128,12 +1122,24 @@ def coalesce(
             should be a minimum of 2.
             """
         )
-    check_column(df, column_names)
+
+    column_names = [*column_names]
+
+    column_names = _select_columns(column_names, df)
+    if target_column_name:
+        check("target_column_name", target_column_name, [str])
+    if default_value:
+        check("default_value", default_value, [int, float, str])
 
     if target_column_name is None:
         target_column_name = column_names[0]
     # bfill/ffill combo is faster than combine_first
-    outcome = df.filter(column_names).bfill(axis=1).ffill(axis=1).iloc[:, 0]
+    outcome = (
+        df.filter(column_names)
+        .bfill(axis="columns")
+        .ffill(axis="columns")
+        .iloc[:, 0]
+    )
     if outcome.hasnans and (default_value is not None):
         outcome = outcome.fillna(default_value)
     return df.assign(**{target_column_name: outcome})


Test patch:
diff --git a/tests/functions/test_coalesce.py b/tests/functions/test_coalesce.py
--- a/tests/functions/test_coalesce.py
+++ b/tests/functions/test_coalesce.py
@@ -11,6 +11,7 @@ def df():
     )
 
 
+@pytest.mark.xfail(reason="column_names is a variable args")
 def test_wrong_type_column_names(df):
     """Raise Error if wrong type is provided for `column_names`."""
     with pytest.raises(TypeError):
@@ -20,42 +21,32 @@ def test_wrong_type_column_names(df):
 def test_wrong_type_target_column_name(df):
     """Raise TypeError if wrong type is provided for `target_column_name`."""
     with pytest.raises(TypeError):
-        df.coalesce(["a", "b"], target_column_name=["new_name"])
+        df.coalesce("a", "b", target_column_name=["new_name"])
 
 
 def test_wrong_type_default_value(df):
     """Raise TypeError if wrong type is provided for `default_value`."""
     with pytest.raises(TypeError):
         df.coalesce(
-            ["a", "b"], target_column_name="new_name", default_value=[1, 2, 3]
+            "a", "b", target_column_name="new_name", default_value=[1, 2, 3]
         )
 
 
 def test_len_column_names_less_than_2(df):
     """Raise Error if column_names length is less than 2."""
     with pytest.raises(ValueError):
-        df.coalesce(["a"])
+        df.coalesce("a")
 
 
 def test_empty_column_names(df):
     """Return dataframe if `column_names` is empty."""
-    assert_frame_equal(df.coalesce([]), df)
+    assert_frame_equal(df.coalesce(), df)
 
 
 @pytest.mark.functions
-def test_coalesce_with_title(df):
-    """Test output if `target_column_name` is provided."""
-    result = df.coalesce(["a", "b", "c"], "d")
-    expected_output = df.assign(
-        d=df["a"].combine_first(df["b"].combine_first(df["c"]))
-    )
-    assert_frame_equal(result, expected_output)
-
-
-@pytest.mark.functions
-def test_coalesce_without_title(df):
+def test_coalesce_without_target(df):
     """Test output if `target_column_name` is not provided."""
-    result = df.coalesce(["a", "b", "c"])
+    result = df.coalesce("a", "b", "c")
     expected_output = df.assign(
         a=df["a"].combine_first(df["b"].combine_first(df["c"]))
     )
@@ -69,7 +60,5 @@ def test_coalesce_without_delete():
         {"s1": [np.nan, np.nan, 6, 9, 9], "s2": [np.nan, 8, 7, 9, 9]}
     )
     expected = df.assign(s3=df.s1.combine_first(df.s2).fillna(0))
-    result = df.coalesce(
-        ["s1", "s2"], target_column_name="s3", default_value=0
-    )
+    result = df.coalesce("s1", "s2", target_column_name="s3", default_value=0)
     assert_frame_equal(result, expected)