Skip to content

Commit 518a62c

Browse files
committed
ENH: Move any/all to NDFrame, support additional arguments for Series. GH8302
1 parent a617b0b commit 518a62c

File tree

11 files changed

+215
-87
lines changed

11 files changed

+215
-87
lines changed

doc/source/whatsnew/v0.15.2.txt

+15-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,20 @@ API changes
2222

2323
- Bug in concat of Series with ``category`` dtype which were coercing to ``object``. (:issue:`8641`)
2424

25+
- ``Series.all`` and ``Series.any`` now support the ``level`` and ``skipna`` parameters. ``Series.all``, ``Series.any``, ``Index.all``, and ``Index.any`` no longer support the ``out`` and ``keepdims`` parameters, which existed for compatibility with ndarray. Various index types no longer support the ``all`` and ``any`` aggregation functions. (:issue:`8302`):
26+
27+
.. ipython:: python
28+
29+
s = pd.Series([False, True, False], index=[0, 0, 1])
30+
s.any(level=0)
31+
32+
- ``Panel`` now supports the ``all`` and ``any`` aggregation functions. (:issue:`8302`):
33+
34+
.. ipython:: python
35+
36+
p = pd.Panel(np.random.rand(2, 5, 4) > 0.1)
37+
p.all()
38+
2539
.. _whatsnew_0152.enhancements:
2640

2741
Enhancements
@@ -44,4 +58,4 @@ Experimental
4458
Bug Fixes
4559
~~~~~~~~~
4660
- Bug in ``groupby`` signatures that didn't include *args or **kwargs (:issue:`8733`).
47-
- ``io.data.Options`` now raises ``RemoteDataError`` when no expiry dates are available from Yahoo (:issue:`8761`).
61+
- ``io.data.Options`` now raises ``RemoteDataError`` when no expiry dates are available from Yahoo (:issue:`8761`).

pandas/core/base.py

-18
Original file line numberDiff line numberDiff line change
@@ -268,18 +268,6 @@ def __unicode__(self):
268268
quote_strings=True)
269269
return "%s(%s, dtype='%s')" % (type(self).__name__, prepr, self.dtype)
270270

271-
def _unbox(func):
272-
@Appender(func.__doc__)
273-
def f(self, *args, **kwargs):
274-
result = func(self.values, *args, **kwargs)
275-
from pandas.core.index import Index
276-
if isinstance(result, (np.ndarray, com.ABCSeries, Index)) and result.ndim == 0:
277-
# return NumPy type
278-
return result.dtype.type(result.item())
279-
else: # pragma: no cover
280-
return result
281-
f.__name__ = func.__name__
282-
return f
283271

284272
class IndexOpsMixin(object):
285273
""" common ops mixin to support a unified inteface / docs for Series / Index """
@@ -528,12 +516,6 @@ def duplicated(self, take_last=False):
528516
from pandas.core.index import Index
529517
return Index(duplicated)
530518

531-
#----------------------------------------------------------------------
532-
# unbox reductions
533-
534-
all = _unbox(np.ndarray.all)
535-
any = _unbox(np.ndarray.any)
536-
537519
#----------------------------------------------------------------------
538520
# abstracts
539521

pandas/core/frame.py

-62
Original file line numberDiff line numberDiff line change
@@ -4133,68 +4133,6 @@ def _count_level(self, level, axis=0, numeric_only=False):
41334133
else:
41344134
return result
41354135

