diff --git a/doc/source/reference/testing.rst b/doc/source/reference/testing.rst index f91db4600daec..f636ee1806c9b 100644 --- a/doc/source/reference/testing.rst +++ b/doc/source/reference/testing.rst @@ -41,6 +41,7 @@ Exceptions and warnings errors.ParserError errors.ParserWarning errors.PerformanceWarning + errors.SpecificationError errors.UnsortedIndexError errors.UnsupportedFunctionCall diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index e9e13c3ed5bbe..cb93012ab504c 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -151,7 +151,7 @@ Other enhancements - A :class:`errors.PerformanceWarning` is now thrown when using ``string[pyarrow]`` dtype with methods that don't dispatch to ``pyarrow.compute`` methods (:issue:`42613`) - Added ``numeric_only`` argument to :meth:`Resampler.sum`, :meth:`Resampler.prod`, :meth:`Resampler.min`, :meth:`Resampler.max`, :meth:`Resampler.first`, and :meth:`Resampler.last` (:issue:`46442`) - ``times`` argument in :class:`.ExponentialMovingWindow` now accepts ``np.timedelta64`` (:issue:`47003`) -- :class:`DataError` now exposed in ``pandas.errors`` (:issue:`27656`) +- :class:`DataError` and :class:`SpecificationError` are now exposed in ``pandas.errors`` (:issue:`27656`) .. --------------------------------------------------------------------------- .. _whatsnew_150.notable_bug_fixes: diff --git a/pandas/core/apply.py b/pandas/core/apply.py index dbb119de99765..6f42a47fd9a07 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -33,7 +33,10 @@ Axis, NDFrameT, ) -from pandas.errors import DataError +from pandas.errors import ( + DataError, + SpecificationError, +) from pandas.util._decorators import cache_readonly from pandas.util._exceptions import find_stack_level @@ -51,10 +54,7 @@ ) from pandas.core.algorithms import safe_sort -from pandas.core.base import ( - SelectionMixin, - SpecificationError, -) +from pandas.core.base import SelectionMixin import pandas.core.common as com from pandas.core.construction import ( create_series_with_explicit_dtype, diff --git a/pandas/core/base.py b/pandas/core/base.py index 38cf41a626deb..d65b920a9cada 100644 --- a/pandas/core/base.py +++ b/pandas/core/base.py @@ -176,10 +176,6 @@ def __setattr__(self, key: str, value): object.__setattr__(self, key, value) -class SpecificationError(Exception): - pass - - class SelectionMixin(Generic[NDFrameT]): """ mixin implementing the selection & aggregation interface on a group-like diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index 2acf5c826eb57..ccfa8c1680341 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -37,6 +37,7 @@ Manager2D, SingleManager, ) +from pandas.errors import SpecificationError from pandas.util._decorators import ( Appender, Substitution, @@ -68,7 +69,6 @@ reconstruct_func, validate_func_kwargs, ) -from pandas.core.base import SpecificationError import pandas.core.common as com from pandas.core.construction import create_series_with_explicit_dtype from pandas.core.frame import DataFrame diff --git a/pandas/errors/__init__.py b/pandas/errors/__init__.py index daef5a38c33c2..a948f0f46e21a 100644 --- a/pandas/errors/__init__.py +++ b/pandas/errors/__init__.py @@ -244,3 +244,26 @@ class DataError(Exception): Or, it can be raised when trying to apply a function to a non-numerical column on a rolling window. """ + + +class SpecificationError(Exception): + """ + Exception raised in two scenarios. The first way is calling agg on a + Dataframe or Series using a nested renamer (dict-of-dict). + The second way is calling agg on a Dataframe with duplicated functions + names without assigning column name. + + Examples + -------- + >>> df = pd.DataFrame({'A': [1, 1, 1, 2, 2], + ... 'B': range(5), + ... 'C': range(5)}) + >>> df.groupby('A').B.agg({'foo': 'count'}) # doctest: +SKIP + ... # SpecificationError: nested renamer is not supported + + >>> df.groupby('A').agg({'B': {'foo': ['sum', 'max']}}) # doctest: +SKIP + ... # SpecificationError: nested renamer is not supported + + >>> df.groupby('A').agg(['min', 'min']) # doctest: +SKIP + ... # SpecificationError: nested renamer is not supported + """ diff --git a/pandas/tests/apply/test_invalid_arg.py b/pandas/tests/apply/test_invalid_arg.py index 6ad46ed209cf7..5a498aa7d6595 100644 --- a/pandas/tests/apply/test_invalid_arg.py +++ b/pandas/tests/apply/test_invalid_arg.py @@ -12,6 +12,8 @@ import numpy as np import pytest +from pandas.errors import SpecificationError + from pandas import ( Categorical, DataFrame, @@ -20,7 +22,6 @@ notna, ) import pandas._testing as tm -from pandas.core.base import SpecificationError @pytest.mark.parametrize("result_type", ["foo", 1]) diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py index 55aad403f1492..d52b6ceaf8990 100644 --- a/pandas/tests/groupby/aggregate/test_aggregate.py +++ b/pandas/tests/groupby/aggregate/test_aggregate.py @@ -9,6 +9,8 @@ import numpy as np import pytest +from pandas.errors import SpecificationError + from pandas.core.dtypes.common import is_integer_dtype import pandas as pd @@ -21,7 +23,6 @@ to_datetime, ) import pandas._testing as tm -from pandas.core.base import SpecificationError from pandas.core.groupby.grouper import Grouping diff --git a/pandas/tests/groupby/aggregate/test_other.py b/pandas/tests/groupby/aggregate/test_other.py index 06044ddd3f4b8..f84abecea37da 100644 --- a/pandas/tests/groupby/aggregate/test_other.py +++ b/pandas/tests/groupby/aggregate/test_other.py @@ -8,6 +8,8 @@ import numpy as np import pytest +from pandas.errors import SpecificationError + import pandas as pd from pandas import ( DataFrame, @@ -19,7 +21,6 @@ period_range, ) import pandas._testing as tm -from pandas.core.base import SpecificationError from pandas.io.formats.printing import pprint_thing diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index 61951292d55a8..97e616ef14cef 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -6,7 +6,10 @@ from pandas._libs import lib from pandas.compat import IS64 -from pandas.errors import PerformanceWarning +from pandas.errors import ( + PerformanceWarning, + SpecificationError, +) import pandas as pd from pandas import ( @@ -24,7 +27,6 @@ ) import pandas._testing as tm from pandas.core.arrays import BooleanArray -from pandas.core.base import SpecificationError import pandas.core.common as com from pandas.core.groupby.base import maybe_normalize_deprecated_kernels diff --git a/pandas/tests/resample/test_resample_api.py b/pandas/tests/resample/test_resample_api.py index 04b629d089925..36f67664a3260 100644 --- a/pandas/tests/resample/test_resample_api.py +++ b/pandas/tests/resample/test_resample_api.py @@ -427,7 +427,7 @@ def test_agg(): msg = "nested renamer is not supported" for t in cases: - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t.aggregate({"A": {"mean": "mean", "sum": "sum"}}) expected = pd.concat([a_mean, a_sum, b_mean, b_sum], axis=1) @@ -435,7 +435,7 @@ def test_agg(): [("A", "mean"), ("A", "sum"), ("B", "mean2"), ("B", "sum2")] ) for t in cases: - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t.aggregate( { "A": {"mean": "mean", "sum": "sum"}, @@ -539,10 +539,10 @@ def test_agg_misc(): # series like aggs for t in cases: - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t["A"].agg({"A": ["sum", "std"]}) - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t["A"].agg({"A": ["sum", "std"], "B": ["mean", "std"]}) # errors @@ -588,17 +588,17 @@ def test_agg_nested_dicts(): msg = "nested renamer is not supported" for t in cases: - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t.aggregate({"r1": {"A": ["mean", "sum"]}, "r2": {"B": ["mean", "sum"]}}) for t in cases: - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t[["A", "B"]].agg( {"A": {"ra": ["mean", "std"]}, "B": {"rb": ["mean", "std"]}} ) - with pytest.raises(pd.core.base.SpecificationError, match=msg): + with pytest.raises(pd.errors.SpecificationError, match=msg): t.agg({"A": {"ra": ["mean", "std"]}, "B": {"rb": ["mean", "std"]}}) diff --git a/pandas/tests/test_errors.py b/pandas/tests/test_errors.py index a1ce5b7a239fe..140297297fdbc 100644 --- a/pandas/tests/test_errors.py +++ b/pandas/tests/test_errors.py @@ -20,6 +20,7 @@ "OptionError", "NumbaUtilError", "DataError", + "SpecificationError", ], ) def test_exception_importable(exc): diff --git a/pandas/tests/window/test_api.py b/pandas/tests/window/test_api.py index 931e52e4efd18..2a8caa1d42d4d 100644 --- a/pandas/tests/window/test_api.py +++ b/pandas/tests/window/test_api.py @@ -1,6 +1,8 @@ import numpy as np import pytest +from pandas.errors import SpecificationError + from pandas import ( DataFrame, Index, @@ -13,7 +15,6 @@ timedelta_range, ) import pandas._testing as tm -from pandas.core.base import SpecificationError def test_getitem(step):