Skip to content

Commit 2816531

Browse files
committed
BUG: non-64-bit numeric dtypes should raise in IntervalDtype constructor
1 parent ddceb8e commit 2816531

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
@@ -1237,6 +1237,8 @@ Interval
12371237
- Bug in :meth:`IntervalIndex.is_overlapping` incorrect output if interval has duplicate left boundaries (:issue:`49581`)
12381238
- Bug in :meth:`Series.infer_objects` failing to infer :class:`IntervalDtype` for an object series of :class:`Interval` objects (:issue:`50090`)
12391239
- Bug in :meth:`Series.shift` with :class:`IntervalDtype` and invalid null ``fill_value`` failing to raise ``TypeError`` (:issue:`51258`)
1240+
- 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.
1241+
Supplying non-64-bit numeric subtypes to :class:`IntervalDtype` now raises a ``TypeError`` (:issue:`45412`)
12401242
-
12411243

12421244
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
@@ -861,6 +861,56 @@ def is_int64_dtype(arr_or_dtype) -> bool:
861861
return _is_dtype_type(arr_or_dtype, classes(np.int64))
862862

863863

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