Skip to content

ENH: add groupby & reduce support to EA #22762

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Oct 12, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
make _reduce an always defined method on Base; raise TypeError if inv…
…oked and not overriden, test the same
  • Loading branch information
jreback committed Oct 11, 2018
commit 49efedfa3dfc19468a389f2ca6a04b49e1a9e420
42 changes: 23 additions & 19 deletions pandas/core/arrays/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,29 @@ def _ndarray_values(self):
"""
return np.array(self)

def _reduce(self, name, skipna=True, **kwargs):
"""Return a scalar result of performing the op

Parameters
----------
name : str
name of the function
axis : int, default 0
axis over which to apply, defined as 0 currently
skipna : bool, default True
if True, skip NaN values
kwargs : dict

Returns
-------
scalar

Raises
------
TypeError : subclass does not define reductions
"""
raise TypeError


class ExtensionOpsMixin(object):
"""
Expand Down Expand Up @@ -717,25 +740,6 @@ def _add_comparison_ops(cls):
cls.__le__ = cls._create_comparison_method(operator.le)
cls.__ge__ = cls._create_comparison_method(operator.ge)

def _reduce(self, name, skipna=True, **kwargs):
"""Return a scalar result of performing the op

Parameters
----------
name : str
name of the function
axis : int, default 0
axis over which to apply, defined as 0 currently
skipna : bool, default True
if True, skip NaN values
kwargs : dict

Returns
-------
scalar
"""
raise AbstractMethodError(self)


class ExtensionScalarOpsMixin(ExtensionOpsMixin):
"""A mixin for defining the arithmetic and logical operations on
Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/arrow/test_bool.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ def test_from_dtype(self, data):
pytest.skip("GH-22666")


class TestReduce(base.BaseNoReduceTests):
pass


def test_is_bool_dtype(data):
assert pd.api.types.is_bool_dtype(data)
assert pd.core.common.is_bool_indexer(data)
Expand Down
2 changes: 1 addition & 1 deletion pandas/tests/extension/base/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class TestMyDtype(BaseDtypeTests):
from .interface import BaseInterfaceTests # noqa
from .methods import BaseMethodsTests # noqa
from .ops import BaseArithmeticOpsTests, BaseComparisonOpsTests, BaseOpsUtil # noqa
from .reduce import BaseNumericReduceTests, BaseBooleanReduceTests # noqa
from .reduce import BaseNoReduceTests, BaseNumericReduceTests, BaseBooleanReduceTests # noqa
from .missing import BaseMissingTests # noqa
from .reshaping import BaseReshapingTests # noqa
from .setitem import BaseSetitemTests # noqa
20 changes: 20 additions & 0 deletions pandas/tests/extension/base/reduce.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,26 @@ def check_reduce(self, s, op_name, skipna):
tm.assert_almost_equal(result, expected)


class BaseNoReduceTests(BaseReduceTests):
""" we don't define any reductions """

@pytest.mark.parametrize('skipna', [True, False])
def test_reduce_series_numeric(self, data, all_numeric_reductions, skipna):
op_name = all_numeric_reductions
s = pd.Series(data)

with pytest.raises(TypeError):
getattr(s, op_name)(skipna=skipna)

@pytest.mark.parametrize('skipna', [True, False])
def test_reduce_series_boolean(self, data, all_boolean_reductions, skipna):
op_name = all_boolean_reductions
s = pd.Series(data)

with pytest.raises(TypeError):
getattr(s, op_name)(skipna=skipna)


class BaseNumericReduceTests(BaseReduceTests):

@pytest.mark.parametrize('skipna', [True, False])
Expand Down
9 changes: 5 additions & 4 deletions pandas/tests/extension/decimal/array.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,14 @@ def _concat_same_type(cls, to_concat):

def _reduce(self, name, axis=0, skipna=True, **kwargs):

# select the nan* ops
if skipna:
op = getattr(self.data, 'nan' + name)
raise NotImplementedError("decimal does not support skipna=True")

# don't skip nans
else:
try:
op = getattr(self.data, name)
except AttributeError:
raise NotImplementedError("decimal does not support "
"the {} operation".format(name))
return op(axis=axis)


Expand Down
22 changes: 22 additions & 0 deletions pandas/tests/extension/decimal/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,28 @@ class TestMissing(BaseDecimal, base.BaseMissingTests):
pass


class Reduce:

def check_reduce(self, s, op_name, skipna):

if skipna or op_name in ['median']:
with pytest.raises(NotImplementedError):
getattr(s, op_name)(skipna=skipna)

else:
result = getattr(s, op_name)(skipna=skipna)
expected = getattr(np.asarray(s), op_name)()
tm.assert_almost_equal(result, expected)


class TestNumericReduce(Reduce, base.BaseNumericReduceTests):
pass


class TestBooleanReduce(Reduce, base.BaseBooleanReduceTests):
pass


class TestMethods(BaseDecimal, base.BaseMethodsTests):
@pytest.mark.parametrize('dropna', [True, False])
@pytest.mark.xfail(reason="value_counts not implemented yet.")
Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/json/test_json.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ def test_fillna_frame(self):
reason="Dictionary order unstable")


class TestReduce(base.BaseNoReduceTests):
pass


class TestMethods(BaseJSON, base.BaseMethodsTests):
@unhashable
def test_value_counts(self, all_data, dropna):
Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/test_categorical.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ def test_fillna_limit_backfill(self):
pass


class TestReduce(base.BaseNoReduceTests):
pass


class TestMethods(base.BaseMethodsTests):
pass

Expand Down
4 changes: 4 additions & 0 deletions pandas/tests/extension/test_interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ class TestInterface(BaseInterval, base.BaseInterfaceTests):
pass


class TestReduce(base.BaseNoReduceTests):
pass


class TestMethods(BaseInterval, base.BaseMethodsTests):

@pytest.mark.skip(reason='addition is not defined for intervals')
Expand Down