Skip to content

Commit dbdf81a

Browse files
jbrockmendelrhshadrach
authored andcommitted
BUG: DTI/TDI.insert doing invalid casting (pandas-dev#33703)
1 parent 3e64125 commit dbdf81a

File tree

6 files changed

+55
-25
lines changed

6 files changed

+55
-25
lines changed

pandas/core/arrays/datetimelike.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -857,10 +857,13 @@ def _validate_setitem_value(self, value):
857857
def _validate_insert_value(self, value):
858858
if isinstance(value, self._recognized_scalars):
859859
value = self._scalar_type(value)
860+
self._check_compatible_with(value, setitem=True)
861+
# TODO: if we dont have compat, should we raise or astype(object)?
862+
# PeriodIndex does astype(object)
860863
elif is_valid_nat_for_dtype(value, self.dtype):
861864
# GH#18295
862865
value = NaT
863-
elif lib.is_scalar(value) and isna(value):
866+
else:
864867
raise TypeError(
865868
f"cannot insert {type(self).__name__} with incompatible label"
866869
)

pandas/core/indexes/datetimelike.py

+13-20
Original file line numberDiff line numberDiff line change
@@ -966,37 +966,30 @@ def insert(self, loc, item):
966966
-------
967967
new_index : Index
968968
"""
969+
if isinstance(item, str):
970+
# TODO: Why are strings special?
971+
# TODO: Should we attempt _scalar_from_string?
972+
return self.astype(object).insert(loc, item)
973+
969974
item = self._data._validate_insert_value(item)
970975

971976
freq = None
972-
if isinstance(item, self._data._scalar_type) or item is NaT:
973-
self._data._check_compatible_with(item, setitem=True)
974-
975-
# check freq can be preserved on edge cases
976-
if self.size and self.freq is not None:
977+
# check freq can be preserved on edge cases
978+
if self.freq is not None:
979+
if self.size:
977980
if item is NaT:
978981
pass
979982
elif (loc == 0 or loc == -len(self)) and item + self.freq == self[0]:
980983
freq = self.freq
981984
elif (loc == len(self)) and item - self.freq == self[-1]:
982985
freq = self.freq
983-
elif self.freq is not None:
986+
else:
984987
# Adding a single item to an empty index may preserve freq
985988
if self.freq.is_on_offset(item):
986989
freq = self.freq
987-
item = item.asm8
988990

989-
try:
990-
new_i8s = np.concatenate(
991-
(self[:loc].asi8, [item.view(np.int64)], self[loc:].asi8)
992-
)
993-
arr = type(self._data)._simple_new(new_i8s, dtype=self.dtype, freq=freq)
994-
return type(self)._simple_new(arr, name=self.name)
995-
except (AttributeError, TypeError) as err:
991+
item = self._data._unbox_scalar(item)
996992

997-
# fall back to object index
998-
if isinstance(item, str):
999-
return self.astype(object).insert(loc, item)
1000-
raise TypeError(
1001-
f"cannot insert {type(self).__name__} with incompatible label"
1002-
) from err
993+
new_i8s = np.concatenate([self[:loc].asi8, [item], self[loc:].asi8])
994+
arr = type(self._data)._simple_new(new_i8s, dtype=self.dtype, freq=freq)
995+
return type(self)._simple_new(arr, name=self.name)

pandas/tests/indexes/datetimes/test_insert.py

+23
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,26 @@ def test_insert(self):
165165
assert result.name == expected.name
166166
assert result.tz == expected.tz
167167
assert result.freq is None
168+
169+
@pytest.mark.parametrize(
170+
"item", [0, np.int64(0), np.float64(0), np.array(0), np.timedelta64(456)]
171+
)
172+
def test_insert_mismatched_types_raises(self, tz_aware_fixture, item):
173+
# GH#33703 dont cast these to dt64
174+
tz = tz_aware_fixture
175+
dti = date_range("2019-11-04", periods=9, freq="-1D", name=9, tz=tz)
176+
177+
msg = "incompatible label"
178+
with pytest.raises(TypeError, match=msg):
179+
dti.insert(1, item)
180+
181+
def test_insert_object_casting(self, tz_aware_fixture):
182+
# GH#33703
183+
tz = tz_aware_fixture
184+
dti = date_range("2019-11-04", periods=3, freq="-1D", name=9, tz=tz)
185+
186+
# ATM we treat this as a string, but we could plausibly wrap it in Timestamp
187+
value = "2019-11-05"
188+
result = dti.insert(0, value)
189+
expected = Index(["2019-11-05"] + list(dti), dtype=object, name=9)
190+
tm.assert_index_equal(result, expected)

pandas/tests/indexes/timedeltas/test_insert.py

+11
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,17 @@ def test_insert_invalid_na(self):
8282
with pytest.raises(TypeError, match="incompatible label"):
8383
idx.insert(0, np.datetime64("NaT"))
8484

85+
@pytest.mark.parametrize(
86+
"item", [0, np.int64(0), np.float64(0), np.array(0), np.datetime64(456, "us")]
87+
)
88+
def test_insert_mismatched_types_raises(self, item):
89+
# GH#33703 dont cast these to td64
90+
tdi = TimedeltaIndex(["4day", "1day", "2day"], name="idx")
91+
92+
msg = "incompatible label"
93+
with pytest.raises(TypeError, match=msg):
94+
tdi.insert(1, item)
95+
8596
def test_insert_dont_cast_strings(self):
8697
# To match DatetimeIndex and PeriodIndex behavior, dont try to
8798
# parse strings to Timedelta

pandas/tests/indexing/test_coercion.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -448,7 +448,7 @@ def test_insert_index_datetimes(self, fill_val, exp_dtype):
448448
with pytest.raises(TypeError, match=msg):
449449
obj.insert(1, pd.Timestamp("2012-01-01", tz="Asia/Tokyo"))
450450

451-
msg = "cannot insert DatetimeIndex with incompatible label"
451+
msg = "cannot insert DatetimeArray with incompatible label"
452452
with pytest.raises(TypeError, match=msg):
453453
obj.insert(1, 1)
454454

@@ -465,12 +465,12 @@ def test_insert_index_timedelta64(self):
465465
)
466466

467467
# ToDo: must coerce to object
468-
msg = "cannot insert TimedeltaIndex with incompatible label"
468+
msg = "cannot insert TimedeltaArray with incompatible label"
469469
with pytest.raises(TypeError, match=msg):
470470
obj.insert(1, pd.Timestamp("2012-01-01"))
471471

472472
# ToDo: must coerce to object
473-
msg = "cannot insert TimedeltaIndex with incompatible label"
473+
msg = "cannot insert TimedeltaArray with incompatible label"
474474
with pytest.raises(TypeError, match=msg):
475475
obj.insert(1, 1)
476476

pandas/tests/indexing/test_partial.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,7 @@ def test_partial_set_invalid(self):
335335
df = orig.copy()
336336

337337
# don't allow not string inserts
338-
msg = "cannot insert DatetimeIndex with incompatible label"
338+
msg = "cannot insert DatetimeArray with incompatible label"
339339

340340
with pytest.raises(TypeError, match=msg):
341341
df.loc[100.0, :] = df.iloc[0]

0 commit comments

Comments
 (0)