Repository: pandas-dev/pandas

Issue:
Rank 'na_option="bottom"' Usage Clarification
#### Code Sample, a copy-pastable example if possible

```python
df = pd.DataFrame({'val': [2, np.nan, 2, 8, 2, np.nan, 6]})

# Works as documented - missing values are highest rank
In []: df.rank(na_option='top')
Out []: 
   val
0  4.0
1  1.5
2  4.0
3  7.0
4  4.0
5  1.5
6  6.0

# Technically works - missing values are lowest rank
In []: df.rank(na_option='bottom')
Out []: 
   val
0  2.0
1  6.5
2  2.0
3  5.0
4  2.0
5  6.5
6  4.0 

# However, we could say anything besides 'foo'
In []: df.rank(na_option='foo')
Out []: 
   val
0  2.0
1  6.5
2  2.0
3  5.0
4  2.0
5  6.5
6  4.0 
```
#### Problem description

For the sake of being explicit it would be better to raise for an unknown `na_option`, or alternately update the documentation to reflect that any value outside of 'keep' and 'top' would trigger this behavior

<details>

INSTALLED VERSIONS
------------------
commit: d3f7d2a666aa824e2df98083aa5c1fd9bb63252e
python: 3.6.3.final.0
python-bits: 64
OS: Darwin
OS-release: 17.4.0
machine: x86_64
processor: i386
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.23.0.dev0+169.gd3f7d2a66.dirty
pytest: 3.2.1
pip: 9.0.1
setuptools: 36.5.0.post20170921
Cython: 0.26.1
numpy: 1.13.3
scipy: 1.0.0
pyarrow: 0.8.0
xarray: 0.10.0
IPython: 6.2.1
sphinx: 1.6.3
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.4
feather: 0.4.0
matplotlib: 2.1.1
openpyxl: 2.5.0b1
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.1.13
pymysql: 0.7.11.None
psycopg2: None
jinja2: 2.10
s3fs: 0.1.2
fastparquet: 0.1.3
pandas_gbq: None
pandas_datareader: None
</details>



Discussion:
I think raising a value error is better so that users will know when a argument is passed by mistake, e.g 
``` df.rank(na_option=True)```
@peterpanmj agreed on the `ValueError` - PRs are welcome if interested!
I'm trying to create a PR for this, but I keep getting the error 
`remote: Permission to pandas-dev/pandas.git denied to raguiar2.`
Any advice? 
I'm assuming from the message you are trying to push directly to the pandas repo instead of to your own. Assuming you have your own fork established as `origin` you could just do:

```sh
git push origin your-branch-name
```

If in doubt be sure to check out the forking section of the contributing guide as well:

https://pandas.pydata.org/pandas-docs/stable/contributing.html#forking

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -7480,6 +7480,10 @@ def rank(self, axis=0, method='average', numeric_only=None,
             msg = "rank does not make sense when ndim > 2"
             raise NotImplementedError(msg)
 
+        if na_option not in {'keep', 'top', 'bottom'}:
+            msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+            raise ValueError(msg)
+
         def ranker(data):
             ranks = algos.rank(data.values, axis=axis, method=method,
                                ascending=ascending, na_option=na_option,


Test patch:
diff --git a/pandas/tests/frame/test_rank.py b/pandas/tests/frame/test_rank.py
--- a/pandas/tests/frame/test_rank.py
+++ b/pandas/tests/frame/test_rank.py
@@ -191,6 +191,16 @@ def test_rank_na_option(self):
         tm.assert_numpy_array_equal(ranks0.values, exp0)
         tm.assert_numpy_array_equal(ranks1.values, exp1)
 
+        # bad values throw error
+        msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+
+        with tm.assert_raises_regex(ValueError, msg):
+            self.frame.rank(na_option='bad', ascending=False)
+
+        # invalid type
+        with tm.assert_raises_regex(ValueError, msg):
+            self.frame.rank(na_option=True, ascending=False)
+
     def test_rank_axis(self):
         # check if using axes' names gives the same result
         df = DataFrame([[2, 1], [4, 3]])
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
@@ -248,6 +248,17 @@ def test_rank_avg_even_vals():
 def test_rank_object_raises(ties_method, ascending, na_option,
                             pct, vals):
     df = DataFrame({'key': ['foo'] * 5, 'val': vals})
+
+    with tm.assert_raises_regex(TypeError, "not callable"):
+        df.groupby('key').rank(method=ties_method,
+                               ascending=ascending,
+                               na_option='bad', 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"):
         df.groupby('key').rank(method=ties_method,
                                ascending=ascending,
diff --git a/pandas/tests/series/test_rank.py b/pandas/tests/series/test_rank.py
--- a/pandas/tests/series/test_rank.py
+++ b/pandas/tests/series/test_rank.py
@@ -183,6 +183,16 @@ def test_rank_categorical(self):
             exp_keep
         )
 
+        # Test invalid values for na_option
+        msg = "na_option must be one of 'keep', 'top', or 'bottom'"
+
+        with tm.assert_raises_regex(ValueError, msg):
+            na_ser.rank(na_option='bad', ascending=False)
+
+        # invalid type
+        with tm.assert_raises_regex(ValueError, msg):
+            na_ser.rank(na_option=True, ascending=False)
+
         # Test with pct=True
         na_ser = Series(['first', 'second', 'third', 'fourth', np.NaN]).astype(
             CategoricalDtype(['first', 'second', 'third', 'fourth'], True))