Skip to content

TYP: define Index.any, Index.all non-dynamically #36929

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
Show file tree
Hide file tree
Changes from all commits
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
131 changes: 61 additions & 70 deletions pandas/core/indexes/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from datetime import datetime
from itertools import zip_longest
import operator
from textwrap import dedent
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -30,7 +29,7 @@
from pandas.compat import set_function_name
from pandas.compat.numpy import function as nv
from pandas.errors import DuplicateLabelError, InvalidIndexError
from pandas.util._decorators import Appender, Substitution, cache_readonly, doc
from pandas.util._decorators import Appender, cache_readonly, doc

from pandas.core.dtypes import concat as _concat
from pandas.core.dtypes.cast import (
Expand Down Expand Up @@ -61,6 +60,7 @@
is_signed_integer_dtype,
is_timedelta64_dtype,
is_unsigned_integer_dtype,
needs_i8_conversion,
pandas_dtype,
validate_all_hashable,
)
Expand Down Expand Up @@ -5447,28 +5447,61 @@ def _add_numeric_methods(cls):
cls._add_numeric_methods_unary()
cls._add_numeric_methods_binary()

@classmethod
def _add_logical_methods(cls):
"""
Add in logical methods.
def any(self, *args, **kwargs):
"""
_doc = """
%(desc)s
Return whether any element is Truthy.

Parameters
----------
*args
These parameters will be passed to numpy.%(outname)s.
These parameters will be passed to numpy.any.
**kwargs
These parameters will be passed to numpy.%(outname)s.
These parameters will be passed to numpy.any.

Returns
-------
%(outname)s : bool or array_like (if axis is specified)
A single element array_like may be converted to bool."""
any : bool or array_like (if axis is specified)
A single element array_like may be converted to bool.

_index_shared_docs["index_all"] = dedent(
"""
See Also
--------
Index.all : Return whether all elements are True.
Series.all : Return whether all elements are True.

Notes
-----
Not a Number (NaN), positive infinity and negative infinity
evaluate to True because these are not equal to zero.

Examples
--------
>>> index = pd.Index([0, 1, 2])
>>> index.any()
True

>>> index = pd.Index([0, 0, 0])
>>> index.any()
False
"""
# FIXME: docstr inaccurate, args/kwargs not passed
self._maybe_disable_logical_methods("any")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we have a numpy validation routine for this and for all? eg. https://github.com/pandas-dev/pandas/blob/master/pandas/compat/numpy/function.py

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

return np.any(self.values)

def all(self):
"""
Return whether all elements are Truthy.

Parameters
----------
*args
These parameters will be passed to numpy.all.
**kwargs
These parameters will be passed to numpy.all.

Returns
-------
all : bool or array_like (if axis is specified)
A single element array_like may be converted to bool.

See Also
--------
Expand Down Expand Up @@ -5507,65 +5540,24 @@ def _add_logical_methods(cls):
>>> pd.Index([0, 0, 0]).any()
False
"""
)

_index_shared_docs["index_any"] = dedent(
"""

See Also
--------
Index.all : Return whether all elements are True.
Series.all : Return whether all elements are True.
# FIXME: docstr inaccurate, args/kwargs not passed

Notes
-----
Not a Number (NaN), positive infinity and negative infinity
evaluate to True because these are not equal to zero.
self._maybe_disable_logical_methods("all")
return np.all(self.values)

Examples
--------
>>> index = pd.Index([0, 1, 2])
>>> index.any()
True

>>> index = pd.Index([0, 0, 0])
>>> index.any()
False
def _maybe_disable_logical_methods(self, opname: str_t):
"""
)

def _make_logical_function(name: str_t, desc: str_t, f):
@Substitution(outname=name, desc=desc)
@Appender(_index_shared_docs["index_" + name])
@Appender(_doc)
def logical_func(self, *args, **kwargs):
result = f(self.values)
if (
isinstance(result, (np.ndarray, ABCSeries, Index))
and result.ndim == 0
):
# return NumPy type
return result.dtype.type(result.item())
else: # pragma: no cover
return result

logical_func.__name__ = name
return logical_func

cls.all = _make_logical_function(
"all", "Return whether all elements are True.", np.all
)
cls.any = _make_logical_function(
"any", "Return whether any element is True.", np.any
)

@classmethod
def _add_logical_methods_disabled(cls):
raise if this Index subclass does not support any or all.
"""
Add in logical methods to disable.
"""
cls.all = make_invalid_op("all")
cls.any = make_invalid_op("any")
if (
isinstance(self, ABCMultiIndex)
or needs_i8_conversion(self.dtype)
or is_interval_dtype(self.dtype)
or is_categorical_dtype(self.dtype)
or is_float_dtype(self.dtype)
):
# This call will raise
make_invalid_op(opname)(self)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could this be done on the subclasses. more repetitive but would be easier to follow I think.

although, does disabling on subclasses obey LSP?

maybe a mixin that classes can opt into, to ensure that the base class or other code does not rely on any/all being available.

This would mean a base class for Index, to allow any/all on object Indexes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ATM we disable on subclasses (though by calling cls.add_disabled_whatever()), and i thought it was clearer to do this in just one place, but if people disagree im fine with going the other direction.

I don't think it's that big a deal because I expect to change it before long since, as mentioned in the OP, i think several of the currently-disabled methods should be enabled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm OK with perhaps revisiting after some of the currently-disabled methods are enabled.


@property
def shape(self):
Expand All @@ -5579,7 +5571,6 @@ def shape(self):


Index._add_numeric_methods()
Index._add_logical_methods()


def ensure_index_from_sequences(sequences, names=None):
Expand Down
3 changes: 0 additions & 3 deletions pandas/core/indexes/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,3 @@ def _wrap_joined_index(
name = get_op_result_name(self, other)
cat = self._data._from_backing_data(joined)
return type(self)._simple_new(cat, name=name)


CategoricalIndex._add_logical_methods_disabled()
3 changes: 0 additions & 3 deletions pandas/core/indexes/datetimes.py
Original file line number Diff line number Diff line change
Expand Up @@ -871,9 +871,6 @@ def indexer_between_time(
return mask.nonzero()[0]


DatetimeIndex._add_logical_methods_disabled()


def date_range(
start=None,
end=None,
Expand Down
3 changes: 0 additions & 3 deletions pandas/core/indexes/interval.py
Original file line number Diff line number Diff line change
Expand Up @@ -1120,9 +1120,6 @@ def __ge__(self, other):
return Index.__ge__(self, other)


IntervalIndex._add_logical_methods_disabled()


def _is_valid_endpoint(endpoint) -> bool:
"""
Helper for interval_range to check if start/end are valid types.
Expand Down
1 change: 0 additions & 1 deletion pandas/core/indexes/multi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3723,7 +3723,6 @@ def _add_numeric_methods_disabled(cls):

MultiIndex._add_numeric_methods_disabled()
MultiIndex._add_numeric_methods_add_sub_disabled()
MultiIndex._add_logical_methods_disabled()


def sparsify_labels(label_list, start: int = 0, sentinel=""):
Expand Down
3 changes: 0 additions & 3 deletions pandas/core/indexes/numeric.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,6 @@ def _can_union_without_object_cast(self, other) -> bool:


Int64Index._add_numeric_methods()
Int64Index._add_logical_methods()

_uint64_descr_args = dict(
klass="UInt64Index", ltype="unsigned integer", dtype="uint64", extra=""
Expand Down Expand Up @@ -323,7 +322,6 @@ def _can_union_without_object_cast(self, other) -> bool:


UInt64Index._add_numeric_methods()
UInt64Index._add_logical_methods()

_float64_descr_args = dict(
klass="Float64Index", dtype="float64", ltype="float", extra=""
Expand Down Expand Up @@ -430,4 +428,3 @@ def _can_union_without_object_cast(self, other) -> bool:


Float64Index._add_numeric_methods()
Float64Index._add_logical_methods_disabled()
3 changes: 0 additions & 3 deletions pandas/core/indexes/period.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,9 +719,6 @@ def memory_usage(self, deep: bool = False) -> int:
return result


PeriodIndex._add_logical_methods_disabled()


def period_range(
start=None, end=None, periods=None, freq=None, name=None
) -> PeriodIndex:
Expand Down
4 changes: 2 additions & 2 deletions pandas/core/indexes/range.py
Original file line number Diff line number Diff line change
Expand Up @@ -804,10 +804,10 @@ def __floordiv__(self, other):
# --------------------------------------------------------------------
# Reductions

def all(self) -> bool:
def all(self, *args, **kwargs) -> bool:
return 0 not in self._range

def any(self) -> bool:
def any(self, *args, **kwargs) -> bool:
return any(self._range)

# --------------------------------------------------------------------
Expand Down
3 changes: 0 additions & 3 deletions pandas/core/indexes/timedeltas.py
Original file line number Diff line number Diff line change
Expand Up @@ -261,9 +261,6 @@ def inferred_type(self) -> str:
return "timedelta64"


TimedeltaIndex._add_logical_methods_disabled()


def timedelta_range(
start=None, end=None, periods=None, freq=None, name=None, closed=None
) -> TimedeltaIndex:
Expand Down