Repository: pandas-dev/pandas

Issue:
ENH: Add is_empty attribute to Interval structures
An `is_empty` attribute would be useful, as it is often necessary to special case empty intervals when dealing with `Interval` methods such as `overlaps`, `contains`, etc. (xref #26893)

For a scalar `Interval` this should return a boolean:
```python
In [2]: pd.Interval(0, 0, closed='neither').is_empty
Out[2]: True

In [3]: pd.Interval(0, 1, closed='neither').is_empty
Out[3]: False
```

For an `IntervalIndex` or `IntervalArray` this should return a boolean ndarray positionally indicating if a given `Interval` is empty:

```python
In [4]: pd.IntervalIndex.from_tuples([(0, 0), (0, 1)]).is_empty
Out[4]: array([ True, False])
```


Discussion:

Resolving patch:
diff --git a/pandas/core/arrays/interval.py b/pandas/core/arrays/interval.py
--- a/pandas/core/arrays/interval.py
+++ b/pandas/core/arrays/interval.py
@@ -66,6 +66,7 @@
 closed
 mid
 length
+is_empty
 is_non_overlapping_monotonic
 %(extra_attributes)s\
 


Test patch:
diff --git a/pandas/tests/arrays/interval/test_interval.py b/pandas/tests/arrays/interval/test_interval.py
--- a/pandas/tests/arrays/interval/test_interval.py
+++ b/pandas/tests/arrays/interval/test_interval.py
@@ -2,7 +2,9 @@
 import pytest
 
 import pandas as pd
-from pandas import Index, Interval, IntervalIndex, date_range, timedelta_range
+from pandas import (
+    Index, Interval, IntervalIndex, Timedelta, Timestamp, date_range,
+    timedelta_range)
 from pandas.core.arrays import IntervalArray
 import pandas.util.testing as tm
 
@@ -23,6 +25,23 @@ def left_right_dtypes(request):
     return request.param
 
 
+class TestAttributes:
+    @pytest.mark.parametrize('left, right', [
+        (0, 1),
+        (Timedelta('0 days'), Timedelta('1 day')),
+        (Timestamp('2018-01-01'), Timestamp('2018-01-02')),
+        pytest.param(Timestamp('2018-01-01', tz='US/Eastern'),
+                     Timestamp('2018-01-02', tz='US/Eastern'),
+                     marks=pytest.mark.xfail(strict=True, reason='GH 27011'))])
+    @pytest.mark.parametrize('constructor', [IntervalArray, IntervalIndex])
+    def test_is_empty(self, constructor, left, right, closed):
+        # GH27219
+        tuples = [(left, left), (left, right), np.nan]
+        expected = np.array([closed != 'both', False, False])
+        result = constructor.from_tuples(tuples, closed=closed).is_empty
+        tm.assert_numpy_array_equal(result, expected)
+
+
 class TestMethods:
 
     @pytest.mark.parametrize('new_closed', [
diff --git a/pandas/tests/scalar/interval/test_interval.py b/pandas/tests/scalar/interval/test_interval.py
--- a/pandas/tests/scalar/interval/test_interval.py
+++ b/pandas/tests/scalar/interval/test_interval.py
@@ -94,6 +94,24 @@ def test_length_timestamp(self, tz, left, right, expected):
         expected = Timedelta(expected)
         assert result == expected
 
+    @pytest.mark.parametrize('left, right', [
+        (0, 1),
+        (Timedelta('0 days'), Timedelta('1 day')),
+        (Timestamp('2018-01-01'), Timestamp('2018-01-02')),
+        (Timestamp('2018-01-01', tz='US/Eastern'),
+         Timestamp('2018-01-02', tz='US/Eastern'))])
+    def test_is_empty(self, left, right, closed):
+        # GH27219
+        # non-empty always return False
+        iv = Interval(left, right, closed)
+        assert iv.is_empty is False
+
+        # same endpoint is empty except when closed='both' (contains one point)
+        iv = Interval(left, left, closed)
+        result = iv.is_empty
+        expected = closed != 'both'
+        assert result is expected
+
     @pytest.mark.parametrize('left, right', [
         ('a', 'z'),
         (('a', 'b'), ('c', 'd')),