From 5b0e70440208a98997a2d60ec16f8eb0760dcf2b Mon Sep 17 00:00:00 2001 From: Jeremy Schendel Date: Mon, 1 Jul 2019 20:33:33 -0600 Subject: [PATCH] BUG: Fix Index constructor with mixed closed Intervals --- doc/source/whatsnew/v0.25.0.rst | 2 +- pandas/core/indexes/base.py | 6 +++++- pandas/tests/indexes/interval/test_construction.py | 14 ++++++++++++++ pandas/tests/indexes/test_base.py | 13 ++++++++++++- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/doc/source/whatsnew/v0.25.0.rst b/doc/source/whatsnew/v0.25.0.rst index fa8519c89b67f..ae8604152d4ce 100644 --- a/doc/source/whatsnew/v0.25.0.rst +++ b/doc/source/whatsnew/v0.25.0.rst @@ -762,7 +762,7 @@ Interval - Construction of :class:`Interval` is restricted to numeric, :class:`Timestamp` and :class:`Timedelta` endpoints (:issue:`23013`) - Fixed bug in :class:`Series`/:class:`DataFrame` not displaying ``NaN`` in :class:`IntervalIndex` with missing values (:issue:`25984`) -- +- Bug in :class:`Index` constructor where passing mixed closed :class:`Interval` objects would result in a ``ValueError`` instead of an ``object`` dtype ``Index`` (:issue:`27172`) Indexing ^^^^^^^^ diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 548791dafea1d..a0bd13f1e4f9e 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -421,7 +421,11 @@ def __new__(cls, data=None, dtype=None, copy=False, name=None, return Float64Index(subarr, copy=copy, name=name) elif inferred == 'interval': from .interval import IntervalIndex - return IntervalIndex(subarr, name=name, copy=copy) + try: + return IntervalIndex(subarr, name=name, copy=copy) + except ValueError: + # GH27172: mixed closed Intervals --> object dtype + pass elif inferred == 'boolean': # don't support boolean explicitly ATM pass diff --git a/pandas/tests/indexes/interval/test_construction.py b/pandas/tests/indexes/interval/test_construction.py index eb9b573cce91d..aabaaa0f297f9 100644 --- a/pandas/tests/indexes/interval/test_construction.py +++ b/pandas/tests/indexes/interval/test_construction.py @@ -364,6 +364,16 @@ def test_index_object_dtype(self, values_constructor): assert type(result) is Index tm.assert_numpy_array_equal(result.values, np.array(values)) + def test_index_mixed_closed(self): + # GH27172 + intervals = [Interval(0, 1, closed='left'), + Interval(1, 2, closed='right'), + Interval(2, 3, closed='neither'), + Interval(3, 4, closed='both')] + result = Index(intervals) + expected = Index(intervals, dtype=object) + tm.assert_index_equal(result, expected) + class TestFromIntervals(TestClassConstructors): """ @@ -388,3 +398,7 @@ def test_deprecated(self): @pytest.mark.skip(reason='parent class test that is not applicable') def test_index_object_dtype(self): pass + + @pytest.mark.skip(reason='parent class test that is not applicable') + def test_index_mixed_closed(self): + pass diff --git a/pandas/tests/indexes/test_base.py b/pandas/tests/indexes/test_base.py index b46e5835f4b41..a3563838e048d 100644 --- a/pandas/tests/indexes/test_base.py +++ b/pandas/tests/indexes/test_base.py @@ -22,7 +22,8 @@ CategoricalIndex, DataFrame, DatetimeIndex, Float64Index, Int64Index, PeriodIndex, RangeIndex, Series, TimedeltaIndex, UInt64Index, date_range, isna, period_range) -from pandas.core.index import _get_combined_index, ensure_index_from_sequences +from pandas.core.index import ( + _get_combined_index, ensure_index, ensure_index_from_sequences) from pandas.core.indexes.api import Index, MultiIndex from pandas.core.sorting import safe_sort from pandas.tests.indexes.common import Base @@ -2432,6 +2433,16 @@ def test_ensure_index_from_sequences(self, data, names, expected): result = ensure_index_from_sequences(data, names) tm.assert_index_equal(result, expected) + def test_ensure_index_mixed_closed_intervals(self): + # GH27172 + intervals = [pd.Interval(0, 1, closed='left'), + pd.Interval(1, 2, closed='right'), + pd.Interval(2, 3, closed='neither'), + pd.Interval(3, 4, closed='both')] + result = ensure_index(intervals) + expected = Index(intervals, dtype=object) + tm.assert_index_equal(result, expected) + @pytest.mark.parametrize('opname', ['eq', 'ne', 'le', 'lt', 'ge', 'gt', 'add', 'radd', 'sub', 'rsub',