Repository: pandas-dev/pandas

Issue:
groupby.rank 'na_option="bottom"' Usage Clarification
#### Code Sample, a copy-pastable example if possible

```python
In [1]: import pandas as pd
   ...: import numpy as np
   ...: df = pd.DataFrame({'val': [2, np.nan, 2, 8, 2, np.nan, 6]})
   ...: df["key"] = pd.Series(["foo"]*7)

In [2]: df
Out[2]:
   val  key
0  2.0  foo
1  NaN  foo
2  2.0  foo
3  8.0  foo
4  2.0  foo
5  NaN  foo
6  6.0  foo

In [5]: df.groupby("key").rank(na_option="not bottom")
Out[5]:
   val
0  2.0
1  6.5
2  2.0
3  5.0
4  2.0
5  6.5
6  4.0

```
#### Problem description

When an invalid value is passed to ``groupby.rank`` for ``na_option`` argument. It didn't raise a ValueError as expected. The same behavior will raise a  ValueError("na_option must be one of 'keep', 'top', or 'bottom'") in ``DataFrame.rank`` or ``Series.rank``
 The expected output is derived from #19499
#### Expected Output
```python
In [1]: import pandas as pd
   ...: import numpy as np
   ...: df = pd.DataFrame({'val': [2, np.nan, 2, 8, 2, np.nan, 6]})
   ...: df["key"] = pd.Series(["foo"]*7)
   ...:

In [2]: df.rank(na_option="no bottom")
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-ec2afc565a7d> in <module>()
----> 1 df.rank(na_option="no bottom")

C:\Users\Public\pandas-peter\pandas\core\generic.py in rank(self, axis, method,
numeric_only, na_option, ascending, pct)
   7523         if na_option not in {'keep', 'top', 'bottom'}:
   7524             msg = "na_option must be one of 'keep', 'top', or 'bottom'"
-> 7525             raise ValueError(msg)
   7526
   7527         def ranker(data):

ValueError: na_option must be one of 'keep', 'top', or 'bottom'
```
#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: d30c4a0696d5fbdc3c7ce36a9b9b19224a557e09
python: 3.6.4.final.0
python-bits: 64
OS: Windows
OS-release: 7
machine: AMD64
processor: Intel64 Family 6 Model 60 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: zh_CN.UTF-8
LOCALE: None.None

pandas: 0.24.0.dev0+377.gd30c4a069
pytest: 3.3.2
pip: 9.0.1
setuptools: 38.4.0
Cython: 0.28.4
numpy: 1.14.0
scipy: 1.0.0
pyarrow: None
xarray: None
IPython: 6.2.1
sphinx: 1.6.7
patsy: 0.5.0
dateutil: 2.6.1
pytz: 2017.3
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.4
feather: None
matplotlib: 2.1.2
openpyxl: 2.4.10
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 1.0.2
lxml: 4.1.1
bs4: 4.6.0
html5lib: 1.0.1
sqlalchemy: 1.2.1
pymysql: 0.7.11.None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>



Discussion:

