pandas-dev/pandas · #70915
BUG: MultiIndex.rename rejects the name keyword (GH#33778)
doc/source/whatsnew/v3.2.0.rst1 + / 0 −
@@ -218,6 +218,7 @@ MultiIndex - Bug in :meth:`DataFrame.sort_index`, :meth:`Series.sort_index`, :meth:`MultiIndex.sort_values` and :meth:`MultiIndex.is_monotonic_increasing` ignoring the category order of a :class:`CategoricalIndex` level (:issue:`47607`) - Bug in :meth:`MultiIndex.append`, :func:`concat`, :meth:`DataFrame.join` and :func:`merge` emitting a spurious ``RuntimeWarning`` when :class:`MultiIndex` level values could not be sorted (:issue:`61477`) - Bug in :meth:`MultiIndex.from_tuples` with an object ``ndarray``, :meth:`MultiIndex.append` and :meth:`Index.map` truncating or raising ``IndexError`` on tuples of different lengths; :meth:`MultiIndex.from_tuples` now raises a ``ValueError`` for an ``ndarray`` of tuples of different lengths (:issue:`50293`)+- Bug in :meth:`MultiIndex.rename` raising ``TypeError`` for the ``name`` keyword documented by :meth:`Index.rename` (:issue:`33778`) - Bug in :meth:`Series.map`, :meth:`Index.get_indexer`, :meth:`DataFrame.reindex` and :attr:`DataFrame.loc` with a :class:`MultiIndex` raising ``IndexError`` or matching on a prefix when looking up tuples whose length differs from the number of levels (:issue:`50293`) - pandas/core/indexes/multi.py90 + / 3 −
@@ -18,6 +18,7 @@ NoReturn, Self, cast,+ overload, ) import warnings @@ -5013,9 +5014,95 @@ def isin(self, values, level=None) -> npt.NDArray[np.bool_]: return np.zeros(len(levs), dtype=np.bool_) return levs.isin(values) - # error: Incompatible types in assignment (expression has type overloaded function,- # base class "Index" defined the type as "Callable[[Index, Any, bool], Any]")- rename = Index.set_names # type: ignore[assignment]+ @overload+ def rename(+ self,+ name: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ *,+ names: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ level: Hashable | Sequence[Hashable] | None = ...,+ inplace: Literal[False] = ...,+ ) -> Self: ...++ @overload+ def rename(+ self,+ name: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ *,+ names: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ level: Hashable | Sequence[Hashable] | None = ...,+ inplace: Literal[True],+ ) -> None: ...++ @overload+ def rename(+ self,+ name: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ *,+ names: Hashable | Sequence[Hashable] | dict[Hashable, Hashable] = ...,+ level: Hashable | Sequence[Hashable] | None = ...,+ inplace: bool = ...,+ ) -> Self | None: ...++ def rename(+ self,+ name: Hashable+ | Sequence[Hashable]+ | dict[Hashable, Hashable]+ | lib.NoDefault = lib.no_default,+ *,+ names: Hashable+ | Sequence[Hashable]+ | dict[Hashable, Hashable]+ | lib.NoDefault = lib.no_default,+ level: Hashable | Sequence[Hashable] | None = None,+ inplace: bool = False,+ ) -> Self | None:+ """+ Alter MultiIndex level names.++ Equivalent to :meth:`MultiIndex.set_names`.++ Parameters+ ----------+ name : Hashable, sequence of Hashable, or dict-like+ Name(s) to set. A dict-like maps old level names to new ones.+ names : Hashable, sequence of Hashable, or dict-like+ Alias for ``name``; pass only one of the two.+ level : int, Hashable or a sequence of the previous, optional+ If ``name`` is not dict-like, level(s) to set (None for all levels).+ inplace : bool, default False+ Modifies the object directly, instead of creating a new MultiIndex.++ Returns+ -------+ MultiIndex or None+ The same type as the caller or None if ``inplace=True``.++ See Also+ --------+ MultiIndex.set_names : Set level names partially and by level.+ Index.rename : Alter Index name.++ Examples+ --------+ >>> mi = pd.MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])+ >>> mi.rename(["a", "b"])+ MultiIndex([(1, 3),+ (2, 4)],+ names=['a', 'b'])+ >>> mi.rename("a", level=0)+ MultiIndex([(1, 3),+ (2, 4)],+ names=['a', 'y'])+ """+ if names is not lib.no_default:+ if name is not lib.no_default:+ raise TypeError("Pass only one of 'name' and 'names'.")+ name = names+ elif name is lib.no_default:+ raise TypeError("rename() missing required argument: 'name'")+ return self.set_names(name, level=level, inplace=inplace) # --------------------------------------------------------------- # Arithmetic/Numeric Methods - Disabledpandas/tests/indexes/multi/test_get_set.py1 + / 2 −
@@ -90,10 +90,9 @@ def test_get_level_number_out_of_bounds(multiindex_dataframe_random_data): def test_set_name_methods(idx):- # so long as these are synonyms, we don't need to test set_names index_names = ["first", "second"]- assert idx.rename == idx.set_names new_names = [name + "SUFFIX" for name in index_names]+ assert idx.rename(new_names).names == idx.set_names(new_names).names ind = idx.set_names(new_names) assert idx.names == index_names assert ind.names == new_namespandas/tests/indexes/multi/test_names.py23 + / 0 −
@@ -200,3 +200,26 @@ def test_multiindex_name_and_level_raising(): mi = pd.MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"]) with pytest.raises(TypeError, match="Can not pass level for dictlike `names`."): mi.set_names(names={"x": "z"}, level={"x": "z"})+++@pytest.mark.parametrize("func", ["rename", "set_names"])+@pytest.mark.parametrize("arg", [False, 0])+def test_rename_positional_second_arg_raises(func, arg):+ # GH#33778 a positional second argument used to be read as `level`+ mi = pd.MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])+ msg = "positional arguments but 3 were given"+ with pytest.raises(TypeError, match=msg):+ getattr(mi, func)(["foo", "bar"], arg)+++def test_rename_name_keyword():+ # GH#33778 the `name` keyword documented on Index.rename was rejected+ mi = pd.MultiIndex.from_arrays([[1, 2], [3, 4]], names=["x", "y"])+ expected = mi.set_names(["foo", "bar"])+ tm.assert_index_equal(mi.rename(name=["foo", "bar"]), expected)+ tm.assert_index_equal(mi.rename(names=["foo", "bar"]), expected)++ with pytest.raises(TypeError, match="Pass only one of 'name' and 'names'"):+ mi.rename(["foo", "bar"], names=["foo", "bar"])+ with pytest.raises(TypeError, match="missing required argument: 'name'"):+ mi.rename()