Repository: pandas-dev/pandas

Issue:
BUG: Series(list_of_intervals) results in object dtype
#### Code Sample, a copy-pastable example if possible

Constructing a `Series` from a list of `Interval` objects results in an object dtype, and is not backed by an `IntervalArray`:
```python
In [2]: s = pd.Series([pd.Interval(0, 1), pd.Interval(1, 2), pd.Interval(2, 3)])

In [3]: s.dtype
Out[3]: dtype('O')

In [4]: s.values
Out[4]:
array([Interval(0, 1, closed='right'), Interval(1, 2, closed='right'),
       Interval(2, 3, closed='right')], dtype=object)
```

Note that constructing a `Series` from an `IntervalArray` results in the correct dtype and is backed by an `IntervalArray`:
```python
In [5]: s2 = pd.Series(pd.core.arrays.IntervalArray.from_breaks(range(4)))

In [6]: s2.dtype
Out[6]: interval[int64]

In [7]: s2.values
Out[7]:
IntervalArray([(0, 1], (1, 2], (2, 3]],
              closed='right',
              dtype='interval[int64]')
```
#### Problem description
The input data is not being inferred as interval dtype, but rather as object dtype, and is not being backed by an `IntervalArray`.

#### Expected Output
I'd expect to the `Series` to have an interval dtype and be backed by an `IntervalArray`.

#### Output of ``pd.show_versions()``

<details>

INSTALLED VERSIONS
------------------
commit: 82120016e6af85afb052d95710e498a6dc58c1ce
python: 3.6.1.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None

pandas: 0.24.0.dev0+948.g82120016e
pytest: 3.8.2
pip: 9.0.1
setuptools: 39.0.1
Cython: 0.28.2
numpy: 1.13.3
scipy: 1.0.0
pyarrow: 0.6.0
xarray: 0.9.6
IPython: 6.1.0
sphinx: 1.5.6
patsy: 0.4.1
dateutil: 2.6.0
pytz: 2017.2
blosc: None
bottleneck: 1.2.1
tables: 3.4.2
numexpr: 2.6.4
feather: 0.4.0
matplotlib: 2.0.2
openpyxl: 2.4.8
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 0.9.8
lxml: 3.8.0
bs4: None
html5lib: 0.999
sqlalchemy: 1.1.13
pymysql: None
psycopg2: None
jinja2: 2.9.6
s3fs: None
fastparquet: 0.1.5
pandas_gbq: None
pandas_datareader: None
gcsfs: None

</details>



Discussion:
For the record, `Index(List[Interval])` does result in an IntervalIndex.

We really need to rewrite `series._sanitize_array` and consolidate with `Index.__new__`. I'll try to make time for that today or tomorrow.

Resolving patch:
diff --git a/pandas/core/construction.py b/pandas/core/construction.py
--- a/pandas/core/construction.py
+++ b/pandas/core/construction.py
@@ -497,13 +497,8 @@ def sanitize_array(
 
         if is_object_dtype(subarr.dtype) and not is_object_dtype(dtype):
             inferred = lib.infer_dtype(subarr, skipna=False)
-            if inferred == "period":
-                from pandas.core.arrays import period_array
-
-                try:
-                    subarr = period_array(subarr)
-                except IncompatibleFrequency:
-                    pass
+            if inferred in {"interval", "period"}:
+                subarr = array(subarr)
 
     return subarr
 


Test patch:
diff --git a/pandas/tests/series/test_constructors.py b/pandas/tests/series/test_constructors.py
--- a/pandas/tests/series/test_constructors.py
+++ b/pandas/tests/series/test_constructors.py
@@ -27,7 +27,7 @@
     timedelta_range,
 )
 import pandas._testing as tm
-from pandas.core.arrays import period_array
+from pandas.core.arrays import IntervalArray, period_array
 
 
 class TestSeriesConstructors:
@@ -967,16 +967,34 @@ def test_constructor_with_naive_string_and_datetimetz_dtype(self, arg):
         expected = Series(pd.Timestamp(arg)).dt.tz_localize("CET")
         tm.assert_series_equal(result, expected)
 
-    def test_construction_interval(self):
+    @pytest.mark.parametrize("interval_constructor", [IntervalIndex, IntervalArray])
+    def test_construction_interval(self, interval_constructor):
         # construction from interval & array of intervals
-        index = IntervalIndex.from_breaks(np.arange(3), closed="right")
-        result = Series(index)
-        repr(result)
-        str(result)
-        tm.assert_index_equal(Index(result.values), index)
+        intervals = interval_constructor.from_breaks(np.arange(3), closed="right")
+        result = Series(intervals)
+        assert result.dtype == "interval[int64]"
+        tm.assert_index_equal(Index(result.values), Index(intervals))
 
-        result = Series(index.values)
-        tm.assert_index_equal(Index(result.values), index)
+    @pytest.mark.parametrize(
+        "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+    )
+    def test_constructor_infer_interval(self, data_constructor):
+        # GH 23563: consistent closed results in interval dtype
+        data = [pd.Interval(0, 1), pd.Interval(0, 2), None]
+        result = pd.Series(data_constructor(data))
+        expected = pd.Series(IntervalArray(data))
+        assert result.dtype == "interval[float64]"
+        tm.assert_series_equal(result, expected)
+
+    @pytest.mark.parametrize(
+        "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+    )
+    def test_constructor_interval_mixed_closed(self, data_constructor):
+        # GH 23563: mixed closed results in object dtype (not interval dtype)
+        data = [pd.Interval(0, 1, closed="both"), pd.Interval(0, 2, closed="neither")]
+        result = Series(data_constructor(data))
+        assert result.dtype == object
+        assert result.tolist() == data
 
     def test_construction_consistency(self):
 
@@ -993,17 +1011,16 @@ def test_construction_consistency(self):
         result = Series(s.values, dtype=s.dtype)
         tm.assert_series_equal(result, s)
 
-    def test_constructor_infer_period(self):
+    @pytest.mark.parametrize(
+        "data_constructor", [list, np.array], ids=["list", "ndarray[object]"]
+    )
+    def test_constructor_infer_period(self, data_constructor):
         data = [pd.Period("2000", "D"), pd.Period("2001", "D"), None]
-        result = pd.Series(data)
+        result = pd.Series(data_constructor(data))
         expected = pd.Series(period_array(data))
         tm.assert_series_equal(result, expected)
         assert result.dtype == "Period[D]"
 
-        data = np.asarray(data, dtype=object)
-        tm.assert_series_equal(result, expected)
-        assert result.dtype == "Period[D]"
-
     def test_constructor_period_incompatible_frequency(self):
         data = [pd.Period("2000", "D"), pd.Period("2001", "A")]
         result = pd.Series(data)