Skip to content

Commit 01af08b

Browse files
authored
TST: xfailed arithmetic tests (pandas-dev#36870)
1 parent 4e55346 commit 01af08b

File tree

7 files changed

+55
-34
lines changed

7 files changed

+55
-34
lines changed

doc/source/whatsnew/v1.2.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ Numeric
336336
- Bug in :class:`Series` where two :class:`Series` each have a :class:`DatetimeIndex` with different timezones having those indexes incorrectly changed when performing arithmetic operations (:issue:`33671`)
337337
- Bug in :meth:`pd._testing.assert_almost_equal` was incorrect for complex numeric types (:issue:`28235`)
338338
- Bug in :meth:`DataFrame.__rmatmul__` error handling reporting transposed shapes (:issue:`21581`)
339+
- Bug in :class:`IntegerArray` multiplication with ``timedelta`` and ``np.timedelta64`` objects (:issue:`36870`)
339340

340341
Conversion
341342
^^^^^^^^^^

pandas/core/arrays/integer.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1+
from datetime import timedelta
12
import numbers
23
from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Type, Union
34
import warnings
45

56
import numpy as np
67

7-
from pandas._libs import lib, missing as libmissing
8+
from pandas._libs import Timedelta, iNaT, lib, missing as libmissing
89
from pandas._typing import ArrayLike, DtypeObj
910
from pandas.compat import set_function_name
1011
from pandas.compat.numpy import function as nv
@@ -581,6 +582,12 @@ def _maybe_mask_result(self, result, mask, other, op_name: str):
581582
result[mask] = np.nan
582583
return result
583584

585+
if result.dtype == "timedelta64[ns]":
586+
from pandas.core.arrays import TimedeltaArray
587+
588+
result[mask] = iNaT
589+
return TimedeltaArray._simple_new(result)
590+
584591
return type(self)(result, mask, copy=False)
585592

586593
@classmethod
@@ -609,6 +616,9 @@ def integer_arithmetic_method(self, other):
609616
if not (is_float_dtype(other) or is_integer_dtype(other)):
610617
raise TypeError("can only perform ops with numeric values")
611618

619+
elif isinstance(other, (timedelta, np.timedelta64)):
620+
other = Timedelta(other)
621+
612622
else:
613623
if not (is_float(other) or is_integer(other) or other is libmissing.NA):
614624
raise TypeError("can only perform ops with numeric values")

pandas/core/arrays/numpy_.py

+21-2
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ def __array_ufunc__(self, ufunc, method: str, *inputs, **kwargs):
218218
if not isinstance(x, self._HANDLED_TYPES + (PandasArray,)):
219219
return NotImplemented
220220

221+
if ufunc not in [np.logical_or, np.bitwise_or, np.bitwise_xor]:
222+
# For binary ops, use our custom dunder methods
223+
# We haven't implemented logical dunder funcs, so exclude these
224+
# to avoid RecursionError
225+
result = ops.maybe_dispatch_ufunc_to_dunder_op(
226+
self, ufunc, method, *inputs, **kwargs
227+
)
228+
if result is not NotImplemented:
229+
return result
230+
221231
# Defer to the implementation of the ufunc on unwrapped values.
222232
inputs = tuple(x._ndarray if isinstance(x, PandasArray) else x for x in inputs)
223233
if out:
@@ -377,19 +387,28 @@ def arithmetic_method(self, other):
377387
if isinstance(a, np.ndarray):
378388
# for e.g. op vs TimedeltaArray, we may already
379389
# have an ExtensionArray, in which case we do not wrap
380-
return cls(a), cls(b)
390+
return self._wrap_ndarray_result(a), self._wrap_ndarray_result(b)
381391
return a, b
382392

383393
if isinstance(result, np.ndarray):
384394
# for e.g. multiplication vs TimedeltaArray, we may already
385395
# have an ExtensionArray, in which case we do not wrap
386-
return cls(result)
396+
return self._wrap_ndarray_result(result)
387397
return result
388398

389399
return compat.set_function_name(arithmetic_method, f"__{op.__name__}__", cls)
390400

391401
_create_comparison_method = _create_arithmetic_method
392402

403+
def _wrap_ndarray_result(self, result: np.ndarray):
404+
# If we have timedelta64[ns] result, return a TimedeltaArray instead
405+
# of a PandasArray
406+
if result.dtype == "timedelta64[ns]":
407+
from pandas.core.arrays import TimedeltaArray
408+
409+
return TimedeltaArray._simple_new(result)
410+
return type(self)(result)
411+
393412
# ------------------------------------------------------------------------
394413
# String methods interface
395414
_str_na_value = np.nan

pandas/tests/arithmetic/common.py

+12-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from pandas import DataFrame, Index, Series, array as pd_array
88
import pandas._testing as tm
9+
from pandas.core.arrays import PandasArray
910

1011

1112
def assert_invalid_addsub_type(left, right, msg=None):
@@ -56,18 +57,25 @@ def assert_invalid_comparison(left, right, box):
5657
# Note: not quite the same as how we do this for tm.box_expected
5758
xbox = box if box not in [Index, pd_array] else np.array
5859

59-
result = left == right
60+
def xbox2(x):
61+
# Eventually we'd like this to be tighter, but for now we'll
62+
# just exclude PandasArray[bool]
63+
if isinstance(x, PandasArray):
64+
return x._ndarray
65+
return x
66+
67+
result = xbox2(left == right)
6068
expected = xbox(np.zeros(result.shape, dtype=np.bool_))
6169

6270
tm.assert_equal(result, expected)
6371

64-
result = right == left
72+
result = xbox2(right == left)
6573
tm.assert_equal(result, expected)
6674

67-
result = left != right
75+
result = xbox2(left != right)
6876
tm.assert_equal(result, ~expected)
6977

70-
result = right != left
78+
result = xbox2(right != left)
7179
tm.assert_equal(result, ~expected)
7280

7381
msg = "|".join(

pandas/tests/arithmetic/conftest.py

-9
Original file line numberDiff line numberDiff line change
@@ -221,15 +221,6 @@ def mismatched_freq(request):
221221
# ------------------------------------------------------------------
222222

223223

224-
@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame], ids=id_func)
225-
def box(request):
226-
"""
227-
Several array-like containers that should have effectively identical
228-
behavior with respect to arithmetic operations.
229-
"""
230-
return request.param
231-
232-
233224
@pytest.fixture(params=[pd.Index, pd.Series, pd.DataFrame, pd.array], ids=id_func)
234225
def box_with_array(request):
235226
"""

pandas/tests/arithmetic/test_datetime64.py

+10-8
Original file line numberDiff line numberDiff line change
@@ -205,8 +205,6 @@ def test_nat_comparisons(self, dtype, index_or_series, reverse, pair):
205205
def test_comparison_invalid(self, tz_naive_fixture, box_with_array):
206206
# GH#4968
207207
# invalid date/int comparisons
208-
if box_with_array is pd.array:
209-
pytest.xfail("assert_invalid_comparison doesnt handle BooleanArray yet")
210208
tz = tz_naive_fixture
211209
ser = Series(range(5))
212210
ser2 = Series(pd.date_range("20010101", periods=5, tz=tz))
@@ -226,32 +224,36 @@ def test_comparison_invalid(self, tz_naive_fixture, box_with_array):
226224
)
227225
@pytest.mark.parametrize("dtype", [None, object])
228226
def test_nat_comparisons_scalar(self, dtype, data, box_with_array):
227+
box = box_with_array
229228
if box_with_array is tm.to_array and dtype is object:
230229
# dont bother testing ndarray comparison methods as this fails
231230
# on older numpys (since they check object identity)
232231
return
233-
if box_with_array is pd.array and dtype is object:
234-
pytest.xfail("reversed comparisons give BooleanArray, not ndarray")
235232

236-
xbox = (
237-
box_with_array if box_with_array not in [pd.Index, pd.array] else np.ndarray
238-
)
233+
xbox = box if box not in [pd.Index, pd.array] else np.ndarray
239234

240235
left = Series(data, dtype=dtype)
241-
left = tm.box_expected(left, box_with_array)
236+
left = tm.box_expected(left, box)
242237

243238
expected = [False, False, False]
244239
expected = tm.box_expected(expected, xbox)
240+
if box is pd.array and dtype is object:
241+
expected = pd.array(expected, dtype="bool")
242+
245243
tm.assert_equal(left == NaT, expected)
246244
tm.assert_equal(NaT == left, expected)
247245

248246
expected = [True, True, True]
249247
expected = tm.box_expected(expected, xbox)
248+
if box is pd.array and dtype is object:
249+
expected = pd.array(expected, dtype="bool")
250250
tm.assert_equal(left != NaT, expected)
251251
tm.assert_equal(NaT != left, expected)
252252

253253
expected = [False, False, False]
254254
expected = tm.box_expected(expected, xbox)
255+
if box is pd.array and dtype is object:
256+
expected = pd.array(expected, dtype="bool")
255257
tm.assert_equal(left < NaT, expected)
256258
tm.assert_equal(NaT > left, expected)
257259
tm.assert_equal(left <= NaT, expected)

pandas/tests/arithmetic/test_numeric.py

-10
Original file line numberDiff line numberDiff line change
@@ -187,10 +187,6 @@ def test_ops_series(self):
187187
def test_numeric_arr_mul_tdscalar(self, scalar_td, numeric_idx, box_with_array):
188188
# GH#19333
189189
box = box_with_array
190-
if box is pd.array:
191-
pytest.xfail(
192-
"we get a PandasArray[timedelta64[ns]] instead of TimedeltaArray"
193-
)
194190
index = numeric_idx
195191
expected = pd.TimedeltaIndex([pd.Timedelta(days=n) for n in range(5)])
196192

@@ -214,8 +210,6 @@ def test_numeric_arr_mul_tdscalar(self, scalar_td, numeric_idx, box_with_array):
214210
)
215211
def test_numeric_arr_mul_tdscalar_numexpr_path(self, scalar_td, box_with_array):
216212
box = box_with_array
217-
if box is pd.array:
218-
pytest.xfail("IntegerArray.__mul__ doesnt handle timedeltas")
219213

220214
arr = np.arange(2 * 10 ** 4).astype(np.int64)
221215
obj = tm.box_expected(arr, box, transpose=False)
@@ -231,8 +225,6 @@ def test_numeric_arr_mul_tdscalar_numexpr_path(self, scalar_td, box_with_array):
231225

232226
def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box_with_array):
233227
box = box_with_array
234-
if box is pd.array:
235-
pytest.xfail("We get PandasArray[td64] instead of TimedeltaArray")
236228

237229
index = numeric_idx[1:3]
238230

@@ -263,8 +255,6 @@ def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box_with_array
263255
)
264256
def test_add_sub_timedeltalike_invalid(self, numeric_idx, other, box_with_array):
265257
box = box_with_array
266-
if box is pd.array:
267-
pytest.xfail("PandasArray[int].__add__ doesnt raise on td64")
268258

269259
left = tm.box_expected(numeric_idx, box)
270260
msg = (

0 commit comments

Comments
 (0)