Repository: pandas-dev/pandas-stubs (language: python)

Issue:
`Series.rename_axis(None)` not accepted
**Describe the bug**
To remove the name of the index of a `Series`, one may call `Series.rename_axis(None)`, however `mypy` does not recognize `None` as a valid argument.

**To Reproduce**
This code:
```python
import pandas as pd

s1 = pd.Series([1, 2, 3], name="values").rename_axis("index")
s2 = s1.rename_axis(None)
print(f"s1\n{s1}\n\ns2\n{s2}\n\n")
```
elicits the expected output when run:
```
s1
index
0    1
1    2
2    3
Name: values, dtype: int64

s2
0    1
1    2
2    3
Name: values, dtype: int64

```
but `mypy` complains with:
```python
foo.py:4: error: No overload variant of "rename_axis" of "Series" matches argument type "None"  [call-overload]
foo.py:4: note: Possible overload variants:
foo.py:4: note:     def [ListLike: (Sequence[Any], ndarray[Any, Any], Series[Any], Index[Any])] rename_axis(self, mapper: str | bytes | date | datetime | timedelta | <9 more items> = ..., index: str | bytes | date | datetime | timedelta | <11 more items> | None = ..., columns: str | bytes | date | datetime | timedelta | <11 more items> | None = ..., axis: Literal['index', 0] | None = ..., copy: bool = ..., *, inplace: Literal[True]) -> None
foo.py:4: note:     def [ListLike: (Sequence[Any], ndarray[Any, Any], Series[Any], Index[Any])] rename_axis(self, mapper: str | bytes | date | datetime | timedelta | <9 more items> = ..., index: str | bytes | date | datetime | timedelta | <11 more items> | None = ..., columns: str | bytes | date | datetime | timedelta | <11 more items> | None = ..., axis: Literal['index', 0] | None = ..., copy: bool = ..., inplace: Literal[False] = ...) -> Series[int]
Found 1 error in 1 file (checked 1 source file)
```

**Please complete the following information:**
 - OS: macOS 14.7.1
 - Python version: 3.12.7
 - Mypy version: 1.13.0
 - Pandas-stubs version: 2.2.3.241009
 - Pandas version: 2.2.3



Discussion / hints:
Hey @dpoznik ,

Thanks for the report! What you are seeing is correct, there exists no overload for passing None (although it is allowed by the `pandas` API https://pandas.pydata.org/docs/reference/api/pandas.Series.rename_axis.html#pandas.Series.rename_axis).
We should add an overload to pass `None` as `mapper`. Do you confirm @Dr-Irv ?
https://github.com/pandas-dev/pandas-stubs/blob/779aab6dd43eb7f17ffde0f5f28efbd88804a143/pandas-stubs/core/series.pyi#L2067-L2087
> We should add an overload to pass `None` as `mapper`. Do you confirm @Dr-Irv ?

I don't think an overload is needed.  Just need to add `None` as a possible argument to `mapper` .

I also think the asterisk needs to be moved to after `mapper` .  In addition, for `Series.rename_axis()`, the `columns` argument should not be allowed and should be removed.

A change to allow `None` should also be made in `frame.pyi` to set the type of the `mapper` argument there.

PR with tests welcome.
Cool, thanks for confirming. I'd be happy to submit a PR with all of the above at some point in the next few days. Could you assign the issue to me to ensure against duplicated effort?

You got it @dpoznik ! Looking forward!

Fix patch:
diff --git a/docs/setup.md b/docs/setup.md
index 402a18ec..88927e02 100644
--- a/docs/setup.md
+++ b/docs/setup.md
@@ -1,6 +1,7 @@
 ## Set Up Environment
 
 - Make sure you have `python >= 3.10` installed.
+- If using macOS, you may need to install `hdf5` (e.g., via `brew install hdf5`).
 - Install poetry: `pip install 'poetry>=1.8'`
 - Install the project dependencies: `poetry update`
 - Enter the virtual environment: `poetry shell`
diff --git a/pandas-stubs/core/frame.pyi b/pandas-stubs/core/frame.pyi
index b574fb21..c179b755 100644
--- a/pandas-stubs/core/frame.pyi
+++ b/pandas-stubs/core/frame.pyi
@@ -2070,40 +2070,44 @@ class DataFrame(NDFrame, OpsMixin):
         limit: int | None = ...,
         tolerance=...,
     ) -> DataFrame: ...
