Skip to content

Commit d8041ca

Browse files
authored
BUG: Series.loc[dtstr] type depends on monotonicity (#42265)
1 parent 4dc6785 commit d8041ca

File tree

3 files changed

+65
-21
lines changed

3 files changed

+65
-21
lines changed

doc/source/whatsnew/v1.4.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Interval
158158

159159
Indexing
160160
^^^^^^^^
161-
-
161+
- Bug in indexing on a :class:`Series` or :class:`DataFrame` with a :class:`DatetimeIndex` when passing a string, the return type depended on whether the index was monotonic (:issue:`24892`)
162162
-
163163

164164
Missing

pandas/core/indexes/datetimes.py

+2-17
Original file line numberDiff line numberDiff line change
@@ -589,23 +589,8 @@ def _parsed_string_to_bounds(self, reso: Resolution, parsed: datetime):
589589
return start, end
590590

591591
def _can_partial_date_slice(self, reso: Resolution) -> bool:
592-
assert isinstance(reso, Resolution), (type(reso), reso)
593-
if (
594-
self.is_monotonic
595-
and reso.attrname in ["day", "hour", "minute", "second"]
596-
and self._resolution_obj >= reso
597-
):
598-
# These resolution/monotonicity validations came from GH3931,
599-
# GH3452 and GH2369.
600-
601-
# See also GH14826
602-
return False
603-
604-
if reso.attrname == "microsecond":
605-
# _partial_date_slice doesn't allow microsecond resolution, but
606-
# _parsed_string_to_bounds allows it.
607-
return False
608-
return True
592+
# History of conversation GH#3452, GH#3931, GH#2369, GH#14826
593+
return reso > self._resolution_obj
609594

610595
def _deprecate_mismatched_indexing(self, key) -> None:
611596
# GH#36148

pandas/tests/indexes/datetimes/test_partial_slicing.py

+62-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,55 @@
2020

2121

2222
class TestSlicing:
23+
def test_return_type_doesnt_depend_on_monotonicity(self):
24+
# GH#24892 we get Series back regardless of whether our DTI is monotonic
25+
dti = date_range(start="2015-5-13 23:59:00", freq="min", periods=3)
26+
ser = Series(range(3), index=dti)
27+
28+
# non-monotonic index
29+
ser2 = Series(range(3), index=[dti[1], dti[0], dti[2]])
30+
31+
# key with resolution strictly lower than "min"
32+
key = "2015-5-14 00"
33+
34+
# monotonic increasing index
35+
result = ser.loc[key]
36+
expected = ser.iloc[1:]
37+
tm.assert_series_equal(result, expected)
38+
39+
# monotonic decreasing index
40+
result = ser.iloc[::-1].loc[key]
41+
expected = ser.iloc[::-1][:-1]
42+
tm.assert_series_equal(result, expected)
43+
44+
# non-monotonic index
45+
result2 = ser2.loc[key]
46+
expected2 = ser2.iloc[::2]
47+
tm.assert_series_equal(result2, expected2)
48+
49+
def test_return_type_doesnt_depend_on_monotonicity_higher_reso(self):
50+
# GH#24892 we get Series back regardless of whether our DTI is monotonic
51+
dti = date_range(start="2015-5-13 23:59:00", freq="min", periods=3)
52+
ser = Series(range(3), index=dti)
53+
54+
# non-monotonic index
55+
ser2 = Series(range(3), index=[dti[1], dti[0], dti[2]])
56+
57+
# key with resolution strictly *higher) than "min"
58+
key = "2015-5-14 00:00:00"
59+
60+
# monotonic increasing index
61+
result = ser.loc[key]
62+
assert result == 1
63+
64+
# monotonic decreasing index
65+
result = ser.iloc[::-1].loc[key]
66+
assert result == 1
67+
68+
# non-monotonic index
69+
result2 = ser2.loc[key]
70+
assert result2 == 0
71+
2372
def test_monotone_DTI_indexing_bug(self):
2473
# GH 19362
2574
# Testing accessing the first element in a monotonic descending
@@ -38,9 +87,19 @@ def test_monotone_DTI_indexing_bug(self):
3887
expected = DataFrame({0: list(range(5)), "date": date_index})
3988
tm.assert_frame_equal(df, expected)
4089

41-
df = DataFrame({"A": [1, 2, 3]}, index=date_range("20170101", periods=3)[::-1])
42-
expected = DataFrame({"A": 1}, index=date_range("20170103", periods=1)[::-1])
43-
tm.assert_frame_equal(df.loc["2017-01-03"], expected)
90+
# We get a slice because df.index's resolution is hourly and we
91+
# are slicing with a daily-resolution string. If both were daily,
92+
# we would get a single item back
93+
dti = date_range("20170101 01:00:00", periods=3)
94+
df = DataFrame({"A": [1, 2, 3]}, index=dti[::-1])
95+
96+
expected = DataFrame({"A": 1}, index=dti[-1:][::-1])
97+
result = df.loc["2017-01-03"]
98+
tm.assert_frame_equal(result, expected)
99+
100+
result2 = df.iloc[::-1].loc["2017-01-03"]
101+
expected2 = expected.iloc[::-1]
102+
tm.assert_frame_equal(result2, expected2)
44103

45104
def test_slice_year(self):
46105
dti = date_range(freq="B", start=datetime(2005, 1, 1), periods=500)

0 commit comments

Comments
 (0)