Skip to content

Commit fafb129

Browse files
authored
BUG: Float64Index.astype('u8') returns Int64Index (#45309)
1 parent 793ac8b commit fafb129

File tree

10 files changed

+51
-11
lines changed

10 files changed

+51
-11
lines changed

doc/source/whatsnew/v1.5.0.rst

+2
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,8 @@ Conversion
186186
^^^^^^^^^^
187187
- Bug in :meth:`DataFrame.astype` not preserving subclasses (:issue:`40810`)
188188
- Bug in constructing a :class:`Series` from a float-containing list or a floating-dtype ndarray-like (e.g. ``dask.Array``) and an integer dtype raising instead of casting like we would with an ``np.ndarray`` (:issue:`40110`)
189+
- Bug in :meth:`Float64Index.astype` to unsigned integer dtype incorrectly casting to ``np.int64`` dtype (:issue:`45309`)
190+
- Bug in :meth:`Series.astype` and :meth:`DataFrame.astype` from floating dtype to unsigned integer dtype failing to raise in the presence of negative values (:issue:`45151`)
189191
-
190192

191193
Strings

pandas/_testing/__init__.py

+9-4
Original file line numberDiff line numberDiff line change
@@ -417,14 +417,19 @@ def all_timeseries_index_generator(k: int = 10) -> Iterable[Index]:
417417

418418

419419
# make series
420-
def makeFloatSeries(name=None):
420+
def make_rand_series(name=None, dtype=np.float64):
421421
index = makeStringIndex(_N)
422-
return Series(np.random.randn(_N), index=index, name=name)
422+
data = np.random.randn(_N)
423+
data = data.astype(dtype, copy=False)
424+
return Series(data, index=index, name=name)
425+
426+
427+
def makeFloatSeries(name=None):
428+
return make_rand_series(name=name)
423429

424430

425431
def makeStringSeries(name=None):
426-
index = makeStringIndex(_N)
427-
return Series(np.random.randn(_N), index=index, name=name)
432+
return make_rand_series(name=name)
428433

429434

430435
def makeObjectSeries(name=None):

pandas/conftest.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -728,7 +728,7 @@ def series_with_multilevel_index():
728728

729729

730730
_narrow_series = {
731-
f"{dtype.__name__}-series": tm.makeFloatSeries(name="a").astype(dtype)
731+
f"{dtype.__name__}-series": tm.make_rand_series(name="a", dtype=dtype)
732732
for dtype in tm.NARROW_NP_DTYPES
733733
}
734734

pandas/core/arrays/interval.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
npt,
4040
)
4141
from pandas.compat.numpy import function as nv
42+
from pandas.errors import IntCastingNaNError
4243
from pandas.util._decorators import Appender
4344

4445
from pandas.core.dtypes.common import (
@@ -906,7 +907,12 @@ def astype(self, dtype, copy: bool = True):
906907
# np.nan entries to int subtypes
907908
new_left = Index(self._left, copy=False).astype(dtype.subtype)
908909
new_right = Index(self._right, copy=False).astype(dtype.subtype)
909-
except TypeError as err:
910+
except IntCastingNaNError:
911+
# e.g test_subtype_integer
912+
raise
913+
except (TypeError, ValueError) as err:
914+
# e.g. test_subtype_integer_errors f8->u8 can be lossy
915+
# and raises ValueError
910916
msg = (
911917
f"Cannot convert {self.dtype} to {dtype}; subtypes are incompatible"
912918
)

pandas/core/dtypes/astype.py

+4
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,10 @@ def _astype_float_to_int_nansafe(
196196
raise IntCastingNaNError(
197197
"Cannot convert non-finite values (NA or inf) to integer"
198198
)
199+
if dtype.kind == "u":
200+
# GH#45151
201+
if not (values >= 0).all():
202+
raise ValueError(f"Cannot losslessly cast from {values.dtype} to {dtype}")
199203
return values.astype(dtype, copy=copy)
200204

201205

pandas/core/indexes/numeric.py

+4-1
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,10 @@ def astype(self, dtype, copy: bool = True):
246246
# GH 13149
247247
arr = astype_nansafe(self._values, dtype=dtype)
248248
if isinstance(self, Float64Index):
249-
return Int64Index(arr, name=self.name)
249+
if dtype.kind == "i":
250+
return Int64Index(arr, name=self.name)
251+
else:
252+
return UInt64Index(arr, name=self.name)
250253
else:
251254
return NumericIndex(arr, name=self.name, dtype=dtype)
252255
elif self._is_backward_compat_public_numeric_index:

pandas/tests/base/test_misc.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def test_memory_usage_components_series(series_with_simple_index):
130130

131131
@pytest.mark.parametrize("dtype", tm.NARROW_NP_DTYPES)
132132
def test_memory_usage_components_narrow_series(dtype):
133-
series = tm.makeFloatSeries(name="a").astype(dtype)
133+
series = tm.make_rand_series(name="a", dtype=dtype)
134134
total_usage = series.memory_usage(index=True)
135135
non_index_usage = series.memory_usage(index=False)
136136
index_usage = series.index.memory_usage()

pandas/tests/indexes/interval/test_astype.py

-3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
import numpy as np
44
import pytest
55

6-
from pandas.compat import is_platform_arm
7-
86
from pandas.core.dtypes.dtypes import (
97
CategoricalDtype,
108
IntervalDtype,
@@ -170,7 +168,6 @@ def test_subtype_integer_with_non_integer_borders(self, subtype):
170168
)
171169
tm.assert_index_equal(result, expected)
172170

173-
@pytest.mark.xfail(is_platform_arm(), reason="GH 41740")
174171
def test_subtype_integer_errors(self):
175172
# float64 -> uint64 fails with negative values
176173
index = interval_range(-10.0, 10.0)

pandas/tests/indexes/numeric/test_astype.py

+12
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,22 @@
1010
from pandas.core.indexes.api import (
1111
Float64Index,
1212
Int64Index,
13+
UInt64Index,
1314
)
1415

1516

1617
class TestAstype:
18+
def test_astype_float64_to_uint64(self):
19+
# GH#45309 used to incorrectly return Int64Index
20+
idx = Float64Index([0.0, 5.0, 10.0, 15.0, 20.0])
21+
result = idx.astype("u8")
22+
expected = UInt64Index([0, 5, 10, 15, 20])
23+
tm.assert_index_equal(result, expected)
24+
25+
idx_with_negatives = idx - 10
26+
with pytest.raises(ValueError, match="losslessly"):
27+
idx_with_negatives.astype(np.uint64)
28+
1729
def test_astype_float64_to_object(self):
1830
float_index = Float64Index([0.0, 2.5, 5.0, 7.5, 10.0])
1931
result = float_index.astype(object)

pandas/tests/series/methods/test_astype.py

+11
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,17 @@ def test_astype_cast_object_int_fail(self, dtype):
322322
with pytest.raises(ValueError, match=msg):
323323
arr.astype(dtype)
324324

325+
def test_astype_float_to_uint_negatives_raise(
326+
self, float_numpy_dtype, any_unsigned_int_numpy_dtype
327+
):
328+
# GH#45151
329+
# TODO: same for EA float/uint dtypes
330+
arr = np.arange(5).astype(float_numpy_dtype) - 3 # includes negatives
331+
ser = Series(arr)
332+
333+
with pytest.raises(ValueError, match="losslessly"):
334+
ser.astype(any_unsigned_int_numpy_dtype)
335+
325336
def test_astype_cast_object_int(self):
326337
arr = Series(["1", "2", "3", "4"], dtype=object)
327338
result = arr.astype(int)

0 commit comments

Comments
 (0)