From 30eed73023b060a610b6526b5bcde989c50dee52 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 16 Jan 2022 13:48:53 -0800 Subject: [PATCH] REF: share NumericIndex.astype+Index.astype --- pandas/core/indexes/base.py | 30 +++++++++++++++++++++++++++++- pandas/core/indexes/numeric.py | 32 -------------------------------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/pandas/core/indexes/base.py b/pandas/core/indexes/base.py index 2c5cbef9f86c2..ae2f3bf6255bc 100644 --- a/pandas/core/indexes/base.py +++ b/pandas/core/indexes/base.py @@ -51,6 +51,7 @@ from pandas.compat.numpy import function as nv from pandas.errors import ( DuplicateLabelError, + IntCastingNaNError, InvalidIndexError, ) from pandas.util._decorators import ( @@ -64,6 +65,7 @@ rewrite_exception, ) +from pandas.core.dtypes.astype import astype_nansafe from pandas.core.dtypes.cast import ( can_hold_element, common_dtype_categorical_compat, @@ -1050,6 +1052,14 @@ def astype(self, dtype, copy: bool = True): with rewrite_exception(type(values).__name__, type(self).__name__): new_values = values.astype(dtype, copy=copy) + elif is_float_dtype(self.dtype) and needs_i8_conversion(dtype): + # NB: this must come before the ExtensionDtype check below + # TODO: this differs from Series behavior; can/should we align them? + raise TypeError( + f"Cannot convert Float64Index to dtype {dtype}; integer " + "values are required for conversion" + ) + elif isinstance(dtype, ExtensionDtype): cls = dtype.construct_array_type() # Note: for RangeIndex and CategoricalDtype self vs self._values @@ -1058,13 +1068,31 @@ def astype(self, dtype, copy: bool = True): else: try: - new_values = values.astype(dtype, copy=copy) + if dtype == str: + # GH#38607 + new_values = values.astype(dtype, copy=copy) + else: + # GH#13149 specifically use astype_nansafe instead of astype + new_values = astype_nansafe(values, dtype=dtype, copy=copy) + except IntCastingNaNError: + raise except (TypeError, ValueError) as err: + if dtype.kind == "u" and "losslessly" in str(err): + # keep the message from _astype_float_to_int_nansafe + raise raise TypeError( f"Cannot cast {type(self).__name__} to dtype {dtype}" ) from err # pass copy=False because any copying will be done in the astype above + if self._is_backward_compat_public_numeric_index: + # this block is needed so e.g. NumericIndex[int8].astype("int32") returns + # NumericIndex[int32] and not Int64Index with dtype int64. + # When Int64Index etc. are removed from the code base, removed this also. + if isinstance(dtype, np.dtype) and is_numeric_dtype(dtype): + return self._constructor( + new_values, name=self.name, dtype=dtype, copy=False + ) return Index(new_values, name=self.name, dtype=new_values.dtype, copy=False) _index_shared_docs[ diff --git a/pandas/core/indexes/numeric.py b/pandas/core/indexes/numeric.py index ffd511b92da43..53bea5e6ff6b0 100644 --- a/pandas/core/indexes/numeric.py +++ b/pandas/core/indexes/numeric.py @@ -23,10 +23,8 @@ ) from pandas.util._exceptions import find_stack_level -from pandas.core.dtypes.astype import astype_nansafe from pandas.core.dtypes.common import ( is_dtype_equal, - is_extension_array_dtype, is_float, is_float_dtype, is_integer_dtype, @@ -34,7 +32,6 @@ is_scalar, is_signed_integer_dtype, is_unsigned_integer_dtype, - needs_i8_conversion, pandas_dtype, ) from pandas.core.dtypes.generic import ABCSeries @@ -232,35 +229,6 @@ def __contains__(self, key) -> bool: except (OverflowError, TypeError, ValueError): return False - @doc(Index.astype) - def astype(self, dtype, copy: bool = True): - dtype = pandas_dtype(dtype) - if is_float_dtype(self.dtype): - if needs_i8_conversion(dtype): - raise TypeError( - f"Cannot convert Float64Index to dtype {dtype}; integer " - "values are required for conversion" - ) - elif is_integer_dtype(dtype) and not is_extension_array_dtype(dtype): - # TODO(ExtensionIndex); this can change once we have an EA Index type - # GH 13149 - arr = astype_nansafe(self._values, dtype=dtype) - if isinstance(self, Float64Index): - if dtype.kind == "i": - return Int64Index(arr, name=self.name) - else: - return UInt64Index(arr, name=self.name) - else: - return NumericIndex(arr, name=self.name, dtype=dtype) - elif self._is_backward_compat_public_numeric_index: - # this block is needed so e.g. NumericIndex[int8].astype("int32") returns - # NumericIndex[int32] and not Int64Index with dtype int64. - # When Int64Index etc. are removed from the code base, removed this also. - if not is_extension_array_dtype(dtype) and is_numeric_dtype(dtype): - return self._constructor(self, dtype=dtype, copy=copy) - - return super().astype(dtype, copy=copy) - # ---------------------------------------------------------------- # Indexing Methods