Skip to content

Commit

Permalink
BUG: FloatingArray * np.timedelta64 (#44772)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbrockmendel committed Dec 6, 2021
1 parent c1c202c commit ca81e6c
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 88 deletions.
1 change: 1 addition & 0 deletions doc/source/whatsnew/v1.4.0.rst
Expand Up @@ -644,6 +644,7 @@ Numeric
- Bug in arithmetic operations involving :class:`RangeIndex` where the result would have the incorrect ``name`` (:issue:`43962`)
- Bug in arithmetic operations involving :class:`Series` where the result could have the incorrect ``name`` when the operands having matching NA or matching tuple names (:issue:`44459`)
- Bug in division with ``IntegerDtype`` or ``BooleanDtype`` array and NA scalar incorrectly raising (:issue:`44685`)
- Bug in multiplying a :class:`Series` with ``FloatingDtype`` with a timedelta-like scalar incorrectly raising (:issue:`44772`)
-

Conversion
Expand Down
31 changes: 0 additions & 31 deletions pandas/core/arrays/boolean.py
Expand Up @@ -23,7 +23,6 @@

from pandas.core.dtypes.common import (
is_bool_dtype,
is_float,
is_float_dtype,
is_integer_dtype,
is_list_like,
Expand Down Expand Up @@ -532,35 +531,5 @@ def _arith_method(self, other, op):

return self._maybe_mask_result(result, mask, other, op_name)

def _maybe_mask_result(self, result, mask, other, op_name: str):
"""
Parameters
----------
result : array-like
mask : array-like bool
other : scalar or array-like
op_name : str
"""
# if we have a float operand we are by-definition
# a float result
# or our op is a divide
if (is_float_dtype(other) or is_float(other)) or (
op_name in ["rtruediv", "truediv"]
):
from pandas.core.arrays import FloatingArray

return FloatingArray(result, mask, copy=False)

elif is_bool_dtype(result):
return BooleanArray(result, mask, copy=False)

elif is_integer_dtype(result):
from pandas.core.arrays import IntegerArray

return IntegerArray(result, mask, copy=False)
else:
result[mask] = np.nan
return result

def __abs__(self):
return self.copy()
21 changes: 0 additions & 21 deletions pandas/core/arrays/floating.py
Expand Up @@ -354,27 +354,6 @@ def max(self, *, skipna=True, axis: int | None = 0, **kwargs):
nv.validate_max((), kwargs)
return super()._reduce("max", skipna=skipna, axis=axis)

def _maybe_mask_result(self, result, mask, other, op_name: str):
"""
Parameters
----------
result : array-like
mask : array-like bool
other : scalar or array-like
op_name : str
"""
# TODO are there cases we don't end up with float?
# if we have a float operand we are by-definition
# a float result
# or our op is a divide
# if (is_float_dtype(other) or is_float(other)) or (
# op_name in ["rtruediv", "truediv"]
# ):
# result[mask] = np.nan
# return result

return type(self)(result, mask, copy=False)


_dtype_docstring = """
An ExtensionDtype for {dtype} data.
Expand Down
29 changes: 0 additions & 29 deletions pandas/core/arrays/integer.py
Expand Up @@ -5,7 +5,6 @@
import numpy as np

from pandas._libs import (
iNaT,
lib,
missing as libmissing,
)
Expand All @@ -26,7 +25,6 @@
from pandas.core.dtypes.common import (
is_bool_dtype,
is_datetime64_dtype,
is_float,
is_float_dtype,
is_integer_dtype,
is_object_dtype,
Expand Down Expand Up @@ -427,33 +425,6 @@ def max(self, *, skipna=True, axis: int | None = 0, **kwargs):
nv.validate_max((), kwargs)
return super()._reduce("max", skipna=skipna, axis=axis)

def _maybe_mask_result(self, result, mask, other, op_name: str):
"""
Parameters
----------
result : array-like
mask : array-like bool
other : scalar or array-like
op_name : str
"""
# if we have a float operand we are by-definition
# a float result
# or our op is a divide
if (is_float_dtype(other) or is_float(other)) or (
op_name in ["rtruediv", "truediv"]
):
from pandas.core.arrays import FloatingArray

return FloatingArray(result, mask, copy=False)

if result.dtype == "timedelta64[ns]":
from pandas.core.arrays import TimedeltaArray

result[mask] = iNaT
return TimedeltaArray._simple_new(result)

return type(self)(result, mask, copy=False)


_dtype_docstring = """
An ExtensionDtype for {dtype} integer data.
Expand Down
45 changes: 45 additions & 0 deletions pandas/core/arrays/masked.py
Expand Up @@ -12,6 +12,7 @@
import numpy as np

from pandas._libs import (
iNaT,
lib,
missing as libmissing,
)
Expand Down Expand Up @@ -39,9 +40,11 @@
is_bool,
is_bool_dtype,
is_dtype_equal,
is_float,
is_float_dtype,
is_integer_dtype,
is_list_like,
is_numeric_dtype,
is_object_dtype,
is_scalar,
is_string_dtype,
Expand Down Expand Up @@ -543,6 +546,48 @@ def _cmp_method(self, other, op) -> BooleanArray:

return BooleanArray(result, mask, copy=False)

def _maybe_mask_result(self, result, mask, other, op_name: str):
"""
Parameters
----------
result : array-like
mask : array-like bool
other : scalar or array-like
op_name : str
"""
# if we have a float operand we are by-definition
# a float result
# or our op is a divide
if (
(is_float_dtype(other) or is_float(other))
or (op_name in ["rtruediv", "truediv"])
or (is_float_dtype(self.dtype) and is_numeric_dtype(result.dtype))
):
from pandas.core.arrays import FloatingArray

return FloatingArray(result, mask, copy=False)

elif is_bool_dtype(result):
from pandas.core.arrays import BooleanArray

return BooleanArray(result, mask, copy=False)

elif result.dtype == "timedelta64[ns]":
# e.g. test_numeric_arr_mul_tdscalar_numexpr_path
from pandas.core.arrays import TimedeltaArray

result[mask] = iNaT
return TimedeltaArray._simple_new(result)

elif is_integer_dtype(result):
from pandas.core.arrays import IntegerArray

return IntegerArray(result, mask, copy=False)

else:
result[mask] = np.nan
return result

def isna(self) -> np.ndarray:
return self._mask.copy()

Expand Down
4 changes: 0 additions & 4 deletions pandas/core/arrays/numeric.py
Expand Up @@ -14,7 +14,6 @@
missing as libmissing,
)
from pandas.compat.numpy import function as nv
from pandas.errors import AbstractMethodError

from pandas.core.dtypes.common import (
is_float,
Expand Down Expand Up @@ -80,9 +79,6 @@ class NumericArray(BaseMaskedArray):
Base class for IntegerArray and FloatingArray.
"""

def _maybe_mask_result(self, result, mask, other, op_name: str):
raise AbstractMethodError(self)

def _arith_method(self, other, op):
op_name = op.__name__
omask = None
Expand Down
11 changes: 8 additions & 3 deletions pandas/tests/arithmetic/test_numeric.py
Expand Up @@ -213,13 +213,18 @@ def test_numeric_arr_mul_tdscalar(self, scalar_td, numeric_idx, box_with_array):
],
ids=lambda x: type(x).__name__,
)
def test_numeric_arr_mul_tdscalar_numexpr_path(self, scalar_td, box_with_array):
@pytest.mark.parametrize("dtype", [np.int64, np.float64])
def test_numeric_arr_mul_tdscalar_numexpr_path(
self, dtype, scalar_td, box_with_array
):
# GH#44772 for the float64 case
box = box_with_array

arr = np.arange(2 * 10 ** 4).astype(np.int64)
arr_i8 = np.arange(2 * 10 ** 4).astype(np.int64, copy=False)
arr = arr_i8.astype(dtype, copy=False)
obj = tm.box_expected(arr, box, transpose=False)

expected = arr.view("timedelta64[D]").astype("timedelta64[ns]")
expected = arr_i8.view("timedelta64[D]").astype("timedelta64[ns]")
expected = tm.box_expected(expected, box, transpose=False)

result = obj * scalar_td
Expand Down

0 comments on commit ca81e6c

Please sign in to comment.