Skip to content

Commit 7b35099

Browse files
TomAugspurgerjreback
authored andcommitted
API: Raise when setting name via level (#30574)
1 parent 6fc9852 commit 7b35099

File tree

8 files changed

+40
-11
lines changed

8 files changed

+40
-11
lines changed

doc/source/user_guide/advanced.rst

+5-9
Original file line numberDiff line numberDiff line change
@@ -565,19 +565,15 @@ When working with an ``Index`` object directly, rather than via a ``DataFrame``,
565565
mi2 = mi.rename("new name", level=0)
566566
mi2
567567
568-
.. warning::
569568
570-
Prior to pandas 1.0.0, you could also set the names of a ``MultiIndex``
571-
by updating the name of a level.
569+
You cannot set the names of the MultiIndex via a level.
572570

573-
.. code-block:: none
571+
.. ipython:: python
572+
:okexcept:
574573
575-
>>> mi.levels[0].name = 'name via level'
576-
>>> mi.names[0] # only works for older pandas
577-
'name via level'
574+
mi.levels[0].name = "name via level"
578575
579-
As of pandas 1.0, this will *silently* fail to update the names
580-
of the MultiIndex. Use :meth:`Index.set_names` instead.
576+
Use :meth:`Index.set_names` instead.
581577

582578
Sorting a ``MultiIndex``
583579
------------------------

doc/source/whatsnew/v1.0.0.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,10 @@ For backwards compatibility, you can still *access* the names via the levels.
260260
mi.levels[0].name
261261
262262
However, it is no longer possible to *update* the names of the ``MultiIndex``
263-
via the name of the level. The following will **silently** fail to update the
264-
name of the ``MultiIndex``
263+
via the level.
265264

266265
.. ipython:: python
266+
:okexcept:
267267
268268
mi.levels[0].name = "new name"
269269
mi.names

pandas/core/indexes/base.py

+10
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ def _outer_indexer(self, left, right):
240240
_data: Union[ExtensionArray, np.ndarray]
241241
_id = None
242242
_name: Optional[Hashable] = None
243+
# MultiIndex.levels previously allowed setting the index name. We
244+
# don't allow this anymore, and raise if it happens rather than
245+
# failing silently.
246+
_no_setting_name: bool = False
243247
_comparables = ["name"]
244248
_attributes = ["name"]
245249
_is_numeric_dtype = False
@@ -1150,6 +1154,12 @@ def name(self):
11501154

11511155
@name.setter
11521156
def name(self, value):
1157+
if self._no_setting_name:
1158+
# Used in MultiIndex.levels to avoid silently ignoring name updates.
1159+
raise RuntimeError(
1160+
"Cannot set name on a level of a MultiIndex. Use "
1161+
"'MultiIndex.set_names' instead."
1162+
)
11531163
maybe_extract_name(value, None, type(self))
11541164
self._name = value
11551165

pandas/core/indexes/category.py

+1
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,7 @@ def _simple_new(cls, values, name=None, dtype=None, **kwargs):
278278
setattr(result, k, v)
279279

280280
result._reset_identity()
281+
result._no_setting_name = False
281282
return result
282283

283284
# --------------------------------------------------------------------

pandas/core/indexes/datetimes.py

+1
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,7 @@ def _simple_new(cls, values, name=None, freq=None, tz=None, dtype=None):
295295
result = object.__new__(cls)
296296
result._data = dtarr
297297
result.name = name
298+
result._no_setting_name = False
298299
# For groupby perf. See note in indexes/base about _index_data
299300
result._index_data = dtarr._data
300301
result._reset_identity()

pandas/core/indexes/interval.py

+1
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ def _simple_new(cls, array, name, closed=None):
261261
result = IntervalMixin.__new__(cls)
262262
result._data = array
263263
result.name = name
264+
result._no_setting_name = False
264265
result._reset_identity()
265266
return result
266267

pandas/core/indexes/multi.py

+3
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,9 @@ def levels(self):
627627
result = [
628628
x._shallow_copy(name=name) for x, name in zip(self._levels, self._names)
629629
]
630+
for level in result:
631+
# disallow midx.levels[0].name = "foo"
632+
level._no_setting_name = True
630633
return FrozenList(result)
631634

632635
@property

pandas/tests/indexes/multi/test_names.py

+17
Original file line numberDiff line numberDiff line change
@@ -124,3 +124,20 @@ def test_get_names_from_levels():
124124

125125
assert idx.levels[0].name == "a"
126126
assert idx.levels[1].name == "b"
127+
128+
129+
def test_setting_names_from_levels_raises():
130+
idx = pd.MultiIndex.from_product([["a"], [1, 2]], names=["a", "b"])
131+
with pytest.raises(RuntimeError, match="set_names"):
132+
idx.levels[0].name = "foo"
133+
134+
with pytest.raises(RuntimeError, match="set_names"):
135+
idx.levels[1].name = "foo"
136+
137+
new = pd.Series(1, index=idx.levels[0])
138+
with pytest.raises(RuntimeError, match="set_names"):
139+
new.index.name = "bar"
140+
141+
assert pd.Index._no_setting_name is False
142+
assert pd.Int64Index._no_setting_name is False
143+
assert pd.RangeIndex._no_setting_name is False

0 commit comments

Comments
 (0)