diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 6b78f63f92988..c802871e8aba5 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -806,6 +806,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 setting the :attr:`MultiIndex.name` attribute, as it is not used. Set :attr:`MultiIndex.names` instead (:issue:`11979`) - 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/groupby/generic.py b/pandas/core/groupby/generic.py index 7240db129b246..d4c8ad5bcf384 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -2180,7 +2180,7 @@ def _wrap_applied_output_series( index = key_index columns = first_not_none.index.copy() - if columns.name is None: + if columns.name is None and not isinstance(columns, MultiIndex): # GH6124 - propagate name of Series when it's consistent names = {v.name for v in values} if len(names) == 1: diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4186a13926c6f..7753c4cc07048 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -1802,6 +1802,15 @@ def name(self, value: Hashable) -> None: "Cannot set name on a level of a MultiIndex. Use " "'MultiIndex.set_names' instead." ) + if self._is_multi: + warnings.warn( + # GH#11979 + "Setting .name on a MultiIndex is deprecated and will " + "raise in a future version. Set 'names' instead.", + Pandas4Warning, + stacklevel=find_stack_level(), + ) + maybe_extract_name(value, None, type(self)) self._name = value @@ -6188,7 +6197,10 @@ def _get_indexer_strict(self, key, axis_name: str_t) -> tuple[Index, np.ndarray] keyarr = self.take(indexer) if isinstance(key, Index): # GH 42790 - Preserve name from an Index - keyarr.name = key.name + if keyarr._is_multi: + keyarr.names = key.names + else: + keyarr.name = key.name if lib.is_np_dtype(keyarr.dtype, "mM") or isinstance( keyarr.dtype, DatetimeTZDtype ): diff --git a/pandas/tests/indexes/test_any_index.py b/pandas/tests/indexes/test_any_index.py index 69ebfc50530e4..33e3159bba70e 100644 --- a/pandas/tests/indexes/test_any_index.py +++ b/pandas/tests/indexes/test_any_index.py @@ -110,10 +110,12 @@ def test_pickle_roundtrip(self, index): assert index.equal_levels(result) def test_pickle_preserves_name(self, index): - original_name, index.name = index.name, "foo" + if not index._is_multi: + original_name, index.name = index.name, "foo" unpickled = tm.round_trip_pickle(index) assert index.equals(unpickled) - index.name = original_name + if not index._is_multi: + index.name = original_name class TestIndexing: @@ -165,8 +167,10 @@ def test_getitem_error(self, index, item): class TestRendering: def test_str(self, index): # test the string repr - index.name = "foo" - assert "'foo'" in str(index) + if index._is_multi: + pytest.skip(reason=".name does not show up in repr") + index.name = "my_index_name" + assert "'my_index_name'" in str(index) assert type(index).__name__ in str(index) diff --git a/pandas/tests/indexes/test_setops.py b/pandas/tests/indexes/test_setops.py index 6922803c325b3..f75595b37d892 100644 --- a/pandas/tests/indexes/test_setops.py +++ b/pandas/tests/indexes/test_setops.py @@ -848,10 +848,12 @@ def test_difference_name_preservation(self, index, second_name, expected, sort): def test_difference_empty_arg(self, index, sort): first = index.copy() first = first[5:20] - first.name = "name" + if not first._is_multi: + first.name = "name" result = first.difference([], sort) expected = index[5:20].unique() - expected.name = "name" + if not index._is_multi: + expected.name = "name" tm.assert_index_equal(result, expected) def test_difference_should_not_compare(self): diff --git a/pandas/tests/io/json/test_json_table_schema.py b/pandas/tests/io/json/test_json_table_schema.py index e4d5eb4758303..9bebb2ff8008e 100644 --- a/pandas/tests/io/json/test_json_table_schema.py +++ b/pandas/tests/io/json/test_json_table_schema.py @@ -640,7 +640,8 @@ def test_set_names_unset(self, idx, nm, prop): def test_warns_non_roundtrippable_names(self, idx): # GH 19130 df = DataFrame(index=idx) - df.index.name = "index" + if not idx._is_multi: + df.index.name = "index" with tm.assert_produces_warning(UserWarning, match="not round-trippable"): set_default_names(df) diff --git a/pandas/tests/resample/test_resampler_grouper.py b/pandas/tests/resample/test_resampler_grouper.py index f3c52a674cf66..5f1c82649967b 100644 --- a/pandas/tests/resample/test_resampler_grouper.py +++ b/pandas/tests/resample/test_resampler_grouper.py @@ -454,7 +454,7 @@ def test_empty(keys): .set_index(keys, drop=False) .set_index(TimedeltaIndex([]), append=True)[expected_columns] ) - if len(keys) == 1: + if len(keys) == 1 and not expected.index._is_multi: expected.index.name = keys[0] tm.assert_frame_equal(result, expected) @@ -545,7 +545,7 @@ def test_resample_no_index(keys): expected = DataFrame(columns=["a", "b", "date"]).set_index(keys, drop=False) expected["date"] = pd.to_datetime(expected["date"]) expected = expected.set_index("date", append=True, drop=True)[expected_columns] - if len(keys) == 1: + if len(keys) == 1 and not expected.index._is_multi: expected.index.name = keys[0] tm.assert_frame_equal(result, expected)