Skip to content

Commit 1925fd5

Browse files
committed
BUG: non-64-bit numeric dtypes should raise in IntervalDtype constructor
1 parent c4caed6 commit 1925fd5

File tree

5 files changed

+72
-5
lines changed

5 files changed

+72
-5
lines changed

doc/source/whatsnew/v2.0.0.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1238,6 +1238,8 @@ Interval
12381238
- Bug in :meth:`IntervalIndex.is_overlapping` incorrect output if interval has duplicate left boundaries (:issue:`49581`)
12391239
- Bug in :meth:`Series.infer_objects` failing to infer :class:`IntervalDtype` for an object series of :class:`Interval` objects (:issue:`50090`)
12401240
- Bug in :meth:`Series.shift` with :class:`IntervalDtype` and invalid null ``fill_value`` failing to raise ``TypeError`` (:issue:`51258`)
1241+
- Bug in :class:`IntervalDtype` where it accepted non-64-bit numeric subtypes, even though :class:`arrays.InterArray` only can hold numeric data if it is 64-bit.
1242+
Supplying non-64-bit numeric subtypes to :class:`IntervalDtype` now raises a ``TypeError`` (:issue:`45412`)
12411243
-
12421244

12431245
Indexing

pandas/core/arrays/interval.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ def __new__(
270270
copy=copy,
271271
dtype=dtype,
272272
)
273+
1 / 0
273274

274275
if verify_integrity:
275276
cls._validate(left, right, dtype=dtype)

pandas/core/dtypes/common.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,56 @@ def is_int64_dtype(arr_or_dtype) -> bool:
863863
return _is_dtype_type(arr_or_dtype, classes(np.int64))
864864

865865

866+
def is_64bit_real_numeric_dtype(arr_or_dtype) -> bool:
867+
"""
868+
Check whether the provided array or dtype is of 64-bit dtype.
869+
870+
Parameters
871+
----------
872+
arr_or_dtype : array-like or dtype
873+
The array or dtype to check.
874+
875+
Returns
876+
-------
877+
boolean
878+
Whether or not the array or dtype is of 64-bit dtype.
879+
880+
Examples
881+
--------
882+
>>> is_64bit_real_numeric_dtype(str)
883+
False
884+
>>> is_64bit_real_numeric_dtype(np.int32)
885+
False
886+
>>> is_64bit_real_numeric_dtype(np.int64)
887+
True
888+
>>> is_64bit_real_numeric_dtype('int8')
889+
False
890+
>>> is_64bit_real_numeric_dtype('Int8')
891+
True
892+
>>> is_64bit_real_numeric_dtype(pd.Int64Dtype)
893+
True
894+
>>> is_64bit_real_numeric_dtype(float)
895+
True
896+
>>> is_64bit_real_numeric_dtype(np.uint64) # unsigned
897+
True
898+
>>> is_64bit_real_numeric_dtype(np.array(['a', 'b']))
899+
False
900+
>>> is_64bit_real_numeric_dtype(np.array([1, 2], dtype=np.int64))
901+
True
902+
>>> is_64bit_real_numeric_dtype(pd.Index([1, 2.])) # float
903+
True
904+
>>> is_64bit_real_numeric_dtype(np.array([1, 2], dtype=np.uint32))
905+
False
906+
"""
907+
return _is_dtype_type(
908+
arr_or_dtype, classes(np.int64, np.uint64, np.float64)
909+
) or _is_dtype(
910+
arr_or_dtype,
911+
lambda typ: isinstance(typ, ExtensionDtype)
912+
and typ.type in (np.int64, np.uint64, np.float64),
913+
)
914+
915+
866916
def is_datetime64_any_dtype(arr_or_dtype) -> bool:
867917
"""
868918
Check whether the provided array or dtype is of the datetime64 dtype.

pandas/core/dtypes/dtypes.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,6 +1082,8 @@ class IntervalDtype(PandasExtensionDtype):
10821082

10831083
def __new__(cls, subtype=None, closed: str_type | None = None):
10841084
from pandas.core.dtypes.common import (
1085+
is_64bit_real_numeric_dtype,
1086+
is_any_real_numeric_dtype,
10851087
is_string_dtype,
10861088
pandas_dtype,
10871089
)
@@ -1132,6 +1134,12 @@ def __new__(cls, subtype=None, closed: str_type | None = None):
11321134
"for IntervalDtype"
11331135
)
11341136
raise TypeError(msg)
1137+
elif is_any_real_numeric_dtype(subtype) and not is_64bit_real_numeric_dtype(
1138+
subtype
1139+
):
1140+
raise TypeError(
1141+
f"numeric subtype must be 64-bit numeric dtype, was {subtype}"
1142+
)
11351143

11361144
key = f"{subtype}{closed}"
11371145
try:

pandas/tests/dtypes/test_dtypes.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
from pandas.core.dtypes.base import _registry as registry
1010
from pandas.core.dtypes.common import (
11+
is_64bit_real_numeric_dtype,
1112
is_bool_dtype,
1213
is_categorical_dtype,
1314
is_datetime64_any_dtype,
@@ -16,6 +17,7 @@
1617
is_datetime64tz_dtype,
1718
is_dtype_equal,
1819
is_interval_dtype,
20+
is_numeric_dtype,
1921
is_period_dtype,
2022
is_string_dtype,
2123
)
@@ -597,6 +599,7 @@ def test_construction_generic(self, subtype):
597599
@pytest.mark.parametrize(
598600
"subtype",
599601
[
602+
*[x for x in tm.ALL_REAL_DTYPES if not is_64bit_real_numeric_dtype(x)],
600603
CategoricalDtype(list("abc"), False),
601604
CategoricalDtype(list("wxyz"), True),
602605
object,
@@ -607,11 +610,14 @@ def test_construction_generic(self, subtype):
607610
],
608611
)
609612
def test_construction_not_supported(self, subtype):
610-
# GH 19016
611-
msg = (
612-
"category, object, and string subtypes are not supported "
613-
"for IntervalDtype"
614-
)
613+
# GH19016, GH45412
614+
if is_numeric_dtype(subtype):
615+
msg = "numeric subtype must be 64-bit numeric dtype, was"
616+
else:
617+
msg = (
618+
"category, object, and string subtypes are not supported "
619+
"for IntervalDtype"
620+
)
615621
with pytest.raises(TypeError, match=msg):
616622
IntervalDtype(subtype)
617623

0 commit comments

Comments
 (0)