BUG: still use object dtype for pyarrow-backed IO methods with infer_strings disabled

xref https://github.com/[USER_REDACTED]ndas-dev/pandas/issues/63803

This actually "fixes" the behaviour, not necessarily because I think it is important to fix, but it was easier to do it this way than to update all the tests to change the expected result for those specific circumstances.

@@ -166,4 +166,26 @@ def _post_convert_dtypes(
             # GH#44901 reraise to keep api consistent
             raise ValueError(str(err)) from err

+    if (
+        not using_string_dtype()
+        and dtype != "str"
+        and (dtype_backend is lib.no_default or dtype_backend == "numpy")
+    ):
+        # Convert any StringDtype columns back to object dtype (pyarrow always
+        # uses string dtype even when the infer_string option is False)
+        for col, dtype in zip(df.columns, df.dtypes, strict=True):
+            if isinstance(dtype, pd.StringDtype) and dtype.na_value is np.nan:
+                df[col] = df[col].astype("object").fillna(None)
+            if isinstance(dtype, pd.CategoricalDtype):
+                cat_dtype = dtype.categories.dtype
+                if (
+                    isinstance(cat_dtype, pd.StringDtype)
+                    and cat_dtype.na_value is np.nan
+                ):
+                    cat_dtype = pd.CategoricalDtype(
+                        categories=dtype.categories.astype("object"),
+                        ordered=dtype.ordered,
+                    )
+                    df[col] = df[col].astype(cat_dtype)
+
     return df

@@ -8,6 +8,8 @@
 )
 import warnings

+import numpy as np
+
 from pandas._config import using_string_dtype

 from pandas._libs import lib
@@ -17,6 +19,7 @@
 from pandas.util._validators import check_dtype_backend

 from pandas.core.api import DataFrame
+from pandas.core.arrays.string_ import StringDtype

 from pandas.io._util import arrow_table_to_pandas
 from pandas.io.common import get_handle
@@ -162,9 +165,15 @@ def read_feather(
                     Pandas4Warning,
                 )