4136-
def any(self, axis=None, bool_only=None, skipna=True, level=None,
4137-
**kwargs):
4138-
"""
4139-
Return whether any element is True over requested axis.
4140-
%(na_action)s
4141-
4142-
Parameters
4143-
----------
4144-
axis : {0, 1}
4145-
0 for row-wise, 1 for column-wise
4146-
skipna : boolean, default True
4147-
Exclude NA/null values. If an entire row/column is NA, the result
4148-
will be NA
4149-
level : int or level name, default None
4150-
If the axis is a MultiIndex (hierarchical), count along a
4151-
particular level, collapsing into a DataFrame
4152-
bool_only : boolean, default None
4153-
Only include boolean data.
4154-
4155-
Returns
4156-
-------
4157-
any : Series (or DataFrame if level specified)
4158-
"""
4159-
if axis is None:
4160-
axis = self._stat_axis_number
4161-
if level is not None:
4162-
return self._agg_by_level('any', axis=axis, level=level,
4163-
skipna=skipna)
4164-
return self._reduce(nanops.nanany, 'any', axis=axis, skipna=skipna,
4165-
numeric_only=bool_only, filter_type='bool')
4166-
4167-
def all(self, axis=None, bool_only=None, skipna=True, level=None,
4168-
**kwargs):
4169-
"""
4170-
Return whether all elements are True over requested axis.
4171-
%(na_action)s
4172-
4173-
Parameters
4174-
----------
4175-
axis : {0, 1}
4176-
0 for row-wise, 1 for column-wise
4177-
skipna : boolean, default True
4178-
Exclude NA/null values. If an entire row/column is NA, the result
4179-
will be NA
4180-
level : int or level name, default None
4181-
If the axis is a MultiIndex (hierarchical), count along a
4182-
particular level, collapsing into a DataFrame
4183-
bool_only : boolean, default None
4184-
Only include boolean data.
4185-
4186-
Returns
4187-
-------
4188-
any : Series (or DataFrame if level specified)
4189-
"""
4190-
if axis is None:
4191-
axis = self._stat_axis_number
4192-
if level is not None:
4193-
return self._agg_by_level('all', axis=axis, level=level,
4194-
skipna=skipna)
4195-
return self._reduce(nanops.nanall, 'all', axis=axis, skipna=skipna,
4196-
numeric_only=bool_only, filter_type='bool')
4197-
41984136
def _reduce(self, op, name, axis=0, skipna=True, numeric_only=None,
41994137
filter_type=None, **kwds):
42004138
axis = self._get_axis_number(axis)

pandas/core/generic.py

