Repository: pyjanitor-devs/pyjanitor

Issue:
What's the best name for a "keep null rows" function?
I have this function implemented in a notebook.

```python
@pf.register_dataframe_method
def keep_null(df, column):
    return df[df[column].isnull() == True]
```

From searching SO and the interwebs, there doesn't seem to be an analogous function like this. Is `keep_null` a good name, or is there a better name for this function?

cc: @szuckerman @zbarry @eli-s-goldberg


Discussion:
Something along the lines of gather_incomplete_rows?
Do we have a two-word name for it?
`gather_incompleterows` or `gatherincomplete_rows` ? :thinking: 
`gather_recordswithnulls`?

On Tue, Feb 19, 2019 at 10:13 AM Zachary Barry <notifications@github.com>
wrote:

> gather_incompleterows or gatherincomplete_rows ? 🤔
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/ericmjl/pyjanitor/issues/132#issuecomment-465168934>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AL2zVpjQP04ewO7WJeAxdIEi3X6XHUJZks5vPBSOgaJpZM4bC9JL>
> .
>

`gather_only_recordswithnullsinthecolumnyouhavespecified`
You guys... XD
Maybe there's a better grammar that we can define.
Just a thought... we should be careful to be consistent with making functions that either take one column name or an arbitrary number of column names across relevant functions. I'd lean towards the latter so there isn't as much `.operate_on_column('col1').operate_on_column('col2').operate_on_column('col3').operate_on_column('col4').operate_on_column('col5').operate_on_column('col6').operate_on_column('col7').operate_on_column('col8')...`
Agreed! I think one example we already have is the `*_column()` paired with `*_columns()` as a companion function. What do you all think about that?
I don't really mind that as long as we consider the energy to maintain both variants of *_ not a problem
What about dropping the word `column` completely? We could then pass either a `list` or `str` of the columns.

There are many examples of this; one that comes to mind is `groupby` in pandas. You just do `df.groupby(['col1', 'col2'])` or `df.groupby('col1')`. It's a common pattern that I think many people are used to.

This way we don't need to maintain `*_column` and `*_columns` variants.
On the *_column(s) thing, that sounds like a good idea. Might be a good change for 0.17. (I now realize I bumped minor version too aggressively early on!)

On the keep null rows function name, I just remembered: this is a bit like the opposite of `dropna`, so a helpful name would be `keepna`! What do you all think?
I think `keepna` is a bit ambiguous since it doesn't imply that the non-na are getting dropped.

I would think `dropnotna` is a bit better for getting the point across that it's the opposite of `dropna`. Also since the `notna` paradigm [already exists in pandas](https://pandas.pydata.org/pandas-docs/version/0.23.4/generated/pandas.notna.html).

Resolving patch:
diff --git a/janitor/functions.py b/janitor/functions.py
--- a/janitor/functions.py
+++ b/janitor/functions.py
@@ -2312,7 +2312,7 @@ def select_columns(df: pd.DataFrame, columns: List, invert: bool = False):
 
     Method-chaining example:
 
-    ..code-block:: python
+    .. code-block:: python
 
         df = pd.DataFrame(...).select_columns(['a', 'b', 'c'], invert=True)
 
@@ -2341,7 +2341,7 @@ def impute(df, column: str, value=None, statistic=None):
 
     Method-chaining example:
 
-    ..code-block:: python
+    .. code-block:: python
 
         df = (
             pd.DataFrame(...)
@@ -2413,16 +2413,30 @@ def impute(df, column: str, value=None, statistic=None):
 @pf.register_dataframe_method
 def then(df: pd.DataFrame, func) -> pd.DataFrame:
     """
-    :Description:
-
     Add an arbitrary function to run in the pyJanitor method chain.
 
     :param df: A pandas dataframe.
-    :param func: A function you would like to run in the method chain.\
-    It should take one parameter and return one parameter, each being the\
-    DataFrame object. After that, do whatever you want in the middle.\
-    Go crazy.
+    :param func: A function you would like to run in the method chain.
+        It should take one parameter and return one parameter, each being the
+        DataFrame object. After that, do whatever you want in the middle.
+        Go crazy.
     """
-
     df = func(df)
     return df
+
+
+@pf.register_dataframe_method
+def dropnotnull(df, column: str):
+    """
+    Drop rows that do not have null values in the given column.
+
+    Example usage:
+
+    .. code-block:: python
+
+        df = pd.DataFrame(...).dropnotnull('column3')
+
+    :param column: The column name to drop rows from.
+    :param df: A pandas DataFrame.
+    """
+    return df[pd.isnull(df[column])]


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,12 +1,11 @@
-import numpy as np
 import os
 
+import numpy as np
 import pandas as pd
 import pytest
 
 from janitor.testing_utils import date_data
 
-
 TEST_DATA_DIR = "tests/test_data"
 EXAMPLES_DIR = "examples/"
 
diff --git a/tests/functions/test_dropnotnull.py b/tests/functions/test_dropnotnull.py
new file mode 100644
--- /dev/null
+++ b/tests/functions/test_dropnotnull.py
@@ -0,0 +1,12 @@
+import pandas as pd
+import pytest
+
+
+@pytest.mark.functions
+def test_dropnotnull(missingdata_df):
+    df = missingdata_df.clean_names()
+    df_drop = df.dropnotnull("bell_chart")
+
+    assert pd.isnull(df_drop["bell_chart"]).all()
+
+    pd.testing.assert_frame_equal(df.loc[df_drop.index], df_drop)