-                return feather.read_feather(
+                df = feather.read_feather(
                     handles.handle, columns=columns, use_threads=bool(use_threads)
                 )
+                # Convert any StringDtype columns to object dtype (pyarrow always
+                # uses string dtype even when the infer_string option is False)
+                for col, dtype in zip(df.columns, df.dtypes, strict=True):
+                    if isinstance(dtype, StringDtype) and dtype.na_value is np.nan:
+                        df[col] = df[col].astype("object")
+                return df

         pa_table = feather.read_table(
             handles.handle, columns=columns, use_threads=bool(use_threads)

@@ -6,8 +6,6 @@
 import numpy as np
 import pytest

-from pandas._config import using_string_dtype
-
 from pandas.compat.pyarrow import pa_version_under19p0

 from pandas.core.dtypes.common import is_dtype_equal
@@ -351,18 +349,16 @@ def test_arrow_roundtrip(dtype, string_storage, using_infer_string):
         assert table.field("a").type == "large_string"
     with pd.option_context("string_storage", string_storage):
         result = table.to_pandas()
-    if dtype.na_value is np.nan and not using_infer_string:
-        assert result["a"].dtype == "object"
-    else:
-        assert isinstance(result["a"].dtype, pd.StringDtype)
-        expected = df.astype(pd.StringDtype(string_storage, na_value=dtype.na_value))
-        if using_infer_string:
-            expected.columns = expected.columns.astype(
-                pd.StringDtype(string_storage, na_value=np.nan)
-            )
-        tm.assert_frame_equal(result, expected)
-        # ensure the missing value is represented by [NAME_REDACTED] or None
-        assert result.loc[2, "a"] is result["a"].dtype.na_value
+
+    assert isinstance(result["a"].dtype, pd.StringDtype)
+    expected = df.astype(pd.StringDtype(string_storage, na_value=dtype.na_value))
+    if using_infer_string:
+        expected.columns = expected.columns.astype(
+            pd.StringDtype(string_storage, na_value=np.nan)
+        )
+    tm.assert_frame_equal(result, expected)
+    # ensure the missing value is represented by [NAME_REDACTED] or None
+    assert result.loc[2, "a"] is result["a"].dtype.na_value

 [USER_REDACTED].mark.filterwarnings("ignore:Passing a BlockManager:DeprecationWarning")
@@ -373,10 +369,17 @@ def test_arrow_from_string(using_infer_string):

     result = table.to_pandas()

-    if using_infer_string and not pa_version_under19p0:
-        expected = pd.DataFrame({"a": ["a", "b", None]}, dtype="str")
-    else:
+    if not using_infer_string:
+        if pa_version_under19p0:
+            expected = pd.DataFrame({"a": ["a", "b", None]}, dtype="object")
+        else:
+            expected = pd.DataFrame(
+                {"a": ["a", "b", None]}, dtype=pd.StringDtype(na_value=np.nan)
+            )
+    elif pa_version_under19p0:
         expected = pd.DataFrame({"a": ["a", "b", None]}, dtype="object")
+    else:
+        expected = pd.DataFrame({"a": ["a", "b", None]}, dtype="str")
     tm.assert_frame_equal(result, expected)

@@ -397,16 +400,13 @@ def test_arrow_load_from_zero_chunks(dtype, string_storage, using_infer_string):
     with pd.option_context("string_storage", string_storage):
         result = table.to_pandas()

-    if dtype.na_value is np.nan and not using_string_dtype():
-        assert result["a"].dtype == "object"
-    else:
-        assert isinstance(result["a"].dtype, pd.StringDtype)
-        expected = df.astype(pd.StringDtype(string_storage, na_value=dtype.na_value))
-        if using_infer_string:
-            expected.columns = expected.columns.astype(
-                pd.StringDtype(string_storage, na_value=np.nan)
-            )
-        tm.assert_frame_equal(result, expected)
+    assert isinstance(result["a"].dtype, pd.StringDtype)
+    expected = df.astype(pd.StringDtype(string_storage, na_value=dtype.na_value))
+    if using_infer_string:
+        expected.columns = expected.columns.astype(
+            pd.StringDtype(string_storage, na_value=np.nan)
+        )
+    tm.assert_frame_equal(result, expected)

 def test_value_counts_na(dtype):

@@ -64,7 +64,9 @@ def test_interpolate_inplace(self, frame_or_series, request):
         assert orig.squeeze()[1] == 1.5

     [USER_REDACTED].mark.parametrize("dtype", ["str", "object"])
-    def test_interp_with_non_numeric(self, dtype):
+    def test_interp_with_non_numeric(self, dtype, using_infer_string):
+        if not using_infer_string:
+            dtype = "object"
         df = DataFrame(
             {
                 "A": [1, 2, np.nan, 4],

@@ -1,5 +1,6 @@
 import ctypes

+import numpy as np
 import pytest

 import pandas.util._test_decorators as td
@@ -65,12 +66,14 @@ def __arrow_c_stream__(self, requested_schema=None):

 [USER_REDACTED].skip_if_no("pyarrow", min_version="14.0")
-def test_dataframe_from_arrow():
+def test_dataframe_from_arrow(using_infer_string):
     # objects with __arrow_c_stream__
     table = pa.table({"a": [1, 2, 3], "b": ["a", "b", "c"]})

     result = pd.DataFrame.from_arrow(table)
     expected = pd.DataFrame({"a": [1, 2, 3], "b": ["a", "b", "c"]})
+    if not using_infer_string:
+        expected["b"] = expected["b"].astype(pd.StringDtype(na_value=np.nan))
     tm.assert_frame_equal(result, expected)

     # not only pyarrow object are supported

@@ -138,7 +138,7 @@ def test_bytesiowrapper_returns_correct_bytes(self):
             assert result == data.encode("utf-8")

     # Test that pyarrow can handle a file opened with get_handle
-    def test_get_handle_pyarrow_compat(self):
+    def test_get_handle_pyarrow_compat(sel, using_infer_string):
         pa_csv = pytest.importorskip("pyarrow.csv")

         # Test latin1, ucs-2, and ucs-4 chars
@@ -154,6 +154,8 @@ def test_get_handle_pyarrow_compat(self):
             df = pa_csv.read_csv(handles.handle).to_pandas()
             if pa_version_under19p0:
                 expected = expected.astype("object")
+            elif not using_infer_string:
+                expected = expected.astype(pd.StringDtype(na_value=np.nan))
             tm.assert_frame_equal(df, expected)
             assert not s.closed

lgtm

question: Could you remind me of the convention for `object` "string" type using `None` vs `NaN` as th missing value in this case?

There is no exact convention, as a user you are free to use both. I think pandas will typically use NaN by [NAME_REDACTED] manually constructed by [NAME_REDACTED] (at least for example `read_csv` will give you NaN), but pyarrow specifically uses `None` for missing values when converting to numpy/pandas for object dtype.

So the `fillna(None)` above here is just to match pyarrow's behaviour


DOC: Add AI contribution guidelines and clarify other contribution policies

I have noticed an uptick of contributions that [irresponsibly use AI](https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63873) or make [meaningless changes](https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63853). I'd like to have documentation to point to when closing or banning users that engage in this behavior.

* The AI policy I borrowed heavily from [Matplotlib](https://matplotlib.org/devdocs/devel/contribute.html#restrictions-on-generative-ai-usage) and [scikit-learn](https://scikit-learn.org/dev/developers/contributing.html#automated-contributions-policy)
* I added additional language emphasizing to follow all guidelines and well as clarifying when it should be appropriate to open a pull request.

Would be good to get feedback from those working with newer contributors lately cc [USER_REDACTED] [USER_REDACTED]

@@ -3,4 +3,5 @@
 - [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
 - [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
 - [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
+- [ ] I have reviewed and followed all the [contribution guidelines](https://pandas.pydata.org/docs/dev/development/contributing.html)
 - [ ] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

@@ -11,7 +11,25 @@ Contributing to pandas

 All contributions, bug reports, bug fixes, documentation improvements,
-enhancements, and ideas are welcome.
+enhancements, and ideas are welcome. We ask that contributors follow
+all contribution guidelines when participating with pandas.
+
+.. _contributing.ai:
+
+Automated contributions policy
+==============================
+
+Contributors may use automated tools such as AI coding assistants while contributing
+to pandas as long as it's used **responsibly**. Any use of these tools in a contribution must abide by [NAME_REDACTED]
+following guidelines:
+
+* You must disclose that you used an automated tool in the contribution.
+* You must fully review and modify the result of the automated tool.
+* You must ensure the contribution fully abides by [NAME_REDACTED], contribution conventions in pandas.
+
+This policy applies to any contribution made to pandas, including submitted issues or pull requests. Maintainers
+reserve the right to discern whether automated tooling was used and reject contributions that do not follow all of
+the above guidelines. Maintainers may also ban users from contributing to pandas who violate these guidelines 2 or more times.

 .. _contributing.bug_reports:

@@ -30,20 +48,27 @@ Finding an issue to contribute to

 If you are brand new to pandas or open-source development, we recommend searching
 the `GitHub "issues" tab <https://github.com/[USER_REDACTED]ndas-dev/pandas/issues>`_
-to find issues that interest you. Unassigned issues labeled `Docs
-<https://github.com/[USER_REDACTED]ndas-dev/pandas/issues?q=is%3Aopen+sort%3Aupdated-desc+label%3ADocs+no%3Aassignee>`_
-and `good first issue
-<https://github.com/[USER_REDACTED]ndas-dev/pandas/issues?q=is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22+no%3Aassignee>`_
-are typically good for newer contributors.
+to find issues that interest you and are available to work on. Issues available to work on are:

-Once you've found an interesting issue, leave a comment with your intention
+* Issues without the label ``[NAME_REDACTED]`` or ``[NAME_REDACTED]``. These issues require clarification and confirmation
+  from a maintainer before proceeding.
+* Issues that have not been started by [NAME_REDACTED] that another contributor has not commented their intent
+  to work on the issue or already submitted an open pull request to address the issue before proceeding.
+
+.. tip::
+    We recommend issues labeled `Docs
+    <https://github.com/[USER_REDACTED]ndas-dev/pandas/issues?q=is%3Aopen+sort%3Aupdated-desc+label%3ADocs+no%3Aassignee>`_
+    and `good first issue
+    <https://github.com/[USER_REDACTED]ndas-dev/pandas/issues?q=is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22+no%3Aassignee>`_
+    for newer contributors.
+
+Once you've found an interesting, available issue, leave a comment with your intention
 to start working on it. If somebody else has
 already commented on the issue but they have shown a lack of activity in the issue
 or a pull request in the past 2-3 weeks, you may take it over.

 If for whatever reason you are not able to continue working with the issue, please
-leave a comment on an issue, so other people know it's available again. You can check the list of
-assigned issues, since people may not be working on them anymore.
+leave a comment on an issue, so other people know it's available again.

 We have several :ref:`contributor community <community>` communication channels, which you are
 welcome to join, and ask questions as you figure things out. Among them are regular meetings for
@@ -241,12 +266,21 @@ and merged into the project to appear in the next release. To submit a pull requ
    * TYP: Type annotations
    * CLN: Code cleanup

-#. Write a description of your changes in the ``[NAME_REDACTED]`` tab
+#. Complete the checklist template in the body of the pull request and write an additional description below the checklist if necessary.
 #. Click ``[NAME_REDACTED]``.

 This request then goes to the repository maintainers, and they will review
 the code.

+.. note::
+    A pull request should be associated with an open Github issue except if the change is trivial such as fixing a typo.
+    Pull requests that do not abide by [NAME_REDACTED] guidelines may be closed by a maintainer. Contributors
+    who have shown continued, quality pull requests may be exempt from following all guidelines strictly.
+
+.. warning::
+    pandas will not accept low quality pull requests that do not meaningfully contribute to the project. Maintainers reserve
+    their right to close these pull requests and ban users who repeatedly submit low quality pull requests.
+
 .. _contributing.update-pr:

 Updating your pull request

Looks good. I am also wondering if we should more actively discourage low-quality PRs by [NAME_REDACTED] thinking of ones that are no where near what is considered acceptable (e.g. [this](https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63880)). If so, should we put something in the template to this effect?

lgtm

Looks good, thanks! Some ideas for how to evolve it, not deal-breakers.

As long as there is a general understanding that valuable contributions put forth by [NAME_REDACTED] a history of contributing to pandas need not abide, I'm good with this.

Yeah that's a good point. Added something to this effect in 24262e9

Hmm. Is this intent here to block low-quality contributions, or something else? Hypothetically, if someone dropped a pull request with a performance improvement, would we close it and ask for an issue first? In the spirit of "demos not memos," wondering if we can achieve the aim without adding too much friction. For example, could say:

- "We prefer a GitHub issue first"
- "For any API changes, please open a GitHub issue first."

Does that make sense? Not a huge deal, I imagine most significant high-quality contributions have an associated issue anyway.

I've been refining the [[NAME_REDACTED] policy](https://computing-in-context.afeld.me/#academic-integrity) I use for my courses, and the current stance is that generative AI is treated like any other source: usage is fine, copying is plagiarism. The corollary here is that we could generalize (at least parts of) this policy to essentially say "if you try and contribute junk twice or more, you'll be banned," regardless of whether it comes from generative AI or not.

> Is this intent here to block low-quality contributions, or something else?

Primarily yes.

> Hypothetically, if someone dropped a pull request with a performance improvement, would we close it and ask for an issue first?

If there wasn't enough context, I would personally be inclined too, especially from new/first time contributors. But from a familiar contributor, I agree I wouldn't want to introduce additional friction (that's what the last sentence is trying to convey).

I'm open to your rephrasing if you'd like to open a follow up PR

I do think we should generally discourage new contributors from opening up PRs without issues, and will ignore this part of the doc for anything that appears to be of value. So I'm good with this as-is.

Another interesting case popped up here: https://github.com/[USER_REDACTED]ndas-dev/pandas/issues/64002

I would generally say "if you believe it cleans the code or increases performance and it's a small change, just go for it without an issue". I'm okay with still going with the above guidance (always make an issue), but it could become problematic if we start getting a lot of issues for small refactors such as these.

[USER_REDACTED] [USER_REDACTED] [USER_REDACTED] One practical concern for me is that if I open an issue first for a small fix I already have in mind, there’s a good chance someone else will submit the PR before I do. That’s a bit discouraging when the fix is already clear and ready.


ENH: Make print_thing respect display.precision for float sequence

Made the change as proposed in https://github.com/[USER_REDACTED]ndas-dev/pandas/issues/60503#issuecomment-2526104263.

- [x] closes #60503 (Replace xxxx with the GitHub issue number)
- [x] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
- [ ] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

@@ -21,7 +21,11 @@

 from pandas._config import get_option

-from pandas.core.dtypes.inference import is_sequence
+from pandas.core.dtypes.inference import (
+    is_float,
+    is_sequence,
+)
+from pandas.core.dtypes.missing import notna

 from pandas.io.formats.console import get_console_size

@@ -129,6 +133,12 @@ def _pprint_seq(
         if (max_items is not None) and (i >= max_items):
             max_items_reached = True
             break
+        if is_float(item) and notna(item):
+            # GH#60503
+            from pandas.io.formats.format import _trim_zeros_single_float
+
+            precision = get_option("display.precision")
+            item = _trim_zeros_single_float(f"{item:.{precision}f}")
         r.append(pprint_thing(item, _nest_lvl + 1, max_seq_items=max_seq_items, **kwds))
     body = ", ".join(r)

@@ -85,6 +85,10 @@ def test_repr_mapping(self):
     def test_repr_frozenset(self):
         assert printing.pprint_thing(frozenset([1, 2])) == "frozenset({1, 2})"

+    def test_repr_seq_float_precision(self):
+        with cf.option_context("display.precision", 3):
+            assert printing.pprint_thing([3.14159265, 3.14159265]) == "[3.142, 3.142]"
+

 class TestFormatBase:
     def test_adjoin(self):

@@ -61,6 +61,7 @@
     "_DatetimeTZBlock",
     "_check_pyarrow_available",
     "_parser",  # https://github.com/[USER_REDACTED]ndas-dev/pandas/issues/60833
+    "_trim_zeros_single_float",
 }

Thanks for the PR!

I think this looks good. Just need a note in the whatsnew for 3.1.0 under `Other enhancements`.

lgtm

Why is this necessary?

Without trimming trailing zeros, many tests in `pandas/tests/util` fail because the generated assertion messages no longer match, such as `tm.assert_numpy_array_equal(np.array([np.nan, 2, 3]), np.array([1, np.nan, 3]))`. `_pprint_seq` relies on `pprint_thing`, which is used by [NAME_REDACTED] a better design would be to apply `display.precision` in other display layers, and keep `_pprint_seq` precision agnostic for assertion output?

Ah, I see - `{n}f` will pad with zeros. Also this function was duplicated from `format.py`. Just import the function, don't make another one.

I've encountered circular `ImportError`, and it triggered `unwanted-patterns-private-import-across-module` from pre-commit.

I implemented the logic locally.

Don't duplicate logic. Instead, add it to the `PRIVATE_IMPORTS_TO_IGNORE` list in `scripts/validate_unwanted_patterns.py` and import it locally (in the function where it is used).

Thanks for pointing out the `PRIVATE_IMPORTS_TO_IGNORE` option. I wasn’t aware of that.


DOC: Fix PR02 for pandas.tseries.offsets.CBMonthBegin

- [ ] related to #60365

Fixes

```
pandas.tseries.offsets.CBMonthBegin PR02
pandas.tseries.offsets.CBMonthEnd PR02
pandas.tseries.offsets.CustomBusinessMonthBegin PR02
pandas.tseries.offsets.CustomBusinessMonthEnd PR02

```

Fixed the PR02 error for CBMonthBegin and CBMonthEnd by adding `__signature__ `attributes to these Cython classes.

Changes Made
File: `pandas/_libs/tslibs/offsets.pyx`
- Added explicit `__signature__` attributes for `CBMonthBegin` and `CBMonthEnd` classes
- This allows Python's `inspect.signature()` to properly introspect these Cython classes
- The signatures match the `__init__` method parameters defined in the parent `_CustomBusinessMonth` class

[NAME_REDACTED] Works
- Root cause: Cython cdef class definitions don't expose their __init__ signatures to Python's inspection tools
- Solution: Manually set the __signature__ attribute using inspect.Parameter and inspect.Signature
- Result: The numpydoc validator can now verify that docstring parameters match the actual function signature

@@ -73,13 +73,9 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         -i "pandas.tseries.offsets.BDay PR02,SA01" \
         -i "pandas.tseries.offsets.BusinessDay PR02,SA01" \
         -i "pandas.tseries.offsets.BusinessHour PR02,SA01" \
-        -i "pandas.tseries.offsets.CBMonthBegin PR02" \
-        -i "pandas.tseries.offsets.CBMonthEnd PR02" \
         -i "pandas.tseries.offsets.CDay PR02,SA01" \
         -i "pandas.tseries.offsets.CustomBusinessDay PR02,SA01" \
         -i "pandas.tseries.offsets.CustomBusinessHour PR02,SA01" \
-        -i "pandas.tseries.offsets.CustomBusinessMonthBegin PR02" \
-        -i "pandas.tseries.offsets.CustomBusinessMonthEnd PR02" \
         -i "pandas.tseries.offsets.Easter.is_on_offset GL08" \
         -i "pandas.tseries.offsets.FY5253Quarter.is_on_offset GL08" \
         -i "pandas.tseries.offsets.FY5253Quarter.rule_code GL08" \

@@ -284,8 +284,28 @@ class CustomBusinessHour(BusinessHour):
         offset: timedelta = ...,
     ) -> None: ...

-class CustomBusinessMonthEnd(_CustomBusinessMonth): ...
-class CustomBusinessMonthBegin(_CustomBusinessMonth): ...
+class CustomBusinessMonthEnd(_CustomBusinessMonth):
+    def __init__(
+        self,
+        n: int = ...,
+        normalize: bool = ...,
+        weekmask: str = ...,
+        holidays: list | None = ...,
+        calendar: OffsetCalendar | None = ...,
+        offset: timedelta = ...,
+    ) -> None: ...
+
+class CustomBusinessMonthBegin(_CustomBusinessMonth):
+    def __init__(
+        self,
+        n: int = ...,
+        normalize: bool = ...,
+        weekmask: str = ...,
+        holidays: list | None = ...,
+        calendar: OffsetCalendar | None = ...,
+        offset: timedelta = ...,
+    ) -> None: ...
+
 class OffsetMeta(type): ...
 class DateOffset(RelativeDeltaOffset, metaclass=OffsetMeta): ...

@@ -6365,7 +6365,11 @@ cdef class _CustomBusinessMonth(BusinessMixin):
         return result

-cdef class CustomBusinessMonthEnd(_CustomBusinessMonth):
+cdef class _CustomBusinessMonthEnd(_CustomBusinessMonth):
+    _prefix = "CBME"
+
+
+class CustomBusinessMonthEnd(_CustomBusinessMonthEnd):
     """
     DateOffset subclass representing custom business month(s).

@@ -6423,10 +6427,30 @@ cdef class CustomBusinessMonthEnd(_CustomBusinessMonth):
                    dtype='datetime64[ns]', freq='CBME')
     """

-    _prefix = "CBME"
+    def __init__(
+        self,
+        n=1,
+        normalize=False,
+        weekmask="[NAME_REDACTED] [NAME_REDACTED]",
+        holidays=None,
+        calendar=None,
+        offset=timedelta(0),
+    ):
+        super().__init__(
+            n=n,
+            normalize=normalize,
+            weekmask=weekmask,
+            holidays=holidays,
+            calendar=calendar,
+            offset=offset,
+        )

-cdef class CustomBusinessMonthBegin(_CustomBusinessMonth):
+cdef class _CustomBusinessMonthBegin(_CustomBusinessMonth):
+    _prefix = "CBMS"
+
+
+class CustomBusinessMonthBegin(_CustomBusinessMonthBegin):
     """
     DateOffset subclass representing custom business month(s).

@@ -6484,7 +6508,23 @@ cdef class CustomBusinessMonthBegin(_CustomBusinessMonth):
                    dtype='datetime64[ns]', freq='CBMS')
     """

-    _prefix = "CBMS"
+    def __init__(
+        self,
+        n=1,
+        normalize=False,
+        weekmask="[NAME_REDACTED] [NAME_REDACTED]",
+        holidays=None,
+        calendar=None,
+        offset=timedelta(0),
+    ):
+        super().__init__(
+            n=n,
+            normalize=normalize,
+            weekmask=weekmask,
+            holidays=holidays,
+            calendar=calendar,
+            offset=offset,
+        )

 BDay = BusinessDay

Instead, could you just define a regular Python `class` that subclasses these cdef classes to give it a docstring? This works but IMO is a little more indirect

yeah. let me try to solve it.

So now   the Cython cdef classes with Python subclasses (`CustomBusinessMonthEnd`, `CustomBusinessMonthBegin`) have their explicit __init__ signatures. The internal cdef classes are renamed to `_CustomBusinessMonthEnd` and `_CustomBusinessMonthBegin`, and the .pyi stubs are updated with the new __init__ signatures.

Do we actually need to fully repeat `__init__` of `_CustomBusinessMonthEnd`? The object should just pick up the base class' implementation

Yes, it picks up the base class implementation but the explicit __init__ is still required. Without it, PR02 returns because inspect.signature() cannot introspect methods inherited from cdef classes. the init exists purely to provide an introspectable Python-level signature for the docstring validation tooling.

Did some research. Looks like its a Cython limitation. Cython cdef classes are C extension types - their methods are compiled to C and don't carry the Python-level metadata (__code__, __annotations__, __defaults__, etc.) that inspect.signature() relies on for introspection.


BUG: Correct length_of_indexer for range objects

`length_of_indexer` computed the length of `range` objects using a manual arithmetic formula, which produced incorrect results for valid ranges.

This PR replaces that calculation with `len(indexer)` and adds a regression test to ensure correct behavior.

@@ -325,7 +325,18 @@ def length_of_indexer(indexer, target=None) -> int:
             return indexer.sum()
         return len(indexer)
     elif isinstance(indexer, range):
-        return (indexer.stop - indexer.start) // indexer.step
+        try:
+            return len(indexer)
+        except OverflowError:
+            step = indexer.step
+            if step > 0:
+                low, high = indexer.start, indexer.stop
+            else:
+                low, high = indexer.stop, indexer.start
+                step = -step
+            if low >= high:
+                return 0
+            return (high - low - 1) // step + 1
     elif not is_list_like_indexer(indexer):
         return 1
     raise AssertionError("cannot find the length of the indexer")

@@ -16,6 +16,20 @@ def test_length_of_indexer():
     assert result == 1

+[USER_REDACTED].mark.parametrize(
+    "start, stop, step, expected",
+    [
+        (10, 0, -3, 4),
+        (0, 1, 5, 1),
+    ],
+)
+def test_length_of_indexer_range_overflow(start, stop, step, expected):
+    # https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63872
+    indexer = range(start, stop, step)
+    result = length_of_indexer(indexer)
+    assert result == expected
+
+
 def test_is_scalar_indexer():
     indexer = (0, 1)
     assert is_scalar_indexer(indexer, 2)

Good find!

I don't know if this really has any user facing implications. If it's not known, I think it's okay to not have a whatsnew entry for this.

lgtm

As you mentioned, this formula can be incorrect. I believe the correct fallback is:

```python
    if indexer.step > 0:
        low, high = indexer.start, indexer.stop
        step = indexer.step
    else:
        high, low = indexer.start, indexer.stop
        step = -indexer.step

    if low >= high:
        length = 0
    else:
        length = (high - low - 1) // step + 1
```

Can you add tests that hit this fallback in both positive and negative step cases.

Instead of duplicating the formula, can you hardcode the correct `expected` as part of the parameters.

Can you add a comment referring to this PR.

```suggestion
    # https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63872
    indexer = range(start, stop, step)
```


CI: Use pytest-cython 0.4.0rc1 with editable install for doctests

- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
- [ ] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

Previously `doctest-cython` was not compatible with editable installs, was not running doctests in `_libs/`, and used the deprecated `pkg_resources`. I'm fixing these issues in 0.4.0. I will be reaching out to other dependent projects to see if I can have them test the new behavior, it will likely be several weeks until 0.4.0. As long as there are no objections, I'd like to get this merged with the pin on `0.4.0rc1`.

@@ -43,15 +43,8 @@ jobs:
     - name: [NAME_REDACTED]
       id: build
       uses: ./.github/actions/build_pandas
-      with:
-        editable: false

     # The following checks are independent of each other and should still be run if one fails
-
-    # TODO: The doctests have to be run first right now, since the Cython doctests only work
-    # with pandas installed in non-editable mode
-    # This can be removed once pytest-cython doesn't require C extensions to be installed inplace
-
     - name: Extra installs
       # https://pytest-qt.readthedocs.io/en/latest/troubleshooting.html#github-actions-azure-pipelines-travis-ci-and-gitlab-ci-cd
       run: sudo apt-get update && sudo apt-get install -y libegl1 libopengl0
@@ -60,13 +53,6 @@ jobs:
       run: cd ci && ./code_checks.sh doctests
       if: ${{ steps.build.outcome == 'success' && always() }}

-    - name: Install pandas in editable mode
-      id: build-editable
-      if: ${{ steps.build.outcome == 'success' && always() }}
-      uses: ./.github/actions/build_pandas
-      with:
-        editable: true
-
     - name: Check for no warnings when building single-page docs
       run: ci/code_checks.sh single-docs
       if: ${{ steps.build.outcome == 'success' && always() }}

@@ -88,7 +88,6 @@ dependencies:
   - numpydoc
   # temporary installed with pip with custom patch until released
   # - pydata-sphinx-theme=0.16
-  - pytest-cython  # doctest
   - sphinx
   - sphinx-design
   - sphinx-copybutton
@@ -128,3 +127,4 @@ dependencies:
   - pip:
       - tzdata>=2023.3
       - https://github.com/[USER_REDACTED]nbossche/pydata-sphinx-theme/archive/refs/heads/v0.16.1+dismissable-announcement-banner.zip
+      - pytest-cython==0.4.0rc1  # doctest

@@ -317,7 +317,7 @@ def item_from_zerodim(val: object) -> object:
     >>> item_from_zerodim('foobar')
     'foobar'
     >>> item_from_zerodim(np.array(1))
-    1
+    np.int64(1)
     >>> item_from_zerodim(np.array([1]))
     array([1])
     """

@@ -783,7 +783,7 @@ cdef class BaseOffset:
         Timestamp is not on the offset (not a month end), so it rolls backward:

         >>> offset.rollback(ts)
-        Timestamp('2024-12-31 00:00:00')
+        Timestamp('2024-12-31 09:00:00')

         If the timestamp is already on the offset, it remains unchanged:

@@ -831,7 +831,7 @@ cdef class BaseOffset:
         Timestamp is not on the offset (not a month end), so it rolls forward:

         >>> offset.rollforward(ts)
-        Timestamp('2025-01-31 00:00:00')
+        Timestamp('2025-01-31 09:00:00')

         If the timestamp is already on the offset, it remains unchanged:

@@ -980,7 +980,7 @@ cdef class BaseOffset:

         Examples
         --------
-        >>> pd.offsets.Week(n=1).nanos
+        >>> pd.offsets.Week(n=1).nanos  # doctest: +SKIP
         ValueError: Week: weekday=None is a non-fixed frequency
         """
         raise ValueError(f"{self} is a non-fixed frequency")
@@ -1504,7 +1504,7 @@ cdef class Day(SingleConstructorOffset):

         Examples
         --------
-        >>> pd.Day(5).freqstr
+        >>> pd.offsets.Day(5).freqstr
         '5D'

         >>> pd.offsets.Day(1).freqstr
@@ -2707,7 +2707,7 @@ cdef class BusinessHour(BusinessMixin):
                    '2022-12-12 06:00:00', '2022-12-12 07:00:00',
                    '2022-12-12 10:00:00', '2022-12-12 11:00:00',
                    '2022-12-12 15:00:00', '2022-12-12 16:00:00'],
-                   dtype='datetime64[ns]', freq='bh')
+                   dtype='datetime64[us]', freq='bh')
     """

     _prefix = "bh"
@@ -6109,7 +6109,7 @@ cdef class FY5253Quarter(FY5253Mixin):
         >>> offset = pd.offsets.FY5253Quarter(
         ...     weekday=5, startingMonth=12, qtr_with_extra_week=1
         ... )
-        >>> offset.year_has_extra_week(pd.Timestamp("2011-04-02"))
+        >>> offset.year_has_extra_week(pd.Timestamp("2014-04-02"))
         True
         >>> offset.year_has_extra_week(pd.Timestamp("2010-04-02"))
         False
@@ -6395,7 +6395,7 @@ cdef class CustomBusinessDay(BusinessDay):
     Index(['Mon 12 Dec 2022 00:00', 'Wed 14 Dec 2022 00:00',
            'Fri 16 Dec 2022 00:00', 'Mon 19 Dec 2022 00:00',
            'Wed 21 Dec 2022 00:00'],
-           dtype='object')
+           dtype='str')

     Using NumPy business day calendar you can define custom holidays.

@@ -6405,7 +6405,7 @@ cdef class CustomBusinessDay(BusinessDay):
     >>> pd.date_range(dt.datetime(2022, 12, 10), dt.datetime(2022, 12, 25), freq=freq)
     DatetimeIndex(['2022-12-13', '2022-12-15', '2022-12-16', '2022-12-19',
                    '2022-12-20', '2022-12-21', '2022-12-22', '2022-12-23'],
-                   dtype='datetime64[ns]', freq='C')
+                   dtype='datetime64[us]', freq='C')

     If you want to shift the result on n day you can use the parameter ``offset``.

@@ -6541,10 +6541,14 @@ cdef class CustomBusinessDay(BusinessDay):
         >>> pd.offsets.CustomBusinessDay(normalize=True).is_on_offset(ts)
         True
         """
+        from pandas.core.dtypes.cast import maybe_unbox_numpy_scalar
+
         if self._normalize and not _is_normalized(dt):
             return False
         day64 = _to_dt64D(dt)
-        return np.is_busday(day64, busdaycal=self._calendar)
+        result = np.is_busday(day64, busdaycal=self._calendar)
+        result = maybe_unbox_numpy_scalar(result)
+        return result

 cdef class CustomBusinessHour(BusinessHour):
@@ -6615,7 +6619,7 @@ cdef class CustomBusinessHour(BusinessHour):
                    '2022-12-12 06:00:00', '2022-12-12 07:00:00',
                    '2022-12-12 10:00:00', '2022-12-12 11:00:00',
                    '2022-12-12 15:00:00', '2022-12-12 16:00:00'],
-                   dtype='datetime64[ns]', freq='cbh')
+                   dtype='datetime64[us]', freq='cbh')

     Business days can be specified by ``weekmask`` parameter. To convert
     the returned datetime object to its string representation
@@ -6631,7 +6635,7 @@ cdef class CustomBusinessHour(BusinessHour):
            'Wed 14 Dec 2022 11:00', 'Wed 14 Dec 2022 12:00',
            'Fri 16 Dec 2022 10:00', 'Fri 16 Dec 2022 11:00',
            'Fri 16 Dec 2022 12:00'],
-           dtype='object')
+           dtype='str')

     Using NumPy business day calendar you can define custom holidays.

