diff --git a/doc/source/whatsnew/v2.0.0.rst b/doc/source/whatsnew/v2.0.0.rst index 1bfe9f50efbc8..f634e21132bed 100644 --- a/doc/source/whatsnew/v2.0.0.rst +++ b/doc/source/whatsnew/v2.0.0.rst @@ -781,6 +781,9 @@ Deprecations - :meth:`Index.is_interval` has been deprecated. Use :func:`pandas.api.types.is_intterval_dtype` instead (:issue:`50042`) - Deprecated ``all`` and ``any`` reductions with ``datetime64`` and :class:`DatetimeTZDtype` dtypes, use e.g. ``(obj != pd.Timestamp(0), tz=obj.tz).all()`` instead (:issue:`34479`) - Deprecated calling ``float`` or ``int`` on a single element :class:`Series` to return a ``float`` or ``int`` respectively. Extract the element before calling ``float`` or ``int`` instead (:issue:`51101`) +- Deprecated :meth:`Grouper.groups`, use :meth:`Groupby.groups` instead (:issue:`51182`) +- Deprecated :meth:`Grouper.grouper`, use :meth:`Groupby.grouper` instead (:issue:`51182`) +- .. --------------------------------------------------------------------------- .. _whatsnew_200.prior_deprecations: diff --git a/pandas/core/groupby/grouper.py b/pandas/core/groupby/grouper.py index d77ad59a4bb82..366be9e79004c 100644 --- a/pandas/core/groupby/grouper.py +++ b/pandas/core/groupby/grouper.py @@ -11,6 +11,7 @@ cast, final, ) +import warnings import numpy as np @@ -24,6 +25,7 @@ ) from pandas.errors import InvalidIndexError from pandas.util._decorators import cache_readonly +from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import ( is_categorical_dtype, @@ -268,7 +270,7 @@ def __init__( self.sort = sort self.dropna = dropna - self.grouper = None + self._grouper_deprecated = None self._gpr_index = None self.obj = None self.indexer = None @@ -308,6 +310,10 @@ def _get_grouper( validate=validate, dropna=self.dropna, ) + # Without setting this, subsequent lookups to .groups raise + # error: Incompatible types in assignment (expression has type "BaseGrouper", + # variable has type "None") + self._grouper_deprecated = grouper # type: ignore[assignment] return grouper, obj @@ -328,7 +334,7 @@ def _set_grouper(self, obj: NDFrame, sort: bool = False) -> None: if self.key is not None and self.level is not None: raise ValueError("The Grouper cannot specify both a key and a level!") - # Keep self.grouper value before overriding + # Keep self._grouper value before overriding if self._grouper is None: # TODO: What are we assuming about subsequent calls? self._grouper = self._gpr_index @@ -387,11 +393,28 @@ def _set_grouper(self, obj: NDFrame, sort: bool = False) -> None: self.obj = obj # type: ignore[assignment] self._gpr_index = ax + @final + @property + def grouper(self): + warnings.warn( + f"{type(self).__name__}.grouper is deprecated and will be removed " + "in a future version. Use GroupBy.grouper instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) + return self._grouper_deprecated + @final @property def groups(self): + warnings.warn( + f"{type(self).__name__}.groups is deprecated and will be removed " + "in a future version. Use GroupBy.groups instead.", + FutureWarning, + stacklevel=find_stack_level(), + ) # error: "None" has no attribute "groups" - return self.grouper.groups # type: ignore[attr-defined] + return self._grouper_deprecated.groups # type: ignore[attr-defined] @final def __repr__(self) -> str: diff --git a/pandas/tests/groupby/test_grouping.py b/pandas/tests/groupby/test_grouping.py index 30cfe638c8540..f7d4adc00260f 100644 --- a/pandas/tests/groupby/test_grouping.py +++ b/pandas/tests/groupby/test_grouping.py @@ -1049,3 +1049,20 @@ def test_grouping_by_key_is_in_axis(): result = gb.sum() expected = DataFrame({"b": [1, 2], "c": [7, 5]}) tm.assert_frame_equal(result, expected) + + +def test_grouper_groups(): + # GH#51182 check Grouper.groups does not raise AttributeError + df = DataFrame({"a": [1, 2, 3], "b": 1}) + grper = Grouper(key="a") + gb = df.groupby(grper) + + msg = "Use GroupBy.groups instead" + with tm.assert_produces_warning(FutureWarning, match=msg): + res = grper.groups + assert res is gb.groups + + msg = "Use GroupBy.grouper instead" + with tm.assert_produces_warning(FutureWarning, match=msg): + res = grper.grouper + assert res is gb.grouper