Repository: pandas-dev/pandas

Issue:
first_valid_index fails when dataframe has non-unique row index
#### Code Sample, a copy-pastable example if possible

```python
In [2]: import pandas as pd

In [3]: pd.__version__
Out[3]: '0.23.0rc2'

In [4]: x = pd.DataFrame({'b': [1,2,3]}, index=[1,1,2])

In [5]: x
Out[5]: 
   b
1  1
1  2
2  3

In [6]: x.first_valid_index()
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-baa2c482d9a6> in <module>()
----> 1 x.first_valid_index()

lib/python3.5/site-packages/pandas/core/generic.py in first_valid_index(self)
   8881                                              'klass': 'NDFrame'})
   8882     def first_valid_index(self):
-> 8883         return self._find_valid_index('first')
   8884 
   8885     @Appender(_shared_docs['valid_index'] % {'position': 'last',

lib/python3.5/site-packages/pandas/core/generic.py in _find_valid_index(self, how)
   8867             # First valid value case
   8868             i = is_valid.idxmax()
-> 8869             if not is_valid[i]:
   8870                 return None
   8871             return i

lib/python3.5/site-packages/pandas/core/generic.py in __nonzero__(self)
   1572         raise ValueError("The truth value of a {0} is ambiguous. "
   1573                          "Use a.empty, a.bool(), a.item(), a.any() or a.all()."
-> 1574                          .format(self.__class__.__name__))
   1575 
   1576     __bool__ = __nonzero__

ValueError: The truth value of a Series is ambiguous. Use a.empty, a.bool(), a.item(), a.any() or a.all().
```
#### Problem description

`first_valid_index` did not raise on a row index with duplicate values on pandas <= 0.22.0

#### Expected Output

`0`

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: None
python: 3.5.4.final.0
python-bits: 64
OS: Linux
OS-release: 4.1.35-pv-ts2
machine: x86_64
processor:
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.23.0
pytest: 3.2.1
pip: 9.0.1
setuptools: 36.4.0
Cython: None
numpy: 1.12.1
scipy: 0.19.1
pyarrow: 0.8.0
xarray: None
IPython: 6.1.0
sphinx: 1.7.5
patsy: 0.4.1
dateutil: 2.6.1
pytz: 2017.2
blosc: None
bottleneck: None
tables: 3.4.2
numexpr: 2.6.2
feather: None
matplotlib: 2.0.2
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: 4.6.0
html5lib: 0.9999999
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>

cc @jreback 


Discussion:
> Expected Output
>
> 0

@adamklein in your example, did you mean the `Expect Output` was `1` ? 

Resolving patch:
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -8969,18 +8969,17 @@ def _find_valid_index(self, how):
             is_valid = is_valid.any(1)  # reduce axis 1
 
         if how == 'first':
-            # First valid value case
-            i = is_valid.idxmax()
-            if not is_valid[i]:
-                return None
-            return i
-
-        elif how == 'last':
-            # Last valid value case
-            i = is_valid.values[::-1].argmax()
-            if not is_valid.iat[len(self) - i - 1]:
-                return None
-            return self.index[len(self) - i - 1]
+            idxpos = is_valid.values[::].argmax()
+
+        if how == 'last':
+            idxpos = len(self) - 1 - is_valid.values[::-1].argmax()
+
+        chk_notna = is_valid.iat[idxpos]
+        idx = self.index[idxpos]
+
+        if not chk_notna:
+            return None
+        return idx
 
     @Appender(_shared_docs['valid_index'] % {'position': 'first',
                                              'klass': 'NDFrame'})


Test patch:
diff --git a/pandas/tests/frame/test_timeseries.py b/pandas/tests/frame/test_timeseries.py
--- a/pandas/tests/frame/test_timeseries.py
+++ b/pandas/tests/frame/test_timeseries.py
@@ -506,7 +506,15 @@ def test_asfreq_fillvalue(self):
         actual_series = ts.asfreq(freq='1S', fill_value=9.0)
         assert_series_equal(expected_series, actual_series)
 
-    def test_first_last_valid(self):
+    @pytest.mark.parametrize("data,idx,expected_first,expected_last", [
+        ({'A': [1, 2, 3]}, [1, 1, 2], 1, 2),
+        ({'A': [1, 2, 3]}, [1, 2, 2], 1, 2),
+        ({'A': [1, 2, 3, 4]}, ['d', 'd', 'd', 'd'], 'd', 'd'),
+        ({'A': [1, np.nan, 3]}, [1, 1, 2], 1, 2),
+        ({'A': [np.nan, np.nan, 3]}, [1, 1, 2], 2, 2),
+        ({'A': [1, np.nan, 3]}, [1, 2, 2], 1, 2)])
+    def test_first_last_valid(self, data, idx,
+                              expected_first, expected_last):
         N = len(self.frame.index)
         mat = randn(N)
         mat[:5] = nan
@@ -539,6 +547,11 @@ def test_first_last_valid(self):
         assert frame.first_valid_index().freq == frame.index.freq
         assert frame.last_valid_index().freq == frame.index.freq
 
+        # GH 21441
+        df = DataFrame(data, index=idx)
+        assert expected_first == df.first_valid_index()
+        assert expected_last == df.last_valid_index()
+
     def test_first_subset(self):
         ts = tm.makeTimeDataFrame(freq='12h')
         result = ts.first('10d')