Skip to content

Commit f3642d2

Browse files
jbrockmendeljreback
authored andcommitted
BUG: DTA/TDA/PA add/sub object-dtype (pandas-dev#30594)
1 parent bac9a1b commit f3642d2

File tree

7 files changed

+97
-60
lines changed

7 files changed

+97
-60
lines changed

pandas/core/arrays/datetimelike.py

+14-13
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
is_integer_dtype,
2828
is_list_like,
2929
is_object_dtype,
30-
is_offsetlike,
3130
is_period_dtype,
3231
is_string_dtype,
3332
is_timedelta64_dtype,
@@ -1075,8 +1074,6 @@ def _sub_period_array(self, other):
10751074
f"cannot subtract {other.dtype}-dtype from {type(self).__name__}"
10761075
)
10771076

1078-
if len(self) != len(other):
1079-
raise ValueError("cannot subtract arrays/indices of unequal length")
10801077
if self.freq != other.freq:
10811078
msg = DIFFERENT_FREQ.format(
10821079
cls=type(self).__name__, own_freq=self.freqstr, other_freq=other.freqstr
@@ -1093,14 +1090,13 @@ def _sub_period_array(self, other):
10931090
new_values[mask] = NaT
10941091
return new_values
10951092

1096-
def _addsub_offset_array(self, other, op):
1093+
def _addsub_object_array(self, other: np.ndarray, op):
10971094
"""
10981095
Add or subtract array-like of DateOffset objects
10991096
11001097
Parameters
11011098
----------
1102-
other : Index, np.ndarray
1103-
object-dtype containing pd.DateOffset objects
1099+
other : np.ndarray[object]
11041100
op : {operator.add, operator.sub}
11051101
11061102
Returns
@@ -1124,7 +1120,12 @@ def _addsub_offset_array(self, other, op):
11241120
kwargs = {}
11251121
if not is_period_dtype(self):
11261122
kwargs["freq"] = "infer"
1127-
return self._from_sequence(res_values, **kwargs)
1123+
try:
1124+
res = type(self)._from_sequence(res_values, **kwargs)
1125+
except ValueError:
1126+
# e.g. we've passed a Timestamp to TimedeltaArray
1127+
res = res_values
1128+
return res
11281129

11291130
def _time_shift(self, periods, freq=None):
11301131
"""
@@ -1187,9 +1188,9 @@ def __add__(self, other):
11871188
elif is_timedelta64_dtype(other):
11881189
# TimedeltaIndex, ndarray[timedelta64]
11891190
result = self._add_delta(other)
1190-
elif is_offsetlike(other):
1191-
# Array/Index of DateOffset objects
1192-
result = self._addsub_offset_array(other, operator.add)
1191+
elif is_object_dtype(other):
1192+
# e.g. Array/Index of DateOffset objects
1193+
result = self._addsub_object_array(other, operator.add)
11931194
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
11941195
# DatetimeIndex, ndarray[datetime64]
11951196
return self._add_datetime_arraylike(other)
@@ -1242,9 +1243,9 @@ def __sub__(self, other):
12421243
elif is_timedelta64_dtype(other):
12431244
# TimedeltaIndex, ndarray[timedelta64]
12441245
result = self._add_delta(-other)
1245-
elif is_offsetlike(other):
1246-
# Array/Index of DateOffset objects
1247-
result = self._addsub_offset_array(other, operator.sub)
1246+
elif is_object_dtype(other):
1247+
# e.g. Array/Index of DateOffset objects
1248+
result = self._addsub_object_array(other, operator.sub)
12481249
elif is_datetime64_dtype(other) or is_datetime64tz_dtype(other):
12491250
# DatetimeIndex, ndarray[datetime64]
12501251
result = self._sub_datetime_arraylike(other)

pandas/core/arrays/timedeltas.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -510,13 +510,13 @@ def _add_datetimelike_scalar(self, other):
510510
dtype = DatetimeTZDtype(tz=other.tz) if other.tz else _NS_DTYPE
511511
return DatetimeArray(result, dtype=dtype, freq=self.freq)
512512

513-
def _addsub_offset_array(self, other, op):
514-
# Add or subtract Array-like of DateOffset objects
513+
def _addsub_object_array(self, other, op):
514+
# Add or subtract Array-like of objects
515515
try:
516516
# TimedeltaIndex can only operate with a subset of DateOffset
517517
# subclasses. Incompatible classes will raise AttributeError,
518518
# which we re-raise as TypeError
519-
return super()._addsub_offset_array(other, op)
519+
return super()._addsub_object_array(other, op)
520520
except AttributeError:
521521
raise TypeError(
522522
f"Cannot add/subtract non-tick DateOffset to {type(self).__name__}"

pandas/core/dtypes/common.py

-32
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
)
1919
from pandas.core.dtypes.generic import (
2020
ABCCategorical,
21-
ABCDateOffset,
2221
ABCDatetimeIndex,
2322
ABCIndexClass,
2423
ABCPeriodArray,
@@ -368,37 +367,6 @@ def is_categorical(arr) -> bool:
368367
return isinstance(arr, ABCCategorical) or is_categorical_dtype(arr)
369368

370369

371-
def is_offsetlike(arr_or_obj) -> bool:
372-
"""
373-
Check if obj or all elements of list-like is DateOffset
374-
375-
Parameters
376-
----------
377-
arr_or_obj : object
378-
379-
Returns
380-
-------
381-
boolean
382-
Whether the object is a DateOffset or listlike of DatetOffsets
383-
384-
Examples
385-
--------
386-
>>> is_offsetlike(pd.DateOffset(days=1))
387-
True
388-
>>> is_offsetlike('offset')
389-
False
390-
>>> is_offsetlike([pd.offsets.Minute(4), pd.offsets.MonthEnd()])
391-
True
392-
>>> is_offsetlike(np.array([pd.DateOffset(months=3), pd.Timestamp.now()]))
393-
False
394-
"""
395-
if isinstance(arr_or_obj, ABCDateOffset):
396-
return True
397-
elif is_list_like(arr_or_obj) and len(arr_or_obj) and is_object_dtype(arr_or_obj):
398-
return all(isinstance(x, ABCDateOffset) for x in arr_or_obj)
399-
return False
400-
401-
402370
def is_datetime64_dtype(arr_or_dtype) -> bool:
403371
"""
404372
Check whether an array-like or dtype is of the datetime64 dtype.

pandas/tests/arithmetic/test_datetime64.py

+26
Original file line numberDiff line numberDiff line change
@@ -2307,6 +2307,32 @@ def test_dti_addsub_offset_arraylike(
23072307
expected = tm.box_expected(expected, xbox)
23082308
tm.assert_equal(res, expected)
23092309

2310+
@pytest.mark.parametrize("other_box", [pd.Index, np.array])
2311+
def test_dti_addsub_object_arraylike(
2312+
self, tz_naive_fixture, box_with_array, other_box
2313+
):
2314+
tz = tz_naive_fixture
2315+
2316+
dti = pd.date_range("2017-01-01", periods=2, tz=tz)
2317+
dtarr = tm.box_expected(dti, box_with_array)
2318+
other = other_box([pd.offsets.MonthEnd(), pd.Timedelta(days=4)])
2319+
xbox = get_upcast_box(box_with_array, other)
2320+
2321+
expected = pd.DatetimeIndex(["2017-01-31", "2017-01-06"], tz=tz_naive_fixture)
2322+
expected = tm.box_expected(expected, xbox)
2323+
2324+
warn = None if box_with_array is pd.DataFrame else PerformanceWarning
2325+
with tm.assert_produces_warning(warn):
2326+
result = dtarr + other
2327+
tm.assert_equal(result, expected)
2328+
2329+
expected = pd.DatetimeIndex(["2016-12-31", "2016-12-29"], tz=tz_naive_fixture)
2330+
expected = tm.box_expected(expected, xbox)
2331+
2332+
with tm.assert_produces_warning(warn):
2333+
result = dtarr - other
2334+
tm.assert_equal(result, expected)
2335+
23102336

23112337
@pytest.mark.parametrize("years", [-1, 0, 1])
23122338
@pytest.mark.parametrize("months", [-2, 0, 2])

pandas/tests/arithmetic/test_period.py

+20
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,26 @@ def test_parr_add_sub_index(self):
10361036
expected = pi - pi
10371037
tm.assert_index_equal(result, expected)
10381038

1039+
def test_parr_add_sub_object_array(self):
1040+
pi = pd.period_range("2000-12-31", periods=3, freq="D")
1041+
parr = pi.array
1042+
1043+
other = np.array([pd.Timedelta(days=1), pd.offsets.Day(2), 3])
1044+
1045+
with tm.assert_produces_warning(PerformanceWarning):
1046+
result = parr + other
1047+
1048+
expected = pd.PeriodIndex(
1049+
["2001-01-01", "2001-01-03", "2001-01-05"], freq="D"
1050+
).array
1051+
tm.assert_equal(result, expected)
1052+
1053+
with tm.assert_produces_warning(PerformanceWarning):
1054+
result = parr - other
1055+
1056+
expected = pd.PeriodIndex(["2000-12-30"] * 3, freq="D").array
1057+
tm.assert_equal(result, expected)
1058+
10391059

10401060
class TestPeriodSeriesArithmetic:
10411061
def test_ops_series_timedelta(self):

pandas/tests/arithmetic/test_timedelta64.py

+34
Original file line numberDiff line numberDiff line change
@@ -1469,6 +1469,40 @@ def test_td64arr_addsub_anchored_offset_arraylike(self, obox, box_with_array):
14691469
with tm.assert_produces_warning(PerformanceWarning):
14701470
anchored - tdi
14711471

1472+
# ------------------------------------------------------------------
1473+
# Unsorted
1474+
1475+
def test_td64arr_add_sub_object_array(self, box_with_array):
1476+
tdi = pd.timedelta_range("1 day", periods=3, freq="D")
1477+
tdarr = tm.box_expected(tdi, box_with_array)
1478+
1479+
other = np.array(
1480+
[pd.Timedelta(days=1), pd.offsets.Day(2), pd.Timestamp("2000-01-04")]
1481+
)
1482+
1483+
warn = PerformanceWarning if box_with_array is not pd.DataFrame else None
1484+
with tm.assert_produces_warning(warn):
1485+
result = tdarr + other
1486+
1487+
expected = pd.Index(
1488+
[pd.Timedelta(days=2), pd.Timedelta(days=4), pd.Timestamp("2000-01-07")]
1489+
)
1490+
expected = tm.box_expected(expected, box_with_array)
1491+
tm.assert_equal(result, expected)
1492+
1493+
with pytest.raises(TypeError):
1494+
with tm.assert_produces_warning(warn):
1495+
tdarr - other
1496+
1497+
with tm.assert_produces_warning(warn):
1498+
result = other - tdarr
1499+
1500+
expected = pd.Index(
1501+
[pd.Timedelta(0), pd.Timedelta(0), pd.Timestamp("2000-01-01")]
1502+
)
1503+
expected = tm.box_expected(expected, box_with_array)
1504+
tm.assert_equal(result, expected)
1505+
14721506

14731507
class TestTimedeltaArraylikeMulDivOps:
14741508
# Tests for timedelta64[ns]

pandas/tests/dtypes/test_common.py

-12
Original file line numberDiff line numberDiff line change
@@ -625,18 +625,6 @@ def test_is_complex_dtype():
625625
assert com.is_complex_dtype(np.array([1 + 1j, 5]))
626626

627627

628-
def test_is_offsetlike():
629-
assert com.is_offsetlike(np.array([pd.DateOffset(month=3), pd.offsets.Nano()]))
630-
assert com.is_offsetlike(pd.offsets.MonthEnd())
631-
assert com.is_offsetlike(pd.Index([pd.DateOffset(second=1)]))
632-
633-
assert not com.is_offsetlike(pd.Timedelta(1))
634-
assert not com.is_offsetlike(np.array([1 + 1j, 5]))
635-
636-
# mixed case
637-
assert not com.is_offsetlike(np.array([pd.DateOffset(), pd.Timestamp(0)]))
638-
639-
640628
@pytest.mark.parametrize(
641629
"input_param,result",
642630
[

0 commit comments

Comments
 (0)