@@ -6644,7 +6648,7 @@ cdef class CustomBusinessHour(BusinessHour):
                    '2022-12-15 11:00:00', '2022-12-15 12:00:00',
                    '2022-12-16 10:00:00', '2022-12-16 11:00:00',
                    '2022-12-16 12:00:00'],
-                   dtype='datetime64[ns]', freq='cbh')
+                   dtype='datetime64[us]', freq='cbh')
     """

     _prefix = "cbh"
@@ -6811,7 +6815,7 @@ class CustomBusinessMonthEnd(_CustomBusinessMonthEnd):
     Index(['Thu 28 Jul 2022 00:00', 'Wed 31 Aug 2022 00:00',
            'Thu 29 Sep 2022 00:00', 'Thu 27 Oct 2022 00:00',
            'Wed 30 Nov 2022 00:00'],
-           dtype='object')
+           dtype='str')

     Using NumPy business day calendar you can define custom holidays.

@@ -6821,7 +6825,7 @@ class CustomBusinessMonthEnd(_CustomBusinessMonthEnd):
     >>> freq = pd.offsets.CustomBusinessMonthEnd(calendar=bdc)
     >>> pd.date_range(dt.datetime(2022, 7, 10), dt.datetime(2022, 11, 10), freq=freq)
     DatetimeIndex(['2022-07-29', '2022-08-31', '2022-09-29', '2022-10-28'],
-                   dtype='datetime64[ns]', freq='CBME')
+                   dtype='datetime64[us]', freq='CBME')
     """

     def __init__(
@@ -6892,7 +6896,7 @@ class CustomBusinessMonthBegin(_CustomBusinessMonthBegin):
     Index(['Wed 03 Aug 2022 00:00', 'Thu 01 Sep 2022 00:00',
            'Wed 05 Oct 2022 00:00', 'Wed 02 Nov 2022 00:00',
            'Thu 01 Dec 2022 00:00'],
-           dtype='object')
+           dtype='str')

     Using NumPy business day calendar you can define custom holidays.

