Skip to content

Commit 982e112

Browse files
jschendelTomAugspurger
authored andcommitted
Fix IntervalDtype Bugs and Inconsistencies (#18997)
* Fix IntervalDtype Bugs and Inconsistencies * remove code for unsupported dtypes and remove 'interval[]'
1 parent 055bfa6 commit 982e112

File tree

3 files changed

+74
-55
lines changed

3 files changed

+74
-55
lines changed

doc/source/whatsnew/v0.23.0.txt

+1
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ Other API Changes
270270
- Subtraction of :class:`Series` with timezone-aware ``dtype='datetime64[ns]'`` with mis-matched timezones will raise ``TypeError`` instead of ``ValueError`` (issue:`18817`)
271271
- :class:`IntervalIndex` and ``IntervalDtype`` no longer support categorical, object, and string subtypes (:issue:`19016`)
272272
- The default ``Timedelta`` constructor now accepts an ``ISO 8601 Duration`` string as an argument (:issue:`19040`)
273+
- ``IntervalDtype`` now returns ``True`` when compared against ``'interval'`` regardless of subtype, and ``IntervalDtype.name`` now returns ``'interval'`` regardless of subtype (:issue:`18980`)
273274

274275
.. _whatsnew_0230.deprecations:
275276

pandas/core/dtypes/dtypes.py

+14-20
Original file line numberDiff line numberDiff line change
@@ -626,6 +626,7 @@ class IntervalDtype(ExtensionDtype):
626626
627627
THIS IS NOT A REAL NUMPY DTYPE
628628
"""
629+
name = 'interval'
629630
type = IntervalDtypeType
630631
kind = None
631632
str = '|O08'
@@ -653,8 +654,8 @@ def __new__(cls, subtype=None):
653654
u.subtype = None
654655
return u
655656
elif (isinstance(subtype, compat.string_types) and
656-
subtype == 'interval'):
657-
subtype = ''
657+
subtype.lower() == 'interval'):
658+
subtype = None
658659
else:
659660
if isinstance(subtype, compat.string_types):
660661
m = cls._match.search(subtype)
@@ -666,11 +667,6 @@ def __new__(cls, subtype=None):
666667
except TypeError:
667668
raise ValueError("could not construct IntervalDtype")
668669

669-
if subtype is None:
670-
u = object.__new__(cls)
671-
u.subtype = None
672-
return u
673-
674670
if is_categorical_dtype(subtype) or is_string_dtype(subtype):
675671
# GH 19016
676672
msg = ('category, object, and string subtypes are not supported '
@@ -692,31 +688,29 @@ def construct_from_string(cls, string):
692688
if its not possible
693689
"""
694690
if isinstance(string, compat.string_types):
695-
try:
696-
return cls(string)
697-
except ValueError:
698-
pass
699-
raise TypeError("could not construct IntervalDtype")
691+
return cls(string)
692+
msg = "a string needs to be passed, got type {typ}"
693+
raise TypeError(msg.format(typ=type(string)))
700694

701695
def __unicode__(self):
702696
if self.subtype is None:
703697
return "interval"
704698
return "interval[{subtype}]".format(subtype=self.subtype)
705699

706-
@property
707-
def name(self):
708-
return str(self)
709-
710700
def __hash__(self):
711701
# make myself hashable
712702
return hash(str(self))
713703

714704
def __eq__(self, other):
715705
if isinstance(other, compat.string_types):
716-
return other == self.name or other == self.name.title()
717-
718-
return (isinstance(other, IntervalDtype) and
719-
self.subtype == other.subtype)
706+
return other.lower() in (self.name.lower(), str(self).lower())
707+
elif not isinstance(other, IntervalDtype):
708+
return False
709+
elif self.subtype is None or other.subtype is None:
710+
# None should match any subtype
711+
return True
712+
else:
713+
return self.subtype == other.subtype
720714

721715
@classmethod
722716
def is_dtype(cls, dtype):

pandas/tests/dtypes/test_dtypes.py

+59-35
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ def test_hash_vs_equality(self):
433433
assert dtype2 == dtype
434434
assert dtype3 == dtype
435435
assert dtype is dtype2
436-
assert dtype2 is dtype
436+
assert dtype2 is dtype3
437437
assert dtype3 is dtype
438438
assert hash(dtype) == hash(dtype2)
439439
assert hash(dtype) == hash(dtype3)
@@ -451,14 +451,19 @@ def test_hash_vs_equality(self):
451451
assert hash(dtype2) == hash(dtype2)
452452
assert hash(dtype2) == hash(dtype3)
453453

454-
def test_construction(self):
455-
with pytest.raises(ValueError):
456-
IntervalDtype('xx')
454+
@pytest.mark.parametrize('subtype', [
455+
'interval[int64]', 'Interval[int64]', 'int64', np.dtype('int64')])
456+
def test_construction(self, subtype):
457+
i = IntervalDtype(subtype)
458+
assert i.subtype == np.dtype('int64')
459+
assert is_interval_dtype(i)
457460

458-
for s in ['interval[int64]', 'Interval[int64]', 'int64']:
459-
i = IntervalDtype(s)
460-
assert i.subtype == np.dtype('int64')
461-
assert is_interval_dtype(i)
461+
@pytest.mark.parametrize('subtype', [None, 'interval', 'Interval'])
462+
def test_construction_generic(self, subtype):
463+
# generic
464+
i = IntervalDtype(subtype)
465+
assert i.subtype is None
466+
assert is_interval_dtype(i)
462467

463468
@pytest.mark.parametrize('subtype', [
464469
CategoricalDtype(list('abc'), False),
@@ -471,17 +476,27 @@ def test_construction_not_supported(self, subtype):
471476
with tm.assert_raises_regex(TypeError, msg):
472477
IntervalDtype(subtype)
473478

474-
def test_construction_generic(self):
475-
# generic
476-
i = IntervalDtype('interval')
477-
assert i.subtype == ''
478-
assert is_interval_dtype(i)
479-
assert str(i) == 'interval[]'
479+
def test_construction_errors(self):
480+
msg = 'could not construct IntervalDtype'
481+
with tm.assert_raises_regex(ValueError, msg):
482+
IntervalDtype('xx')
480483

481-
i = IntervalDtype()
482-
assert i.subtype is None
483-
assert is_interval_dtype(i)
484-
assert str(i) == 'interval'
484+
def test_construction_from_string(self):
485+
result = IntervalDtype('interval[int64]')
486+
assert is_dtype_equal(self.dtype, result)
487+
result = IntervalDtype.construct_from_string('interval[int64]')
488+
assert is_dtype_equal(self.dtype, result)
489+
490+
@pytest.mark.parametrize('string', [
491+
'foo', 'interval[foo]', 'foo[int64]', 0, 3.14, ('a', 'b'), None])
492+
def test_construction_from_string_errors(self, string):
493+
if isinstance(string, string_types):
494+
error, msg = ValueError, 'could not construct IntervalDtype'
495+
else:
496+
error, msg = TypeError, 'a string needs to be passed, got type'
497+
498+
with tm.assert_raises_regex(error, msg):
499+
IntervalDtype.construct_from_string(string)
485500

486501
def test_subclass(self):
487502
a = IntervalDtype('interval[int64]')
@@ -506,36 +521,45 @@ def test_is_dtype(self):
506521
assert not IntervalDtype.is_dtype(np.int64)
507522
assert not IntervalDtype.is_dtype(np.float64)
508523

509-
def test_identity(self):
510-
assert (IntervalDtype('interval[int64]') ==
511-
IntervalDtype('interval[int64]'))
512-
513524
def test_coerce_to_dtype(self):
514525
assert (_coerce_to_dtype('interval[int64]') ==
515526
IntervalDtype('interval[int64]'))
516527

517-
def test_construction_from_string(self):
518-
result = IntervalDtype('interval[int64]')
519-
assert is_dtype_equal(self.dtype, result)
520-
result = IntervalDtype.construct_from_string('interval[int64]')
521-
assert is_dtype_equal(self.dtype, result)
522-
with pytest.raises(TypeError):
523-
IntervalDtype.construct_from_string('foo')
524-
with pytest.raises(TypeError):
525-
IntervalDtype.construct_from_string('interval[foo]')
526-
with pytest.raises(TypeError):
527-
IntervalDtype.construct_from_string('foo[int64]')
528-
529528
def test_equality(self):
530529
assert is_dtype_equal(self.dtype, 'interval[int64]')
531530
assert is_dtype_equal(self.dtype, IntervalDtype('int64'))
532-
assert is_dtype_equal(self.dtype, IntervalDtype('int64'))
533531
assert is_dtype_equal(IntervalDtype('int64'), IntervalDtype('int64'))
534532

535533
assert not is_dtype_equal(self.dtype, 'int64')
536534
assert not is_dtype_equal(IntervalDtype('int64'),
537535
IntervalDtype('float64'))
538536

537+
@pytest.mark.parametrize('subtype', [
538+
None, 'interval', 'Interval', 'int64', 'uint64', 'float64',
539+
'complex128', 'datetime64', 'timedelta64', PeriodDtype('Q')])
540+
def test_equality_generic(self, subtype):
541+
# GH 18980
542+
dtype = IntervalDtype(subtype)
543+
assert is_dtype_equal(dtype, 'interval')
544+
assert is_dtype_equal(dtype, IntervalDtype())
545+
546+
@pytest.mark.parametrize('subtype', [
547+
'int64', 'uint64', 'float64', 'complex128', 'datetime64',
548+
'timedelta64', PeriodDtype('Q')])
549+
def test_name_repr(self, subtype):
550+
# GH 18980
551+
dtype = IntervalDtype(subtype)
552+
expected = 'interval[{subtype}]'.format(subtype=subtype)
553+
assert str(dtype) == expected
554+
assert dtype.name == 'interval'
555+
556+
@pytest.mark.parametrize('subtype', [None, 'interval', 'Interval'])
557+
def test_name_repr_generic(self, subtype):
558+
# GH 18980
559+
dtype = IntervalDtype(subtype)
560+
assert str(dtype) == 'interval'
561+
assert dtype.name == 'interval'
562+
539563
def test_basic(self):
540564
assert is_interval_dtype(self.dtype)
541565

0 commit comments

Comments
 (0)