Skip to content

Commit 38b4e96

Browse files
topper-123Terji PetersenTerji Petersen
authored
BUG: NumericIndex should not support float16 dtype (#49536)
* BUG: NumericIndex should not support float16 dtype * make NumericIndex fail with float16 dtype * make NumericIndex fail with float16 dtype, II * fix failures * NotImplementedError * NotImplementedError II * fail on float16, but allow np.exp(int8_arrays) * fail on float16, but allow np.exp(int8_arrays) II * fix NumericIndex_ensure_dtype Co-authored-by: Terji Petersen <[email protected]> Co-authored-by: Terji Petersen <[email protected]>
1 parent 35a7f80 commit 38b4e96

File tree

10 files changed

+92
-16
lines changed

10 files changed

+92
-16
lines changed

pandas/core/algorithms.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,8 @@ def value_counts(
891891
else:
892892
values = _ensure_arraylike(values)
893893
keys, counts = value_counts_arraylike(values, dropna)
894+
if keys.dtype == np.float16:
895+
keys = keys.astype(np.float32)
894896

895897
# For backwards compatibility, we let Index do its normal type
896898
# inference, _except_ for if if infers from object to bool.

pandas/core/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,8 @@ def factorize(
11401140
codes, uniques = algorithms.factorize(
11411141
self._values, sort=sort, use_na_sentinel=use_na_sentinel
11421142
)
1143+
if uniques.dtype == np.float16:
1144+
uniques = uniques.astype(np.float32)
11431145

11441146
if isinstance(self, ABCIndex):
11451147
# preserve e.g. NumericIndex, preserve MultiIndex

pandas/core/indexes/base.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -881,6 +881,9 @@ def __array_ufunc__(self, ufunc: np.ufunc, method: str_t, *inputs, **kwargs):
881881
# i.e. np.divmod, np.modf, np.frexp
882882
return tuple(self.__array_wrap__(x) for x in result)
883883

884+
if result.dtype == np.float16:
885+
result = result.astype(np.float32)
886+
884887
return self.__array_wrap__(result)
885888

886889
def __array_wrap__(self, result, context=None):

pandas/core/indexes/numeric.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ class NumericIndex(Index):
7575
Notes
7676
-----
7777
An NumericIndex instance can **only** contain numpy int64/32/16/8, uint64/32/16/8 or
78-
float64/32/16 dtype. In particular, ``NumericIndex`` *can not* hold Pandas numeric
79-
dtypes (:class:`Int64Dtype`, :class:`Int32Dtype` etc.).
78+
float64/32 dtype. In particular, ``NumericIndex`` *can not* hold numpy float16
79+
dtype or Pandas numeric dtypes (:class:`Int64Dtype`, :class:`Int32Dtype` etc.).
8080
"""
8181

8282
_typ = "numericindex"
@@ -133,6 +133,10 @@ def _ensure_array(cls, data, dtype, copy: bool):
133133
Ensure we have a valid array to pass to _simple_new.
134134
"""
135135
cls._validate_dtype(dtype)
136+
if dtype == np.float16:
137+
138+
# float16 not supported (no indexing engine)
139+
raise NotImplementedError("float16 indexes are not supported")
136140

137141
if not isinstance(data, (np.ndarray, Index)):
138142
# Coerce to ndarray if not already ndarray or Index
@@ -176,6 +180,10 @@ def _ensure_array(cls, data, dtype, copy: bool):
176180
raise ValueError("Index data must be 1-dimensional")
177181

178182
subarr = np.asarray(subarr)
183+
if subarr.dtype == "float16":
184+
# float16 not supported (no indexing engine)
185+
raise NotImplementedError("float16 indexes are not implemented")
186+
179187
return subarr
180188

181189
@classmethod
@@ -202,6 +210,9 @@ def _ensure_dtype(cls, dtype: Dtype | None) -> np.dtype | None:
202210
dtype = pandas_dtype(dtype)
203211
if not isinstance(dtype, np.dtype):
204212
raise TypeError(f"{dtype} not a numpy type")
213+
elif dtype == np.float16:
214+
# float16 not supported (no indexing engine)
215+
raise NotImplementedError("float16 indexes are not supported")
205216

206217
if cls._is_backward_compat_public_numeric_index:
207218
# dtype for NumericIndex

pandas/tests/arithmetic/test_numeric.py

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
from datetime import timedelta
88
from decimal import Decimal
99
import operator
10-
from typing import Any
1110

1211
import numpy as np
1312
import pytest
@@ -72,15 +71,10 @@ def compare_op(series, other, op):
7271
# TODO: remove this kludge once mypy stops giving false positives here
7372
# List comprehension has incompatible type List[PandasObject]; expected List[RangeIndex]
7473
# See GH#29725
75-
ser_or_index: list[Any] = [Series, Index]
76-
lefts: list[Any] = [RangeIndex(10, 40, 10)]
77-
lefts.extend(
78-
[
79-
cls([10, 20, 30], dtype=dtype)
80-
for dtype in ["i1", "i2", "i4", "i8", "u1", "u2", "u4", "u8", "f2", "f4", "f8"]
81-
for cls in ser_or_index
82-
]
83-
)
74+
_ldtypes = ["i1", "i2", "i4", "i8", "u1", "u2", "u4", "u8", "f2", "f4", "f8"]
75+
lefts: list[Index | Series] = [RangeIndex(10, 40, 10)]
76+
lefts.extend([Series([10, 20, 30], dtype=dtype) for dtype in _ldtypes])
77+
lefts.extend([Index([10, 20, 30], dtype=dtype) for dtype in _ldtypes if dtype != "f2"])
8478

8579
# ------------------------------------------------------------------
8680
# Comparisons

pandas/tests/base/test_conversion.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ def test_iterable(self, index_or_series, method, dtype, rdtype):
6262
# gh-13258
6363
# coerce iteration to underlying python / pandas types
6464
typ = index_or_series
65+
if dtype == "float16" and issubclass(typ, pd.Index):
66+
with pytest.raises(NotImplementedError, match="float16 indexes are not "):
67+
typ([1], dtype=dtype)
68+
return
6569
s = typ([1], dtype=dtype)
6670
result = method(s)[0]
6771
assert isinstance(result, rdtype)
@@ -115,6 +119,10 @@ def test_iterable_map(self, index_or_series, dtype, rdtype):
115119
# gh-13236
116120
# coerce iteration to underlying python / pandas types
117121
typ = index_or_series
122+
if dtype == "float16" and issubclass(typ, pd.Index):
123+
with pytest.raises(NotImplementedError, match="float16 indexes are not "):
124+
typ([1], dtype=dtype)
125+
return
118126
s = typ([1], dtype=dtype)
119127
result = s.map(type)[0]
120128
if not isinstance(rdtype, tuple):

pandas/tests/base/test_value_counts.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ def test_value_counts(index_or_series_obj):
2828

2929
counter = collections.Counter(obj)
3030
expected = Series(dict(counter.most_common()), dtype=np.int64, name=obj.name)
31-
expected.index = expected.index.astype(obj.dtype)
31+
32+
if obj.dtype != np.float16:
33+
expected.index = expected.index.astype(obj.dtype)
34+
else:
35+
with pytest.raises(NotImplementedError, match="float16 indexes are not "):
36+
expected.index.astype(obj.dtype)
37+
return
3238

3339
if not isinstance(result.dtype, np.dtype):
3440
# i.e IntegerDtype
@@ -73,7 +79,13 @@ def test_value_counts_null(null_obj, index_or_series_obj):
7379
# np.nan would be duplicated, whereas None wouldn't
7480
counter = collections.Counter(obj.dropna())
7581
expected = Series(dict(counter.most_common()), dtype=np.int64)
76-
expected.index = expected.index.astype(obj.dtype)
82+
83+
if obj.dtype != np.float16:
84+
expected.index = expected.index.astype(obj.dtype)
85+
else:
86+
with pytest.raises(NotImplementedError, match="float16 indexes are not "):
87+
expected.index.astype(obj.dtype)
88+
return
7789

7890
result = obj.value_counts()
7991
if obj.duplicated().any():

pandas/tests/indexes/numeric/test_numeric.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,44 @@ def test_coerce_list(self):
471471
assert type(arr) is Index
472472

473473

474+
class TestFloat16Index:
475+
# float 16 indexes not supported
476+
# GH 49535
477+
_index_cls = NumericIndex
478+
479+
def test_constructor(self):
480+
index_cls = self._index_cls
481+
dtype = np.float16
482+
483+
msg = "float16 indexes are not supported"
484+
485+
# explicit construction
486+
with pytest.raises(NotImplementedError, match=msg):
487+
index_cls([1, 2, 3, 4, 5], dtype=dtype)
488+
489+
with pytest.raises(NotImplementedError, match=msg):
490+
index_cls(np.array([1, 2, 3, 4, 5]), dtype=dtype)
491+
492+
with pytest.raises(NotImplementedError, match=msg):
493+
index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
494+
495+
with pytest.raises(NotImplementedError, match=msg):
496+
index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
497+
498+
with pytest.raises(NotImplementedError, match=msg):
499+
index_cls([1.0, 2, 3, 4, 5], dtype=dtype)
500+
501+
with pytest.raises(NotImplementedError, match=msg):
502+
index_cls(np.array([1.0, 2, 3, 4, 5]), dtype=dtype)
503+
504+
# nan handling
505+
with pytest.raises(NotImplementedError, match=msg):
506+
index_cls([np.nan, np.nan], dtype=dtype)
507+
508+
with pytest.raises(NotImplementedError, match=msg):
509+
index_cls(np.array([np.nan]), dtype=dtype)
510+
511+
474512
class TestUIntNumericIndex(NumericInt):
475513

476514
_index_cls = NumericIndex

pandas/tests/indexes/test_numpy_compat.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,10 @@ def test_numpy_ufuncs_basic(index, func):
7777
# coerces to float (e.g. np.sin)
7878
with np.errstate(all="ignore"):
7979
result = func(index)
80-
exp = Index(func(index.values), name=index.name)
80+
arr_result = func(index.values)
81+
if arr_result.dtype == np.float16:
82+
arr_result = arr_result.astype(np.float32)
83+
exp = Index(arr_result, name=index.name)
8184

8285
tm.assert_index_equal(result, exp)
8386
if type(index) is not Index or index.dtype == bool:

pandas/tests/test_algos.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,10 @@ def test_factorize(self, index_or_series_obj, sort):
6666
constructor = Index
6767
if isinstance(obj, MultiIndex):
6868
constructor = MultiIndex.from_tuples
69-
expected_uniques = constructor(obj.unique())
69+
expected_arr = obj.unique()
70+
if expected_arr.dtype == np.float16:
71+
expected_arr = expected_arr.astype(np.float32)
72+
expected_uniques = constructor(expected_arr)
7073
if (
7174
isinstance(obj, Index)
7275
and expected_uniques.dtype == bool

0 commit comments

Comments
 (0)