Repository: pandas-dev/pandas

Issue:
BUG: Index constructor raises for a list of mixed closed Interval objects
#### Code Sample, a copy-pastable example if possible

```python-traceback
In [1]: import pandas as pd; pd.__version__
Out[1]: '0.25.0.dev0+865.g3b3b79157'

In [2]: ivs = [pd.Interval(0, 1, closed='left'), 
   ...:        pd.Interval(1, 2, closed='right')]

In [3]: pd.Index(ivs)
---------------------------------------------------------------------------
ValueError: intervals must all be closed on the same side
```
This propagates to other things failing with the same error, e.g. `ensure_index`.

#### Problem description
The `Index` constructor doesn't properly construct an `Index` from a list of mixed closed `Interval` objects.  Note that this works if `dtype=object` is explicitly passed:
```python-traceback
In [4]: pd.Index(ivs, dtype=object)
Out[4]: Index([[0, 1), (1, 2]], dtype='object')
```

Note that an `object` dtype `Index` is successfully created from a list of mixed frequency `Period` objects, which is conceptually similar:
```python-traceback
In [5]: periods = [pd.Period('2018Q1', freq='Q'),  
   ...:            pd.Period('2018-01-01', freq='D')]

In [6]: pd.Index(periods)
Out[6]: Index([2018Q1, 2018-01-01], dtype='object')
```
#### Expected Output
I'd expect an `object` dtype `Index` to be successfully created and produce the same output as `Out[4]`.

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit           : 3b3b7915723e014331210e64dfb7a9574305c3e9
python           : 3.7.3.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.19.14-041914-generic
machine          : x86_64
processor        : x86_64
byteorder        : little
LC_ALL           : None
LANG             : en_US.UTF-8
LOCALE           : en_US.UTF-8

pandas           : 0.25.0.dev0+865.g3b3b79157
numpy            : 1.16.4
pytz             : 2019.1
dateutil         : 2.8.0
pip              : 19.1.1
setuptools       : 41.0.1
Cython           : 0.29.10
pytest           : 4.6.2
hypothesis       : 4.23.6
sphinx           : 1.8.5
blosc            : None
feather          : None
xlsxwriter       : 1.1.8
lxml.etree       : 4.3.3
html5lib         : 1.0.1
pymysql          : None
psycopg2         : None
jinja2           : 2.10.1
IPython          : 7.5.0
pandas_datareader: None
bs4              : 4.7.1
bottleneck       : 1.2.1
fastparquet      : 0.3.0
gcsfs            : None
lxml.etree       : 4.3.3
matplotlib       : 3.1.0
numexpr          : 2.6.9
openpyxl         : 2.6.2
pandas_gbq       : None
pyarrow          : 0.11.1
pytables         : None
s3fs             : 0.2.1
scipy            : 1.2.1
sqlalchemy       : 1.3.4
tables           : 3.5.2
xarray           : 0.12.1
xlrd             : 1.2.0
xlwt             : 1.3.0
xlsxwriter       : 1.1.8

</details>



Discussion:

Resolving patch:
diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py
--- 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


Test patch:
diff --git a/pandas/tests/indexes/interval/test_construction.py b/pandas/tests/indexes/interval/test_construction.py
--- 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
--- 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',