From 9f9c269e7f9caf50fba95484c8166ed53fb6b8c2 Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 10 Jan 2021 12:23:00 -0800 Subject: [PATCH] REF: implement dtypes.cast.can_hold_element --- pandas/core/dtypes/cast.py | 43 ++++++++++++++++++++++++++++ pandas/core/internals/blocks.py | 50 ++++----------------------------- 2 files changed, 48 insertions(+), 45 deletions(-) diff --git a/pandas/core/dtypes/cast.py b/pandas/core/dtypes/cast.py index b1b7c28c04ebd..00c1d420fdb98 100644 --- a/pandas/core/dtypes/cast.py +++ b/pandas/core/dtypes/cast.py @@ -1880,3 +1880,46 @@ def validate_numeric_casting(dtype: np.dtype, value: Scalar) -> None: and is_bool(value) ): raise ValueError("Cannot assign bool to float/integer series") + + +def can_hold_element(dtype: np.dtype, element: Any) -> bool: + """ + Can we do an inplace setitem with this element in an array with this dtype? + + Parameters + ---------- + dtype : np.dtype + element : Any + + Returns + ------- + bool + """ + tipo = maybe_infer_dtype_type(element) + + if dtype.kind in ["i", "u"]: + if tipo is not None: + return tipo.kind in ["i", "u"] and dtype.itemsize >= tipo.itemsize + + # We have not inferred an integer from the dtype + # check if we have a builtin int or a float equal to an int + return is_integer(element) or (is_float(element) and element.is_integer()) + + elif dtype.kind == "f": + if tipo is not None: + return tipo.kind in ["f", "i", "u"] + return lib.is_integer(element) or lib.is_float(element) + + elif dtype.kind == "c": + if tipo is not None: + return tipo.kind in ["c", "f", "i", "u"] + return ( + lib.is_integer(element) or lib.is_complex(element) or lib.is_float(element) + ) + + elif dtype.kind == "b": + if tipo is not None: + return tipo.kind == "b" + return lib.is_bool(element) + + raise NotImplementedError(dtype) diff --git a/pandas/core/internals/blocks.py b/pandas/core/internals/blocks.py index 653b5ccf5f1ba..9eb4bdc5dbae3 100644 --- a/pandas/core/internals/blocks.py +++ b/pandas/core/internals/blocks.py @@ -21,6 +21,7 @@ from pandas.core.dtypes.cast import ( astype_dt64_to_dt64tz, astype_nansafe, + can_hold_element, convert_scalar_for_putitemlike, find_common_type, infer_dtype_from, @@ -40,7 +41,6 @@ is_datetime64tz_dtype, is_dtype_equal, is_extension_array_dtype, - is_float, is_integer, is_list_like, is_object_dtype, @@ -678,11 +678,7 @@ def convert( def _can_hold_element(self, element: Any) -> bool: """ require the same dtype as ourselves """ - dtype = self.values.dtype.type - tipo = maybe_infer_dtype_type(element) - if tipo is not None: - return issubclass(tipo.type, dtype) - return isinstance(element, dtype) + raise NotImplementedError("Implemented on subclasses") def should_store(self, value: ArrayLike) -> bool: """ @@ -1910,24 +1906,14 @@ class NumericBlock(Block): is_numeric = True _can_hold_na = True + def _can_hold_element(self, element: Any) -> bool: + return can_hold_element(self.dtype, element) + class FloatBlock(NumericBlock): __slots__ = () is_float = True - def _can_hold_element(self, element: Any) -> bool: - tipo = maybe_infer_dtype_type(element) - if tipo is not None: - return issubclass(tipo.type, (np.floating, np.integer)) and not issubclass( - tipo.type, np.timedelta64 - ) - return isinstance( - element, (float, int, np.floating, np.int_) - ) and not isinstance( - element, - (bool, np.bool_, np.timedelta64), - ) - def to_native_types( self, na_rep="", float_format=None, decimal=".", quoting=None, **kwargs ): @@ -1966,32 +1952,12 @@ class ComplexBlock(NumericBlock): __slots__ = () is_complex = True - def _can_hold_element(self, element: Any) -> bool: - tipo = maybe_infer_dtype_type(element) - if tipo is not None: - return issubclass(tipo.type, (np.floating, np.integer, np.complexfloating)) - return isinstance( - element, (float, int, complex, np.float_, np.int_) - ) and not isinstance(element, (bool, np.bool_)) - class IntBlock(NumericBlock): __slots__ = () is_integer = True _can_hold_na = False - def _can_hold_element(self, element: Any) -> bool: - tipo = maybe_infer_dtype_type(element) - if tipo is not None: - return ( - issubclass(tipo.type, np.integer) - and not issubclass(tipo.type, np.timedelta64) - and self.dtype.itemsize >= tipo.itemsize - ) - # We have not inferred an integer from the dtype - # check if we have a builtin int or a float equal to an int - return is_integer(element) or (is_float(element) and element.is_integer()) - class DatetimeLikeBlockMixin(Block): """Mixin class for DatetimeBlock, DatetimeTZBlock, and TimedeltaBlock.""" @@ -2288,12 +2254,6 @@ class BoolBlock(NumericBlock): is_bool = True _can_hold_na = False - def _can_hold_element(self, element: Any) -> bool: - tipo = maybe_infer_dtype_type(element) - if tipo is not None: - return issubclass(tipo.type, np.bool_) - return isinstance(element, (bool, np.bool_)) - class ObjectBlock(Block): __slots__ = ()