diff --git a/doc/source/whatsnew/v1.3.0.rst b/doc/source/whatsnew/v1.3.0.rst index 388c5dbf6a7ee..d6dba9497054f 100644 --- a/doc/source/whatsnew/v1.3.0.rst +++ b/doc/source/whatsnew/v1.3.0.rst @@ -434,6 +434,8 @@ Groupby/resample/rolling - Bug in :meth:`Series.resample` would raise when the index was a :class:`PeriodIndex` consisting of ``NaT`` (:issue:`39227`) - Bug in :meth:`core.window.rolling.RollingGroupby.corr` and :meth:`core.window.expanding.ExpandingGroupby.corr` where the groupby column would return 0 instead of ``np.nan`` when providing ``other`` that was longer than each group (:issue:`39591`) - Bug in :meth:`core.window.expanding.ExpandingGroupby.corr` and :meth:`core.window.expanding.ExpandingGroupby.cov` where 1 would be returned instead of ``np.nan`` when providing ``other`` that was longer than each group (:issue:`39591`) +- Bug in :meth:`.GroupBy.mean`, :meth:`.GroupBy.median` and :meth:`DataFrame.pivot_table` not propagating metadata (:issue:`28283`) +- Reshaping ^^^^^^^^^ diff --git a/pandas/core/groupby/groupby.py b/pandas/core/groupby/groupby.py index bc277bf67614d..e939c184d501a 100644 --- a/pandas/core/groupby/groupby.py +++ b/pandas/core/groupby/groupby.py @@ -1546,11 +1546,12 @@ def mean(self, numeric_only: bool = True): 2 4.0 Name: B, dtype: float64 """ - return self._cython_agg_general( + result = self._cython_agg_general( "mean", alt=lambda x, axis: Series(x).mean(numeric_only=numeric_only), numeric_only=numeric_only, ) + return result.__finalize__(self.obj, method="groupby") @final @Substitution(name="groupby") @@ -1572,11 +1573,12 @@ def median(self, numeric_only=True): Series or DataFrame Median of values within each group. """ - return self._cython_agg_general( + result = self._cython_agg_general( "median", alt=lambda x, axis: Series(x).median(axis=axis, numeric_only=numeric_only), numeric_only=numeric_only, ) + return result.__finalize__(self.obj, method="groupby") @final @Substitution(name="groupby") diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py index 778e37bc07eb5..8feb379a82ada 100644 --- a/pandas/core/reshape/pivot.py +++ b/pandas/core/reshape/pivot.py @@ -17,6 +17,9 @@ import numpy as np from pandas._typing import ( + AggFuncType, + AggFuncTypeBase, + AggFuncTypeDict, FrameOrSeriesUnion, IndexLabel, ) @@ -57,11 +60,11 @@ @Substitution("\ndata : DataFrame") @Appender(_shared_docs["pivot_table"], indents=1) def pivot_table( - data, + data: DataFrame, values=None, index=None, columns=None, - aggfunc="mean", + aggfunc: AggFuncType = "mean", fill_value=None, margins=False, dropna=True, @@ -75,7 +78,7 @@ def pivot_table( pieces: List[DataFrame] = [] keys = [] for func in aggfunc: - table = pivot_table( + _table = __internal_pivot_table( data, values=values, index=index, @@ -87,11 +90,42 @@ def pivot_table( margins_name=margins_name, observed=observed, ) - pieces.append(table) + pieces.append(_table) keys.append(getattr(func, "__name__", func)) - return concat(pieces, keys=keys, axis=1) + table = concat(pieces, keys=keys, axis=1) + return table.__finalize__(data, method="pivot_table") + + table = __internal_pivot_table( + data, + values, + index, + columns, + aggfunc, + fill_value, + margins, + dropna, + margins_name, + observed, + ) + return table.__finalize__(data, method="pivot_table") + +def __internal_pivot_table( + data: DataFrame, + values, + index, + columns, + aggfunc: Union[AggFuncTypeBase, AggFuncTypeDict], + fill_value, + margins: bool, + dropna: bool, + margins_name: str, + observed: bool, +) -> DataFrame: + """ + Helper of :func:`pandas.pivot_table` for any non-list ``aggfunc``. + """ keys = index + columns values_passed = values is not None diff --git a/pandas/tests/generic/test_finalize.py b/pandas/tests/generic/test_finalize.py index 73a68e8508644..15c51e5f3e6e4 100644 --- a/pandas/tests/generic/test_finalize.py +++ b/pandas/tests/generic/test_finalize.py @@ -149,13 +149,15 @@ marks=not_implemented_mark, ), (pd.DataFrame, frame_data, operator.methodcaller("pivot", columns="A")), - pytest.param( - ( - pd.DataFrame, - {"A": [1], "B": [1]}, - operator.methodcaller("pivot_table", columns="A"), - ), - marks=not_implemented_mark, + ( + pd.DataFrame, + ({"A": [1], "B": [1]},), + operator.methodcaller("pivot_table", columns="A"), + ), + ( + pd.DataFrame, + ({"A": [1], "B": [1]},), + operator.methodcaller("pivot_table", columns="A", aggfunc=["mean", "sum"]), ), (pd.DataFrame, frame_data, operator.methodcaller("stack")), pytest.param( @@ -740,6 +742,8 @@ def test_categorical_accessor(method): [ operator.methodcaller("sum"), lambda x: x.agg("sum"), + lambda x: x.agg("mean"), + lambda x: x.agg("median"), ], ) def test_groupby_finalize(obj, method): @@ -757,6 +761,12 @@ def test_groupby_finalize(obj, method): lambda x: x.agg(["sum", "count"]), lambda x: x.transform(lambda y: y), lambda x: x.apply(lambda y: y), + lambda x: x.agg("std"), + lambda x: x.agg("var"), + lambda x: x.agg("sem"), + lambda x: x.agg("size"), + lambda x: x.agg("ohlc"), + lambda x: x.agg("describe"), ], ) @not_implemented_mark