Skip to content

Commit 85a1067

Browse files
skwbcjreback
authored andcommitted
BUG: DataFrame.first_valid_index() fails if there is no valid entry. (#17488)
Closes #17400
1 parent e2757a2 commit 85a1067

File tree

5 files changed

+38
-14
lines changed

5 files changed

+38
-14
lines changed

doc/source/whatsnew/v0.21.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,7 @@ Indexing
518518
- Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`)
519519
- Bug in intersection of ``RangeIndex`` with negative step (:issue:`17296`)
520520
- Bug in ``IntervalIndex`` where performing a scalar lookup fails for included right endpoints of non-overlapping monotonic decreasing indexes (:issue:`16417`, :issue:`17271`)
521+
- Bug in :meth:`DataFrame.first_valid_index` and :meth:`DataFrame.last_valid_index` when no valid entry (:issue:`17400`)
521522

522523
I/O
523524
^^^

pandas/core/frame.py

+12-8
Original file line numberDiff line numberDiff line change
@@ -4063,23 +4063,27 @@ def update(self, other, join='left', overwrite=True, filter_func=None,
40634063
# ----------------------------------------------------------------------
40644064
# Misc methods
40654065

4066+
def _get_valid_indices(self):
4067+
is_valid = self.count(1) > 0
4068+
return self.index[is_valid]
4069+
4070+
@Appender(_shared_docs['valid_index'] % {
4071+
'position': 'first', 'klass': 'DataFrame'})
40664072
def first_valid_index(self):
4067-
"""
4068-
Return label for first non-NA/null value
4069-
"""
40704073
if len(self) == 0:
40714074
return None
40724075

4073-
return self.index[self.count(1) > 0][0]
4076+
valid_indices = self._get_valid_indices()
4077+
return valid_indices[0] if len(valid_indices) else None
40744078

4079+
@Appender(_shared_docs['valid_index'] % {
4080+
'position': 'first', 'klass': 'DataFrame'})
40754081
def last_valid_index(self):
4076-
"""
4077-
Return label for last non-NA/null value
4078-
"""
40794082
if len(self) == 0:
40804083
return None
40814084

4082-
return self.index[self.count(1) > 0][-1]
4085+
valid_indices = self._get_valid_indices()
4086+
return valid_indices[-1] if len(valid_indices) else None
40834087

40844088
# ----------------------------------------------------------------------
40854089
# Data reshaping

pandas/core/generic.py

+16
Original file line numberDiff line numberDiff line change
@@ -6757,6 +6757,22 @@ def transform(self, func, *args, **kwargs):
67576757

67586758
cls.transform = transform
67596759

6760+
# ----------------------------------------------------------------------
6761+
# Misc methods
6762+
6763+
_shared_docs['valid_index'] = """
6764+
Return index for %(position)s non-NA/null value.
6765+
6766+
Notes
6767+
--------
6768+
If all elements are non-NA/null, returns None.
6769+
Also returns None for empty %(klass)s.
6770+
6771+
Returns
6772+
--------
6773+
scalar : type of index
6774+
"""
6775+
67606776

67616777
def _doc_parms(cls):
67626778
"""Return a tuple of the doc parms."""

pandas/core/series.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -2825,10 +2825,9 @@ def dropna(self, axis=0, inplace=False, **kwargs):
28252825
valid = lambda self, inplace=False, **kwargs: self.dropna(inplace=inplace,
28262826
**kwargs)
28272827

2828+
@Appender(generic._shared_docs['valid_index'] % {
2829+
'position': 'first', 'klass': 'Series'})
28282830
def first_valid_index(self):
2829-
"""
2830-
Return label for first non-NA/null value
2831-
"""
28322831
if len(self) == 0:
28332832
return None
28342833

@@ -2839,10 +2838,9 @@ def first_valid_index(self):
28392838
else:
28402839
return self.index[i]
28412840

2841+
@Appender(generic._shared_docs['valid_index'] % {
2842+
'position': 'last', 'klass': 'Series'})
28422843
def last_valid_index(self):
2843-
"""
2844-
Return label for last non-NA/null value
2845-
"""
28462844
if len(self) == 0:
28472845
return None
28482846

pandas/tests/frame/test_timeseries.py

+5
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,11 @@ def test_first_last_valid(self):
440440
assert empty.last_valid_index() is None
441441
assert empty.first_valid_index() is None
442442

443+
# GH17400: no valid entries
444+
frame[:] = nan
445+
assert frame.last_valid_index() is None
446+
assert frame.first_valid_index() is None
447+
443448
def test_at_time_frame(self):
444449
rng = date_range('1/1/2000', '1/5/2000', freq='5min')
445450
ts = DataFrame(np.random.randn(len(rng), 2), index=rng)

0 commit comments

Comments
 (0)