diff --git a/doc/source/whatsnew/v1.0.4.rst b/doc/source/whatsnew/v1.0.4.rst index 95007f4dd1caf..4020dfc50150a 100644 --- a/doc/source/whatsnew/v1.0.4.rst +++ b/doc/source/whatsnew/v1.0.4.rst @@ -26,6 +26,7 @@ Fixed regressions - Fix regression in :meth:`DataFrame.describe` raising ``TypeError: unhashable type: 'dict'`` (:issue:`32409`) - Bug in :meth:`DataFrame.replace` casts columns to ``object`` dtype if items in ``to_replace`` not in values (:issue:`32988`) - Bug in :meth:`GroupBy.rolling.apply` ignores args and kwargs parameters (:issue:`33433`) +- More informative error message with ``np.min`` or ``np.max`` on unordered :class:`Categorical` (:issue:`33115`) - .. _whatsnew_104.bug_fixes: diff --git a/pandas/compat/numpy/function.py b/pandas/compat/numpy/function.py index 7158f251ad805..9cae127a0a549 100644 --- a/pandas/compat/numpy/function.py +++ b/pandas/compat/numpy/function.py @@ -209,7 +209,7 @@ def validate_cum_func_with_skipna(skipna, args, kwargs, name): LOGICAL_FUNC_DEFAULTS = dict(out=None, keepdims=False) validate_logical_func = CompatValidator(LOGICAL_FUNC_DEFAULTS, method="kwargs") -MINMAX_DEFAULTS = dict(out=None, keepdims=False) +MINMAX_DEFAULTS = dict(axis=None, out=None, keepdims=False) validate_min = CompatValidator( MINMAX_DEFAULTS, fname="min", method="both", max_fname_arg_count=1 ) diff --git a/pandas/core/arrays/categorical.py b/pandas/core/arrays/categorical.py index f7e2ebdc8a17a..c7f99866bdfc0 100644 --- a/pandas/core/arrays/categorical.py +++ b/pandas/core/arrays/categorical.py @@ -2125,7 +2125,7 @@ def _reduce(self, name, axis=0, **kwargs): return func(**kwargs) @deprecate_kwarg(old_arg_name="numeric_only", new_arg_name="skipna") - def min(self, skipna=True): + def min(self, skipna=True, **kwargs): """ The minimum value of the object. @@ -2144,6 +2144,7 @@ def min(self, skipna=True): ------- min : the minimum of this `Categorical` """ + nv.validate_min((), kwargs) self.check_for_ordered("min") if not len(self._codes): @@ -2160,7 +2161,7 @@ def min(self, skipna=True): return self.categories[pointer] @deprecate_kwarg(old_arg_name="numeric_only", new_arg_name="skipna") - def max(self, skipna=True): + def max(self, skipna=True, **kwargs): """ The maximum value of the object. @@ -2179,6 +2180,7 @@ def max(self, skipna=True): ------- max : the maximum of this `Categorical` """ + nv.validate_max((), kwargs) self.check_for_ordered("max") if not len(self._codes): diff --git a/pandas/tests/arrays/categorical/test_analytics.py b/pandas/tests/arrays/categorical/test_analytics.py index 6af3d18fa24ff..1fca0fa5c3f2b 100644 --- a/pandas/tests/arrays/categorical/test_analytics.py +++ b/pandas/tests/arrays/categorical/test_analytics.py @@ -1,3 +1,4 @@ +import re import sys import numpy as np @@ -105,6 +106,37 @@ def test_deprecate_numeric_only_min_max(self, method): with tm.assert_produces_warning(expected_warning=FutureWarning): getattr(cat, method)(numeric_only=True) + @pytest.mark.parametrize("method", ["min", "max"]) + def test_numpy_min_max_raises(self, method): + cat = Categorical(["a", "b", "c", "b"], ordered=False) + msg = ( + f"Categorical is not ordered for operation {method}\n" + "you can use .as_ordered() to change the Categorical to an ordered one" + ) + method = getattr(np, method) + with pytest.raises(TypeError, match=re.escape(msg)): + method(cat) + + @pytest.mark.parametrize("kwarg", ["axis", "out", "keepdims"]) + @pytest.mark.parametrize("method", ["min", "max"]) + def test_numpy_min_max_unsupported_kwargs_raises(self, method, kwarg): + cat = Categorical(["a", "b", "c", "b"], ordered=True) + msg = ( + f"the '{kwarg}' parameter is not supported in the pandas implementation " + f"of {method}" + ) + kwargs = {kwarg: 42} + method = getattr(np, method) + with pytest.raises(ValueError, match=msg): + method(cat, **kwargs) + + @pytest.mark.parametrize("method, expected", [("min", "a"), ("max", "c")]) + def test_numpy_min_max_axis_equals_none(self, method, expected): + cat = Categorical(["a", "b", "c", "b"], ordered=True) + method = getattr(np, method) + result = method(cat, axis=None) + assert result == expected + @pytest.mark.parametrize( "values,categories,exp_mode", [