+52
Original file line numberDiff line numberDiff line change
@@ -3888,6 +3888,7 @@ def _add_numeric_operations(cls):
38883888
])
38893889
name = (cls._constructor_sliced.__name__
38903890
if cls._AXIS_LEN > 1 else 'scalar')
3891+
38913892
_num_doc = """
38923893
38933894
%(desc)s
@@ -3905,6 +3906,27 @@ def _add_numeric_operations(cls):
39053906
Include only float, int, boolean data. If None, will attempt to use
39063907
everything, then use only numeric data
39073908
3909+
Returns
3910+
-------
3911+
%(outname)s : """ + name + " or " + cls.__name__ + " (if level specified)\n"
3912+
3913+
_bool_doc = """
3914+
3915+
%(desc)s
3916+
3917+
Parameters
3918+
----------
3919+
axis : """ + axis_descr + """
3920+
skipna : boolean, default True
3921+
Exclude NA/null values. If an entire row/column is NA, the result
3922+
will be NA
3923+
level : int or level name, default None
3924+
If the axis is a MultiIndex (hierarchical), count along a
3925+
particular level, collapsing into a """ + name + """
3926+
bool_only : boolean, default None
3927+
Include only boolean data. If None, will attempt to use everything,
3928+
then use only boolean data
3929+
39083930
Returns
39093931
-------
39103932
%(outname)s : """ + name + " or " + cls.__name__ + " (if level specified)\n"
@@ -3971,6 +3993,36 @@ def stat_func(self, axis=None, skipna=None, level=None,
39713993
want the *index* of the minimum, use ``idxmin``. This is the
39723994
equivalent of the ``numpy.ndarray`` method ``argmin``.""", nanops.nanmin)
39733995

3996+
def _make_logical_function(name, desc, f):
3997+
3998+
@Substitution(outname=name, desc=desc)
3999+
@Appender(_bool_doc)
4000+
def logical_func(self, axis=None, bool_only=None, skipna=None,
4001+
level=None, **kwargs):
4002+
if skipna is None:
4003+
skipna = True
4004+
if axis is None:
4005+
axis = self._stat_axis_number
4006+
if level is not None:
4007+
if bool_only is not None:
4008+
raise NotImplementedError(
4009+
"Option bool_only is not implemented with option "
4010+
"level.")
4011+
return self._agg_by_level(name, axis=axis, level=level,
4012+
skipna=skipna)
4013+
return self._reduce(f, axis=axis, skipna=skipna,
4014+
numeric_only=bool_only, filter_type='bool',
4015+
name=name)
4016+
logical_func.__name__ = name
4017+
return logical_func
4018+
4019+
cls.any = _make_logical_function(
4020+
'any', 'Return whether any element is True over requested axis',
4021+
nanops.nanany)
4022+
cls.all = _make_logical_function(
4023+
'all', 'Return whether all elements are True over requested axis',
4024+
nanops.nanall)
4025+
39744026
@Substitution(outname='mad',
39754027
desc="Return the mean absolute deviation of the values "
39764028
"for the requested axis")

pandas/core/index.py

+73-6
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
import pandas.index as _index
1515
from pandas.lib import Timestamp, Timedelta, is_datetime_array
1616
from pandas.core.base import PandasObject, FrozenList, FrozenNDArray, IndexOpsMixin, _shared_docs
17-
from pandas.util.decorators import Appender, cache_readonly, deprecate
17+
from pandas.util.decorators import (Appender, Substitution, cache_readonly,
18+
deprecate)
1819
from pandas.core.common import isnull, array_equivalent
1920
import pandas.core.common as com
2021
from pandas.core.common import (_values_from_object, is_float, is_integer,
@@ -2088,12 +2089,13 @@ def _evaluate_with_datetime_like(self, other, op, opstr):
20882089
def _add_numeric_methods_disabled(cls):
20892090
""" add in numeric methods to disable """
20902091

2091-
def _make_invalid_op(opstr):
2092+
def _make_invalid_op(name):
20922093

2093-
def _invalid_op(self, other=None):
2094-
raise TypeError("cannot perform {opstr} with this index type: {typ}".format(opstr=opstr,
2095-
typ=type(self)))
2096-
return _invalid_op
2094+
def invalid_op(self, other=None):
2095+
raise TypeError("cannot perform {name} with this index type: {typ}".format(name=name,
2096+
typ=type(self)))
2097+
invalid_op.__name__ = name
2098+
return invalid_op
20972099

20982100
cls.__mul__ = cls.__rmul__ = _make_invalid_op('__mul__')
20992101
cls.__floordiv__ = cls.__rfloordiv__ = _make_invalid_op('__floordiv__')
@@ -2178,8 +2180,62 @@ def _evaluate_numeric_unary(self):
21782180
cls.__abs__ = _make_evaluate_unary(lambda x: np.abs(x),'__abs__')
21792181
cls.__inv__ = _make_evaluate_unary(lambda x: -x,'__inv__')
21802182

2183+
@classmethod
2184+
def _add_logical_methods(cls):
2185+
""" add in logical methods """
2186+
2187+
_doc = """
2188+
2189+
%(desc)s
2190+
2191+
Parameters
2192+
----------
2193+
All arguments to numpy.%(outname)s are accepted.
2194+
2195+
Returns
2196+
-------
2197+
%(outname)s : bool or array_like (if axis is specified)
2198+
A single element array_like may be converted to bool."""
2199+
2200+
def _make_logical_function(name, desc, f):
2201+
2202+
@Substitution(outname=name, desc=desc)
2203+
@Appender(_doc)
2204+
def logical_func(self, *args, **kwargs):
2205+
result = f(self.values)
2206+
if isinstance(result, (np.ndarray, com.ABCSeries, Index)) \
2207+
and result.ndim == 0:
2208+
# return NumPy type
2209+
return result.dtype.type(result.item())
2210+
else: # pragma: no cover
2211+
return result
2212+
logical_func.__name__ = name
2213+
return logical_func
2214+
2215+
cls.all = _make_logical_function(
2216+
'all', 'Return whether all elements are True', np.all)
2217+
cls.any = _make_logical_function(
2218+
'any', 'Return whether any element is True', np.any)
2219+
2220+
@classmethod
2221+
def _add_logical_methods_disabled(cls):
2222+
""" add in logical methods to disable """
2223+
2224+
def _make_invalid_op(name):
2225+
2226+
def invalid_op(self, other=None):
2227+
raise TypeError("cannot perform {name} with this index type: {typ}".format(name=name,
2228+
typ=type(self)))
2229+
invalid_op.__name__ = name
2230+
return invalid_op
2231+
2232+
cls.all = _make_invalid_op('all')
2233+
cls.any = _make_invalid_op('any')
2234+
21812235

21822236
Index._add_numeric_methods_disabled()
2237+
Index._add_logical_methods()
2238+
21832239

21842240
class NumericIndex(Index):
21852241
"""
@@ -2291,7 +2347,11 @@ def equals(self, other):
22912347
def _wrap_joined_index(self, joined, other):
22922348
name = self.name if self.name == other.name else None
22932349
return Int64Index(joined, name=name)
2350+
2351+
22942352
Int64Index._add_numeric_methods()
2353+
Int64Index._add_logical_methods()
2354+
22952355

22962356
class Float64Index(NumericIndex):
22972357

@@ -2483,7 +2543,10 @@ def isin(self, values, level=None):
24832543
self._validate_index_level(level)
24842544
return lib.ismember_nans(self._array_values(), value_set,
24852545
isnull(list(value_set)).any())
2546+
2547+
24862548
Float64Index._add_numeric_methods()
2549+
Float64Index._add_logical_methods_disabled()
24872550

24882551

24892552
class MultiIndex(Index):
@@ -4436,7 +4499,11 @@ def isin(self, values, level=None):
44364499
return np.zeros(len(labs), dtype=np.bool_)
44374500
else:
44384501
return np.lib.arraysetops.in1d(labs, sought_labels)
4502+
4503+
44394504
MultiIndex._add_numeric_methods_disabled()
4505+
MultiIndex._add_logical_methods_disabled()
4506+
44404507

44414508
# For utility purposes
44424509

pandas/tests/test_index.py

+19
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,15 @@ def test_numeric_compat(self):
7575
"cannot perform __floordiv__",
7676
lambda : 1 // idx)
7777

78+
def test_logical_compat(self):
79+
idx = self.create_index()
80+
tm.assertRaisesRegexp(TypeError,
81+
'cannot perform all',
82+
lambda : idx.all())
83+
tm.assertRaisesRegexp(TypeError,
84+
'cannot perform any',
85+
lambda : idx.any())
86+
7887
def test_boolean_context_compat(self):
7988

8089
# boolean context compat
@@ -820,6 +829,11 @@ def test_take(self):
820829
expected = self.dateIndex[indexer]
821830
self.assertTrue(result.equals(expected))
822831

832+
def test_logical_compat(self):
833+
idx = self.create_index()
834+
self.assertEqual(idx.all(), idx.values.all())
835+
self.assertEqual(idx.any(), idx.values.any())
836+
823837
def _check_method_works(self, method):
824838
method(self.empty)
825839
method(self.dateIndex)
@@ -1467,6 +1481,11 @@ def test_equals(self):
14671481
self.assertTrue(self.index.equals(same_values))
14681482
self.assertTrue(same_values.equals(self.index))
14691483

1484+
def test_logical_compat(self):
1485+
idx = self.create_index()
1486+
self.assertEqual(idx.all(), idx.values.all())
1487+
self.assertEqual(idx.any(), idx.values.any())
1488+
14701489
def test_identical(self):
14711490
i = Index(self.index.copy())
14721491
self.assertTrue(i.identical(self.index))

0 commit comments

Comments
 (0)