+    # Rename axis with `mapper`, `axis`, and `inplace=True`
     @overload
     def rename_axis(
         self,
-        mapper=...,
+        mapper: Scalar | ListLike | None = ...,
+        *,
         axis: Axis | None = ...,
         copy: _bool = ...,
-        *,
         inplace: Literal[True],
     ) -> None: ...
+    # Rename axis with `mapper`, `axis`, and `inplace=False`
     @overload
     def rename_axis(
         self,
-        mapper=...,
+        mapper: Scalar | ListLike | None = ...,
+        *,
         axis: Axis | None = ...,
         copy: _bool = ...,
-        *,
         inplace: Literal[False] = ...,
     ) -> DataFrame: ...
+    # Rename axis with `index` and/or `columns` and `inplace=True`
     @overload
     def rename_axis(
         self,
+        *,
         index: _str | Sequence[_str] | dict[_str | int, _str] | Callable | None = ...,
         columns: _str | Sequence[_str] | dict[_str | int, _str] | Callable | None = ...,
         copy: _bool = ...,
-        *,
         inplace: Literal[True],
     ) -> None: ...
+    # Rename axis with `index` and/or `columns` and `inplace=False`
     @overload
     def rename_axis(
         self,
+        *,
         index: _str | Sequence[_str] | dict[_str | int, _str] | Callable | None = ...,
         columns: _str | Sequence[_str] | dict[_str | int, _str] | Callable | None = ...,
         copy: _bool = ...,
-        *,
         inplace: Literal[False] = ...,
     ) -> DataFrame: ...
     def rfloordiv(
diff --git a/pandas-stubs/core/series.pyi b/pandas-stubs/core/series.pyi
index e629c5e0..c60548e9 100644
--- a/pandas-stubs/core/series.pyi
+++ b/pandas-stubs/core/series.pyi
@@ -2076,24 +2076,41 @@ class Series(IndexOpsMixin[S1], NDFrame):
         numeric_only: _bool = ...,
         **kwargs,
     ) -> Scalar: ...
+    # Rename axis with `mapper`, `axis`, and `inplace=True`
     @overload
     def rename_axis(
         self,
-        mapper: Scalar | ListLike = ...,
-        index: Scalar | ListLike | Callable | dict | None = ...,
-        columns: Scalar | ListLike | Callable | dict | None = ...,
+        mapper: Scalar | ListLike | None = ...,
+        *,
         axis: AxisIndex | None = ...,
         copy: _bool = ...,
+        inplace: Literal[True],
+    ) -> None: ...
+    # Rename axis with `mapper`, `axis`, and `inplace=False`
+    @overload
+    def rename_axis(
+        self,
+        mapper: Scalar | ListLike | None = ...,
         *,
+        axis: AxisIndex | None = ...,
+        copy: _bool = ...,
+        inplace: Literal[False] = ...,
+    ) -> Self: ...
+    # Rename axis with `index` and `inplace=True`
+    @overload
+    def rename_axis(
+        self,
+        *,
+        index: Scalar | ListLike | Callable | dict | None = ...,
+        copy: _bool = ...,
         inplace: Literal[True],
     ) -> None: ...
+    # Rename axis with `index` and `inplace=False`
     @overload
     def rename_axis(
         self,
-        mapper: Scalar | ListLike = ...,
+        *,
         index: Scalar | ListLike | Callable | dict | None = ...,
-        columns: Scalar | ListLike | Callable | dict | None = ...,
-        axis: AxisIndex | None = ...,
         copy: _bool = ...,
         inplace: Literal[False] = ...,
     ) -> Self: ...


