Repository: pandas-dev/pandas (python)

Issue:
DOC: Clarify `df.describe()` behavior with Timestamp columns
### Pandas version checks

- [X] I have checked that the issue still exists on the latest versions of the docs on `main` [here](https://pandas.pydata.org/docs/dev/)


### Location of the documentation

https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.describe.html

### Documentation problem

The Notes section for `describe` states the following (emphasis mine):
> For object data (e.g. strings or timestamps), the result’s index will include `count`, `unique`, `top`, and `freq`. The `top` is the most common value. The `freq` is the most common value’s frequency. **Timestamps also include the `first` and `last` items.**

Since pandas 2.0 began treating Timestamps as numeric data, as far as I can tell, calling `describe` on a Series/DF with Timestamp data no longer yields the `first` or `last` rows. In fact, the example included in the documentation also has this behavior:
```python
>>> s = pd.Series([
...     np.datetime64("2000-01-01"),
...     np.datetime64("2010-01-01"),
...     np.datetime64("2010-01-01")
... ])
>>> s.describe()
count                      3
mean     2006-09-01 08:00:00
min      2000-01-01 00:00:00
25%      2004-12-31 12:00:00
50%      2010-01-01 00:00:00
75%      2010-01-01 00:00:00
max      2010-01-01 00:00:00
dtype: object
```

### Suggested fix for documentation

Assuming this behavior is intended: remove mention of the `first` and `last` columns, and of timestamps as object data.
> For object data (such as strings), the result’s index will include `count`, `unique`, `top`, and `freq`. The `top` is the most common value. The `freq` is the most common value’s frequency.

Pull request description:
DOC: Remove inconsistency with timestamp data in describe() method docs
- [x] closes #56918
- [ ] [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.


Patch:
diff --git a/ci/code_checks.sh b/ci/code_checks.sh
index da4d95f9ae..5e6c02eab5 100755
--- a/ci/code_checks.sh
+++ b/ci/code_checks.sh
@@ -80,10 +80,6 @@ if [[ -z "$CHECK" || "$CHECK" == "docstrings" ]]; then
         pandas.io.formats.style.Styler.to_latex \
         pandas.read_parquet \
         pandas.DataFrame.to_sql \
-        pandas.io.formats.style.Styler.map \
-        pandas.io.formats.style.Styler.apply_index \
-        pandas.io.formats.style.Styler.map_index \
-        pandas.io.formats.style.Styler.format \
     RET=$(($RET + $?)) ; echo $MSG "DONE"
 
 fi
diff --git a/doc/source/whatsnew/v2.3.0.rst b/doc/source/whatsnew/v2.3.0.rst
index 6c10710d6c..4ab55491c9 100644
--- a/doc/source/whatsnew/v2.3.0.rst
+++ b/doc/source/whatsnew/v2.3.0.rst
@@ -188,7 +188,7 @@ Plotting
 
 Groupby/resample/rolling
 ^^^^^^^^^^^^^^^^^^^^^^^^
--
+- Bug in :meth:`.DataFrameGroupBy.quantile` when ``interpolation="nearest"`` is inconsistent with :meth:`DataFrame.quantile` (:issue:`47942`)
 -
 
 Reshaping
diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx
index 8f4dfd9dcb..dc8f13a919 100644
--- a/pandas/_libs/groupby.pyx
+++ b/pandas/_libs/groupby.pyx
@@ -1286,7 +1286,9 @@ def group_quantile(
                     elif interp == INTERPOLATION_MIDPOINT:
                         out[i, k] = (val + next_val) / 2.0
                     elif interp == INTERPOLATION_NEAREST:
-                        if frac > .5 or (frac == .5 and q_val > .5):  # Always OK?
+                        if frac > .5 or (frac == .5 and idx % 2 == 1):
+                            # If quantile lies in the middle of two indexes,
+                            # take the even index, as np.quantile.
                             out[i, k] = next_val
                         else:
                             out[i, k] = val
diff --git a/pandas/core/generic.py b/pandas/core/generic.py
index 86c4a99ac0..db60e68ec6 100644
--- a/pandas/core/generic.py
+++ b/pandas/core/generic.py
@@ -11815,10 +11815,10 @@ class NDFrame(PandasObject, indexing.IndexingMixin):
         upper percentile is ``75``. The ``50`` percentile is the
         same as the median.
 
-        For object data (e.g. strings or timestamps), the result's index
+        For object data (e.g. strings), the result's index
         will include ``count``, ``unique``, ``top``, and ``freq``. The ``top``
         is the most common value. The ``freq`` is the most common value's
-        frequency. Timestamps also include the ``first`` and ``last`` items.
+        frequency.
 
         If multiple object values have the highest count, then the
         ``count`` and ``top`` results will be arbitrarily chosen from
diff --git a/pandas/io/formats/style.py b/pandas/io/formats/style.py
index 6f255fdfc8..7bbdfdfc6a 100644
--- a/pandas/io/formats/style.py
+++ b/pandas/io/formats/style.py
@@ -1880,9 +1880,9 @@ class Styler(StylerRenderer):
         func="take a Series and return a string array of the same length",
         input_note="the index as a Series, if an Index, or a level of a MultiIndex",
         output_note="an identically sized array of CSS styles as strings",
-        var="s",
-        ret='np.where(s == "B", "background-color: yellow;", "")',
-        ret2='["background-color: yellow;" if "x" in v else "" for v in s]',
+        var="label",
+        ret='np.where(label == "B", "background-color: yellow;", "")',
+        ret2='["background-color: yellow;" if "x" in v else "" for v in label]',
     )
     def apply_index(
         self,
@@ -1932,8 +1932,8 @@ class Styler(StylerRenderer):
         --------
         Basic usage to conditionally highlight values in the index.
 
-        >>> df = pd.DataFrame([[1,2], [3,4]], index=["A", "B"])
-        >>> def color_b(s):
+        >>> df = pd.DataFrame([[1, 2], [3, 4]], index=["A", "B"])
+        >>> def color_b({var}):
         ...     return {ret}
         >>> df.style.{this}_index(color_b)  # doctest: +SKIP
 
@@ -1945,8 +1945,8 @@ class Styler(StylerRenderer):
         >>> df = pd.DataFrame([np.arange(8)], columns=midx)
         >>> def highlight_x({var}):
         ...     return {ret2}
-        >>> df.style.{this}_index(highlight_x, axis="columns", level=[0, 2])
-        ...  # doctest: +SKIP
+        >>> df.style.{this}_index(
+        ...     highlight_x, axis="columns", level=[0, 2])  # doctest: +SKIP
 
         .. figure:: ../../_static/style/appmaphead2.png
         """
@@ -1968,9 +1968,9 @@ class Styler(StylerRenderer):
         func="take a scalar and return a string",
         input_note="an index value, if an Index, or a level value of a MultiIndex",
         output_note="CSS styles as a string",
-        var="v",
-        ret='"background-color: yellow;" if v == "B" else None',
-        ret2='"background-color: yellow;" if "x" in v else None',
+        var="label",
+        ret='"background-color: yellow;" if label == "B" else None',
+        ret2='"background-color: yellow;" if "x" in label else None',
     )
     def map_index(
         self,
@@ -2073,17 +2073,16 @@ class Styler(StylerRenderer):
 
         Using ``subset`` to restrict application to a single column or multiple columns
 
-        >>> df.style.map(color_negative, color='red', subset="A")
-        ...  # doctest: +SKIP
-        >>> df.style.map(color_negative, color='red', subset=["A", "B"])
-        ...  # doctest: +SKIP
+        >>> df.style.map(color_negative, color='red', subset="A")  # doctest: +SKIP
+        >>> df.style.map(color_negative,
+        ...              color='red', subset=["A", "B"])  # doctest: +SKIP
 
         Using a 2d input to ``subset`` to select rows in addition to columns
 
         >>> df.style.map(color_negative, color='red',
-        ...  subset=([0,1,2], slice(None)))  # doctest: +SKIP
-        >>> df.style.map(color_negative, color='red', subset=(slice(0,5,2), "A"))
-        ...  # doctest: +SKIP
+        ...              subset=([0, 1, 2], slice(None)))  # doctest: +SKIP
+        >>> df.style.map(color_negative,
+        ...              color='red', subset=(slice(0, 5, 2), "A"))  # doctest: +SKIP
 
         See `Table Visualization <../../user_guide/style.ipynb>`_ user guide for
         more details.
diff --git a/pandas/io/formats/style_render.py b/pandas/io/formats/style_render.py
index 622e047b9f..38afde1eb7 100644
--- a/pandas/io/formats/style_render.py
+++ b/pandas/io/formats/style_render.py
@@ -1063,15 +1063,15 @@ class StylerRenderer:
 
         Using a ``formatter`` specification on consistent column dtypes
 
-        >>> df.style.format('{:.2f}', na_rep='MISS', subset=[0,1])  # doctest: +SKIP
+        >>> df.style.format('{:.2f}', na_rep='MISS', subset=[0, 1])  # doctest: +SKIP
                 0      1          2
         0    MISS   1.00          A
         1    2.00   MISS   3.000000
 
         Using the default ``formatter`` for unspecified columns
 
-        >>> df.style.format({0: '{:.2f}', 1: '£ {:.1f}'}, na_rep='MISS', precision=1)
-        ...  # doctest: +SKIP
+        >>> df.style.format({0: '{:.2f}', 1: '£ {:.1f}'},
+        ...                 na_rep='MISS', precision=1)  # doctest: +SKIP
                  0      1     2
         0    MISS   £ 1.0     A
         1    2.00    MISS   3.0
@@ -1079,8 +1079,8 @@ class StylerRenderer:
         Multiple ``na_rep`` or ``precision`` specifications under the default
         ``formatter``.
 
-        >>> (df.style.format(na_rep='MISS', precision=1, subset=[0])
-        ...     .format(na_rep='PASS', precision=2, subset=[1, 2]))  # doctest: +SKIP
+        >>> (df.style.format(na_rep='MISS', precision=1, subset=[0]).format(
+        ...     na_rep='PASS', precision=2, subset=[1, 2]))  # doctest: +SKIP
                 0      1      2
         0    MISS   1.00      A
         1     2.0   PASS   3.00
@@ -1088,8 +1088,8 @@ class StylerRenderer:
         Using a callable ``formatter`` function.
 
         >>> func = lambda s: 'STRING' if isinstance(s, str) else 'FLOAT'
-        >>> df.style.format({0: '{:.1f}', 2: func}, precision=4, na_rep='MISS')
-        ...  # doctest: +SKIP
+        >>> df.style.format({0: '{:.1f}', 2: func},
+        ...                 precision=4, na_rep='MISS')  # doctest: +SKIP
                 0        1        2
         0    MISS   1.0000   STRING
         1     2.0     MISS    FLOAT
@@ -1098,8 +1098,7 @@ class StylerRenderer:
 
         >>> df = pd.DataFrame([['<div></div>', '"A&B"', None]])
         >>> s = df.style.format(
-        ...     '<a href="a.com/{0}">{0}</a>', escape="html", na_rep="NA"
-        ...     )
+        ...     '<a href="a.com/{0}">{0}</a>', escape="html", na_rep="NA")
         >>> s.to_html()  # doctest: +SKIP
         ...
         <td .. ><a href="a.com/&lt;div&gt;&lt;/div&gt;">&lt;div&gt;&lt;/div&gt;</a></td>
@@ -1110,8 +1109,8 @@ class StylerRenderer:
         Using a ``formatter`` with ``escape`` in 'latex' mode.
 
         >>> df = pd.DataFrame([["123"], ["~ ^"], ["$%#"]])
-        >>> df.style.format("\\textbf{{{}}}", escape="latex").to_latex()
-        ...  # doctest: +SKIP
+        >>> df.style.format("\\textbf{{{}}}",
+        ...                 escape="latex").to_latex()  # doctest: +SKIP
         \begin{tabular}{ll}
          & 0 \\
         0 & \textbf{123} \\
@@ -1122,10 +1121,10 @@ class StylerRenderer:
         Applying ``escape`` in 'latex-math' mode. In the example below
         we enter math mode using the character ``$``.
 
-        >>> df = pd.DataFrame([[r"$\sum_{i=1}^{10} a_i$ a~b $\alpha \
-        ...     = \frac{\beta}{\zeta^2}$"], ["%#^ $ \$x^2 $"]])
-        >>> df.style.format(escape="latex-math").to_latex()
-        ...  # doctest: +SKIP
+        >>> df = pd.DataFrame([
+        ...     [r"$\sum_{i=1}^{10} a_i$ a~b $\alpha = \frac{\beta}{\zeta^2}$"],
+        ...     [r"%#^ $ \$x^2 $"]])
+        >>> df.style.format(escape="latex-math").to_latex()  # doctest: +SKIP
         \begin{tabular}{ll}
          & 0 \\
         0 & $\sum_{i=1}^{10} a_i$ a\textasciitilde b $\alpha = \frac{\beta}{\zeta^2}$ \\
@@ -1135,10 +1134,10 @@ class StylerRenderer:
         We can use the character ``\(`` to enter math mode and the character ``\)``
         to close math mode.
 
-        >>> df = pd.DataFrame([[r"\(\sum_{i=1}^{10} a_i\) a~b \(\alpha \
-        ...     = \frac{\beta}{\zeta^2}\)"], ["%#^ \( \$x^2 \)"]])
-        >>> df.style.format(escape="latex-math").to_latex()
-        ...  # doctest: +SKIP
+        >>> df = pd.DataFrame([
+        ...     [r"\(\sum_{i=1}^{10} a_i\) a~b \(\alpha = \frac{\beta}{\zeta^2}\)"],
+        ...     [r"%#^ \( \$x^2 \)"]])
+        >>> df.style.format(escape="latex-math").to_latex()  # doctest: +SKIP
         \begin{tabular}{ll}
          & 0 \\
         0 & \(\sum_{i=1}^{10} a_i\) a\textasciitilde b \(\alpha
@@ -1149,10 +1148,10 @@ class StylerRenderer:
         If we have in one DataFrame cell a combination of both shorthands
         for math formulas, the shorthand with the sign ``$`` will be applied.
 
-        >>> df = pd.DataFrame([[r"\( x^2 \)  $x^2$"], \
+        >>> df = pd.DataFrame([
+        ...     [r"\( x^2 \)  $x^2$"],
         ...     [r"$\frac{\beta}{\zeta}$ \(\frac{\beta}{\zeta}\)"]])
-        >>> df.style.format(escape="latex-math").to_latex()
-        ...  # doctest: +SKIP
+        >>> df.style.format(escape="latex-math").to_latex()  # doctest: +SKIP
         \begin{tabular}{ll}
          & 0 \\
         0 & \textbackslash ( x\textasciicircum 2 \textbackslash )  $x^2$ \\
@@ -1169,7 +1168,7 @@ class StylerRenderer:
         >>> df = pd.DataFrame({"A": [1, 0, -1]})
         >>> pseudo_css = "number-format: 0§[Red](0)§-§@;"
         >>> filename = "formatted_file.xlsx"
-        >>> df.style.map(lambda v: pseudo_css).to_excel(filename) # doctest: +SKIP
+        >>> df.style.map(lambda v: pseudo_css).to_excel(filename)  # doctest: +SKIP
 
         .. figure:: ../../_static/style/format_excel_css.png
         """
diff --git a/pandas/plotting/_core.py b/pandas/plotting/_core.py
index 9848ac00aa..0f15b2edc0 100644
--- a/pandas/plotting/_core.py
+++ b/pandas/plotting/_core.py
@@ -98,7 +98,7 @@ def hist_series(
 
     Returns
     -------
-    matplotlib.AxesSubplot
+    matplotlib.Axes
         A histogram plot.
 
     See Also
@@ -227,7 +227,7 @@ def hist_frame(
 
     Returns
     -------
-    matplotlib.AxesSubplot or numpy.ndarray of them
+    matplotlib.Axes or numpy.ndarray of them
 
     See Also
     --------
@@ -1374,7 +1374,7 @@ class PlotAccessor(PandasObject):
 
         Returns
         -------
-        class:`matplotlib.AxesSubplot`
+        class:`matplotlib.Axes`
             Return a histogram plot.
 
         See Also
@@ -1794,7 +1794,7 @@ class PlotAccessor(PandasObject):
 
         Returns
         -------
-        matplotlib.AxesSubplot
+        matplotlib.Axes
             The matplotlib ``Axes`` on which the hexbin is plotted.
 
         See Also


Test patch:
diff --git a/pandas/tests/groupby/methods/test_quantile.py b/pandas/tests/groupby/methods/test_quantile.py
index 361a8c27fb..1d2e639314 100644
--- a/pandas/tests/groupby/methods/test_quantile.py
+++ b/pandas/tests/groupby/methods/test_quantile.py
@@ -38,19 +38,7 @@ import pandas._testing as tm
     ],
 )
 @pytest.mark.parametrize("q", [0, 0.25, 0.5, 0.75, 1])
-def test_quantile(interpolation, a_vals, b_vals, q, request):
-    if (
-        interpolation == "nearest"
-        and q == 0.5
-        and isinstance(b_vals, list)
-        and b_vals == [4, 3, 2, 1]
-    ):
-        request.applymarker(
-            pytest.mark.xfail(
-                reason="Unclear numpy expectation for nearest "
-                "result with equidistant data"
-            )
-        )
+def test_quantile(interpolation, a_vals, b_vals, q):
     all_vals = pd.concat([pd.Series(a_vals), pd.Series(b_vals)])
 
     a_expected = pd.Series(a_vals).quantile(q, interpolation=interpolation)