@@ -6902,7 +6906,7 @@ class CustomBusinessMonthBegin(_CustomBusinessMonthBegin):
     >>> freq = pd.offsets.CustomBusinessMonthBegin(calendar=bdc)
     >>> pd.date_range(dt.datetime(2022, 7, 10), dt.datetime(2022, 11, 10), freq=freq)
     DatetimeIndex(['2022-08-02', '2022-09-01', '2022-10-03', '2022-11-02'],
-                   dtype='datetime64[ns]', freq='CBMS')
+                   dtype='datetime64[us]', freq='CBMS')
     """

     def __init__(
@@ -7180,7 +7184,7 @@ cpdef to_offset(freq, bint is_period=False):
     <2 * BusinessDays>

     >>> to_offset(pd.Timedelta(days=1))
-    <Day>
+    <24 * Hours>

     >>> to_offset(pd.offsets.Hour())
     <Hour>

@@ -2049,13 +2049,13 @@ cdef class _Period(PeriodMixin):

         >>> period = pd.Period('2023-01', freq='M')
         >>> period.asfreq('h', how='start')
-        Period('2023-01-01 00:00', 'H')
+        Period('2023-01-01 00:00', 'h')

         Convert a weekly period to a daily period, aligning to the last day of the week:

         >>> period = pd.Period('2023-08-01', freq='W')
         >>> period.asfreq('D', how='end')
-        Period('2023-08-04', 'D')
+        Period('2023-08-06', 'D')
         """
         freq = self._maybe_convert_freq(freq)
         how = validate_end_alias(how)

@@ -1267,7 +1267,7 @@ cdef class _Timedelta(timedelta):

         Examples
         --------
-        >>> td = pd.Timedelta(1, "d")
+        >>> td = pd.Timedelta(1, "D")
         >>> td.days
         1

@@ -1402,7 +1402,8 @@ cdef class _Timedelta(timedelta):
         Examples
         --------
         >>> td = pd.Timedelta(42, unit='us')
-        'ns'
+        >>> td.unit
+        'us'
         """
         return npy_unit_to_abbrev(self._creso)

@@ -1602,7 +1603,7 @@ cdef class _Timedelta(timedelta):
         >>> td
         Timedelta('3 days 00:00:00')
         >>> td.to_timedelta64()
-        numpy.timedelta64(259200000000000,'ns')
+        np.timedelta64(259200000000,'us')
         """
         cdef:
             str abbrev = npy_unit_to_abbrev(self._creso)
@@ -1639,7 +1640,7 @@ cdef class _Timedelta(timedelta):
         >>> td
         Timedelta('3 days 00:00:00')
         >>> td.to_numpy()
-        numpy.timedelta64(259200000000000,'ns')
+        np.timedelta64(259200000000,'us')
         """
         if dtype is not None or copy is not False:
             raise ValueError(
@@ -1678,7 +1679,7 @@ cdef class _Timedelta(timedelta):
         >>> td
         Timedelta('3 days 00:00:00')
         >>> td.view(int)
-        259200000000000
+        np.int64(259200000000)
         """
         return np.timedelta64(self._value).view(dtype)

@@ -1735,19 +1736,19 @@ cdef class _Timedelta(timedelta):
         --------
         >>> td = pd.Timedelta('1 days 2 min 3 us 42 ns')
         >>> td.asm8
-        numpy.timedelta64(86520000003042,'ns')
+        np.timedelta64(86520000003042,'ns')

         >>> td = pd.Timedelta('2 min 3 s')
         >>> td.asm8
-        numpy.timedelta64(123000000000,'ns')
+        np.timedelta64(123000000,'us')

         >>> td = pd.Timedelta('3 ms 5 us')
         >>> td.asm8
-        numpy.timedelta64(3005000,'ns')
+        np.timedelta64(3005,'us')

         >>> td = pd.Timedelta(42, unit='ns')
         >>> td.asm8
-        numpy.timedelta64(42,'ns')
+        np.timedelta64(42,'ns')
         """
         return self.to_timedelta64()

@@ -193,6 +193,7 @@ def pytest_collection_modifyitems(items, config) -> None:
             "DataFrameGroupBy.fillna with 'method' is deprecated",
         ),
         ("read_parquet", "Passing a BlockManager to DataFrame is deprecated"),
+        ("Timestamp.utcfromtimestamp", "Timestamp.utcfromtimestamp is deprecated"),
     ]

     if is_doctest:

@@ -61,7 +61,6 @@ gitpython
 natsort
 pickleshare
 numpydoc
-pytest-cython
 sphinx
 sphinx-design
 sphinx-copybutton
@@ -87,3 +86,4 @@ jupyterlite-core
 jupyterlite-pyodide-kernel
 tzdata>=2023.3
 https://github.com/[USER_REDACTED]nbossche/pydata-sphinx-theme/archive/refs/heads/v0.16.1+dismissable-announcement-banner.zip
+pytest-cython==0.4.0rc1

Just a request for a cleanup otherwise LGTM

Feel free to set the milestone to whatever you feel appropriate and merge. There might be merge conflicts if you set it to 3.0.1 with all the docstring changes recently

Appears this workflow was the only use of this `input` in .github/actions/build_pandas/action.yml. Could you further remove this flag then from that action?

Also just an fyi, I think ideally we want our CI to not use editable installs

Agreed on ideally not using an editable install with testing, however with code checks we want to run these on code that is not in the installed package, so I think this is necessary (and okay - we don't really care if code checks fail on end users who are not using editable installs).

With the desire to set to false (at least in testing), leave the `editable` input?

Hmm can you remind me which of these checks require that pandas is installed in editable mode and what the limitation would be not to run them in non-editable mode?

For code-checks, it's really just the doctests with pytest-cython. This uses the `pyx` files to determine docstrings to run. These files do not exist in a non-editable install.

All other linting we do on Cython files is done in pre-commit.

Ah ok gotcha!


ENH: [NAME_REDACTED] when not np.busdaycalendar is passed to custom subclasses

- [x] closes #60647

Custom subclasses accept not np.busdaycalendar. [NAME_REDACTED].

@@ -217,6 +217,11 @@ cdef _get_calendar(weekmask, holidays, calendar):
             pass
         return calendar, holidays

+    elif calendar is not None:
+        from pandas.tseries.holiday import AbstractHolidayCalendar
+        if not isinstance(calendar, AbstractHolidayCalendar):
+            raise TypeError("Custom subclasses only accept np.busdaycalendar.")
+
     if holidays is None:
         holidays = []
     try:

@@ -97,3 +97,9 @@ def test_pickle_compat_0_14_1(self, datapath):
         cday0_14_1 = read_pickle(pth)
         cday = CDay(holidays=hdays)
         assert cday == cday0_14_1
+
+    def test_cbd_raises_if_calendar_not_busdaycalendar(self):
+        # GH#60647
+        msg = "Custom subclasses only accept np.busdaycalendar."
+        with pytest.raises(TypeError, match=msg):
+            CDay(calendar=object())

Nit: Could you make this an `elif` with the `if` statement above?

Due to the circular dependency, could we just instead locally import `AbstractHolidayCalendar` in this condition and check for it here?

Yes, I changed it.

Thanks, I did as you suggested.
Could you explain why inserting a local import of `AbstractHolidayCalendar` in this condition and checking for an instance of it is better than checking whether the calendar has an attribute `holidays`?

There could be an edge case where someone passes an object that's not a `AbstractHolidayCalendar` but defines `holidays` that this object does not expect.

In my opinion is almost always better to be strict and check for the exact types than to rely on duck typing. It often leads to less surprises that way

Thanks for explaining.


DEPR: Deprecate decorator Appender and Substitution

- [ ] closes #xxxx (Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [ ] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
- [ ] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

@@ -438,6 +438,13 @@ def some_function(x):
     """

     def __init__(self, *args, **kwargs) -> None:
+        from pandas.errors import Pandas4Warning
+
+        warnings.warn(
+            "Substitution is deprecated and will be removed in a future version.",
+            Pandas4Warning,
+            stacklevel=find_stack_level(),
+        )
         if args and kwargs:
             raise AssertionError("Only positional or keyword args are allowed")

@@ -478,6 +485,13 @@ def my_dog(has='fleas'):
     addendum: str | None

     def __init__(self, addendum: str | None, join: str = "", indents: int = 0) -> None:
+        from pandas.errors import Pandas4Warning
+
+        warnings.warn(
+            "Appender is deprecated and will be removed in a future version.",
+            Pandas4Warning,
+            stacklevel=find_stack_level(),
+        )
         if indents > 0:
             self.addendum = indent(addendum, indents=indents)
         else:

Instead, could you put this warning in the `Appender/Substitution.__init__`s?

One more item. Could you use `stacklevel=find_stack_level()` (available from `pandas.errors`) in both places?

Also could you move the import to the top of the file if there's no circular reference?

sure!
importing `Pandas4Warning` at the top of the file caused circular reference, so I moved it here


DOC: Add User Guide links to GroupBy aggregate, transform, and apply methods

## [NAME_REDACTED] PR adds cross-references from the GroupBy API documentation back to the [NAME_REDACTED], addressing #62357.

## Changes

Added `:ref:` links to the [NAME_REDACTED] in the `See Also` sections of:

| Method | [NAME_REDACTED] |
|--------|-----------------|
| `SeriesGroupBy.aggregate` | `groupby.aggregate` |
| `DataFrameGroupBy.aggregate` | `groupby.aggregate` |
| `transform` (via `_transform_template`) | `groupby.transform` |
| `GroupBy.apply` | `groupby.apply` |

Also added a note in the API reference (`doc/source/reference/groupby.rst`) with links to relevant [NAME_REDACTED] sections.

## [NAME_REDACTED] noted in the issue, users navigating from the [NAME_REDACTED] to API docs couldn't easily navigate back. This improves discoverability of the comprehensive [NAME_REDACTED] sections on groupby operations.

## Files Changed

- `pandas/core/groupby/generic.py` - Added User Guide links to aggregate docstrings
- `pandas/core/groupby/groupby.py` - Added User Guide links to apply and transform docstrings
- `doc/source/reference/groupby.rst` - Added User Guide references in Function application section

Closes #62357

@@ -251,6 +251,8 @@ def apply(self, func, *args, **kwargs) -> Series:

         Notes
         -----
+        See :ref:`groupby.apply` in the [NAME_REDACTED] for more details and examples.
+
         The resulting dtype will reflect the return value of the passed ``func``,
         see the examples below.

@@ -382,6 +384,9 @@ def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs)

         Notes
         -----
+        See :ref:`groupby.aggregate` in the [NAME_REDACTED] for more details
+        and examples.
+
         When using ``engine='numba'``, there will be no "fall back" behavior internally.
         The group data and group index will be passed as numpy arrays to the JITed
         user defined function, and no alternative execution attempts will be tried.
@@ -711,6 +716,8 @@ def transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):

         Notes
         -----
+        See :ref:`groupby.transform` in the [NAME_REDACTED] for more details and examples.
+
         Each group is endowed the attribute 'name' in case you need to know
         which group you are working on.

@@ -2180,6 +2187,9 @@ def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs)

         Notes
         -----
+        See :ref:`groupby.aggregate` in the [NAME_REDACTED] for more details
+        and examples.
+
         When using ``engine='numba'``, there will be no "fall back" behavior internally.
         The group data and group index will be passed as numpy arrays to the JITed
         user defined function, and no alternative execution attempts will be tried.
@@ -2706,6 +2716,8 @@ def transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):

         Notes
         -----
+        See :ref:`groupby.transform` in the [NAME_REDACTED] for more details and examples.
+
         Each group is endowed the attribute 'name' in case you need to know
         which group you are working on.

@@ -1541,6 +1541,8 @@ def apply(self, func, *args, include_groups: bool = False, **kwargs) -> NDFrameT

         Notes
         -----
+        See :ref:`groupby.apply` in the [NAME_REDACTED] for more details and examples.
+
         The resulting dtype will reflect the return value of the passed ``func``,
         see the examples below.

Thanks for the PR!

lgtm

This transform template appears no longer used. Can you update the two methods `transform` in `generic.py`.

Can you also add this line to `SeriesGroupBy.apply` in `generic.py`.

Can also remove this template I think; but that can also be done in a separate PR.

sure, Thanks for letting me know


DOC: Fix GL08 for pandas.tseries.offsets.WeekOfMonth.week

- [ ] related to #60365

Fixes

```
pandas.tseries.offsets.WeekOfMonth.week GL08
pandas.tseries.offsets.LastWeekOfMonth.week GL08
```

@@ -93,15 +93,13 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         -i "pandas.tseries.offsets.FY5253Quarter.startingMonth GL08" \
         -i "pandas.tseries.offsets.FY5253Quarter.year_has_extra_week GL08" \
         -i "pandas.tseries.offsets.LastWeekOfMonth.is_on_offset GL08" \
-        -i "pandas.tseries.offsets.LastWeekOfMonth.week GL08" \
         -i "pandas.tseries.offsets.LastWeekOfMonth.weekday GL08" \
         -i "pandas.tseries.offsets.SemiMonthBegin.is_on_offset GL08" \
         -i "pandas.tseries.offsets.SemiMonthBegin.rule_code GL08" \
         -i "pandas.tseries.offsets.SemiMonthEnd.is_on_offset GL08" \
         -i "pandas.tseries.offsets.SemiMonthEnd.rule_code GL08" \
         -i "pandas.tseries.offsets.Week.is_on_offset GL08" \
         -i "pandas.tseries.offsets.WeekOfMonth.is_on_offset GL08" \
-        -i "pandas.tseries.offsets.WeekOfMonth.week GL08" \
         -i "pandas.tseries.offsets.WeekOfMonth.weekday GL08" # There should be no backslash in the final line, please keep this comment in the last ignored function

     RET=$(($RET + $?)) ; echo $MSG "DONE"

@@ -148,6 +148,8 @@ class WeekOfMonthMixin(SingleConstructorOffset):
     def __init__(
         self, n: int = ..., normalize: bool = ..., weekday: int = ...
     ) -> None: ...