Test patch:
diff --git a/tests/test_frame.py b/tests/test_frame.py
index 1d7e0468..69f257b8 100644
--- a/tests/test_frame.py
+++ b/tests/test_frame.py
@@ -1955,6 +1955,48 @@ def test_types_rename() -> None:
     df.rename(columns=lambda s: s.upper())
 
 
+def test_types_rename_axis() -> None:
+    df = pd.DataFrame({"col_name": [1, 2, 3]})
+    df.index.name = "a"
+    df.columns.name = "b"
+
+    # Rename axes with `mapper` and `axis`
+    check(assert_type(df.rename_axis("A"), pd.DataFrame), pd.DataFrame)
+    check(assert_type(df.rename_axis(["A"]), pd.DataFrame), pd.DataFrame)
+    check(assert_type(df.rename_axis(None), pd.DataFrame), pd.DataFrame)
+    check(assert_type(df.rename_axis("B", axis=1), pd.DataFrame), pd.DataFrame)
+    check(assert_type(df.rename_axis(["B"], axis=1), pd.DataFrame), pd.DataFrame)
+    check(assert_type(df.rename_axis(None, axis=1), pd.DataFrame), pd.DataFrame)
+
+    # Rename axes with `index` and `columns`
+    check(
+        assert_type(df.rename_axis(index="A", columns="B"), pd.DataFrame),
+        pd.DataFrame,
+    )
+    check(
+        assert_type(df.rename_axis(index=["A"], columns=["B"]), pd.DataFrame),
+        pd.DataFrame,
+    )
+    check(
+        assert_type(df.rename_axis(index={"a": "A"}, columns={"b": "B"}), pd.DataFrame),
+        pd.DataFrame,
+    )
+    check(
+        assert_type(
+            df.rename_axis(
+                index=lambda name: name.upper(),
+                columns=lambda name: name.upper(),
+            ),
+            pd.DataFrame,
+        ),
+        pd.DataFrame,
+    )
+    check(
+        assert_type(df.rename_axis(index=None, columns=None), pd.DataFrame),
+        pd.DataFrame,
+    )
+
+
 def test_types_eq() -> None:
     df1 = pd.DataFrame([[1, 2], [8, 9]], columns=["A", "B"])
     res1: pd.DataFrame = df1 == 1
diff --git a/tests/test_series.py b/tests/test_series.py
index 98284cc0..d48f0bef 100644
--- a/tests/test_series.py
+++ b/tests/test_series.py
@@ -1121,7 +1121,26 @@ def test_types_eq() -> None:
 
 
 def test_types_rename_axis() -> None:
-    s: pd.Series = pd.Series([1, 2, 3]).rename_axis("A")
+    s = pd.Series([1, 2, 3])
+    s.index.name = "a"
+
+    # Rename index with `mapper`
+    check(assert_type(s.rename_axis("A"), "pd.Series[int]"), pd.Series)
+    check(assert_type(s.rename_axis(["A"]), "pd.Series[int]"), pd.Series)
+    check(assert_type(s.rename_axis(None), "pd.Series[int]"), pd.Series)
+
+    # Rename index with `index`
+    check(assert_type(s.rename_axis(index="A"), "pd.Series[int]"), pd.Series)
+    check(assert_type(s.rename_axis(index=["A"]), "pd.Series[int]"), pd.Series)
+    check(assert_type(s.rename_axis(index={"a": "A"}), "pd.Series[int]"), pd.Series)
+    check(
+        assert_type(s.rename_axis(index=lambda name: name.upper()), "pd.Series[int]"),
+        pd.Series,
+    )
+    check(assert_type(s.rename_axis(index=None), "pd.Series[int]"), pd.Series)
+
+    if TYPE_CHECKING_INVALID_USAGE:
+        s.rename_axis(columns="A")  # type: ignore[call-overload] # pyright: ignore[reportCallIssue]
 
 
 def test_types_values() -> None: