Skip to content

Commit 6c65879

Browse files
jorisvandenbosschejreback
authored andcommitted
API: change IntervalIndex.contains to work elementwise (#17753)
1 parent 3b3b791 commit 6c65879

File tree

15 files changed

+99
-67
lines changed

15 files changed

+99
-67
lines changed

doc/source/reference/arrays.rst

+1
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,7 @@ A collection of intervals may be stored in an :class:`arrays.IntervalArray`.
335335
arrays.IntervalArray.from_arrays
336336
arrays.IntervalArray.from_tuples
337337
arrays.IntervalArray.from_breaks
338+
arrays.IntervalArray.contains
338339
arrays.IntervalArray.overlaps
339340
arrays.IntervalArray.set_closed
340341
arrays.IntervalArray.to_tuples

doc/source/reference/indexing.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ IntervalIndex components
248248
IntervalIndex.from_arrays
249249
IntervalIndex.from_tuples
250250
IntervalIndex.from_breaks
251-
IntervalIndex.contains
252251
IntervalIndex.left
253252
IntervalIndex.right
254253
IntervalIndex.mid
@@ -260,6 +259,7 @@ IntervalIndex components
260259
IntervalIndex.get_loc
261260
IntervalIndex.get_indexer
262261
IntervalIndex.set_closed
262+
IntervalIndex.contains
263263
IntervalIndex.overlaps
264264
IntervalIndex.to_tuples
265265

doc/source/whatsnew/v0.25.0.rst

+1
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,7 @@ Other deprecations
624624
- :attr:`Series.imag` and :attr:`Series.real` are deprecated. (:issue:`18262`)
625625
- :meth:`Series.put` is deprecated. (:issue:`18262`)
626626
- :meth:`Index.item` and :meth:`Series.item` is deprecated. (:issue:`18262`)
627+
- :meth:`Index.contains` is deprecated. Use ``key in index`` (``__contains__``) instead (:issue:`17753`).
627628

628629
.. _whatsnew_0250.prior_deprecations:
629630

pandas/core/arrays/interval.py

+47
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
from_arrays
8080
from_tuples
8181
from_breaks
82+
contains
8283
overlaps
8384
set_closed
8485
to_tuples
@@ -1017,6 +1018,52 @@ def repeat(self, repeats, axis=None):
10171018
right_repeat = self.right.repeat(repeats)
10181019
return self._shallow_copy(left=left_repeat, right=right_repeat)
10191020

1021+
_interval_shared_docs['contains'] = """
1022+
Check elementwise if the Intervals contain the value.
1023+
1024+
Return a boolean mask whether the value is contained in the Intervals
1025+
of the %(klass)s.
1026+
1027+
.. versionadded:: 0.25.0
1028+
1029+
Parameters
1030+
----------
1031+
other : scalar
1032+
The value to check whether it is contained in the Intervals.
1033+
1034+
Returns
1035+
-------
1036+
boolean array
1037+
1038+
See Also
1039+
--------
1040+
Interval.contains : Check whether Interval object contains value.
1041+
%(klass)s.overlaps : Check if an Interval overlaps the values in the
1042+
%(klass)s.
1043+
1044+
Examples
1045+
--------
1046+
>>> intervals = pd.%(qualname)s.from_tuples([(0, 1), (1, 3), (2, 4)])
1047+
>>> intervals
1048+
%(klass)s([(0, 1], (1, 3], (2, 4]],
1049+
closed='right',
1050+
dtype='interval[int64]')
1051+
>>> intervals.contains(0.5)
1052+
array([ True, False, False])
1053+
"""
1054+
1055+
@Appender(_interval_shared_docs['contains'] % _shared_docs_kwargs)
1056+
def contains(self, other):
1057+
if isinstance(other, Interval):
1058+
raise NotImplementedError(
1059+
'contains not implemented for two intervals'
1060+
)
1061+
1062+
return (
1063+
(self.left < other if self.open_left else self.left <= other) &
1064+
(other < self.right if self.open_right else other <= self.right)
1065+
)
1066+
10201067
_interval_shared_docs['overlaps'] = """
10211068
Check elementwise if an Interval overlaps the values in the %(klass)s.
10221069

pandas/core/indexes/base.py

+14-8
Original file line numberDiff line numberDiff line change
@@ -4019,13 +4019,6 @@ def is_type_compatible(self, kind):
40194019
>>> idx
40204020
Int64Index([1, 2, 3, 4], dtype='int64')
40214021
4022-
>>> idx.contains(2)
4023-
True
4024-
>>> idx.contains(6)
4025-
False
4026-
4027-
This is equivalent to:
4028-
40294022
>>> 2 in idx
40304023
True
40314024
>>> 6 in idx
@@ -4040,8 +4033,21 @@ def __contains__(self, key):
40404033
except (OverflowError, TypeError, ValueError):
40414034
return False
40424035

4043-
@Appender(_index_shared_docs['contains'] % _index_doc_kwargs)
40444036
def contains(self, key):
4037+
"""
4038+
Return a boolean indicating whether the provided key is in the index.
4039+
4040+
.. deprecated:: 0.25.0
4041+
Use ``key in index`` instead of ``index.contains(key)``.
4042+
4043+
Returns
4044+
-------
4045+
bool
4046+
"""
4047+
warnings.warn(
4048+
"The 'contains' method is deprecated and will be removed in a "
4049+
"future version. Use 'key in index' instead of "
4050+
"'index.contains(key)'", FutureWarning, stacklevel=2)
40454051
return key in self
40464052

40474053
def __hash__(self):

pandas/core/indexes/category.py

-4
Original file line numberDiff line numberDiff line change
@@ -386,10 +386,6 @@ def __contains__(self, key):
386386

387387
return contains(self, key, container=self._engine)
388388

389-
@Appender(_index_shared_docs['contains'] % _index_doc_kwargs)
390-
def contains(self, key):
391-
return key in self
392-
393389
def __array__(self, dtype=None):
394390
""" the array interface, return my values """
395391
return np.array(self._data, dtype=dtype)

pandas/core/indexes/datetimelike.py

-2
Original file line numberDiff line numberDiff line change
@@ -221,8 +221,6 @@ def __contains__(self, key):
221221
except (KeyError, TypeError, ValueError):
222222
return False
223223

224-
contains = __contains__
225-
226224
# Try to run function on index first, and then on elements of index
227225
# Especially important for group-by functionality
228226
def map(self, mapper, na_action=None):

pandas/core/indexes/interval.py

+5-22
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ def func(intvidx_self, other, sort=False):
139139
name=_index_doc_kwargs['name'],
140140
versionadded="0.20.0",
141141
extra_attributes="is_overlapping\nvalues\n",
142-
extra_methods="contains\n",
142+
extra_methods="",
143143
examples=textwrap.dedent("""\
144144
Examples
145145
--------
@@ -291,27 +291,6 @@ def __contains__(self, key):
291291
except KeyError:
292292
return False
293293

294-
def contains(self, key):
295-
"""
296-
Return a boolean indicating if the key is IN the index
297-
298-
We accept / allow keys to be not *just* actual
299-
objects.
300-
301-
Parameters
302-
----------
303-
key : int, float, Interval
304-
305-
Returns
306-
-------
307-
boolean
308-
"""
309-
try:
310-
self.get_loc(key)
311-
return True
312-
except KeyError:
313-
return False
314-
315294
@Appender(_interval_shared_docs['to_tuples'] % dict(
316295
return_type="Index",
317296
examples="""
@@ -1137,6 +1116,10 @@ def equals(self, other):
11371116
self.right.equals(other.right) and
11381117
self.closed == other.closed)
11391118

1119+
@Appender(_interval_shared_docs['contains'] % _index_doc_kwargs)
1120+
def contains(self, other):
1121+
return self._data.contains(other)
1122+
11401123
@Appender(_interval_shared_docs['overlaps'] % _index_doc_kwargs)
11411124
def overlaps(self, other):
11421125
return self._data.overlaps(other)

pandas/core/indexes/multi.py

-2
Original file line numberDiff line numberDiff line change
@@ -922,8 +922,6 @@ def __contains__(self, key):
922922
except (LookupError, TypeError, ValueError):
923923
return False
924924

925-
contains = __contains__
926-
927925
@Appender(_index_shared_docs['_shallow_copy'])
928926
def _shallow_copy(self, values=None, **kwargs):
929927
if values is not None:

pandas/core/indexes/period.py

-2
Original file line numberDiff line numberDiff line change
@@ -433,8 +433,6 @@ def __contains__(self, key):
433433
except Exception:
434434
return False
435435

436-
contains = __contains__
437-
438436
@cache_readonly
439437
def _int64index(self):
440438
return Int64Index._simple_new(self.asi8, name=self.name)

pandas/core/indexing.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2393,7 +2393,7 @@ def convert_to_index_sliceable(obj, key):
23932393
elif isinstance(key, str):
23942394

23952395
# we are an actual column
2396-
if obj._data.items.contains(key):
2396+
if key in obj._data.items:
23972397
return None
23982398

23992399
# We might have a datetimelike string that we can translate to a

pandas/tests/indexes/interval/test_interval.py

+20-17
Original file line numberDiff line numberDiff line change
@@ -753,23 +753,28 @@ def test_contains(self):
753753
assert Interval(3, 5) not in i
754754
assert Interval(-1, 0, closed='left') not in i
755755

756-
# To be removed, replaced by test_interval_new.py (see #16316, #16386)
757-
def testcontains(self):
756+
def test_contains_method(self):
758757
# can select values that are IN the range of a value
759758
i = IntervalIndex.from_arrays([0, 1], [1, 2])
760759

761-
assert i.contains(0.1)
762-
assert i.contains(0.5)
763-
assert i.contains(1)
764-
assert i.contains(Interval(0, 1))
765-
assert i.contains(Interval(0, 2))
760+
expected = np.array([False, False], dtype='bool')
761+
actual = i.contains(0)
762+
tm.assert_numpy_array_equal(actual, expected)
763+
actual = i.contains(3)
764+
tm.assert_numpy_array_equal(actual, expected)
766765

767-
# these overlaps completely
768-
assert i.contains(Interval(0, 3))
769-
assert i.contains(Interval(1, 3))
766+
expected = np.array([True, False], dtype='bool')
767+
actual = i.contains(0.5)
768+
tm.assert_numpy_array_equal(actual, expected)
769+
actual = i.contains(1)
770+
tm.assert_numpy_array_equal(actual, expected)
770771

771-
assert not i.contains(20)
772-
assert not i.contains(-20)
772+
# __contains__ not implemented for "interval in interval", follow
773+
# that for the contains method for now
774+
with pytest.raises(
775+
NotImplementedError,
776+
match='contains not implemented for two'):
777+
i.contains(Interval(0, 1))
773778

774779
def test_dropna(self, closed):
775780

@@ -939,11 +944,9 @@ def test_datetime(self, tz):
939944
assert iv_false not in index
940945

941946
# .contains does check individual points
942-
assert not index.contains(Timestamp('2000-01-01', tz=tz))
943-
assert index.contains(Timestamp('2000-01-01T12', tz=tz))
944-
assert index.contains(Timestamp('2000-01-02', tz=tz))
945-
assert index.contains(iv_true)
946-
assert not index.contains(iv_false)
947+
assert not index.contains(Timestamp('2000-01-01', tz=tz)).any()
948+
assert index.contains(Timestamp('2000-01-01T12', tz=tz)).any()
949+
assert index.contains(Timestamp('2000-01-02', tz=tz)).any()
947950

948951
# test get_indexer
949952
start = Timestamp('1999-12-31T12:00', tz=tz)

pandas/tests/indexes/period/test_indexing.py

-6
Original file line numberDiff line numberDiff line change
@@ -464,19 +464,13 @@ def test_contains(self):
464464
idx0 = pd.PeriodIndex(ps0)
465465

466466
for p in ps0:
467-
assert idx0.contains(p)
468467
assert p in idx0
469-
470-
assert idx0.contains(str(p))
471468
assert str(p) in idx0
472469

473-
assert idx0.contains('2017-09-01 00:00:01')
474470
assert '2017-09-01 00:00:01' in idx0
475471

476-
assert idx0.contains('2017-09')
477472
assert '2017-09' in idx0
478473

479-
assert not idx0.contains(p3)
480474
assert p3 not in idx0
481475

482476
def test_get_value(self):

pandas/tests/indexes/test_base.py

+5
Original file line numberDiff line numberDiff line change
@@ -2159,6 +2159,11 @@ def test_tab_complete_warning(self, ip):
21592159
with provisionalcompleter('ignore'):
21602160
list(ip.Completer.completions('idx.', 4))
21612161

2162+
def test_deprecated_contains(self):
2163+
for index in self.indices.values():
2164+
with tm.assert_produces_warning(FutureWarning):
2165+
index.contains(1)
2166+
21622167

21632168
class TestMixedIntIndex(Base):
21642169
# Mostly the tests from common.py for which the results differ

pandas/tests/indexes/test_range.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -273,10 +273,12 @@ def test_cached_data(self):
273273
91 in idx
274274
assert idx._cached_data is None
275275

276-
idx.contains(90)
276+
with tm.assert_produces_warning(FutureWarning):
277+
idx.contains(90)
277278
assert idx._cached_data is None
278279

279-
idx.contains(91)
280+
with tm.assert_produces_warning(FutureWarning):
281+
idx.contains(91)
280282
assert idx._cached_data is None
281283

282284
idx.all()

0 commit comments

Comments
 (0)