+    [USER_REDACTED]
+    def week(self) -> int: ...

 class YearOffset(SingleConstructorOffset):
     def __init__(

@@ -3020,7 +3020,7 @@ cdef class WeekOfMonthMixin(SingleConstructorOffset):
     """

     cdef readonly:
-        int weekday, week
+        int weekday, _week

     def __init__(self, n=1, normalize=False, weekday=0):
         BaseOffset.__init__(self, n, normalize)
@@ -3045,6 +3045,38 @@ cdef class WeekOfMonthMixin(SingleConstructorOffset):
             return False
         return dt.day == self._get_offset_day(dt)

+    [USER_REDACTED]
+    def week(self) -> int:
+        """
+        Return the week of the month on which this offset applies.
+
+        Returns an integer representing the week of the month (0-3) that this
+        offset targets. The week is zero-indexed, where 0 corresponds to the
+        first week of the month.
+
+        Returns
+        -------
+        int
+            An integer representing the week of the month (0-3).
+
+        See Also
+        --------
+        tseries.offsets.WeekOfMonth : Describes monthly dates in a specific week.
+        tseries.offsets.LastWeekOfMonth : Describes monthly dates in last week.
+
+        Examples
+        --------
+        >>> pd.offsets.WeekOfMonth(week=0, weekday=0).week
+        0
+
+        >>> pd.offsets.WeekOfMonth(week=2, weekday=3).week
+        2
+
+        >>> pd.offsets.LastWeekOfMonth(weekday=0).week
+        -1
+        """
+        return self._week
+
     [USER_REDACTED]
     def rule_code(self) -> str:
         """
@@ -3066,10 +3098,10 @@ cdef class WeekOfMonthMixin(SingleConstructorOffset):
         'WOM-1MON'
         """
         weekday = int_to_weekday.get(self.weekday, "")
-        if self.week == -1:
+        if self._week == -1:
             # LastWeekOfMonth
             return f"{self._prefix}-{weekday}"
-        return f"{self._prefix}-{self.week + 1}{weekday}"
+        return f"{self._prefix}-{self._week + 1}{weekday}"

 # ----------------------------------------------------------------------
@@ -4813,21 +4845,21 @@ cdef class WeekOfMonth(WeekOfMonthMixin):

     def __init__(self, n=1, normalize=False, week=0, weekday=0):
         WeekOfMonthMixin.__init__(self, n, normalize, weekday)
-        self.week = week
+        self._week = week

-        if self.week < 0 or self.week > 3:
-            raise ValueError(f"Week must be 0<=week<=3, got {self.week}")
+        if self._week < 0 or self._week > 3:
+            raise ValueError(f"Week must be 0<=week<=3, got {self._week}")

     cpdef __setstate__(self, state):
         self._n = state.pop("n")
         self._normalize = state.pop("normalize")
         self.weekday = state.pop("weekday")
-        self.week = state.pop("week")
+        self._week = state.pop("week")

     def _get_offset_day(self, other: datetime) -> int:
         """
         Find the day in the same month as other that has the same
-        weekday as self.weekday and is the self.week'th such day in the month.
+        weekday as self.weekday and is the self._week'th such day in the month.

         Parameters
         ----------
@@ -4840,7 +4872,7 @@ cdef class WeekOfMonth(WeekOfMonthMixin):
         mstart = datetime(other.year, other.month, 1)
         wday = mstart.weekday()
         shift_days = (self.weekday - wday) % 7
-        return 1 + shift_days + self.week * 7
+        return 1 + shift_days + self._week * 7

     [USER_REDACTED]
     def _from_name(cls, suffix=None):
@@ -4896,7 +4928,7 @@ cdef class LastWeekOfMonth(WeekOfMonthMixin):

     def __init__(self, n=1, normalize=False, weekday=0):
         WeekOfMonthMixin.__init__(self, n, normalize, weekday)
-        self.week = -1
+        self._week = -1

         if self._n == 0:
             raise ValueError("N cannot be 0")
@@ -4905,7 +4937,7 @@ cdef class LastWeekOfMonth(WeekOfMonthMixin):
         self._n = state.pop("n")
         self._normalize = state.pop("normalize")
         self.weekday = state.pop("weekday")
-        self.week = -1
+        self._week = -1

     def _get_offset_day(self, other: datetime) -> int:
         """

Should this be defined on `WeekOfMonthMixin`?

yes. added example for LastWeekOfMonth.week as well. Let me know if this looks good.


DOC: Add py:type reference targets to pandas.api.typing.aliases

- [x] closes #63810(Replace xxxx with the GitHub issue number)
- [ ] [Tests added and passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [x] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [ ] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
- [x] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

## This PR fixes the type aliases documentation to use proper Sphinx directives instead of references.

### Changes
- Replace `:py:type:` references with `.. py:type::` directives
- This makes the type aliases available in the intersphinx inventory
- Allows external projects to link to these type aliases using Sphinx's :type: role

@@ -2,6 +2,8 @@

 .. _api.typing.aliases:

+{{ header }}
+
 ======================================
 pandas typing aliases
 ======================================
@@ -10,7 +12,14 @@ pandas typing aliases
 Typing aliases
 **************

-.. currentmodule:: pandas.api.typing.aliases
+.. module:: pandas.api.typing.aliases
+
+.. raw:: html
+
+    <style>
+    td > dl.py.type { margin-bottom: 0; }
+    td > dl.py.type .descclassname { display: none; }
+    </style>

 The typing declarations in ``pandas/_typing.py`` are considered private, and used
 by [NAME_REDACTED] checking of the pandas code base.  For users, it is
@@ -25,72 +34,139 @@ own python code that calls pandas or expects certain results.

 Each of these aliases listed in the table below can be found by [NAME_REDACTED] :py:mod:`pandas.api.typing.aliases`.

-==================================== ================================================================
-[NAME_REDACTED]
-==================================== ================================================================
-:py:type:`AggFuncType`               Type of functions that can be passed to :meth:`agg` methods
-:py:type:`AlignJoin`                 Argument type for ``join`` in :meth:`DataFrame.join`
-:py:type:`AnyAll`                    Argument type for ``how`` in :meth:`dropna`
-:py:type:`AnyArrayLike`              Used to represent :class:`ExtensionArray`, ``numpy`` arrays, :class:`Index` and :class:`Series`
-:py:type:`ArrayLike`                 Used to represent :class:`ExtensionArray`, ``numpy`` arrays
-:py:type:`AstypeArg`                 Argument type in :meth:`astype`
-:py:type:`Axes`                      :py:type:`AnyArrayLike` plus sequences (not strings) and ``range``
-:py:type:`Axis`                      Argument type for ``axis`` in many methods
-:py:type:`CSVEngine`                 Argument type for ``engine`` in :meth:`DataFrame.read_csv`
-:py:type:`ColspaceArgType`           Argument type for ``colspace`` in :meth:`DataFrame.to_html`
-:py:type:`CompressionOptions`        Argument type for ``compression`` in all I/O output methods except :meth:`DataFrame.to_parquet`
-:py:type:`CorrelationMethod`         Argument type for ``correlation`` in :meth:`corr`
-:py:type:`DropKeep`                  Argument type for ``keep`` in :meth:`drop_duplicates`
-:py:type:`Dtype`                     Types as objects that can be used to specify dtypes
-:py:type:`DtypeArg`                  Argument type for ``dtype`` in various methods
-:py:type:`DtypeBackend`              Argument type for ``dtype_backend`` in various methods
-:py:type:`DtypeObj`                  Numpy dtypes and Extension dtypes
-:py:type:`ExcelWriterIfSheetExists`  Argument type for ``if_sheet_exists`` in :class:`ExcelWriter`
-:py:type:`ExcelWriterMergeCells`     Argument type for ``merge_cells`` in :meth:`to_excel`
-:py:type:`FilePath`                  Type of paths for files for I/O methods
-:py:type:`FillnaOptions`             Argument type for ``method`` in various methods where NA values are filled
-:py:type:`FloatFormatType`           Argument type for ``float_format`` in :meth:`to_string`
-:py:type:`FormattersType`            Argument type for ``formatters`` in :meth:`to_string`
-:py:type:`FromDictOrient`            Argument type for ``orient`` in :meth:`DataFrame.from_dict`
-:py:type:`HTMLFlavors`               Argument type for ``flavor`` in :meth:`pandas.read_html`
-:py:type:`IgnoreRaise`               Argument type for ``errors`` in multiple methods
-:py:type:`IndexLabel`                Argument type for ``level`` in multiple methods
-:py:type:`InterpolateOptions`        Argument type for ``interpolate`` in :meth:`interpolate`
-:py:type:`JSONEngine`                Argument type for ``engine`` in :meth:`read_json`
-:py:type:`JSONSerializable`          Argument type for the return type of a callable for argument ``default_handler`` in :meth:`to_json`
-:py:type:`JoinHow`                   Argument type for ``how`` in :meth:`pandas.merge_ordered` and for ``join`` in :meth:`Series.align`
-:py:type:`JoinValidate`              Argument type for ``validate`` in :meth:`DataFrame.join`
-:py:type:`MergeHow`                  Argument type for ``how`` in :meth:`merge`
-:py:type:`MergeValidate`             Argument type for ``validate`` in :meth:`merge`
-:py:type:`NaPosition`                Argument type for ``na_position`` in :meth:`sort_index` and :meth:`sort_values`
-:py:type:`NsmallestNlargestKeep`     Argument type for ``keep`` in :meth:`nlargest` and :meth:`nsmallest`
-:py:type:`OpenFileErrors`            Argument type for ``errors`` in :meth:`to_hdf` and :meth:`to_csv`
-:py:type:`Ordered`                   Return type for :py:attr:`ordered` in :class:`CategoricalDtype` and :class:`Categorical`
-:py:type:`ParquetCompressionOptions` Argument type for ``compression`` in :meth:`DataFrame.to_parquet`
-:py:type:`QuantileInterpolation`     Argument type for ``interpolation`` in :meth:`quantile`
-:py:type:`ReadBuffer`                Additional argument type corresponding to buffers for various file reading methods
-:py:type:`ReadCsvBuffer`             Additional argument type corresponding to buffers for :meth:`pandas.read_csv`
-:py:type:`ReadPickleBuffer`          Additional argument type corresponding to buffers for :meth:`pandas.read_pickle`
-:py:type:`ReindexMethod`             Argument type for ``reindex`` in :meth:`reindex`
-:py:type:`Scalar`                    Types that can be stored in :class:`Series` with non-object dtype
-:py:type:`SequenceNotStr`            Used for arguments that require sequences, but not plain strings
-:py:type:`SliceType`                 Argument types for ``start`` and ``end`` in :meth:`Index.slice_locs`
-:py:type:`SortKind`                  Argument type for ``kind`` in :meth:`sort_index` and :meth:`sort_values`
-:py:type:`StorageOptions`            Argument type for ``storage_options`` in various file output methods
-:py:type:`Suffixes`                  Argument type for ``suffixes`` in :meth:`merge`, :meth:`compare` and :meth:`merge_ordered`
-:py:type:`TakeIndexer`               Argument type for ``indexer`` and ``indices`` in :meth:`take`
-:py:type:`TimeAmbiguous`             Argument type for ``ambiguous`` in time operations
-:py:type:`TimeGrouperOrigin`         Argument type for ``origin`` in :meth:`resample` and :class:`TimeGrouper`
-:py:type:`TimeNonexistent`           Argument type for ``nonexistent`` in time operations
-:py:type:`TimeUnit`                  Time unit argument and return type for :py:attr:`unit`, arguments ``unit`` and ``date_unit``
-:py:type:`TimedeltaConvertibleTypes` Argument type for ``offset`` in :meth:`resample`, ``halflife`` in :meth:`ewm` and ``start`` and ``end`` in :meth:`pandas.timedelta_range`
-:py:type:`TimestampConvertibleTypes` Argument type for ``origin`` in :meth:`resample` and :meth:`pandas.to_datetime`
-:py:type:`ToStataByteorder`          Argument type for ``byteorder`` in :meth:`DataFrame.to_stata`
-:py:type:`ToTimestampHow`            Argument type for ``how`` in :meth:`to_timestamp` and ``convention`` in :meth:`resample`
-:py:type:`UpdateJoin`                Argument type for ``join`` in :meth:`DataFrame.update`
-:py:type:`UsecolsArgType`            Argument type for ``usecols`` in :meth:`pandas.read_clipboard`, :meth:`pandas.read_csv` and :meth:`pandas.read_excel`
-:py:type:`WindowingRankType`         Argument type for ``method`` in :meth:`rank` in rolling and expanding window operations
-:py:type:`WriteBuffer`               Additional argument type corresponding to buffers for various file output methods
-:py:type:`WriteExcelBuffer`          Additional argument type corresponding to buffers for :meth:`to_excel`
-:py:type:`XMLParsers`                Argument type for ``parser`` in :meth:`DataFrame.to_xml` and :meth:`pandas.read_xml`
-==================================== ================================================================
+.. list-table::
+   :header-rows: 1
+   :widths: 30 70
+
+   * - Alias
+     - Meaning
+   * - .. type:: AggFuncType
+     - Type of functions that can be passed to :meth:`DataFrame's <pandas.DataFrame.agg>`, :meth:`Series' <pandas.Series.agg>`, and :meth:`DataFrameGroupBy's <pandas.api.typing.DataFrameGroupBy.aggregate>` ``aggregate()`` methods
+   * - .. type:: AlignJoin
+     - Argument type for ``join`` in :meth:`DataFrame's <pandas.DataFrame.align>` and :meth:`Series' <pandas.Series.align>` ``align()`` methods
+   * - .. type:: AnyAll
+     - Argument type for ``how`` in :meth:`DataFrame's <pandas.DataFrame.dropna>` and :meth:`Series' <pandas.Series.dropna>` ``dropna()`` methods
+   * - .. type:: AnyArrayLike
+     - Used to represent :class:`~pandas.api.extensions.ExtensionArray`, ``numpy`` arrays, :class:`~pandas.Index` and :class:`~pandas.Series`
+   * - .. type:: ArrayLike
+     - Used to represent :class:`~pandas.api.extensions.ExtensionArray`, ``numpy`` arrays
+   * - .. type:: AstypeArg
+     - Argument type in :meth:`DataFrame's <pandas.DataFrame.astype>` and :meth:`Series' <pandas.Series.astype>` ``astype()`` methods
+   * - .. type:: Axes
+     - :py:type:`AnyArrayLike` plus sequences (not strings) and ``range``
+   * - .. type:: Axis
+     - Argument type for ``axis`` in many methods
+   * - .. type:: CSVEngine
+     - Argument type for ``engine`` in :meth:`pandas.read_csv`
+   * - .. type:: ColspaceArgType
+     - Argument type for ``colspace`` in :meth:`pandas.DataFrame.to_html`
+   * - .. type:: CompressionOptions
+     - Argument type for ``compression`` in all I/O output methods except :meth:`pandas.DataFrame.to_parquet`
+   * - .. type:: CorrelationMethod
+     - Argument type for ``correlation`` in :meth:`DataFrame's <pandas.DataFrame.corr>` and :meth:`Series' <pandas.Series.corr>` ``corr()`` methods
+   * - .. type:: DropKeep
+     - Argument type for ``keep`` in :meth:`DataFrame's <pandas.DataFrame.drop_duplicates>` and :meth:`Series' <pandas.Series.drop_duplicates>` ``drop_duplicates()`` methods
+   * - .. type:: Dtype
+     - Types as objects that can be used to specify dtypes
+   * - .. type:: DtypeArg
+     - Argument type for ``dtype`` in various methods
+   * - .. type:: DtypeBackend
+     - Argument type for ``dtype_backend`` in various methods
+   * - .. type:: DtypeObj
+     - Numpy dtypes and Extension dtypes
+   * - .. type:: ExcelWriterIfSheetExists
+     - Argument type for ``if_sheet_exists`` in :class:`~pandas.ExcelWriter`
+   * - .. type:: ExcelWriterMergeCells
+     - Argument type for ``merge_cells`` in :meth:`DataFrame's <pandas.DataFrame.to_excel>` and :meth:`Series' <pandas.Series.to_excel>` ``to_excel()`` methods
+   * - .. type:: FilePath
+     - Type of paths for files for I/O methods
+   * - .. type:: FillnaOptions
+     - Argument type for ``method`` in various methods where NA values are filled
+   * - .. type:: FloatFormatType
+     - Argument type for ``float_format`` in :meth:`DataFrame's <pandas.DataFrame.to_string>` and :meth:`Series' <pandas.Series.to_string>` ``to_string()`` methods
+   * - .. type:: FormattersType
+     - Argument type for ``formatters`` in :meth:`DataFrame's <pandas.DataFrame.to_string>` and :meth:`Series' <pandas.Series.to_string>` ``to_string()`` methods
+   * - .. type:: FromDictOrient
+     - Argument type for ``orient`` in :meth:`DataFrame.from_dict() <pandas.DataFrame.from_dict>`
+   * - .. type:: HTMLFlavors
+     - Argument type for ``flavor`` in :meth:`pandas.read_html`
+   * - .. type:: IgnoreRaise
+     - Argument type for ``errors`` in multiple methods
+   * - .. type:: IndexLabel
+     - Argument type for ``level`` in multiple methods
+   * - .. type:: InterpolateOptions
+     - Argument type for ``interpolate`` in :meth:`DataFrame's <pandas.DataFrame.interpolate>` and :meth:`Series' <pandas.Series.interpolate>` ``interpolate()`` methods
+   * - .. type:: JSONEngine
+     - Argument type for ``engine`` in :meth:`pandas.read_json`
+   * - .. type:: JSONSerializable
+     - Argument type for the return type of a callable for argument ``default_handler`` in :meth:`DataFrame's <pandas.DataFrame.to_json>` and :meth:`Series' <pandas.Series.to_json>` ``to_json()`` methods
+   * - .. type:: JoinHow
+     - Argument type for ``how`` in :meth:`pandas.merge_ordered` and for ``join`` in :meth:`Series.align() <pandas.Series.align>`
+   * - .. type:: JoinValidate
+     - Argument type for ``validate`` in :meth:`DataFrame.join() <pandas.DataFrame.join>`
+   * - .. type:: MergeHow
+     - Argument type for ``how`` in :meth:`pandas.merge`
+   * - .. type:: MergeValidate
+     - Argument type for ``validate`` in :meth:`pandas.merge`
+   * - .. type:: NaPosition
+     - Argument type for ``na_position`` in :meth:`DataFrame's <pandas.DataFrame.sort_values>` and :meth:`Series' <pandas.Series.sort_values>` ``sort_values()`` methods
+   * - .. type:: NsmallestNlargestKeep
+     - Argument type for ``keep`` in :meth:`DataFrame's <pandas.DataFrame.nlargest>` and :meth:`Series' <pandas.Series.nlargest>` ``nlargest()``, :meth:`DataFrame's <pandas.DataFrame.nsmallest>` and :meth:`Series' <pandas.Series.nsmallest>` ``nsmallest()``, and :meth:`SeriesGroupBy's <pandas.api.typing.SeriesGroupBy.nlargest>` ``nlargest()`` methods
+   * - .. type:: OpenFileErrors
+     - Argument type for ``errors`` in :meth:`DataFrame's <pandas.DataFrame.to_hdf>`, :meth:`Series' <pandas.Series.to_hdf>` ``to_hdf()`` methods, and :meth:`DataFrame's <pandas.DataFrame.to_csv>` and :meth:`Series' <pandas.Series.to_csv>` ``to_csv()`` methods
+   * - .. type:: Ordered
+     - Return type for :attr:`ordered <pandas.CategoricalDtype.ordered>` in :class:`pandas.CategoricalDtype` and :class:`pandas.Categorical`
+   * - .. type:: ParquetCompressionOptions
+     - Argument type for ``compression`` in :meth:`DataFrame.to_parquet() <pandas.DataFrame.to_parquet>`
+   * - .. type:: QuantileInterpolation
+     - Argument type for ``interpolation`` in :meth:`DataFrame's <pandas.DataFrame.quantile>` and :meth:`Series' <pandas.Series.quantile>` ``quantile()`` methods
+   * - .. type:: ReadBuffer
+     - Additional argument type corresponding to buffers for various file reading methods
+   * - .. type:: ReadCsvBuffer
+     - Additional argument type corresponding to buffers for :meth:`pandas.read_csv`
+   * - .. type:: ReadPickleBuffer
+     - Additional argument type corresponding to buffers for :meth:`pandas.read_pickle`
+   * - .. type:: ReindexMethod
+     - Argument type for ``reindex`` in :meth:`DataFrame's <pandas.DataFrame.reindex>` and :meth:`Series' <pandas.Series.reindex>` ``reindex()`` methods
+   * - .. type:: Scalar
+     - Types that can be stored in :class:`~pandas.Series` with non-object dtype
+   * - .. type:: SequenceNotStr
+     - Used for arguments that require sequences, but not plain strings
+   * - .. type:: SliceType
+     - Argument types for ``start`` and ``end`` in :meth:`Index.slice_locs() <pandas.Index.slice_locs>`
+   * - .. type:: SortKind
+     - Argument type for ``kind`` in :meth:`DataFrame's <pandas.DataFrame.sort_values>` and :meth:`Series' <pandas.Series.sort_values>` ``sort_values()`` methods
+   * - .. type:: StorageOptions
+     - Argument type for ``storage_options`` in various file output methods
+   * - .. type:: Suffixes
+     - Argument type for ``suffixes`` in :meth:`pandas.merge`, :meth:`pandas.merge_ordered`,  and :meth:`DataFrame's <pandas.DataFrame.compare>` and :meth:`Series' <pandas.Series.compare>` ``compare()`` methods
+   * - .. type:: TakeIndexer
+     - Argument type for ``indexer`` and ``indices`` in :meth:`DataFrame's <pandas.DataFrame.take>` and :meth:`Series' <pandas.Series.take>` ``take()`` methods
+   * - .. type:: TimeAmbiguous
+     - Argument type for ``ambiguous`` in time operations
+   * - .. type:: TimeGrouperOrigin
+     - Argument type for ``origin`` in :meth:`DataFrame's <pandas.DataFrame.resample>`, :meth:`Series' <pandas.Series.resample>` ``resample()`` methods and for :class:`~pandas.Grouper`
+   * - .. type:: TimeNonexistent
+     - Argument type for ``nonexistent`` in time operations
+   * - .. type:: TimeUnit
+     - Time unit argument and return type for :attr:`pandas.Timedelta.unit`, arguments ``unit`` and ``date_unit``
+   * - .. type:: TimedeltaConvertibleTypes
+     - Argument type for ``offset`` in various methods, such as :meth:`DataFrame's <pandas.DataFrame.resample>` and :meth:`Series' <pandas.Series.resample>` ``resample()``, ``halflife`` in :meth:`DataFrame's <pandas.DataFrame.ewm>`, :meth:`DataFrameGroupBy's <pandas.api.typing.DataFrameGroupBy.ewm>`, and :meth:`Series' <pandas.Series.ewm>` ``ewm()``, and ``start`` and ``end`` in :meth:`pandas.timedelta_range`
+   * - .. type:: TimestampConvertibleTypes
+     - Argument type for ``origin`` in :meth:`DataFrame's <pandas.DataFrame.resample>` and :meth:`Series' <pandas.Series.resample>` ``resample()``, and in :meth:`pandas.to_datetime`
+   * - .. type:: ToStataByteorder
+     - Argument type for ``byteorder`` in :meth:`DataFrame.to_stata() <pandas.DataFrame.to_stata>`
+   * - .. type:: ToTimestampHow
+     - Argument type for ``how`` in :meth:`DataFrame's <pandas.DataFrame.to_timestamp>` and :meth:`Series' <pandas.Series.to_timestamp>` ``to_timestamp()`` methods, and ``convention`` in :meth:`DataFrame's <pandas.DataFrame.resample>` and :meth:`Series' <pandas.Series.resample>` ``resample()`` methods
+   * - .. type:: UpdateJoin
+     - Argument type for ``join`` in :meth:`DataFrame.update() <pandas.DataFrame.update>`
+   * - .. type:: UsecolsArgType
+     - Argument type for ``usecols`` in :meth:`pandas.read_clipboard`, :meth:`pandas.read_csv` and :meth:`pandas.read_excel`
+   * - .. type:: WindowingRankType
+     - Argument type for ``method`` in :meth:`Rolling's <pandas.api.typing.Rolling.rank>` and :meth:`Expanding's <pandas.api.typing.Expanding.rank>` ``rank()`` methods, applicable in rolling and expanding window operations
+   * - .. type:: WriteBuffer
+     - Additional argument type corresponding to buffers for various file output methods
+   * - .. type:: WriteExcelBuffer
+     - Additional argument type corresponding to buffers for :meth:`DataFrame's <pandas.DataFrame.to_excel>`, :meth:`Series' <pandas.Series.to_excel>`  and :meth:`Styler's <pandas.io.formats.style.Styler.to_excel>` ``to_excel()`` methods
+   * - .. type:: XMLParsers
+     - Argument type for ``parser`` in :meth:`DataFrame.to_xml() <pandas.DataFrame.to_xml>` and :meth:`pandas.read_xml`

the imports only work from `pandas.api.typing.aliases`

I have no clue why you keep breaking this. It *must* be this:

```
.. module:: pandas.api.typing.aliases
```

and the table needs to look like this

```
==================================== ================================================================
[NAME_REDACTED]
==================================== ================================================================
.. type:: [NAME_REDACTED] of functions that can be passed to :meth:`agg` methods
.. type:: [NAME_REDACTED] type for ``join`` in :meth:`DataFrame.join`
```

just do both of these things and the PR actually does what it says.

This is still rendering incorrectly, e.g.:

<img width="738" height="120" alt="Image" src="https://github.com/[USER_REDACTED]nts/assets/a6ec898b-3e8f-4f14-97bd-fbba3f93b532" />

You probably need to do what [USER_REDACTED] suggested here:  https://github.com/[USER_REDACTED]ndas-dev/pandas/pull/63812#discussion_r2732711546

I appreciate the work you have done on this.  But if you're going to fix links to various methods and classes in this document, you might as well go for all of them.  I think I caught the ones that you missed, as well as a few that are incorrect.

I have found a number of grammatical and formatting things, plus a few incorrect links.   I think I caught them all, not 100% sure.

Main grammatical things are where you currently see in the rendered output, for example:  "Argument type for indexer and indices in DataFrame's and Series' take method" and it needs to read "Argument type for indexer and indices in DataFrame's and Series' take() methods", so that there are parentheses after the method name in the text, and "method" becomes plural.

Another suggestion is that you fix all of these at once and do one commit, as opposed to a commit for each single change.

It's pretty close.  I caught a few things on this review, and you also missed doing a full change as previously suggested, so I unresolved those comments

Thanks [USER_REDACTED] and [USER_REDACTED]

Currentmodule is wrong, it means “the module definition is somewhere else, but pretend we're in there for other directives”

But the module definition isn't actually anywhere else, it's right here

```suggestion
.. module:: pandas.api.typing.aliases
```

[USER_REDACTED] [NAME_REDACTED] [NAME_REDACTED]

Why can't we continue to use the markdown table with `=====` as we do through most of the pandas docs?

When this is rendered it looks like:
```text
type pandas.api.typing.aliases.AggFuncType
```
The module `pandas.api.typing.aliases` is quite redundant for every entry.  Is there a way that could be removed?

ok thanks for clarification i think two backtick method will work by [NAME_REDACTED] to current module

I didn't check but as said multiple times, rST has a lot of implicit rules about where it accepts inline syntax, block syntax, both, or neither.

I *assume* table syntax might only support inline syntax and not block syntax, and directives are block syntax.

But as said, that's only an assumption based on the PR author [NAME_REDACTED]

No, double backticks mean “this is code”. There point of this PR is to create reference targets.

In reference roles, using `~` will leave out the redundant part, but I'm not sure if it's possible to shorten here.

```suggestion
Each of these aliases listed in the table below can be found by [NAME_REDACTED] :py:mod:`pandas.api.typing.aliases`.
```

```suggestion
.. currentmodule:: pandas.api.typing.aliases
```

There is no `Series.to_parquet`

and `:meth:`~pandas.api.typing.SeriesGroupBy.nlargest`

No `Series.to_parquet()`

This is rendering as follows:

<img width="702" height="60" alt="Image" src="https://github.com/[USER_REDACTED]nts/assets/3e1e9a86-a376-45dc-a647-f8a46f8b9162" />

It really should say `DataFrame.agg()` and `Series.agg()` and `pandas.api.typing.DataFrameGroupBy.agg()`  (note the reference to aggregate is incorrect)

I think (but I'm not 100% sure) that the solution here is to use (for example - remove the double quotes):  ":meth:\`DataFrame.agg\`"

You may need to do this throughout the PR - i.e., remove the `~`

[USER_REDACTED] FYI you can format this syntax properly on GitHub like this:

```markdown
renders as `` :meth:`~pandas.api.typing.SeriesGroupBy.nlargest` ``!
```

> renders as `` :meth:`~pandas.api.typing.SeriesGroupBy.nlargest` ``!

note the double backticks and space on both sides.

Removing the tilde makes things very long, that’s why I used the whole “DataFrame’s and Series’ `method`” shenanigans in my PR.

Thanks.  I spent a few minutes trying to get it to work and just gave up.

I think it would render correctly (and do the proper links) with `` :meth:`DataFrame.to_parquet` `` .  [USER_REDACTED] am I correct about that?  We use that elsewhere in the docs.

I dove into the GFM spec instead of giving up haha!

Good catch [USER_REDACTED] that simplified version data frame to parquet works in other files but remember we are use module not current module in this file so the simplified version will break the links
because sphinx looks for DataFrame inside the aliases namespace

Oh I have little confusion over it also
Ok removed !

[USER_REDACTED] Thanks for the screenshot—that ambiguity was definitely confusing!

I will the references to use explicit title syntax, such as :meth:DataFrame.agg <pandas.DataFrame.agg>``. This renders the specific class name (e.g., DataFrame.agg) for clarity as requested, but links to the fully qualified path to prevent build errors caused by [NAME_REDACTED]

The solution will paid off for us making links little bit longer but not being confusing for anyone anymore

Are it sounds good and to be fixed with this pr or should merge this one and open another issue to fix this?

Both `module` and `currentmodule` set the lookup context. In fact, setting it is `currentmodule`’s only purpose. The only thing `module` does differently is creating a link target for the module itself, which we want here.

```suggestion
.. type:: [NAME_REDACTED] type for ``formatters`` in :meth:`DataFrame.to_string <pandas.DataFrame.to_string>` and :meth:`Series.to_string <pandas.Series.to_string>`
```

[USER_REDACTED] I am using table syntax and got the errors all the times it means [USER_REDACTED]  not assuming
rST is not good choice we should use list approach

```suggestion
     - Argument type for ``engine`` in :meth:`pandas.read_csv`
```
This was just wrong in previous version

```suggestion
     - Argument type for ``compression`` in all I/O output methods except :meth:`pandas.DataFrame.to_parquet`
```

```suggestion
     - Type of functions that can be passed to :meth:`DataFrame's <pandas.DataFrame.agg>`, :meth:`Series' <pandas.Series.agg>`, and :meth:`DataFrameGroupBy's <pandas.api.typing.DataFrameGroupBy.aggregate>` aggregate methods
```

```suggestion
     - Argument type for ``keep`` in :meth:`DataFrame's <pandas.DataFrame.nlargest>` and :meth:`Series' <pandas.Series.nlargest>` ``nlargest``, :meth:`DataFrame's <pandas.DataFrame.nsmallest>` and :meth:`Series' <pandas.Series.nsmallest>` ``nsmallest``, and :meth:`SeriesGroupBy's <pandas.api.typing.SeriesGroupBy.nlargest>` ``nlargest`` methods
```

```suggestion
     - Argument type for ``offset`` in various methods, such as :meth:`DataFrame's <pandas.DataFrame.resample>` and :meth:`Series' <pandas.Series.resample>` ``resample()``, ``halflife`` in :meth:`DataFrame's <pandas.DataFrame.ewm>`, :meth:`DataFrameGroupBy's <pandas.api.typing.DataFrameGroupBy.ewm>`, and :meth:`Series' <pandas.Series.ewm>` ``ewm()``, and ``start`` and ``end`` in :meth:`pandas.timedelta_range`
```

fix links to `Series` and `Index` here

create links to `Index.dropna()`, `Series.dropna()` and `DataFrame.dropna()`

create links to `Series.astype()`, `DataFrame.astype()`, and `Index.astype()`

create link to `pandas.read_json()`

create links to `DataFrame.merge()` and `pandas.merge()`

create references to `DataFrame.to_hdf()` and `Series.to_hdf()` and  `DataFrame.to_csv()` and `Series.to_csv()`

fix links here

create links to `DataFrame.reindex()`, `Series.reindex()` and `Index.reindex()`

fix link

fix link

fix links - there are multiple classes with these methods

fix link

fix links.  There are multiple `resample` methods

`DataFrame.to_excel()`, `Series.to_excel()`, and `pandas.io.formats.style.Styler.to_excel()`

fixed

Thanks for clarification [USER_REDACTED]
Fixed

Thanks for Pointing me out [USER_REDACTED]
Fixed

```suggestion
     - Argument type for ``errors`` in :meth:`DataFrame's <pandas.DataFrame.to_hdf>`, :meth:`Series' <pandas.Series.to_hdf>` ``to_hdf()`` methods, and :meth:`DataFrame's <pandas.DataFrame.to_csv>` and :meth:`Series' <pandas.Series.to_csv>` ``to_csv()`` methods
```

```suggestion
     - Argument type for ``suffixes`` in :meth:`pandas.merge`, :meth:`pandas.merge_ordered`,  and :meth:`DataFrame's <pandas.DataFrame.compare>` and :meth:`Series' <pandas.Series.compare>` ``compare()`` methods
```

```suggestion
     - Argument type for ``origin`` in :meth:`DataFrame's <pandas.DataFrame.resample>`, :meth:`Series' <pandas.Series.resample>` ``resample()`` methods and for :class:`~pandas.Grouper`
```

```suggestion
     - Type of functions that can be passed to :meth:`DataFrame's <pandas.DataFrame.agg>`, :meth:`Series' <pandas.Series.agg>`, and :meth:`DataFrameGroupBy's <pandas.api.typing.DataFrameGroupBy.aggregate>` ``aggregate()`` methods
```

```suggestion
     - Argument type for ``join`` in :meth:`DataFrame's <pandas.DataFrame.align>` and :meth:`Series' <pandas.Series.align>` ``align()`` methods
```

```suggestion
     - Argument type for ``how`` in :meth:`DataFrame's <pandas.DataFrame.dropna>` and :meth:`Series' <pandas.Series.dropna>` ``dropna()`` methods
```

```suggestion
     - Argument type in :meth:`DataFrame's <pandas.DataFrame.astype>` and :meth:`Series' <pandas.Series.astype>` ``astype()`` methods
```

```suggestion
     - Argument type for ``correlation`` in :meth:`DataFrame's <pandas.DataFrame.corr>` and :meth:`Series' <pandas.Series.corr>` ``corr()`` methods
```

```suggestion
     - Argument type for ``keep`` in :meth:`DataFrame's <pandas.DataFrame.drop_duplicates>` and :meth:`Series' <pandas.Series.drop_duplicates>` ``drop_duplicates()`` methods
```

```suggestion
     - Argument type for ``merge_cells`` in :meth:`DataFrame's <pandas.DataFrame.to_excel>` and :meth:`Series' <pandas.Series.to_excel>` ``to_excel()`` methods
```

```suggestion
     - Argument type for ``float_format`` in :meth:`DataFrame's <pandas.DataFrame.to_string>` and :meth:`Series' <pandas.Series.to_string>` ``to_string()`` methods
```

```suggestion
     - Argument type for ``formatters`` in :meth:`DataFrame's <pandas.DataFrame.to_string>` and :meth:`Series' <pandas.Series.to_string>` ``to_string()`` methods
```

```suggestion
     - Argument type for ``orient`` in :meth:`DataFrame.from_dict() <pandas.DataFrame.from_dict>`
```

```suggestion
     - Argument type for ``interpolate`` in :meth:`DataFrame's <pandas.DataFrame.interpolate>` and :meth:`Series' <pandas.Series.interpolate>` ``interpolate()`` methods
```

```suggestion
     - Argument type for the return type of a callable for argument ``default_handler`` in :meth:`DataFrame's <pandas.DataFrame.to_json>` and :meth:`Series' <pandas.Series.to_json>` ``to_json()`` methods
```

```suggestion
     - Argument type for ``validate`` in :meth:`DataFrame.join() <pandas.DataFrame.join>`
```

```suggestion
     - Argument type for ``na_position`` in :meth:`DataFrame's <pandas.DataFrame.sort_values>` and :meth:`Series' <pandas.Series.sort_values>` ``sort_values()`` methods
```

```suggestion
     - Argument type for ``keep`` in :meth:`DataFrame's <pandas.DataFrame.nlargest>` and :meth:`Series' <pandas.Series.nlargest>` ``nlargest()``, :meth:`DataFrame's <pandas.DataFrame.nsmallest>` and :meth:`Series' <pandas.Series.nsmallest>` ``nsmallest()``, and :meth:`SeriesGroupBy's <pandas.api.typing.SeriesGroupBy.nlargest>` ``nlargest()`` methods
```

```suggestion
     - Argument type for ``compression`` in :meth:`DataFrame.to_parquet() <pandas.DataFrame.to_parquet>`
```

```suggestion
     - Argument type for ``interpolation`` in :meth:`DataFrame's <pandas.DataFrame.quantile>` and :meth:`Series' <pandas.Series.quantile>` ``quantile()`` methods
```

```suggestion
     - Argument type for ``reindex`` in :meth:`DataFrame's <pandas.DataFrame.reindex>` and :meth:`Series' <pandas.Series.reindex>` ``reindex()`` methods
```

```suggestion
     - Argument types for ``start`` and ``end`` in :meth:`Index.slice_locs() <pandas.Index.slice_locs>`
```

```suggestion
     - Argument type for ``kind`` in :meth:`DataFrame's <pandas.DataFrame.sort_values>` and :meth:`Series' <pandas.Series.sort_values>` ``sort_values()`` methods
```

```suggestion
     - Argument type for ``indexer`` and ``indices`` in :meth:`DataFrame's <pandas.DataFrame.take>` and :meth:`Series' <pandas.Series.take>` ``take()`` methods
```

```suggestion
     - Argument type for ``byteorder`` in :meth:`DataFrame.to_stata() <pandas.DataFrame.to_stata>`
```

```suggestion
     - Argument type for ``how`` in :meth:`DataFrame's <pandas.DataFrame.to_timestamp>` and :meth:`Series' <pandas.Series.to_timestamp>` ``to_timestamp()`` methods, and ``convention`` in :meth:`DataFrame's <pandas.DataFrame.resample>` and :meth:`Series' <pandas.Series.resample>` ``resample()`` methods
```

```suggestion
     - Argument type for ``join`` in :meth:`DataFrame.update() <pandas.DataFrame.update>`
```

```suggestion
     - Argument type for ``method`` in :meth:`Rolling's <pandas.api.typing.Rolling.rank>` and :meth:`Expanding <pandas.api.typing.Expanding.rank>` ``rank()`` methods, applicable in rolling and expanding window operations
```

```suggestion
     - Additional argument type corresponding to buffers for :meth:`DataFrame's <pandas.DataFrame.to_excel>`, :meth:`Series' <pandas.Series.to_excel>`  and :meth:`Styler's <pandas.io.formats.style.Styler.to_excel>` ``to_excel()`` methods
```

```suggestion
     - Argument type for ``parser`` in :meth:`DataFrame.to_xml() <pandas.DataFrame.to_xml>` and :meth:`pandas.read_xml`
```

```suggestion
     - Argument type for ``how`` in :meth:`pandas.merge_ordered` and for ``join`` in :meth:`Series.align() <pandas.Series.align>`
```

You didn't make all the changes suggested here.

you didn't make all the changes here

```suggestion
     - Time unit argument and return type for :attr:`pandas.Timedelta.unit`, arguments ``unit`` and ``date_unit``
```

```suggestion
     - Argument type for ``method`` in :meth:`Rolling's <pandas.api.typing.Rolling.rank>` and :meth:`Expanding's <pandas.api.typing.Expanding.rank>` ``rank()`` methods, applicable in rolling and expanding window operations
```

you  have so sharp eyes chief [USER_REDACTED]

Oh sorry chief [USER_REDACTED]


BUG: NotImplementedError: eq not implemented for <class 'range'> when comparing columns and range in pandas 3.0.0rc0 with pyarrow

- [x] closes #63429
- [x] [All test cases passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#writing-tests) if fixing a bug or adding a new feature
- [ ] All [code checks passed](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#pre-commit).
- [x] Added [type annotations](https://pandas.pydata.org/pandas-docs/dev/development/contributing_codebase.html#type-hints) to new arguments/methods/functions.
- [x] Added an entry in the latest `doc/source/whatsnew/vX.X.X.rst` file if fixing a bug or adding a new feature.
- [x] If I used AI to develop this pull request, I prompted it to follow `AGENTS.md`.

I know I'll probably have to update the whatsnew to be 3.0.1.rst instead, but it doesn't exist just yet.

Defaulted to non-pyarrow behavior for range class

@@ -13,6 +13,7 @@ including other versions of pandas.

 Bug fixes
 ^^^^^^^^^
+- Fixed a bug for comparison operators between :py:class:`range` and objects with :class:`StringDtype` with ``storage="pyarrow"`` (:issue:`63429`)
 - Fixed a bug in the :class:`DataFrame` constructor when passed a :class:`Series` or
   :class:`Index` correctly handling Copy-on-Write (:issue:`63899`)

@@ -906,7 +906,7 @@ def _cmp_method(self, other, op) -> ArrowExtensionArray:
         pc_func = ARROW_CMP_FUNCS[op.__name__]
         ltype = self._pa_array.type

-        if isinstance(other, (ExtensionArray, np.ndarray, list)):
+        if isinstance(other, (ExtensionArray, np.ndarray, list, range)):
             try:
                 boxed = self._box_pa(other)
             except pa.lib.ArrowInvalid:
@@ -915,8 +915,10 @@ def _cmp_method(self, other, op) -> ArrowExtensionArray:
                 result = pa.array(res_values, type=pa.bool_(), from_pandas=True)
             else:
                 rtype = boxed.type
-                if (pa.types.is_timestamp(ltype) and pa.types.is_date(rtype)) or (
-                    pa.types.is_timestamp(rtype) and pa.types.is_date(ltype)
+                if (
+                    (pa.types.is_timestamp(ltype) and pa.types.is_date(rtype))
+                    or (pa.types.is_timestamp(rtype) and pa.types.is_date(ltype))
+                    or isinstance(other, range)
                 ):
                     # GH#62157 match non-pyarrow behavior
                     result = ops.invalid_comparison(self, other, op)

@@ -25,7 +25,7 @@

 def invalid_comparison(
     left: ArrayLike,
-    right: ArrayLike | list | Scalar,
+    right: ArrayLike | list | range | Scalar,
     op: Callable[[Any, Any], bool],
 ) -> npt.NDArray[np.bool_]:
     """

@@ -296,6 +296,12 @@ def test_compare_scalar(self, data, comparison_op):
         ser = pd.Series(data)
         self._compare_other(ser, data, comparison_op, data[0])

+    def test_compare_range_len(self, data, comparison_op):
+        # GH#63429
+        ser = pd.Series(data)
+        range_test = range(len(ser))
+        self._compare_other(ser, range_test, comparison_op, range_test)
+
     [USER_REDACTED].mark.parametrize("na_action", [None, "ignore"])
     def test_map(self, data_missing, na_action, using_nan_is_na):
         if data_missing.dtype.kind in "mM":

Is there a specific readability reason to wrap the last two conditions in parentheses?

Can we keep this removed blank line?

Could you move this to `v3.0.1.rst`?

```suggestion
- Fixed a bug for comparison operators between :py:class:`range` and objects with :class:`StringDtype` with `storage="pyarrow"` (:issue:`63429`)
```