From 4fc12273786c4eefe27c0f2f600cdb9ce34b333b Mon Sep 17 00:00:00 2001 From: Brock Date: Sat, 3 Oct 2020 16:35:51 -0700 Subject: [PATCH 1/2] REF: separate arith_method_FRAME from flex_arith_method_FRAME --- pandas/core/ops/__init__.py | 38 +++++++++++++++++++-------- pandas/core/ops/methods.py | 3 ++- pandas/tests/frame/test_arithmetic.py | 7 +++++ pandas/tests/series/test_operators.py | 13 --------- 4 files changed, 36 insertions(+), 25 deletions(-) diff --git a/pandas/core/ops/__init__.py b/pandas/core/ops/__init__.py index 36e3a0e37c1ae..673de48968528 100644 --- a/pandas/core/ops/__init__.py +++ b/pandas/core/ops/__init__.py @@ -604,18 +604,13 @@ def _maybe_align_series_as_frame(frame: "DataFrame", series: "Series", axis: int return type(frame)(rvalues, index=frame.index, columns=frame.columns) -def arith_method_FRAME(cls: Type["DataFrame"], op, special: bool): - # This is the only function where `special` can be either True or False +def flex_arith_method_FRAME(cls: Type["DataFrame"], op, special: bool): + assert not special op_name = _get_op_name(op, special) default_axis = None if special else "columns" na_op = get_array_op(op) - - if op_name in _op_descriptions: - # i.e. include "add" but not "__add__" - doc = _make_flex_doc(op_name, "dataframe") - else: - doc = _arith_doc_FRAME % op_name + doc = _make_flex_doc(op_name, "dataframe") @Appender(doc) def f(self, other, axis=default_axis, level=None, fill_value=None): @@ -632,8 +627,6 @@ def f(self, other, axis=default_axis, level=None, fill_value=None): axis = self._get_axis_number(axis) if axis is not None else 1 - # TODO: why are we passing flex=True instead of flex=not special? - # 15 tests fail if we pass flex=not special instead self, other = align_method_FRAME(self, other, axis, flex=True, level=level) if isinstance(other, ABCDataFrame): @@ -656,6 +649,29 @@ def f(self, other, axis=default_axis, level=None, fill_value=None): return f +def arith_method_FRAME(cls: Type["DataFrame"], op, special: bool): + assert special + op_name = _get_op_name(op, special) + doc = _arith_doc_FRAME % op_name + + @Appender(doc) + def f(self, other): + + if _should_reindex_frame_op(self, other, op, 1, 1, None, None): + return _frame_arith_method_with_reindex(self, other, op) + + axis = 1 # only relevant for Series other case + + self, other = align_method_FRAME(self, other, axis, flex=True, level=None) + + new_data = dispatch_to_series(self, other, op, axis=axis) + return self._construct_result(new_data) + + f.__name__ = op_name + + return f + + def flex_comp_method_FRAME(cls: Type["DataFrame"], op, special: bool): assert not special # "special" also means "not flex" op_name = _get_op_name(op, special) @@ -687,7 +703,7 @@ def comp_method_FRAME(cls: Type["DataFrame"], op, special: bool): def f(self, other): axis = 1 # only relevant for Series other case - self, other = align_method_FRAME(self, other, axis, level=None, flex=False) + self, other = align_method_FRAME(self, other, axis, flex=False, level=None) # See GH#4537 for discussion of scalar op behavior new_data = dispatch_to_series(self, other, op, axis=axis) diff --git a/pandas/core/ops/methods.py b/pandas/core/ops/methods.py index 852157e52d5fe..7570ac7c23b0b 100644 --- a/pandas/core/ops/methods.py +++ b/pandas/core/ops/methods.py @@ -49,6 +49,7 @@ def _get_method_wrappers(cls): bool_method_SERIES, comp_method_FRAME, comp_method_SERIES, + flex_arith_method_FRAME, flex_comp_method_FRAME, flex_method_SERIES, ) @@ -61,7 +62,7 @@ def _get_method_wrappers(cls): comp_special = comp_method_SERIES bool_special = bool_method_SERIES elif issubclass(cls, ABCDataFrame): - arith_flex = arith_method_FRAME + arith_flex = flex_arith_method_FRAME comp_flex = flex_comp_method_FRAME arith_special = arith_method_FRAME comp_special = comp_method_FRAME diff --git a/pandas/tests/frame/test_arithmetic.py b/pandas/tests/frame/test_arithmetic.py index d9ef19e174700..94f813fd08128 100644 --- a/pandas/tests/frame/test_arithmetic.py +++ b/pandas/tests/frame/test_arithmetic.py @@ -1484,6 +1484,13 @@ def test_no_warning(self, all_arithmetic_operators): df = pd.DataFrame({"A": [0.0, 0.0], "B": [0.0, None]}) b = df["B"] with tm.assert_produces_warning(None): + getattr(df, all_arithmetic_operators)(b) + + def test_dunder_methods_binary(self, all_arithmetic_operators): + # GH#??? frame.__foo__ should only accept one argument + df = pd.DataFrame({"A": [0.0, 0.0], "B": [0.0, None]}) + b = df["B"] + with pytest.raises(TypeError, match="takes 2 positional arguments"): getattr(df, all_arithmetic_operators)(b, 0) diff --git a/pandas/tests/series/test_operators.py b/pandas/tests/series/test_operators.py index a796023c75b78..df6b8187964e8 100644 --- a/pandas/tests/series/test_operators.py +++ b/pandas/tests/series/test_operators.py @@ -276,25 +276,12 @@ def test_scalar_na_logical_ops_corners_aligns(self): expected = DataFrame(False, index=range(9), columns=["A"] + list(range(9))) - result = d.__and__(s, axis="columns") - tm.assert_frame_equal(result, expected) - - result = d.__and__(s, axis=1) - tm.assert_frame_equal(result, expected) - result = s & d tm.assert_frame_equal(result, expected) result = d & s tm.assert_frame_equal(result, expected) - expected = (s & s).to_frame("A") - result = d.__and__(s, axis="index") - tm.assert_frame_equal(result, expected) - - result = d.__and__(s, axis=0) - tm.assert_frame_equal(result, expected) - @pytest.mark.parametrize("op", [operator.and_, operator.or_, operator.xor]) def test_logical_ops_with_index(self, op): # GH#22092, GH#19792 From ac0cf195ef54e06b74767ae3a03b8a330cda2bdd Mon Sep 17 00:00:00 2001 From: Brock Date: Wed, 7 Oct 2020 10:39:28 -0700 Subject: [PATCH 2/2] whatsnew --- doc/source/whatsnew/v1.2.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v1.2.0.rst b/doc/source/whatsnew/v1.2.0.rst index 5be9155b3ff0b..7f652097c044d 100644 --- a/doc/source/whatsnew/v1.2.0.rst +++ b/doc/source/whatsnew/v1.2.0.rst @@ -359,6 +359,7 @@ Numeric - Bug in :meth:`DataFrame.__rmatmul__` error handling reporting transposed shapes (:issue:`21581`) - Bug in :class:`Series` flex arithmetic methods where the result when operating with a ``list``, ``tuple`` or ``np.ndarray`` would have an incorrect name (:issue:`36760`) - Bug in :class:`IntegerArray` multiplication with ``timedelta`` and ``np.timedelta64`` objects (:issue:`36870`) +- Bug in :class:`DataFrame` arithmetic ops incorrectly accepting keyword arguments (:issue:`36843`) Conversion ^^^^^^^^^^