Skip to content

Commit c988567

Browse files
authored
REF: tighten what we accept in TimedeltaIndex._simple_new (#31315)
1 parent 79ca148 commit c988567

File tree

5 files changed

+63
-40
lines changed

5 files changed

+63
-40
lines changed

pandas/core/arrays/timedeltas.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -195,9 +195,12 @@ def __init__(self, values, dtype=_TD_DTYPE, freq=None, copy=False):
195195
def _simple_new(cls, values, freq=None, dtype=_TD_DTYPE):
196196
assert dtype == _TD_DTYPE, dtype
197197
assert isinstance(values, np.ndarray), type(values)
198+
if values.dtype != _TD_DTYPE:
199+
assert values.dtype == "i8"
200+
values = values.view(_TD_DTYPE)
198201

199202
result = object.__new__(cls)
200-
result._data = values.view(_TD_DTYPE)
203+
result._data = values
201204
result._freq = to_offset(freq)
202205
result._dtype = _TD_DTYPE
203206
return result

pandas/core/indexes/datetimelike.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,8 @@ def delete(self, loc):
582582
if loc.start in (0, None) or loc.stop in (len(self), None):
583583
freq = self.freq
584584

585-
return self._shallow_copy(new_i8s, freq=freq)
585+
arr = type(self._data)._simple_new(new_i8s, dtype=self.dtype, freq=freq)
586+
return type(self)._simple_new(arr, name=self.name)
586587

587588

588589
class DatetimeTimedeltaMixin(DatetimeIndexOpsMixin, Int64Index):
@@ -623,6 +624,14 @@ def _shallow_copy(self, values=None, **kwargs):
623624
if values is None:
624625
values = self._data
625626

627+
if isinstance(values, type(self)):
628+
values = values._data
629+
if isinstance(values, np.ndarray):
630+
# TODO: We would rather not get here
631+
if kwargs.get("freq") is not None:
632+
raise ValueError(kwargs)
633+
values = type(self._data)(values, dtype=self.dtype)
634+
626635
attributes = self._get_attributes_dict()
627636

628637
if "freq" not in kwargs and self.freq is not None:
@@ -801,7 +810,10 @@ def _union(self, other, sort):
801810
this, other = self._maybe_utc_convert(other)
802811

803812
if this._can_fast_union(other):
804-
return this._fast_union(other, sort=sort)
813+
result = this._fast_union(other, sort=sort)
814+
if result.freq is None:
815+
result._set_freq("infer")
816+
return result
805817
else:
806818
i8self = Int64Index._simple_new(self.asi8, name=self.name)
807819
i8other = Int64Index._simple_new(other.asi8, name=other.name)
@@ -934,7 +946,8 @@ def insert(self, loc, item):
934946
new_i8s = np.concatenate(
935947
(self[:loc].asi8, [item.view(np.int64)], self[loc:].asi8)
936948
)
937-
return self._shallow_copy(new_i8s, freq=freq)
949+
arr = type(self._data)._simple_new(new_i8s, dtype=self.dtype, freq=freq)
950+
return type(self)._simple_new(arr, name=self.name)
938951
except (AttributeError, TypeError):
939952

940953
# fall back to object index

pandas/core/indexes/timedeltas.py

+4-11
Original file line numberDiff line numberDiff line change
@@ -173,22 +173,15 @@ def __new__(
173173
def _simple_new(cls, values, name=None, freq=None, dtype=_TD_DTYPE):
174174
# `dtype` is passed by _shallow_copy in corner cases, should always
175175
# be timedelta64[ns] if present
176-
177-
if not isinstance(values, TimedeltaArray):
178-
values = TimedeltaArray._simple_new(values, dtype=dtype, freq=freq)
179-
else:
180-
if freq is None:
181-
freq = values.freq
182-
assert isinstance(values, TimedeltaArray), type(values)
183176
assert dtype == _TD_DTYPE, dtype
184-
assert values.dtype == "m8[ns]", values.dtype
177+
assert isinstance(values, TimedeltaArray)
178+
assert freq is None or values.freq == freq
185179

186-
tdarr = TimedeltaArray._simple_new(values._data, freq=freq)
187180
result = object.__new__(cls)
188-
result._data = tdarr
181+
result._data = values
189182
result._name = name
190183
# For groupby perf. See note in indexes/base about _index_data
191-
result._index_data = tdarr._data
184+
result._index_data = values._data
192185

193186
result._reset_identity()
194187
return result

pandas/core/resample.py

+28-5
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from pandas.core.groupby.groupby import GroupBy, _GroupBy, _pipe_template, get_groupby
2424
from pandas.core.groupby.grouper import Grouper
2525
from pandas.core.groupby.ops import BinGrouper
26+
from pandas.core.indexes.api import Index
2627
from pandas.core.indexes.datetimes import DatetimeIndex, date_range
2728
from pandas.core.indexes.period import PeriodIndex, period_range
2829
from pandas.core.indexes.timedeltas import TimedeltaIndex, timedelta_range
@@ -424,10 +425,7 @@ def _wrap_result(self, result):
424425

425426
if isinstance(result, ABCSeries) and result.empty:
426427
obj = self.obj
427-
if isinstance(obj.index, PeriodIndex):
428-
result.index = obj.index.asfreq(self.freq)
429-
else:
430-
result.index = obj.index._shallow_copy(freq=self.freq)
428+
result.index = _asfreq_compat(obj.index, freq=self.freq)
431429
result.name = getattr(obj, "name", None)
432430

433431
return result
@@ -1787,8 +1785,8 @@ def asfreq(obj, freq, method=None, how=None, normalize=False, fill_value=None):
17871785

17881786
elif len(obj.index) == 0:
17891787
new_obj = obj.copy()
1790-
new_obj.index = obj.index._shallow_copy(freq=to_offset(freq))
17911788

1789+
new_obj.index = _asfreq_compat(obj.index, freq)
17921790
else:
17931791
dti = date_range(obj.index[0], obj.index[-1], freq=freq)
17941792
dti.name = obj.index.name
@@ -1797,3 +1795,28 @@ def asfreq(obj, freq, method=None, how=None, normalize=False, fill_value=None):
17971795
new_obj.index = new_obj.index.normalize()
17981796

17991797
return new_obj
1798+
1799+
1800+
def _asfreq_compat(index, freq):
1801+
"""
1802+
Helper to mimic asfreq on (empty) DatetimeIndex and TimedeltaIndex.
1803+
1804+
Parameters
1805+
----------
1806+
index : PeriodIndex, DatetimeIndex, or TimedeltaIndex
1807+
freq : DateOffset
1808+
1809+
Returns
1810+
-------
1811+
same type as index
1812+
"""
1813+
if len(index) != 0:
1814+
# This should never be reached, always checked by the caller
1815+
raise ValueError(
1816+
"Can only set arbitrary freq for empty DatetimeIndex or TimedeltaIndex"
1817+
)
1818+
if isinstance(index, PeriodIndex):
1819+
new_index = index.asfreq(freq=freq)
1820+
else:
1821+
new_index = Index([], dtype=index.dtype, freq=freq, name=index.name)
1822+
return new_index

pandas/tests/resample/test_base.py

+11-20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pandas.core.indexes.datetimes import date_range
1212
from pandas.core.indexes.period import PeriodIndex, period_range
1313
from pandas.core.indexes.timedeltas import TimedeltaIndex, timedelta_range
14+
from pandas.core.resample import _asfreq_compat
1415

1516
# a fixture value can be overridden by the test parameter value. Note that the
1617
# value of the fixture can be overridden this way even if the test doesn't use
@@ -103,10 +104,8 @@ def test_resample_empty_series(freq, empty_series, resample_method):
103104
result = getattr(s.resample(freq), resample_method)()
104105

105106
expected = s.copy()
106-
if isinstance(s.index, PeriodIndex):
107-
expected.index = s.index.asfreq(freq=freq)
108-
else:
109-
expected.index = s.index._shallow_copy(freq=freq)
107+
expected.index = _asfreq_compat(s.index, freq)
108+
110109
tm.assert_index_equal(result.index, expected.index)
111110
assert result.index.freq == expected.index.freq
112111
tm.assert_series_equal(result, expected, check_dtype=False)
@@ -119,10 +118,8 @@ def test_resample_count_empty_series(freq, empty_series, resample_method):
119118
# GH28427
120119
result = getattr(empty_series.resample(freq), resample_method)()
121120

122-
if isinstance(empty_series.index, PeriodIndex):
123-
index = empty_series.index.asfreq(freq=freq)
124-
else:
125-
index = empty_series.index._shallow_copy(freq=freq)
121+
index = _asfreq_compat(empty_series.index, freq)
122+
126123
expected = pd.Series([], dtype="int64", index=index, name=empty_series.name)
127124

128125
tm.assert_series_equal(result, expected)
@@ -141,10 +138,8 @@ def test_resample_empty_dataframe(empty_frame, freq, resample_method):
141138
# GH14962
142139
expected = Series([], dtype=object)
143140

144-
if isinstance(df.index, PeriodIndex):
145-
expected.index = df.index.asfreq(freq=freq)
146-
else:
147-
expected.index = df.index._shallow_copy(freq=freq)
141+
expected.index = _asfreq_compat(df.index, freq)
142+
148143
tm.assert_index_equal(result.index, expected.index)
149144
assert result.index.freq == expected.index.freq
150145
tm.assert_almost_equal(result, expected, check_dtype=False)
@@ -162,10 +157,8 @@ def test_resample_count_empty_dataframe(freq, empty_frame):
162157

163158
result = empty_frame.resample(freq).count()
164159

165-
if isinstance(empty_frame.index, PeriodIndex):
166-
index = empty_frame.index.asfreq(freq=freq)
167-
else:
168-
index = empty_frame.index._shallow_copy(freq=freq)
160+
index = _asfreq_compat(empty_frame.index, freq)
161+
169162
expected = pd.DataFrame({"a": []}, dtype="int64", index=index)
170163

171164
tm.assert_frame_equal(result, expected)
@@ -181,10 +174,8 @@ def test_resample_size_empty_dataframe(freq, empty_frame):
181174

182175
result = empty_frame.resample(freq).size()
183176

184-
if isinstance(empty_frame.index, PeriodIndex):
185-
index = empty_frame.index.asfreq(freq=freq)
186-
else:
187-
index = empty_frame.index._shallow_copy(freq=freq)
177+
index = _asfreq_compat(empty_frame.index, freq)
178+
188179
expected = pd.Series([], dtype="int64", index=index)
189180

190181
tm.assert_series_equal(result, expected)

0 commit comments

Comments
 (0)