diff --git a/doc/source/reference/arrays.rst b/doc/source/reference/arrays.rst index 77a87cafb9258..bf9520c54040d 100644 --- a/doc/source/reference/arrays.rst +++ b/doc/source/reference/arrays.rst @@ -335,6 +335,7 @@ A collection of intervals may be stored in an :class:`arrays.IntervalArray`. arrays.IntervalArray.from_arrays arrays.IntervalArray.from_tuples arrays.IntervalArray.from_breaks + arrays.IntervalArray.contains arrays.IntervalArray.overlaps arrays.IntervalArray.set_closed arrays.IntervalArray.to_tuples diff --git a/doc/source/reference/indexing.rst b/doc/source/reference/indexing.rst index bbac964e8a201..65860eb5c2f51 100644 --- a/doc/source/reference/indexing.rst +++ b/doc/source/reference/indexing.rst @@ -248,7 +248,6 @@ IntervalIndex components IntervalIndex.from_arrays IntervalIndex.from_tuples IntervalIndex.from_breaks - IntervalIndex.contains IntervalIndex.left IntervalIndex.right IntervalIndex.mid @@ -260,6 +259,7 @@ IntervalIndex components IntervalIndex.get_loc IntervalIndex.get_indexer IntervalIndex.set_closed + IntervalIndex.contains IntervalIndex.overlaps IntervalIndex.to_tuples diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index 008f6f0b8643e..48d0675c86d0c 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -615,6 +615,7 @@ Other deprecations - :attr:`Series.imag` and :attr:`Series.real` are deprecated. (:issue:`18262`) - :meth:`Series.put` is deprecated. (:issue:`18262`) - :meth:`Index.item` and :meth:`Series.item` is deprecated. (:issue:`18262`) +- :meth:`Index.contains` is deprecated. Use ``key in index`` (``__contains__``) instead (:issue:`17753`). .. _whatsnew_0250.prior_deprecations: diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py index aaa4124182598..8ed28065ee7aa 100644 --- a/pandas/core/arrays/interval.py +++ b/pandas/core/arrays/interval.py @@ -79,6 +79,7 @@ from_arrays from_tuples from_breaks +contains overlaps set_closed to_tuples @@ -1017,6 +1018,52 @@ def repeat(self, repeats, axis=None): right_repeat = self.right.repeat(repeats) return self._shallow_copy(left=left_repeat, right=right_repeat) + _interval_shared_docs['contains'] = """ + Check elementwise if the Intervals contain the value. + + Return a boolean mask whether the value is contained in the Intervals + of the %(klass)s. + + .. versionadded:: 0.25.0 + + Parameters + ---------- + other : scalar + The value to check whether it is contained in the Intervals. + + Returns + ------- + boolean array + + See Also + -------- + Interval.contains : Check whether Interval object contains value. + %(klass)s.overlaps : Check if an Interval overlaps the values in the + %(klass)s. + + Examples + -------- + >>> intervals = pd.%(qualname)s.from_tuples([(0, 1), (1, 3), (2, 4)]) + >>> intervals + %(klass)s([(0, 1], (1, 3], (2, 4]], + closed='right', + dtype='interval[int64]') + >>> intervals.contains(0.5) + array([ True, False, False]) + """ + + @Appender(_interval_shared_docs['contains'] % _shared_docs_kwargs) + def contains(self, other): + if isinstance(other, Interval): + raise NotImplementedError( + 'contains not implemented for two intervals' + ) + + return ( + (self.left < other if self.open_left else self.left <= other) & + (other < self.right if self.open_right else other <= self.right) + ) + _interval_shared_docs['overlaps'] = """ Check elementwise if an Interval overlaps the values in the %(klass)s. diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 4b7582fcf7cc0..f122810a2fe21 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -4015,13 +4015,6 @@ def is_type_compatible(self, kind): >>> idx Int64Index([1, 2, 3, 4], dtype='int64') - >>> idx.contains(2) - True - >>> idx.contains(6) - False - - This is equivalent to: - >>> 2 in idx True >>> 6 in idx @@ -4036,8 +4029,21 @@ def __contains__(self, key): except (OverflowError, TypeError, ValueError): return False - @Appender(_index_shared_docs['contains'] % _index_doc_kwargs) def contains(self, key): + """ + Return a boolean indicating whether the provided key is in the index. + + .. deprecated:: 0.25.0 + Use ``key in index`` instead of ``index.contains(key)``. + + Returns + ------- + bool + """ + warnings.warn( + "The 'contains' method is deprecated and will be removed in a " + "future version. Use 'key in index' instead of " + "'index.contains(key)'", FutureWarning, stacklevel=2) return key in self def __hash__(self): diff --git a/pandas/core/indexes/category.py b/pandas/core/indexes/category.py index 3d3774ce48e8b..4040d889b91eb 100644 --- a/pandas/core/indexes/category.py +++ b/pandas/core/indexes/category.py @@ -385,10 +385,6 @@ def __contains__(self, key): return contains(self, key, container=self._engine) - @Appender(_index_shared_docs['contains'] % _index_doc_kwargs) - def contains(self, key): - return key in self - def __array__(self, dtype=None): """ the array interface, return my values """ return np.array(self._data, dtype=dtype) diff --git a/pandas/core/indexes/datetimelike.py b/pandas/core/indexes/datetimelike.py index 7c90fb11aa1bf..e141f7b5c5b23 100644 --- a/pandas/core/indexes/datetimelike.py +++ b/pandas/core/indexes/datetimelike.py @@ -221,8 +221,6 @@ def __contains__(self, key): except (KeyError, TypeError, ValueError): return False - contains = __contains__ - # Try to run function on index first, and then on elements of index # Especially important for group-by functionality def map(self, mapper, na_action=None): diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 777fa2eadd289..9f9ebcf67cee6 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -139,7 +139,7 @@ def func(intvidx_self, other, sort=False): name=_index_doc_kwargs['name'], versionadded="0.20.0", extra_attributes="is_overlapping\nvalues\n", - extra_methods="contains\n", + extra_methods="", examples=textwrap.dedent("""\ Examples -------- @@ -291,27 +291,6 @@ def __contains__(self, key): except KeyError: return False - def contains(self, key): - """ - Return a boolean indicating if the key is IN the index - - We accept / allow keys to be not *just* actual - objects. - - Parameters - ---------- - key : int, float, Interval - - Returns - ------- - boolean - """ - try: - self.get_loc(key) - return True - except KeyError: - return False - @Appender(_interval_shared_docs['to_tuples'] % dict( return_type="Index", examples=""" @@ -1137,6 +1116,10 @@ def equals(self, other): self.right.equals(other.right) and self.closed == other.closed) + @Appender(_interval_shared_docs['contains'] % _index_doc_kwargs) + def contains(self, other): + return self._data.contains(other) + @Appender(_interval_shared_docs['overlaps'] % _index_doc_kwargs) def overlaps(self, other): return self._data.overlaps(other) diff --git a/pandas/core/indexes/multi.py b/pandas/core/indexes/multi.py index a06d304fb5a22..628cf500621d8 100644 --- a/pandas/core/indexes/multi.py +++ b/pandas/core/indexes/multi.py @@ -922,8 +922,6 @@ def __contains__(self, key): except (LookupError, TypeError, ValueError): return False - contains = __contains__ - @Appender(_index_shared_docs['_shallow_copy']) def _shallow_copy(self, values=None, **kwargs): if values is not None: diff --git a/pandas/core/indexes/period.py b/pandas/core/indexes/period.py index dc11099c3e903..f61b2e679f0c8 100644 --- a/pandas/core/indexes/period.py +++ b/pandas/core/indexes/period.py @@ -433,8 +433,6 @@ def __contains__(self, key): except Exception: return False - contains = __contains__ - @cache_readonly def _int64index(self): return Int64Index._simple_new(self.asi8, name=self.name) diff --git a/pandas/core/indexing.py b/pandas/core/indexing.py index 1539feb2e0856..7e199c6c9f66b 100755 --- a/pandas/core/indexing.py +++ b/pandas/core/indexing.py @@ -2393,7 +2393,7 @@ def convert_to_index_sliceable(obj, key): elif isinstance(key, str): # we are an actual column - if obj._data.items.contains(key): + if key in obj._data.items: return None # We might have a datetimelike string that we can translate to a diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index b2f409837344a..a5e9f5902f565 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -753,23 +753,28 @@ def test_contains(self): assert Interval(3, 5) not in i assert Interval(-1, 0, closed='left') not in i - # To be removed, replaced by test_interval_new.py (see #16316, #16386) - def testcontains(self): + def test_contains_method(self): # can select values that are IN the range of a value i = IntervalIndex.from_arrays([0, 1], [1, 2]) - assert i.contains(0.1) - assert i.contains(0.5) - assert i.contains(1) - assert i.contains(Interval(0, 1)) - assert i.contains(Interval(0, 2)) + expected = np.array([False, False], dtype='bool') + actual = i.contains(0) + tm.assert_numpy_array_equal(actual, expected) + actual = i.contains(3) + tm.assert_numpy_array_equal(actual, expected) - # these overlaps completely - assert i.contains(Interval(0, 3)) - assert i.contains(Interval(1, 3)) + expected = np.array([True, False], dtype='bool') + actual = i.contains(0.5) + tm.assert_numpy_array_equal(actual, expected) + actual = i.contains(1) + tm.assert_numpy_array_equal(actual, expected) - assert not i.contains(20) - assert not i.contains(-20) + # __contains__ not implemented for "interval in interval", follow + # that for the contains method for now + with pytest.raises( + NotImplementedError, + match='contains not implemented for two'): + i.contains(Interval(0, 1)) def test_dropna(self, closed): @@ -939,11 +944,9 @@ def test_datetime(self, tz): assert iv_false not in index # .contains does check individual points - assert not index.contains(Timestamp('2000-01-01', tz=tz)) - assert index.contains(Timestamp('2000-01-01T12', tz=tz)) - assert index.contains(Timestamp('2000-01-02', tz=tz)) - assert index.contains(iv_true) - assert not index.contains(iv_false) + assert not index.contains(Timestamp('2000-01-01', tz=tz)).any() + assert index.contains(Timestamp('2000-01-01T12', tz=tz)).any() + assert index.contains(Timestamp('2000-01-02', tz=tz)).any() # test get_indexer start = Timestamp('1999-12-31T12:00', tz=tz) diff --git a/pandas/tests/indexes/period/test_indexing.py b/pandas/tests/indexes/period/test_indexing.py index 0801b36402870..27a690e58b70f 100644 --- a/pandas/tests/indexes/period/test_indexing.py +++ b/pandas/tests/indexes/period/test_indexing.py @@ -464,19 +464,13 @@ def test_contains(self): idx0 = pd.PeriodIndex(ps0) for p in ps0: - assert idx0.contains(p) assert p in idx0 - - assert idx0.contains(str(p)) assert str(p) in idx0 - assert idx0.contains('2017-09-01 00:00:01') assert '2017-09-01 00:00:01' in idx0 - assert idx0.contains('2017-09') assert '2017-09' in idx0 - assert not idx0.contains(p3) assert p3 not in idx0 def test_get_value(self): diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index 1de20dc765655..fc55887a933f8 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -2156,6 +2156,11 @@ def test_tab_complete_warning(self, ip): with provisionalcompleter('ignore'): list(ip.Completer.completions('idx.', 4)) + def test_deprecated_contains(self): + for index in self.indices.values(): + with tm.assert_produces_warning(FutureWarning): + index.contains(1) + class TestMixedIntIndex(Base): # Mostly the tests from common.py for which the results differ diff --git a/pandas/tests/indexes/test_range.py b/pandas/tests/indexes/test_range.py index 5f7f10e881ced..de756bf720a81 100644 --- a/pandas/tests/indexes/test_range.py +++ b/pandas/tests/indexes/test_range.py @@ -271,10 +271,12 @@ def test_cached_data(self): 91 in idx assert idx._cached_data is None - idx.contains(90) + with tm.assert_produces_warning(FutureWarning): + idx.contains(90) assert idx._cached_data is None - idx.contains(91) + with tm.assert_produces_warning(FutureWarning): + idx.contains(91) assert idx._cached_data is None idx.all()