Resolving patch:
diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py
--- a/pandas/core/groupby/groupby.py
+++ b/pandas/core/groupby/groupby.py
@@ -1705,6 +1705,9 @@ def rank(self, method='average', ascending=True, na_option='keep',
         -----
         DataFrame with ranking of values within each group
         """
+        if na_option not in {'keep', 'top', 'bottom'}:
+            msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+            raise ValueError(msg)
         return self._cython_transform('rank', numeric_only=False,
                                       ties_method=method, ascending=ascending,
                                       na_option=na_option, pct=pct, axis=axis)


Test patch:
diff --git a/pandas/tests/groupby/test_rank.py b/pandas/tests/groupby/test_rank.py
--- a/pandas/tests/groupby/test_rank.py
+++ b/pandas/tests/groupby/test_rank.py
@@ -172,35 +172,35 @@ def test_infs_n_nans(grps, vals, ties_method, ascending, na_option, exp):
         [3., 3., np.nan, 1., 3., 2., np.nan, np.nan]),
     ('dense', False, 'keep', True,
         [3. / 3., 3. / 3., np.nan, 1. / 3., 3. / 3., 2. / 3., np.nan, np.nan]),
-    ('average', True, 'no_na', False, [2., 2., 7., 5., 2., 4., 7., 7.]),
-    ('average', True, 'no_na', True,
+    ('average', True, 'bottom', False, [2., 2., 7., 5., 2., 4., 7., 7.]),
+    ('average', True, 'bottom', True,
         [0.25, 0.25, 0.875, 0.625, 0.25, 0.5, 0.875, 0.875]),
-    ('average', False, 'no_na', False, [4., 4., 7., 1., 4., 2., 7., 7.]),
-    ('average', False, 'no_na', True,
+    ('average', False, 'bottom', False, [4., 4., 7., 1., 4., 2., 7., 7.]),
+    ('average', False, 'bottom', True,
         [0.5, 0.5, 0.875, 0.125, 0.5, 0.25, 0.875, 0.875]),
-    ('min', True, 'no_na', False, [1., 1., 6., 5., 1., 4., 6., 6.]),
-    ('min', True, 'no_na', True,
+    ('min', True, 'bottom', False, [1., 1., 6., 5., 1., 4., 6., 6.]),
+    ('min', True, 'bottom', True,
         [0.125, 0.125, 0.75, 0.625, 0.125, 0.5, 0.75, 0.75]),
-    ('min', False, 'no_na', False, [3., 3., 6., 1., 3., 2., 6., 6.]),
-    ('min', False, 'no_na', True,
+    ('min', False, 'bottom', False, [3., 3., 6., 1., 3., 2., 6., 6.]),
+    ('min', False, 'bottom', True,
         [0.375, 0.375, 0.75, 0.125, 0.375, 0.25, 0.75, 0.75]),
-    ('max', True, 'no_na', False, [3., 3., 8., 5., 3., 4., 8., 8.]),
-    ('max', True, 'no_na', True,
+    ('max', True, 'bottom', False, [3., 3., 8., 5., 3., 4., 8., 8.]),
+    ('max', True, 'bottom', True,
         [0.375, 0.375, 1., 0.625, 0.375, 0.5, 1., 1.]),
-    ('max', False, 'no_na', False, [5., 5., 8., 1., 5., 2., 8., 8.]),
-    ('max', False, 'no_na', True,
+    ('max', False, 'bottom', False, [5., 5., 8., 1., 5., 2., 8., 8.]),
+    ('max', False, 'bottom', True,
         [0.625, 0.625, 1., 0.125, 0.625, 0.25, 1., 1.]),
-    ('first', True, 'no_na', False, [1., 2., 6., 5., 3., 4., 7., 8.]),
-    ('first', True, 'no_na', True,
+    ('first', True, 'bottom', False, [1., 2., 6., 5., 3., 4., 7., 8.]),
+    ('first', True, 'bottom', True,
         [0.125, 0.25, 0.75, 0.625, 0.375, 0.5, 0.875, 1.]),
-    ('first', False, 'no_na', False, [3., 4., 6., 1., 5., 2., 7., 8.]),
-    ('first', False, 'no_na', True,
+    ('first', False, 'bottom', False, [3., 4., 6., 1., 5., 2., 7., 8.]),
+    ('first', False, 'bottom', True,
         [0.375, 0.5, 0.75, 0.125, 0.625, 0.25, 0.875, 1.]),
-    ('dense', True, 'no_na', False, [1., 1., 4., 3., 1., 2., 4., 4.]),
-    ('dense', True, 'no_na', True,
+    ('dense', True, 'bottom', False, [1., 1., 4., 3., 1., 2., 4., 4.]),
+    ('dense', True, 'bottom', True,
      [0.25, 0.25, 1., 0.75, 0.25, 0.5, 1., 1.]),
-    ('dense', False, 'no_na', False, [3., 3., 4., 1., 3., 2., 4., 4.]),
-    ('dense', False, 'no_na', True,
+    ('dense', False, 'bottom', False, [3., 3., 4., 1., 3., 2., 4., 4.]),
+    ('dense', False, 'bottom', True,
      [0.75, 0.75, 1., 0.25, 0.75, 0.5, 1., 1.])
 ])
 def test_rank_args_missing(grps, vals, ties_method, ascending,
@@ -252,14 +252,24 @@ def test_rank_object_raises(ties_method, ascending, na_option,
     with tm.assert_raises_regex(TypeError, "not callable"):
         df.groupby('key').rank(method=ties_method,
                                ascending=ascending,
-                               na_option='bad', pct=pct)
+                               na_option=na_option, pct=pct)
 
-    with tm.assert_raises_regex(TypeError, "not callable"):
-        df.groupby('key').rank(method=ties_method,
-                               ascending=ascending,
-                               na_option=True, pct=pct)
 
-    with tm.assert_raises_regex(TypeError, "not callable"):
+@pytest.mark.parametrize("na_option", [True, "bad", 1])
+@pytest.mark.parametrize("ties_method", [
+    'average', 'min', 'max', 'first', 'dense'])
+@pytest.mark.parametrize("ascending", [True, False])
+@pytest.mark.parametrize("pct", [True, False])
+@pytest.mark.parametrize("vals", [
+    ['bar', 'bar', 'foo', 'bar', 'baz'],
+    ['bar', np.nan, 'foo', np.nan, 'baz'],
+    [1, np.nan, 2, np.nan, 3]
+])
+def test_rank_naoption_raises(ties_method, ascending, na_option, pct, vals):
+    df = DataFrame({'key': ['foo'] * 5, 'val': vals})
+    msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+
+    with tm.assert_raises_regex(ValueError, msg):
         df.groupby('key').rank(method=ties_method,
                                ascending=ascending,
                                na_option=na_option, pct=pct)