From cf66930aba62ae696f97ffcab2f3e3a9299b7adf Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 18 Dec 2021 12:35:44 -0800 Subject: [PATCH 1/7] TST: parametrize/share td64 arithmetic tests --- pandas/tests/arithmetic/conftest.py | 8 +- pandas/tests/arithmetic/test_timedelta64.py | 196 ++++++-------------- 2 files changed, 64 insertions(+), 140 deletions(-) diff --git a/pandas/tests/arithmetic/conftest.py b/pandas/tests/arithmetic/conftest.py index 55cbfaf76d5a7..01b447aa855a3 100644 --- a/pandas/tests/arithmetic/conftest.py +++ b/pandas/tests/arithmetic/conftest.py @@ -107,15 +107,15 @@ def numeric_idx(request): @pytest.fixture( params=[ - pd.Timedelta("5m4s").to_pytimedelta(), - pd.Timedelta("5m4s"), - pd.Timedelta("5m4s").to_timedelta64(), + pd.Timedelta("10m7s").to_pytimedelta(), + pd.Timedelta("10m7s"), + pd.Timedelta("10m7s").to_timedelta64(), ], ids=lambda x: type(x).__name__, ) def scalar_td(request): """ - Several variants of Timedelta scalars representing 5 minutes and 4 seconds + Several variants of Timedelta scalars representing 10 minutes and 7 seconds. """ return request.param diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 765708bd195dd..6100b56d8daa2 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -663,9 +663,10 @@ def test_tdi_ops_attributes(self): class TestAddSubNaTMasking: # TODO: parametrize over boxes - def test_tdi_add_timestamp_nat_masking(self): + def test_tdarr_add_timestamp_nat_masking(self, box_with_array): # GH#17991 checking for overflow-masking with NaT tdinat = pd.to_timedelta(["24658 days 11:15:00", "NaT"]) + tdobj = tm.box_expected(tdinat, box_with_array) tsneg = Timestamp("1950-01-01") ts_neg_variants = [ @@ -684,8 +685,11 @@ def test_tdi_add_timestamp_nat_masking(self): ] for variant in ts_neg_variants + ts_pos_variants: - res = tdinat + variant - assert res[1] is NaT + res = tdobj + variant + if box_with_array is DataFrame: + assert res.iloc[1, 1] is NaT + else: + assert res[1] is NaT def test_tdi_add_overflow(self): # See GH#14068 @@ -990,49 +994,25 @@ def test_timedelta64_ops_nat(self): # ------------------------------------------------------------- # Binary operations td64 arraylike and datetime-like - def test_td64arr_sub_timestamp_raises(self, box_with_array): - idx = TimedeltaIndex(["1 day", "2 day"]) - idx = tm.box_expected(idx, box_with_array) - - msg = ( - "cannot subtract a datelike from|" - "Could not operate|" - "cannot perform operation" - ) - with pytest.raises(TypeError, match=msg): - idx - Timestamp("2011-01-01") - - def test_td64arr_add_timestamp(self, box_with_array, tz_naive_fixture): - # GH#23215 - - # TODO: parametrize over scalar datetime types? + @pytest.mark.parametrize("cls", [Timestamp, datetime, np.datetime64]) + def test_td64arr_add_sub_datetimelike_scalar( + self, cls, box_with_array, tz_naive_fixture + ): + # GH#11925, GH#29558, GH#23215 tz = tz_naive_fixture - other = Timestamp("2011-01-01", tz=tz) - - idx = TimedeltaIndex(["1 day", "2 day"]) - expected = DatetimeIndex(["2011-01-02", "2011-01-03"], tz=tz) - - idx = tm.box_expected(idx, box_with_array) - expected = tm.box_expected(expected, box_with_array) - - result = idx + other - tm.assert_equal(result, expected) - result = other + idx - tm.assert_equal(result, expected) + dt_scalar = Timestamp("2012-01-01", tz=tz) + if cls is datetime: + ts = dt_scalar.to_pydatetime() + elif cls is np.datetime64: + if tz_naive_fixture is not None: + return + ts = dt_scalar.to_datetime64() + else: + ts = dt_scalar - @pytest.mark.parametrize( - "ts", - [ - Timestamp("2012-01-01"), - Timestamp("2012-01-01").to_pydatetime(), - Timestamp("2012-01-01").to_datetime64(), - ], - ) - def test_td64arr_add_sub_datetimelike_scalar(self, ts, box_with_array): - # GH#11925, GH#29558 tdi = timedelta_range("1 day", periods=3) - expected = pd.date_range("2012-01-02", periods=3) + expected = pd.date_range("2012-01-02", periods=3, tz=tz) tdarr = tm.box_expected(tdi, box_with_array) expected = tm.box_expected(expected, box_with_array) @@ -1040,7 +1020,7 @@ def test_td64arr_add_sub_datetimelike_scalar(self, ts, box_with_array): tm.assert_equal(ts + tdarr, expected) tm.assert_equal(tdarr + ts, expected) - expected2 = pd.date_range("2011-12-31", periods=3, freq="-1D") + expected2 = pd.date_range("2011-12-31", periods=3, freq="-1D", tz=tz) expected2 = tm.box_expected(expected2, box_with_array) tm.assert_equal(ts - tdarr, expected2) @@ -1050,9 +1030,22 @@ def test_td64arr_add_sub_datetimelike_scalar(self, ts, box_with_array): with pytest.raises(TypeError, match=msg): tdarr - ts - def test_tdi_sub_dt64_array(self, box_with_array): + def test_td64arr_add_datetime64_nat(self, box_with_array): + # GH#23215 + other = np.datetime64("NaT") + + tdi = timedelta_range("1 day", periods=3) + expected = DatetimeIndex(["NaT", "NaT", "NaT"]) + + tdser = tm.box_expected(tdi, box_with_array) + expected = tm.box_expected(expected, box_with_array) + + tm.assert_equal(tdser + other, expected) + tm.assert_equal(other + tdser, expected) + + def test_td64arr_sub_dt64_array(self, box_with_array): dti = pd.date_range("2016-01-01", periods=3) - tdi = dti - dti.shift(1) + tdi = TimedeltaIndex(["-1 Day"] * 3) dtarr = dti.values expected = DatetimeIndex(dtarr) - tdi @@ -1067,9 +1060,9 @@ def test_tdi_sub_dt64_array(self, box_with_array): result = dtarr - tdi tm.assert_equal(result, expected) - def test_tdi_add_dt64_array(self, box_with_array): + def test_td64arr_add_dt64_array(self, box_with_array): dti = pd.date_range("2016-01-01", periods=3) - tdi = dti - dti.shift(1) + tdi = TimedeltaIndex(["-1 Day"] * 3) dtarr = dti.values expected = DatetimeIndex(dtarr) + tdi @@ -1081,19 +1074,6 @@ def test_tdi_add_dt64_array(self, box_with_array): result = dtarr + tdi tm.assert_equal(result, expected) - def test_td64arr_add_datetime64_nat(self, box_with_array): - # GH#23215 - other = np.datetime64("NaT") - - tdi = timedelta_range("1 day", periods=3) - expected = DatetimeIndex(["NaT", "NaT", "NaT"]) - - tdser = tm.box_expected(tdi, box_with_array) - expected = tm.box_expected(expected, box_with_array) - - tm.assert_equal(tdser + other, expected) - tm.assert_equal(other + tdser, expected) - # ------------------------------------------------------------------ # Invalid __add__/__sub__ operations @@ -1569,6 +1549,19 @@ def test_td64arr_div_nat_invalid(self, box_with_array): with pytest.raises(TypeError, match="Cannot divide NaTType by"): NaT / rng + dt64nat = np.datetime64("NaT", "ns") + msg = "|".join( + [ + "ufunc 'true_divide' cannot use operands", + "cannot perform __r?truediv__", + "Cannot divide datetime64 by TimedeltaArray", + ] + ) + with pytest.raises(TypeError, match=msg): + rng / dt64nat + with pytest.raises(TypeError, match=msg): + dt64nat / rng + def test_td64arr_div_td64nat(self, box_with_array): # GH#23829 box = box_with_array @@ -1742,52 +1735,28 @@ def test_td64arr_floordiv_td64arr_with_nat( tm.assert_equal(result, expected) def test_td64arr_floordiv_tdscalar(self, box_with_array, scalar_td): - # GH#18831 + # GH#18831, GH#19125 box = box_with_array xbox = np.ndarray if box is pd.array else box + td = Timedelta("5m3s") # i.e. (scalar_td - 1sec) / 2 - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan + td1 = Series([td, td, NaT], dtype="m8[ns]") + td1 = tm.box_expected(td1, box, transpose=False) expected = Series([0, 0, np.nan]) - - td1 = tm.box_expected(td1, box, transpose=False) expected = tm.box_expected(expected, xbox, transpose=False) result = td1 // scalar_td tm.assert_equal(result, expected) - def test_td64arr_rfloordiv_tdscalar(self, box_with_array, scalar_td): - # GH#18831 - box = box_with_array - xbox = np.ndarray if box is pd.array else box - - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan - - expected = Series([1, 1, np.nan]) - - td1 = tm.box_expected(td1, box, transpose=False) + # Reversed op + expected = Series([2, 2, np.nan]) expected = tm.box_expected(expected, xbox, transpose=False) result = scalar_td // td1 tm.assert_equal(result, expected) - def test_td64arr_rfloordiv_tdscalar_explicit(self, box_with_array, scalar_td): - # GH#18831 - box = box_with_array - xbox = np.ndarray if box is pd.array else box - - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) - td1.iloc[2] = np.nan - - expected = Series([1, 1, np.nan]) - - td1 = tm.box_expected(td1, box, transpose=False) - expected = tm.box_expected(expected, xbox, transpose=False) - - # We can test __rfloordiv__ using this syntax, - # see `test_timedelta_rfloordiv` + # same thing buts let's be explicit about calling __rfloordiv__ result = td1.__rfloordiv__(scalar_td) tm.assert_equal(result, expected) @@ -1801,49 +1770,6 @@ def test_td64arr_floordiv_int(self, box_with_array): with pytest.raises(TypeError, match=pattern): 1 // idx - def test_td64arr_floordiv_tdlike_scalar(self, two_hours, box_with_array): - box = box_with_array - xbox = np.ndarray if box is pd.array else box - - tdi = timedelta_range("1 days", "10 days", name="foo") - expected = Int64Index((np.arange(10) + 1) * 12, name="foo") - - tdi = tm.box_expected(tdi, box) - expected = tm.box_expected(expected, xbox) - - result = tdi // two_hours - tm.assert_equal(result, expected) - - # TODO: Is this redundant with test_td64arr_floordiv_tdlike_scalar? - @pytest.mark.parametrize( - "scalar_td", - [ - timedelta(minutes=10, seconds=7), - Timedelta("10m7s"), - Timedelta("10m7s").to_timedelta64(), - ], - ids=lambda x: type(x).__name__, - ) - def test_td64arr_rfloordiv_tdlike_scalar(self, scalar_td, box_with_array): - # GH#19125 - box = box_with_array - xbox = np.ndarray if box_with_array is pd.array else box_with_array - - tdi = TimedeltaIndex(["00:05:03", "00:05:03", NaT], freq=None) - expected = pd.Index([2.0, 2.0, np.nan]) - - tdi = tm.box_expected(tdi, box, transpose=False) - expected = tm.box_expected(expected, xbox, transpose=False) - - res = tdi.__rfloordiv__(scalar_td) - tm.assert_equal(res, expected) - - expected = pd.Index([0.0, 0.0, np.nan]) - expected = tm.box_expected(expected, xbox, transpose=False) - - res = tdi // (scalar_td) - tm.assert_equal(res, expected) - # ------------------------------------------------------------------ # mod, divmod # TODO: operations with timedelta-like arrays, numeric arrays, @@ -2108,8 +2034,6 @@ def test_td64arr_mul_int_series(self, box_with_array, names): # TODO: Should we be parametrizing over types for `ser` too? def test_float_series_rdiv_td64arr(self, box_with_array, names): # GH#19042 test for correct name attachment - # TODO: the direct operation TimedeltaIndex / Series still - # needs to be fixed. box = box_with_array tdi = TimedeltaIndex( ["0days", "1day", "2days", "3days", "4days"], name=names[0] From 9edc1aff1591771f1e518f7285ca8b159478d602 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 18 Dec 2021 13:28:42 -0800 Subject: [PATCH 2/7] TST: share/parametriz dt64 arith tests --- pandas/tests/arithmetic/test_datetime64.py | 172 ++++++++------------ pandas/tests/arithmetic/test_timedelta64.py | 4 +- 2 files changed, 74 insertions(+), 102 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 6a5d88cc8d4a6..850e85ccd77bf 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1043,6 +1043,48 @@ def test_dt64arr_add_timestamp_raises(self, box_with_array): # ------------------------------------------------------------- # Other Invalid Addition/Subtraction + # Note: freq here includes both Tick and non-Tick offsets; this is + # relevant because historically integer-addition was allowed if we had + # a freq. + @pytest.mark.parametrize("freq", ["H", "D", "W", "M", "MS", "Q", "B", None]) + @pytest.mark.parametrize("dtype", [None, "uint8"]) + def test_dt64arr_addsub_intlike( + self, dtype, box_with_array, freq, tz_naive_fixture + ): + # GH#19959, GH#19123, GH#19012 + tz = tz_naive_fixture + if box_with_array is pd.DataFrame: + # alignment headaches + return + + if freq is None: + dti = DatetimeIndex(["NaT", "2017-04-05 06:07:08"], tz=tz) + else: + dti = date_range("2016-01-01", periods=2, freq=freq, tz=tz) + + obj = box_with_array(dti) + other = np.array([4, -1], dtype=dtype) + + msg = "|".join( + [ + "Addition/subtraction of integers", + "cannot subtract DatetimeArray from", + # IntegerArray + "can only perform ops with numeric values", + "unsupported operand type.*Categorical", + ] + ) + assert_invalid_addsub_type(obj, 1, msg) + assert_invalid_addsub_type(obj, np.int64(2), msg) + assert_invalid_addsub_type(obj, np.array(3, dtype=np.int64), msg) + assert_invalid_addsub_type(obj, other, msg) + assert_invalid_addsub_type(obj, np.array(other), msg) + assert_invalid_addsub_type(obj, pd.array(other), msg) + assert_invalid_addsub_type(obj, pd.Categorical(other), msg) + assert_invalid_addsub_type(obj, pd.Index(other), msg) + assert_invalid_addsub_type(obj, pd.core.indexes.api.NumericIndex(other), msg) + assert_invalid_addsub_type(obj, Series(other), msg) + @pytest.mark.parametrize( "other", [ @@ -1101,6 +1143,7 @@ def test_dt64arr_addsub_time_objects_raises(self, box_with_array, tz_naive_fixtu obj1 = tm.box_expected(obj1, box_with_array) obj2 = tm.box_expected(obj2, box_with_array) + # TODO: can we use assert_invalid_addsub_type? with warnings.catch_warnings(record=True): # pandas.errors.PerformanceWarning: Non-vectorized DateOffset being # applied to Series or DatetimeIndex @@ -1144,6 +1187,35 @@ def test_dt64arr_addsub_time_objects_raises(self, box_with_array, tz_naive_fixtu with pytest.raises(TypeError, match=msg): obj2 + obj1 + # ------------------------------------------------------------- + # Other invalid operations + + @pytest.mark.parametrize( + "dt64_series", + [ + Series([Timestamp("19900315"), Timestamp("19900315")]), + Series([NaT, Timestamp("19900315")]), + Series([NaT, NaT], dtype="datetime64[ns]"), + ], + ) + @pytest.mark.parametrize("one", [1, 1.0, np.array(1)]) + def test_dt64_mul_div_numeric_invalid(self, one, dt64_series, box_with_array): + obj = tm.box_expected(dt64_series, box_with_array) + + msg = "cannot perform .* with this index type" + + # multiplication + with pytest.raises(TypeError, match=msg): + obj * one + with pytest.raises(TypeError, match=msg): + one * obj + + # division + with pytest.raises(TypeError, match=msg): + obj / one + with pytest.raises(TypeError, match=msg): + one / obj + class TestDatetime64DateOffsetArithmetic: @@ -1867,50 +1939,6 @@ def test_datetime64_ops_nat(self): # ------------------------------------------------------------- # Invalid Operations - # TODO: this block also needs to be de-duplicated and parametrized - - @pytest.mark.parametrize( - "dt64_series", - [ - Series([Timestamp("19900315"), Timestamp("19900315")]), - Series([NaT, Timestamp("19900315")]), - Series([NaT, NaT], dtype="datetime64[ns]"), - ], - ) - @pytest.mark.parametrize("one", [1, 1.0, np.array(1)]) - def test_dt64_mul_div_numeric_invalid(self, one, dt64_series): - # multiplication - msg = "cannot perform .* with this index type" - with pytest.raises(TypeError, match=msg): - dt64_series * one - with pytest.raises(TypeError, match=msg): - one * dt64_series - - # division - with pytest.raises(TypeError, match=msg): - dt64_series / one - with pytest.raises(TypeError, match=msg): - one / dt64_series - - # TODO: parametrize over box - def test_dt64_series_add_intlike(self, tz_naive_fixture): - # GH#19123 - tz = tz_naive_fixture - dti = DatetimeIndex(["2016-01-02", "2016-02-03", "NaT"], tz=tz) - ser = Series(dti) - - other = Series([20, 30, 40], dtype="uint8") - - msg = "|".join( - [ - "Addition/subtraction of integers and integer-arrays", - "cannot subtract .* from ndarray", - ] - ) - assert_invalid_addsub_type(ser, 1, msg) - assert_invalid_addsub_type(ser, other, msg) - assert_invalid_addsub_type(ser, np.array(other), msg) - assert_invalid_addsub_type(ser, pd.Index(other), msg) # ------------------------------------------------------------- # Timezone-Centric Tests @@ -1979,62 +2007,6 @@ def test_operators_datetimelike_with_timezones(self): class TestDatetimeIndexArithmetic: - - # ------------------------------------------------------------- - # Binary operations DatetimeIndex and int - - def test_dti_addsub_int(self, tz_naive_fixture, one): - # Variants of `one` for #19012 - tz = tz_naive_fixture - rng = date_range("2000-01-01 09:00", freq="H", periods=10, tz=tz) - msg = "Addition/subtraction of integers" - - with pytest.raises(TypeError, match=msg): - rng + one - with pytest.raises(TypeError, match=msg): - rng += one - with pytest.raises(TypeError, match=msg): - rng - one - with pytest.raises(TypeError, match=msg): - rng -= one - - # ------------------------------------------------------------- - # __add__/__sub__ with integer arrays - - @pytest.mark.parametrize("freq", ["H", "D"]) - @pytest.mark.parametrize("int_holder", [np.array, pd.Index]) - def test_dti_add_intarray_tick(self, int_holder, freq): - # GH#19959 - dti = date_range("2016-01-01", periods=2, freq=freq) - other = int_holder([4, -1]) - - msg = "|".join( - ["Addition/subtraction of integers", "cannot subtract DatetimeArray from"] - ) - assert_invalid_addsub_type(dti, other, msg) - - @pytest.mark.parametrize("freq", ["W", "M", "MS", "Q"]) - @pytest.mark.parametrize("int_holder", [np.array, pd.Index]) - def test_dti_add_intarray_non_tick(self, int_holder, freq): - # GH#19959 - dti = date_range("2016-01-01", periods=2, freq=freq) - other = int_holder([4, -1]) - - msg = "|".join( - ["Addition/subtraction of integers", "cannot subtract DatetimeArray from"] - ) - assert_invalid_addsub_type(dti, other, msg) - - @pytest.mark.parametrize("int_holder", [np.array, pd.Index]) - def test_dti_add_intarray_no_freq(self, int_holder): - # GH#19959 - dti = DatetimeIndex(["2016-01-01", "NaT", "2017-04-05 06:07:08"]) - other = int_holder([9, 4, -1]) - msg = "|".join( - ["cannot subtract DatetimeArray from", "Addition/subtraction of integers"] - ) - assert_invalid_addsub_type(dti, other, msg) - # ------------------------------------------------------------- # Binary operations DatetimeIndex and TimedeltaIndex/array diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index 6100b56d8daa2..a9e0f8595a359 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -746,7 +746,7 @@ def test_timedelta_ops_with_missing_values(self): with pytest.raises(TypeError, match=msg): # Passing datetime64-dtype data to TimedeltaIndex is no longer # supported GH#29794 - pd.to_timedelta(Series([NaT])) + pd.to_timedelta(Series([NaT])) # TODO: belongs elsewhere? sn = pd.to_timedelta(Series([NaT], dtype="m8[ns]")) @@ -755,7 +755,7 @@ def test_timedelta_ops_with_missing_values(self): with pytest.raises(TypeError, match=msg): # Passing datetime64-dtype data to TimedeltaIndex is no longer # supported GH#29794 - DataFrame([NaT]).apply(pd.to_timedelta) + DataFrame([NaT]).apply(pd.to_timedelta) # TODO: belongs elsewhere? dfn = DataFrame([NaT.value]).apply(pd.to_timedelta) From f1f1a07f8950733de829e09ef2bf3a6ad516a7d2 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 18 Dec 2021 13:33:36 -0800 Subject: [PATCH 3/7] use helper --- pandas/tests/arithmetic/test_datetime64.py | 45 ++++------------------ 1 file changed, 8 insertions(+), 37 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 850e85ccd77bf..9e0509b27f69d 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -1143,49 +1143,20 @@ def test_dt64arr_addsub_time_objects_raises(self, box_with_array, tz_naive_fixtu obj1 = tm.box_expected(obj1, box_with_array) obj2 = tm.box_expected(obj2, box_with_array) - # TODO: can we use assert_invalid_addsub_type? + msg = "|".join( + [ + "unsupported operand", + "cannot subtract DatetimeArray from ndarray", + ] + ) + with warnings.catch_warnings(record=True): # pandas.errors.PerformanceWarning: Non-vectorized DateOffset being # applied to Series or DatetimeIndex # we aren't testing that here, so ignore. warnings.simplefilter("ignore", PerformanceWarning) - # If `x + y` raises, then `y + x` should raise here as well - - msg = ( - r"unsupported operand type\(s\) for -: " - "'(Timestamp|DatetimeArray)' and 'datetime.time'" - ) - with pytest.raises(TypeError, match=msg): - obj1 - obj2 - - msg = "|".join( - [ - "cannot subtract DatetimeArray from ndarray", - "ufunc (subtract|'subtract') cannot use operands with types " - r"dtype\('O'\) and dtype\(' Date: Sat, 18 Dec 2021 14:01:05 -0800 Subject: [PATCH 4/7] share more --- pandas/tests/arithmetic/test_datetime64.py | 125 +++++++-------------- 1 file changed, 41 insertions(+), 84 deletions(-) diff --git a/pandas/tests/arithmetic/test_datetime64.py b/pandas/tests/arithmetic/test_datetime64.py index 9e0509b27f69d..8194f47541e4c 100644 --- a/pandas/tests/arithmetic/test_datetime64.py +++ b/pandas/tests/arithmetic/test_datetime64.py @@ -814,6 +814,9 @@ def test_dt64arr_add_timedeltalike_scalar( result = rng + two_hours tm.assert_equal(result, expected) + result = two_hours + rng + tm.assert_equal(result, expected) + rng += two_hours tm.assert_equal(rng, expected) @@ -834,34 +837,6 @@ def test_dt64arr_sub_timedeltalike_scalar( rng -= two_hours tm.assert_equal(rng, expected) - # TODO: redundant with test_dt64arr_add_timedeltalike_scalar - def test_dt64arr_add_td64_scalar(self, box_with_array): - # scalar timedeltas/np.timedelta64 objects - # operate with np.timedelta64 correctly - ser = Series([Timestamp("20130101 9:01"), Timestamp("20130101 9:02")]) - - expected = Series( - [Timestamp("20130101 9:01:01"), Timestamp("20130101 9:02:01")] - ) - - dtarr = tm.box_expected(ser, box_with_array) - expected = tm.box_expected(expected, box_with_array) - - result = dtarr + np.timedelta64(1, "s") - tm.assert_equal(result, expected) - result = np.timedelta64(1, "s") + dtarr - tm.assert_equal(result, expected) - - expected = Series( - [Timestamp("20130101 9:01:00.005"), Timestamp("20130101 9:02:00.005")] - ) - expected = tm.box_expected(expected, box_with_array) - - result = dtarr + np.timedelta64(5, "ms") - tm.assert_equal(result, expected) - result = np.timedelta64(5, "ms") + dtarr - tm.assert_equal(result, expected) - def test_dt64arr_add_sub_td64_nat(self, box_with_array, tz_naive_fixture): # GH#23320 special handling for timedelta64("NaT") tz = tz_naive_fixture @@ -918,6 +893,9 @@ def test_dt64arr_add_sub_td64ndarray(self, tz_naive_fixture, box_with_array): Timestamp("2013-01-01"), Timestamp("2013-01-01").to_pydatetime(), Timestamp("2013-01-01").to_datetime64(), + # GH#7996, GH#22163 ensure non-nano datetime64 is converted to nano + # for DataFrame operation + np.datetime64("2013-01-01", "D"), ], ) def test_dt64arr_sub_dtscalar(self, box_with_array, ts): @@ -931,25 +909,11 @@ def test_dt64arr_sub_dtscalar(self, box_with_array, ts): result = idx - ts tm.assert_equal(result, expected) - def test_dt64arr_sub_datetime64_not_ns(self, box_with_array): - # GH#7996, GH#22163 ensure non-nano datetime64 is converted to nano - # for DataFrame operation - dt64 = np.datetime64("2013-01-01") - assert dt64.dtype == "datetime64[D]" - - dti = date_range("20130101", periods=3)._with_freq(None) - dtarr = tm.box_expected(dti, box_with_array) - - expected = TimedeltaIndex(["0 Days", "1 Day", "2 Days"]) - expected = tm.box_expected(expected, box_with_array) - - result = dtarr - dt64 - tm.assert_equal(result, expected) - - result = dt64 - dtarr + result = ts - idx + tm.assert_equal(result, -expected) tm.assert_equal(result, -expected) - def test_dt64arr_sub_timestamp(self, box_with_array): + def test_dt64arr_sub_timestamp_tzaware(self, box_with_array): ser = date_range("2014-03-17", periods=2, freq="D", tz="US/Eastern") ser = ser._with_freq(None) ts = ser[0] @@ -1024,21 +988,27 @@ def test_dt64arr_aware_sub_dt64ndarray_raises( # ------------------------------------------------------------- # Addition of datetime-like others (invalid) - def test_dt64arr_add_dt64ndarray_raises(self, tz_naive_fixture, box_with_array): - + def test_dt64arr_add_dtlike_raises(self, tz_naive_fixture, box_with_array): + # GH#22163 ensure DataFrame doesn't cast Timestamp to i8 + # GH#9631 tz = tz_naive_fixture - dti = date_range("2016-01-01", periods=3, tz=tz) - dt64vals = dti.values + dti = date_range("2016-01-01", periods=3, tz=tz) + if tz is None: + dti2 = dti.tz_localize("US/Eastern") + else: + dti2 = dti.tz_localize(None) dtarr = tm.box_expected(dti, box_with_array) - assert_cannot_add(dtarr, dt64vals) - def test_dt64arr_add_timestamp_raises(self, box_with_array): - # GH#22163 ensure DataFrame doesn't cast Timestamp to i8 - idx = DatetimeIndex(["2011-01-01", "2011-01-02"]) - ts = idx[0] - idx = tm.box_expected(idx, box_with_array) - assert_cannot_add(idx, ts) + assert_cannot_add(dtarr, dti.values) + assert_cannot_add(dtarr, dti) + assert_cannot_add(dtarr, dtarr) + assert_cannot_add(dtarr, dti[0]) + assert_cannot_add(dtarr, dti[0].to_pydatetime()) + assert_cannot_add(dtarr, dti[0].to_datetime64()) + assert_cannot_add(dtarr, dti2[0]) + assert_cannot_add(dtarr, dti2[0].to_pydatetime()) + assert_cannot_add(dtarr, np.datetime64("2011-01-01", "D")) # ------------------------------------------------------------- # Other Invalid Addition/Subtraction @@ -1263,13 +1233,21 @@ def test_dti_add_tick_tzaware(self, tz_aware_fixture, box_with_array): dates = tm.box_expected(dates, box_with_array) expected = tm.box_expected(expected, box_with_array) - # TODO: sub? for scalar in [pd.offsets.Hour(5), np.timedelta64(5, "h"), timedelta(hours=5)]: offset = dates + scalar tm.assert_equal(offset, expected) offset = scalar + dates tm.assert_equal(offset, expected) + roundtrip = offset - scalar + tm.assert_equal(roundtrip, dates) + + msg = "|".join( + ["bad operand type for unary -", "cannot subtract DatetimeArray"] + ) + with pytest.raises(TypeError, match=msg): + scalar - dates + # ------------------------------------------------------------- # RelativeDelta DateOffsets @@ -1719,13 +1697,15 @@ def test_datetimeindex_sub_datetimeindex_overflow(self): class TestTimestampSeriesArithmetic: - def test_empty_series_add_sub(self): + def test_empty_series_add_sub(self, box_with_array): # GH#13844 a = Series(dtype="M8[ns]") b = Series(dtype="m8[ns]") - tm.assert_series_equal(a, a + b) - tm.assert_series_equal(a, a - b) - tm.assert_series_equal(a, b + a) + a = box_with_array(a) + b = box_with_array(b) + tm.assert_equal(a, a + b) + tm.assert_equal(a, a - b) + tm.assert_equal(a, b + a) msg = "cannot subtract" with pytest.raises(TypeError, match=msg): b - a @@ -1908,9 +1888,6 @@ def test_datetime64_ops_nat(self): NaT + nat_series_dtype_timestamp, nat_series_dtype_timestamp ) - # ------------------------------------------------------------- - # Invalid Operations - # ------------------------------------------------------------- # Timezone-Centric Tests @@ -2097,26 +2074,6 @@ def test_dti_isub_tdi(self, tz_naive_fixture): # TODO: A couple other tests belong in this section. Move them in # A PR where there isn't already a giant diff. - @pytest.mark.parametrize( - "addend", - [ - datetime(2011, 1, 1), - DatetimeIndex(["2011-01-01", "2011-01-02"]), - DatetimeIndex(["2011-01-01", "2011-01-02"]).tz_localize("US/Eastern"), - np.datetime64("2011-01-01"), - Timestamp("2011-01-01"), - ], - ids=lambda x: type(x).__name__, - ) - @pytest.mark.parametrize("tz", [None, "US/Eastern"]) - def test_add_datetimelike_and_dtarr(self, box_with_array, addend, tz): - # GH#9631 - dti = DatetimeIndex(["2011-01-01", "2011-01-02"]).tz_localize(tz) - dtarr = tm.box_expected(dti, box_with_array) - msg = "cannot add DatetimeArray and" - - assert_cannot_add(dtarr, addend, msg) - # ------------------------------------------------------------- def test_dta_add_sub_index(self, tz_naive_fixture): From dd05f23ae68e98f1691f3980e227e5b115eb5e21 Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 18 Dec 2021 15:16:25 -0800 Subject: [PATCH 5/7] use helper function --- pandas/tests/arithmetic/test_numeric.py | 58 ++++++++----------------- 1 file changed, 18 insertions(+), 40 deletions(-) diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index 8348bf2a79b8e..063e3c7e6dd19 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -29,7 +29,10 @@ UInt64Index, ) from pandas.core.computation import expressions as expr -from pandas.tests.arithmetic.common import assert_invalid_comparison +from pandas.tests.arithmetic.common import ( + assert_invalid_addsub_type, + assert_invalid_comparison, +) @pytest.fixture(params=[Index, Series, tm.to_array]) @@ -258,63 +261,38 @@ def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box_with_array np.timedelta64("NaT", "D"), pd.offsets.Minute(3), pd.offsets.Second(0), - ], - ) - def test_add_sub_timedeltalike_invalid(self, numeric_idx, other, box_with_array): - box = box_with_array - - left = tm.box_expected(numeric_idx, box) - msg = ( - "unsupported operand type|" - "Addition/subtraction of integers and integer-arrays|" - "Instead of adding/subtracting|" - "cannot use operands with types dtype|" - "Concatenation operation is not implemented for NumPy arrays" - ) - with pytest.raises(TypeError, match=msg): - left + other - with pytest.raises(TypeError, match=msg): - other + left - with pytest.raises(TypeError, match=msg): - left - other - with pytest.raises(TypeError, match=msg): - other - left - - @pytest.mark.parametrize( - "other", - [ + # GH#28080 numeric+datetimelike should raise; Timestamp used + # to raise NullFrequencyError but that behavior was removed in 1.0 + pd.Timestamp("2021-01-01", tz="Asia/Tokyo"), + pd.Timestamp("2021-01-01"), pd.Timestamp("2021-01-01").to_pydatetime(), pd.Timestamp("2021-01-01", tz="UTC").to_pydatetime(), pd.Timestamp("2021-01-01").to_datetime64(), + np.datetime64("NaT", "ns"), pd.NaT, ], ) - @pytest.mark.filterwarnings("ignore:elementwise comp:DeprecationWarning") - def test_add_sub_datetimelike_invalid(self, numeric_idx, other, box_with_array): - # GH#28080 numeric+datetime64 should raise; Timestamp raises - # NullFrequencyError instead of TypeError so is excluded. + def test_add_sub_datetimedeltalike_invalid( + self, numeric_idx, other, box_with_array + ): box = box_with_array - left = tm.box_expected(numeric_idx, box) + left = tm.box_expected(numeric_idx, box) msg = "|".join( [ "unsupported operand type", - "Cannot (add|subtract) NaT (to|from) ndarray", "Addition/subtraction of integers and integer-arrays", + "Instead of adding/subtracting", + "cannot use operands with types dtype", "Concatenation operation is not implemented for NumPy arrays", + "Cannot (add|subtract) NaT (to|from) ndarray", # pd.array vs np.datetime64 case r"operand type\(s\) all returned NotImplemented from __array_ufunc__", "can only perform ops with numeric values", + "cannot subtract DatetimeArray from ndarray", ] ) - with pytest.raises(TypeError, match=msg): - left + other - with pytest.raises(TypeError, match=msg): - other + left - with pytest.raises(TypeError, match=msg): - left - other - with pytest.raises(TypeError, match=msg): - other - left + assert_invalid_addsub_type(left, other, msg) # ------------------------------------------------------------------ From 884298dfe15102377232215bbe1ce002496ee41f Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 18 Dec 2021 16:52:37 -0800 Subject: [PATCH 6/7] npdev compat --- pandas/tests/arithmetic/test_timedelta64.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index a9e0f8595a359..64da7cc968c19 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -1552,7 +1552,8 @@ def test_td64arr_div_nat_invalid(self, box_with_array): dt64nat = np.datetime64("NaT", "ns") msg = "|".join( [ - "ufunc 'true_divide' cannot use operands", + # 'divide' on npdev as of 2021-12-18 + "ufunc '(true_divide|divide)' cannot use operands", "cannot perform __r?truediv__", "Cannot divide datetime64 by TimedeltaArray", ] From 5f88b424d2478070e3931d529db397948e826f6e Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 19 Dec 2021 10:35:17 -0800 Subject: [PATCH 7/7] Suppress warning --- pandas/core/indexes/range.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pandas/core/indexes/range.py b/pandas/core/indexes/range.py index fdb1ee754a7e6..887c8da6305dd 100644 --- a/pandas/core/indexes/range.py +++ b/pandas/core/indexes/range.py @@ -1041,7 +1041,11 @@ def _arith_method(self, other, op): rstop = op(left.stop, right) res_name = ops.get_op_result_name(self, other) - result = type(self)(rstart, rstop, rstep, name=res_name) + with warnings.catch_warnings(): + warnings.simplefilter("ignore", DeprecationWarning) + # The constructor validation can lead to a DeprecationWarning + # from numpy, e.g. with RangeIndex + np.datetime64("now") + result = type(self)(rstart, rstop, rstep, name=res_name) # for compat with numpy / Int64Index # even if we can represent as a RangeIndex, return