From 11af5acea5b0a10fc89e769437e375984564c478 Mon Sep 17 00:00:00 2001 From: kvnwng Date: Mon, 22 Apr 2024 15:19:12 -0400 Subject: [PATCH 1/3] Fix multiplying Timedelta Series with nullable dtype --- pandas/core/arrays/timedeltas.py | 11 ++++++++ pandas/tests/series/test_arithmetic.py | 38 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 6eb4d234b349d..1999283378949 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -46,6 +46,8 @@ is_object_dtype, is_scalar, is_string_dtype, + is_numeric_dtype, + is_bool_dtype, pandas_dtype, ) from pandas.core.dtypes.dtypes import ExtensionDtype @@ -491,6 +493,15 @@ def __mul__(self, other) -> Self: result = [arr[n] * other[n] for n in range(len(self))] result = np.array(result) return type(self)._simple_new(result, dtype=result.dtype) + + if is_bool_dtype(other.dtype) or is_numeric_dtype(other.dtype): + # this multiplication will succeed only if all elements of other + # are any of the pandas nullable dtypes ('Int8', 'Int16', 'Int32', 'Int64', + # 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float32', 'Float64', or 'boolean'), + # so we will end up with timedelta64[ns]-dtyped result + result = [self._ndarray[n] * other[n] for n in range(len(self))] + result = np.array(result) + return type(self)(result) # numpy will accept float or int dtype, raise TypeError for others result = self._ndarray * other diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 44bf3475b85a6..18df7d96fb214 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -368,6 +368,44 @@ def test_add_list_to_masked_array_boolean(self, request): result = [True, None, True] + ser tm.assert_series_equal(result, expected) + def test_mul_nullable_dtype(self): + # GH#58054. Multiplying a TimeDelta Series with another series containing any of the + # Pandas nullable dtypes should work the same as with the Numpy nullable dtypes + td_series = pd.Series(np.random.rand(5) * timedelta(hours=1)) + other = pd.Series(np.random.rand(5) < 0.5) + + pandas_types = [ + "Int8", + "Int16", + "Int32", + "Int64", + "UInt8", + "UInt16", + "UInt32", + "UInt64", + "Float32", + "Float64", + "boolean", + ] + numpy_types = [ + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float32", + "float64", + "bool", + ] + + for dtype1, dtype2 in zip(pandas_types, numpy_types): + result = td_series * other.astype(dtype1) + expected = td_series * other.astype(dtype2) + tm.assert_series_equal(result, expected) + # ------------------------------------------------------------------ # Comparisons From 0792d34f2078fa704d727aa7bfd2e6763e60eb31 Mon Sep 17 00:00:00 2001 From: kvnwng Date: Mon, 22 Apr 2024 15:19:22 -0400 Subject: [PATCH 2/3] Add documentation --- doc/source/whatsnew/v3.0.0.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 7823f74b7a153..48c06dc049e4e 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -365,6 +365,7 @@ Timedelta ^^^^^^^^^ - Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`) - Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`) +- Bug in :meth:`TimedeltaArray._simple_new` which was raising ``AssertionError`` When multiplying a Series with a timedelta64 dtype with another Series that uses any of the pandas nullable dtypes ``('Int8', 'Int16', 'Int32', 'Int64', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float32', 'Float64', or 'boolean')`` (:issue:`58054`) Timezones ^^^^^^^^^ From 17f0fc7398eefd0e0d576e6a3be5449969b94b90 Mon Sep 17 00:00:00 2001 From: kvnwng Date: Mon, 22 Apr 2024 16:00:39 -0400 Subject: [PATCH 3/3] Fix errors raised by precommit --- pandas/core/arrays/timedeltas.py | 13 +++++++------ pandas/tests/series/test_arithmetic.py | 9 +++++---- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 1999283378949..4ae8da889ad1d 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -41,13 +41,13 @@ from pandas.core.dtypes.common import ( TD64NS_DTYPE, + is_bool_dtype, is_float_dtype, is_integer_dtype, + is_numeric_dtype, is_object_dtype, is_scalar, is_string_dtype, - is_numeric_dtype, - is_bool_dtype, pandas_dtype, ) from pandas.core.dtypes.dtypes import ExtensionDtype @@ -493,12 +493,13 @@ def __mul__(self, other) -> Self: result = [arr[n] * other[n] for n in range(len(self))] result = np.array(result) return type(self)._simple_new(result, dtype=result.dtype) - + if is_bool_dtype(other.dtype) or is_numeric_dtype(other.dtype): # this multiplication will succeed only if all elements of other - # are any of the pandas nullable dtypes ('Int8', 'Int16', 'Int32', 'Int64', - # 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float32', 'Float64', or 'boolean'), - # so we will end up with timedelta64[ns]-dtyped result + # are any of the pandas nullable dtypes ('Int8', 'Int16', 'Int32', + # 'Int64', 'UInt8', 'UInt16', 'UInt32', 'UInt64', 'Float32', + # 'Float64', or 'boolean'), so we will end up with + # timedelta64[ns]-dtyped result result = [self._ndarray[n] * other[n] for n in range(len(self))] result = np.array(result) return type(self)(result) diff --git a/pandas/tests/series/test_arithmetic.py b/pandas/tests/series/test_arithmetic.py index 18df7d96fb214..bbd1bc001fc16 100644 --- a/pandas/tests/series/test_arithmetic.py +++ b/pandas/tests/series/test_arithmetic.py @@ -369,10 +369,11 @@ def test_add_list_to_masked_array_boolean(self, request): tm.assert_series_equal(result, expected) def test_mul_nullable_dtype(self): - # GH#58054. Multiplying a TimeDelta Series with another series containing any of the - # Pandas nullable dtypes should work the same as with the Numpy nullable dtypes - td_series = pd.Series(np.random.rand(5) * timedelta(hours=1)) - other = pd.Series(np.random.rand(5) < 0.5) + # GH#58054. Multiplying a TimeDelta Series with another series containing + # any of the Pandas nullable dtypes should work the same as with the + # Numpy nullable dtypes + td_series = Series([timedelta(hours=1)]) + other = Series([True]) pandas_types = [ "Int8",