diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index bebd928924214..7bf5c7263c16d 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -854,6 +854,7 @@ Other Deprecations - Deprecated backward-compatibility behavior for :meth:`DataFrame.select_dtypes` matching ``str`` dtype when ``np.object_`` is specified (:issue:`61916`) - Deprecated option ``future.no_silent_downcasting``, as it is no longer used. In a future version accessing this option will raise (:issue:`59502`) - Deprecated passing non-Index types to :meth:`Index.join`; explicitly convert to Index first (:issue:`62897`) +- Deprecated silent alignment on arithmetic operations between :class:`Series` with non-aligned ``MultiIndexes`` (:issue:`25891`) - Deprecated silent casting of non-datetime ``other`` to datetime in :meth:`Series.combine_first` (:issue:`62931`) - Deprecated silently casting strings to :class:`Timedelta` in binary operations with :class:`Timedelta` (:issue:`59653`) - Deprecated slicing on a :class:`Series` or :class:`DataFrame` with a :class:`DatetimeIndex` using a ``datetime.date`` object, explicitly cast to :class:`Timestamp` instead (:issue:`35830`) diff --git a/pandas/core/series.py b/pandas/core/series.py index 1ea8bbbaa0cfb..fb4bb1a00225a 100644 --- a/pandas/core/series.py +++ b/pandas/core/series.py @@ -6662,6 +6662,21 @@ def _logical_method(self, other, op): return self._construct_result(res_values, name=res_name, other=other) def _arith_method(self, other, op): + if isinstance(other, Series): + if ( + isinstance(self.index, MultiIndex) + and isinstance(other.index, MultiIndex) + and self.index.names != other.index.names + ): + # GH#25891 + warnings.warn( + "The silent alignment on arithmetic operations between " + "'Series' with non-aligned MultiIndexes will be removed " + "in a future version, please align MultiIndexes.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) + self, other = self._align_for_op(other) return base.IndexOpsMixin._arith_method(self, other, op) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index a77e55612e23d..e76e6e36b2861 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -14,6 +14,7 @@ import pytest from pandas._libs import lib +from pandas.errors import Pandas4Warning import pandas as pd from pandas import ( @@ -1043,7 +1044,10 @@ def test_series_varied_multiindex_alignment(): [1000 * i for i in range(1, 5)], index=pd.MultiIndex.from_product([list("xy"), [1, 2]], names=["xy", "num"]), ) - result = s1.loc[pd.IndexSlice[["a"], :, :]] + s2 + msg = ( + "The silent alignment on arithmetic operations between " + "'Series' with non-aligned MultiIndexes" + ) expected = Series( [1000, 2001, 3002, 4003], index=pd.MultiIndex.from_tuples( @@ -1051,6 +1055,8 @@ def test_series_varied_multiindex_alignment(): names=["ab", "xy", "num"], ), ) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = s1.loc[pd.IndexSlice[["a"], :, :]] + s2 tm.assert_series_equal(result, expected) @@ -1060,3 +1066,36 @@ def test_rmod_consistent_large_series(): expected = Series([1] * 10001) tm.assert_series_equal(result, expected) + + +def test_alignment_on_arithmetic_with_nonaligned_multiindex_deprecated( + all_arithmetic_operators, +): + # GH#25891 + index1 = pd.MultiIndex.from_product([[], []], names=["T", "N"]) + s1 = Series(index=index1) + s1["T.1A", "N.0"] = 0.5 + s1["T.1B", "N.0"] = 0.5 + op = tm.get_op_from_name(all_arithmetic_operators) + + index2 = pd.MultiIndex.from_product([[], []], names=["N", "M"]) + s2 = Series(index=index2) + s2["N.0", "M.0"] = 0.5 + + expected = Series( + [0.25, 0.25], + index=pd.MultiIndex.from_tuples( + [("T.1A", "N.0", "M.0"), ("T.1B", "N.0", "M.0")], + names=["T", "N", "M"], + ), + ) + msg = ( + "The silent alignment on arithmetic operations between " + "'Series' with non-aligned MultiIndexes" + ) + with tm.assert_produces_warning(Pandas4Warning, match=msg): + result = s1 * s2 + tm.assert_series_equal(result, expected) + + with tm.assert_produces_warning(Pandas4Warning, match=msg): + op(s1, s2)