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 numberOriginal file lineDiff line numberDiff line change
@@ -518,6 +518,7 @@ Indexing
518
- Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`)
518
- Bug in ``CategoricalIndex`` reindexing in which specified indices containing duplicates were not being respected (:issue:`17323`)
519
- Bug in intersection of ``RangeIndex`` with negative step (:issue:`17296`)
519
- Bug in intersection of ``RangeIndex`` with negative step (:issue:`17296`)
520
- Bug in ``IntervalIndex`` where performing a scalar lookup fails for included right endpoints of non-overlapping monotonic decreasing indexes (:issue:`16417`, :issue:`17271`)
520
- 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`)
521

522

522
I/O
523
I/O
523
^^^
524
^^^

pandas/core/frame.py

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

4065

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'})
4066
def first_valid_index(self):
4072
def first_valid_index(self):
4067-
"""
4068-
Return label for first non-NA/null value
4069-
"""
4070
if len(self) == 0:
4073
if len(self) == 0:
4071
return None
4074
return None
4072

4075

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
4074

4078

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

4084

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
4083

4087

4084
# ----------------------------------------------------------------------
4088
# ----------------------------------------------------------------------
4085
# Data reshaping
4089
# Data reshaping

pandas/core/generic.py

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

6757

6758
cls.transform = transform
6758
cls.transform = transform
6759

6759

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+
6760

6776

6761
def _doc_parms(cls):
6777
def _doc_parms(cls):
6762
"""Return a tuple of the doc parms."""
6778
"""Return a tuple of the doc parms."""

pandas/core/series.py

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

2827

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

2833

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

2840

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

2846

pandas/tests/frame/test_timeseries.py

+5
Original file line numberOriginal file lineDiff line numberDiff line change
@@ -440,6 +440,11 @@ def test_first_last_valid(self):
440
assert empty.last_valid_index() is None
440
assert empty.last_valid_index() is None
441
assert empty.first_valid_index() is None
441
assert empty.first_valid_index() is None
442

442

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+
443
def test_at_time_frame(self):
448
def test_at_time_frame(self):
444
rng = date_range('1/1/2000', '1/5/2000', freq='5min')
449
rng = date_range('1/1/2000', '1/5/2000', freq='5min')
445
ts = DataFrame(np.random.randn(len(rng), 2), index=rng)
450
ts = DataFrame(np.random.randn(len(rng), 2), index=rng)

0 commit comments

Comments
 (0)