From c1580489b839320c91aee9b42b45c6eaa1d6e354 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Wed, 10 Oct 2018 08:58:54 -0700 Subject: [PATCH 1/8] Fix ndarray with frame op --- pandas/core/generic.py | 7 +++++++ pandas/tests/arithmetic/test_object.py | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 0b5a10283946c..0b30bef020744 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1799,6 +1799,13 @@ def __array__(self, dtype=None): return com.values_from_object(self) def __array_wrap__(self, result, context=None): + if (context is not None and context[1] is not None and + len(context[1]) == 2 and context[1][1] is self and + isinstance(context[1][0], np.ndarray)): + # TODO: Can we return NotImplemented earlier? By the time we + # get here we've calculated `result`, which may not be cheap + return NotImplemented + d = self._construct_axes_dict(self._AXIS_ORDERS, copy=False) return self._constructor(result, **d).__finalize__(self) diff --git a/pandas/tests/arithmetic/test_object.py b/pandas/tests/arithmetic/test_object.py index 64d7cbc47fddd..511d74a2e790c 100644 --- a/pandas/tests/arithmetic/test_object.py +++ b/pandas/tests/arithmetic/test_object.py @@ -140,9 +140,6 @@ def test_objarr_radd_str_invalid(self, dtype, data, box): operator.sub, ops.rsub]) def test_objarr_add_invalid(self, op, box): # invalid ops - if box is pd.DataFrame and op is ops.radd: - pytest.xfail(reason="DataFrame op incorrectly casts the np.array" - "case to M8[ns]") obj_ser = tm.makeObjectSeries() obj_ser.name = 'objects' From a28302a3acbd5c33e491a9269c95ec168427e7e9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 09:18:16 -0700 Subject: [PATCH 2/8] typo fixup --- pandas/tests/arithmetic/test_numeric.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/tests/arithmetic/test_numeric.py b/pandas/tests/arithmetic/test_numeric.py index 0449212713048..e9316221b125b 100644 --- a/pandas/tests/arithmetic/test_numeric.py +++ b/pandas/tests/arithmetic/test_numeric.py @@ -156,7 +156,7 @@ def test_numeric_arr_rdiv_tdscalar(self, three_days, numeric_idx, box): if box is not pd.Index and broken: # np.timedelta64(3, 'D') / 2 == np.timedelta64(1, 'D') raise pytest.xfail("timedelta64 not converted to nanos; " - "Tick division not imlpemented") + "Tick division not implemented") expected = TimedeltaIndex(['3 Days', '36 Hours']) From 4e84e532331296437855e1d5ca19965b513f896d Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 16:02:18 -0700 Subject: [PATCH 3/8] set __array_priority__ --- pandas/core/generic.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 16d95d99baba4..ba050bfc8db77 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -1795,17 +1795,14 @@ def __round__(self, decimals=0): # ---------------------------------------------------------------------- # Array Interface + # This is also set in IndexOpsMixin + # GH#23114 Ensure ndarray.__op__(DataFrame) returns NotImplemented + __array_priority__ = 1000 + def __array__(self, dtype=None): return com.values_from_object(self) def __array_wrap__(self, result, context=None): - if (context is not None and context[1] is not None and - len(context[1]) == 2 and context[1][1] is self and - isinstance(context[1][0], np.ndarray)): - # TODO: Can we return NotImplemented earlier? By the time we - # get here we've calculated `result`, which may not be cheap - return NotImplemented - d = self._construct_axes_dict(self._AXIS_ORDERS, copy=False) return self._constructor(result, **d).__finalize__(self) From d280cf1a53631b4dee99cff28ded8b2ca24dd3e0 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 17:17:13 -0700 Subject: [PATCH 4/8] update tests --- pandas/tests/arithmetic/test_timedelta64.py | 7 ++----- pandas/tests/frame/test_analytics.py | 4 +++- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index a8e61b3fd9d3a..a10fd9dbc8767 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -363,14 +363,11 @@ def test_td64arr_add_str_invalid(self, box): @pytest.mark.parametrize('op', [operator.add, ops.radd, operator.sub, ops.rsub], ids=lambda x: x.__name__) - def test_td64arr_add_sub_float(self, box, op, other): + def test_td64arr_add_sub_float(self, box_df_broadcast_failure, op, other): + box = box_df_broadcast_failure tdi = TimedeltaIndex(['-1 days', '-1 days']) tdi = tm.box_expected(tdi, box) - if box is pd.DataFrame and op in [operator.add, operator.sub]: - pytest.xfail(reason="Tries to align incorrectly, " - "raises ValueError") - with pytest.raises(TypeError): op(tdi, other) diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index 66bbc1f1a649b..2330d42237536 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -2046,8 +2046,10 @@ def test_matmul(self): # np.array @ DataFrame result = operator.matmul(a.values, b) + assert isinstance(result, DataFrame) + assert result._indexed_same(b) expected = np.dot(a.values, b.values) - tm.assert_almost_equal(result, expected) + tm.assert_almost_equal(result.values, expected) # nested list @ DataFrame (__rmatmul__) result = operator.matmul(a.values.tolist(), b) From d62a6c8245ad56816e4d6b6334a12c96e997eea9 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 18:46:28 -0700 Subject: [PATCH 5/8] fix test assertion --- pandas/tests/frame/test_analytics.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pandas/tests/frame/test_analytics.py b/pandas/tests/frame/test_analytics.py index 2330d42237536..8864e5fffeb12 100644 --- a/pandas/tests/frame/test_analytics.py +++ b/pandas/tests/frame/test_analytics.py @@ -2047,7 +2047,8 @@ def test_matmul(self): # np.array @ DataFrame result = operator.matmul(a.values, b) assert isinstance(result, DataFrame) - assert result._indexed_same(b) + assert result.columns.equals(b.columns) + assert result.index.equals(pd.Index(range(3))) expected = np.dot(a.values, b.values) tm.assert_almost_equal(result.values, expected) From afd53e095428ca26cd5e3c4fd905677d4025274e Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 19:28:30 -0700 Subject: [PATCH 6/8] update test --- pandas/tests/arithmetic/test_timedelta64.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index a10fd9dbc8767..cfe21dece79fe 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -363,8 +363,11 @@ def test_td64arr_add_str_invalid(self, box): @pytest.mark.parametrize('op', [operator.add, ops.radd, operator.sub, ops.rsub], ids=lambda x: x.__name__) - def test_td64arr_add_sub_float(self, box_df_broadcast_failure, op, other): - box = box_df_broadcast_failure + def test_td64arr_add_sub_float(self, box, op, other): + if box is pd.DataFrame and isinstance(other, np.ndarray): + pytest.xfail("Tries to broadcast, raising " + "ValueError instead of TypeError") + tdi = TimedeltaIndex(['-1 days', '-1 days']) tdi = tm.box_expected(tdi, box) From bc55de721551ba31ed1fb6871b45e2c78e397521 Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Fri, 12 Oct 2018 19:31:24 -0700 Subject: [PATCH 7/8] un-xfail now-passing test --- pandas/tests/arithmetic/test_timedelta64.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pandas/tests/arithmetic/test_timedelta64.py b/pandas/tests/arithmetic/test_timedelta64.py index cfe21dece79fe..fa1a2d9df9a58 100644 --- a/pandas/tests/arithmetic/test_timedelta64.py +++ b/pandas/tests/arithmetic/test_timedelta64.py @@ -1126,9 +1126,6 @@ def test_td64arr_floordiv_tdscalar(self, box, scalar_td): def test_td64arr_rfloordiv_tdscalar(self, box, scalar_td): # GH#18831 - if box is pd.DataFrame and isinstance(scalar_td, np.timedelta64): - pytest.xfail(reason="raises TypeError, not sure why") - td1 = Series([timedelta(minutes=5, seconds=3)] * 3) td1.iloc[2] = np.nan From c0100b2b989dc913bf20094dab44016ae056f4df Mon Sep 17 00:00:00 2001 From: Brock Mendel Date: Sun, 14 Oct 2018 09:03:24 -0700 Subject: [PATCH 8/8] whatsnew --- doc/source/whatsnew/v0.24.0.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index a05ef67a7238f..5e11e2b1b2be9 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -811,7 +811,7 @@ Numeric - Bug in :class:`DataFrame` multiplication between boolean dtype and integer returning ``object`` dtype instead of integer dtype (:issue:`22047`, :issue:`22163`) - Bug in :meth:`DataFrame.apply` where, when supplied with a string argument and additional positional or keyword arguments (e.g. ``df.apply('sum', min_count=1)``), a ``TypeError`` was wrongly raised (:issue:`22376`) - Bug in :meth:`DataFrame.astype` to extension dtype may raise ``AttributeError`` (:issue:`22578`) - +- Bug in :class:`DataFrame` with ``timedelta64[ns]`` dtype arithmetic operations with ``ndarray`` with integer dtype incorrectly treating the narray as ``timedelta64[ns]`` dtype (:issue:`23114`) Strings ^^^^^^^