From 9428144c029061b4164af90f6927e90e016026ed Mon Sep 17 00:00:00 2001 From: jschendel Date: Mon, 2 Jul 2018 18:59:20 -0600 Subject: [PATCH] ENH: Add set_closed method to IntervalIndex --- doc/source/api.rst | 1 + doc/source/whatsnew/v0.24.0.txt | 1 + pandas/core/indexes/interval.py | 36 +++++++++++++++++++ .../tests/indexes/interval/test_interval.py | 17 +++++++++ 4 files changed, 55 insertions(+) diff --git a/doc/source/api.rst b/doc/source/api.rst index 8dc5d0e9fc023..f19e5534fc1b3 100644 --- a/doc/source/api.rst +++ b/doc/source/api.rst @@ -1642,6 +1642,7 @@ IntervalIndex Components IntervalIndex.is_non_overlapping_monotonic IntervalIndex.get_loc IntervalIndex.get_indexer + IntervalIndex.set_closed .. _api.multiindex: diff --git a/doc/source/whatsnew/v0.24.0.txt b/doc/source/whatsnew/v0.24.0.txt index dfb7a3675fdd5..cc17a5cbf13eb 100644 --- a/doc/source/whatsnew/v0.24.0.txt +++ b/doc/source/whatsnew/v0.24.0.txt @@ -41,6 +41,7 @@ Other Enhancements (:issue:`21627`) - New method :meth:`HDFStore.walk` will recursively walk the group hierarchy of an HDF5 file (:issue:`10932`) - :meth:`Series.nlargest`, :meth:`Series.nsmallest`, :meth:`DataFrame.nlargest`, and :meth:`DataFrame.nsmallest` now accept the value ``"all"`` for the ``keep` argument. This keeps all ties for the nth largest/smallest value (:issue:`16818`) +- :class:`IntervalIndex` has gained the :meth:`~IntervalIndex.set_closed` method to change the existing ``closed`` value (:issue:`21670`) - .. _whatsnew_0240.api_breaking: diff --git a/pandas/core/indexes/interval.py b/pandas/core/indexes/interval.py index 80619c7beb28c..e653c88ab019f 100644 --- a/pandas/core/indexes/interval.py +++ b/pandas/core/indexes/interval.py @@ -180,6 +180,7 @@ class IntervalIndex(IntervalMixin, Index): from_tuples get_indexer get_loc + set_closed Examples --------- @@ -708,6 +709,41 @@ def closed(self): """ return self._closed + def set_closed(self, closed): + """ + Return an IntervalIndex identical to the current one, but closed on the + specified side + + .. versionadded:: 0.24.0 + + Parameters + ---------- + closed : {'left', 'right', 'both', 'neither'} + Whether the intervals are closed on the left-side, right-side, both + or neither. + + Returns + ------- + new_index : IntervalIndex + + Examples + -------- + >>> index = pd.interval_range(0, 3) + >>> index + IntervalIndex([(0, 1], (1, 2], (2, 3]] + closed='right', + dtype='interval[int64]') + >>> index.set_closed('both') + IntervalIndex([[0, 1], [1, 2], [2, 3]] + closed='both', + dtype='interval[int64]') + """ + if closed not in _VALID_CLOSED: + msg = "invalid option for 'closed': {closed}" + raise ValueError(msg.format(closed=closed)) + + return self._shallow_copy(closed=closed) + @property def length(self): """ diff --git a/pandas/tests/indexes/interval/test_interval.py b/pandas/tests/indexes/interval/test_interval.py index 6a7330f8cfb68..431833f2627d8 100644 --- a/pandas/tests/indexes/interval/test_interval.py +++ b/pandas/tests/indexes/interval/test_interval.py @@ -977,3 +977,20 @@ def test_to_tuples_na(self, tuples, na_tuple): assert all(isna(x) for x in result_na) else: assert isna(result_na) + + @pytest.mark.parametrize('new_closed', [ + 'left', 'right', 'both', 'neither']) + def test_set_closed(self, name, closed, new_closed): + # GH 21670 + index = interval_range(0, 5, closed=closed, name=name) + result = index.set_closed(new_closed) + expected = interval_range(0, 5, closed=new_closed, name=name) + tm.assert_index_equal(result, expected) + + @pytest.mark.parametrize('bad_closed', ['foo', 10, 'LEFT', True, False]) + def test_set_closed_errors(self, bad_closed): + # GH 21670 + index = interval_range(0, 5) + msg = "invalid option for 'closed': {closed}".format(closed=bad_closed) + with tm.assert_raises_regex(ValueError, msg): + index.set_closed(bad_closed)