Repository: pyjanitor-devs/pyjanitor

Issue:
bin_numeric function
I cam across this in my data cleaning routine, where I wanted to bin my numeric variables into a categorial column.  Would this be of interest to you to add to pyjanitor?  I don't know if there is already a function for this, I looked but didn't see it.  Let me know if there is something like this already or if you think it is worth adding. Thanks.


![image](https://user-images.githubusercontent.com/20308031/57183915-6b354300-6e81-11e9-9cdd-dd33c41fb402.png)



Discussion:
@jcvall to the best of my knowledge, this definitely does not exist! I would love to see it in pyjanitor :smile:. Don't forget to pull in the latest changes into your fork of pyjanitor!
@ericmjl If nobody else is currently working on this, I could give it a go. 
Sure...no objections. Have at it.  I have a PR request in so talk to @ericmjl  the best way to handle. 
@jcvall I'd love to invite you to review @kurtispinkney's PR when it comes up! @kurtispinkney, don't forget to reference this issue in your branch name - apparently there's GitHub superpowers associated with putting issue numbers in your branch (i.e. `feature-branch-#291`).
For inspiration: `R` has a built-in method called `cut` which does this: https://www.dummies.com/programming/r/how-to-create-subgroups-of-data-in-r/

I like that yours has a `labels` arg since that's probably the most important aspect of such a function.

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -2725,6 +2725,42 @@ def groupby_agg(
     return df
 
 
+@pf.register_dataframe_method
+def bin_numeric(
+    df: pd.DataFrame,
+    from_column: str,
+    to_column: str,
+    num_bins: int = 5,
+    labels: str = None,
+):
+    """
+    Makes use of pandas cut() function to bin data of one column, generating a
+    new column with the results.
+
+
+    :param df: A pandas DataFrame.
+    :param from_column: The column whose data you want binned.
+    :param to_column: The new column to be created with the binned data.
+    :param num_bins: The number of bins to be utilized.
+    :param labels: Optionally rename numeric bin ranges with labels. Number of
+    label names must match number of bins specified.
+
+    :return: A pandas DataFrame.
+    """
+
+    if not labels:
+        df[str(to_column)] = pd.cut(df[str(from_column)], bins=num_bins)
+    else:
+        if not len(labels) == num_bins:
+            raise ValueError(f"Number of labels must match number of bins.")
+
+        df[str(to_column)] = pd.cut(
+            df[str(from_column)], bins=num_bins, labels=labels
+        )
+
+    return df
+
+
 @pf.register_dataframe_method
 def drop_duplicate_columns(
     df: pd.DataFrame, column_name: str, nth_index: int = 0


Test patch:
diff --git a/tests/functions/test_bin_numeric.py b/tests/functions/test_bin_numeric.py
new file mode 100644
--- /dev/null
+++ b/tests/functions/test_bin_numeric.py
@@ -0,0 +1,31 @@
+import pytest
+from hypothesis import given
+from janitor.testing_utils.strategies import df_strategy
+
+
+@pytest.mark.functions
+@given(df=df_strategy())
+def test_bin_numeric_expected_columns(df):
+
+    df = df.bin_numeric(from_column="a", to_column="a_bin")
+    expected_columns = [
+        "a",
+        "Bell__Chart",
+        "decorated-elephant",
+        "animals@#$%^",
+        "cities",
+        "a_bin",
+    ]
+
+    assert set(df.columns) == set(expected_columns)
+
+
+@pytest.mark.functions
+@given(df=df_strategy())
+def test_bin_numeric_num_labels(df):
+
+    with pytest.raises(ValueError):
+        labels = ["a", "b", "c", "d", "e"]
+        df.bin_numeric(
+            from_column="a", to_column="a_bin", num_bins=6, labels=labels
+        )