Skip to content

Commit 8809294

Browse files
committed
Merge remote-tracking branch 'upstream/main' into deps/optional/bump
2 parents 9eed7c4 + ade778e commit 8809294

File tree

7 files changed

+118
-0
lines changed

7 files changed

+118
-0
lines changed

doc/source/whatsnew/v3.0.0.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,7 @@ Timedelta
913913
- Accuracy improvement in :meth:`Timedelta.to_pytimedelta` to round microseconds consistently for large nanosecond based Timedelta (:issue:`57841`)
914914
- Bug in :class:`Timedelta` constructor failing to raise when passed an invalid keyword (:issue:`53801`)
915915
- Bug in :meth:`DataFrame.cumsum` which was raising ``IndexError`` if dtype is ``timedelta64[ns]`` (:issue:`57956`)
916+
- Bug in multiplication operations with ``timedelta64`` dtype failing to raise ``TypeError`` when multiplying by ``bool`` objects or dtypes (:issue:`58054`)
916917

917918
Timezones
918919
^^^^^^^^^
@@ -969,6 +970,7 @@ Missing
969970
^^^^^^^
970971
- Bug in :meth:`DataFrame.fillna` and :meth:`Series.fillna` that would ignore the ``limit`` argument on :class:`.ExtensionArray` dtypes (:issue:`58001`)
971972
- Bug in :meth:`NA.__and__`, :meth:`NA.__or__` and :meth:`NA.__xor__` when operating with ``np.bool_`` objects (:issue:`58427`)
973+
- Bug in ``divmod`` between :class:`NA` and ``Int64`` dtype objects (:issue:`62196`)
972974
-
973975

974976
MultiIndex
@@ -1137,6 +1139,7 @@ Other
11371139
- Bug in ``Series.list`` methods not preserving the original :class:`Index`. (:issue:`58425`)
11381140
- Bug in ``Series.list`` methods not preserving the original name. (:issue:`60522`)
11391141
- Bug in ``Series.replace`` when the Series was created from an :class:`Index` and Copy-On-Write is enabled (:issue:`61622`)
1142+
- Bug in ``divmod`` and ``rdivmod`` with :class:`DataFrame`, :class:`Series`, and :class:`Index` with ``bool`` dtypes failing to raise, which was inconsistent with ``__floordiv__`` behavior (:issue:`46043`)
11401143
- Bug in printing a :class:`DataFrame` with a :class:`DataFrame` stored in :attr:`DataFrame.attrs` raised a ``ValueError`` (:issue:`60455`)
11411144
- Bug in printing a :class:`Series` with a :class:`DataFrame` stored in :attr:`Series.attrs` raised a ``ValueError`` (:issue:`60568`)
11421145
- Fixed bug where the :class:`DataFrame` constructor misclassified array-like objects with a ``.name`` attribute as :class:`Series` or :class:`Index` (:issue:`61443`)

pandas/core/arrays/masked.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,10 @@ def _arith_method(self, other, op):
791791
# will be all-True, but since this is division, we want
792792
# to end up with floating dtype.
793793
result = result.astype(np.float64)
794+
elif op_name in {"divmod", "rdivmod"}:
795+
# GH#62196
796+
res = self._maybe_mask_result(result, mask)
797+
return res, res.copy()
794798
else:
795799
# Make sure we do this before the "pow" mask checks
796800
# to get an expected exception message on shape mismatch.

pandas/core/arrays/timedeltas.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,11 @@ def _add_offset(self, other):
472472
@unpack_zerodim_and_defer("__mul__")
473473
def __mul__(self, other) -> Self:
474474
if is_scalar(other):
475+
if lib.is_bool(other):
476+
raise TypeError(
477+
f"Cannot multiply '{self.dtype}' by bool, explicitly cast to "
478+
"integers instead"
479+
)
475480
# numpy will accept float and int, raise TypeError for others
476481
result = self._ndarray * other
477482
if result.dtype.kind != "m":
@@ -489,6 +494,13 @@ def __mul__(self, other) -> Self:
489494
if not hasattr(other, "dtype"):
490495
# list, tuple
491496
other = np.array(other)
497+
498+
if other.dtype.kind == "b":
499+
# GH#58054
500+
raise TypeError(
501+
f"Cannot multiply '{self.dtype}' by bool, explicitly cast to "
502+
"integers instead"
503+
)
492504
if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"):
493505
# Exclude timedelta64 here so we correctly raise TypeError
494506
# for that instead of ValueError

pandas/core/ops/array_ops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,8 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape):
585585
roperator.rfloordiv,
586586
operator.pow,
587587
roperator.rpow,
588+
divmod,
589+
roperator.rdivmod,
588590
}
589591

590592

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import pytest
2+
3+
from pandas import (
4+
DataFrame,
5+
Series,
6+
)
7+
import pandas._testing as tm
8+
9+
10+
def test_divmod_bool_raises(box_with_array):
11+
# GH#46043 // raises, so divmod should too
12+
ser = Series([True, False])
13+
obj = tm.box_expected(ser, box_with_array)
14+
15+
msg = "operator 'floordiv' not implemented for bool dtypes"
16+
with pytest.raises(NotImplementedError, match=msg):
17+
obj // obj
18+
19+
if box_with_array is DataFrame:
20+
msg = "operator 'floordiv' not implemented for bool dtypes"
21+
else:
22+
msg = "operator 'divmod' not implemented for bool dtypes"
23+
with pytest.raises(NotImplementedError, match=msg):
24+
divmod(obj, obj)
25+
26+
# go through __rdivmod__
27+
with pytest.raises(NotImplementedError, match=msg):
28+
divmod(True, obj)

pandas/tests/arithmetic/test_timedelta64.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from pandas._libs.tslibs import timezones
1212
from pandas.compat import WASM
1313
from pandas.errors import OutOfBoundsDatetime
14+
import pandas.util._test_decorators as td
1415

1516
import pandas as pd
1617
from pandas import (
@@ -1556,6 +1557,51 @@ def test_tdi_rmul_arraylike(self, other, box_with_array):
15561557
commute = tdi * other
15571558
tm.assert_equal(commute, expected)
15581559

1560+
def test_td64arr_mul_bool_scalar_raises(self, box_with_array):
1561+
# GH#58054
1562+
ser = Series(np.arange(5) * timedelta(hours=1))
1563+
obj = tm.box_expected(ser, box_with_array)
1564+
1565+
msg = r"Cannot multiply 'timedelta64\[ns\]' by bool"
1566+
with pytest.raises(TypeError, match=msg):
1567+
True * obj
1568+
with pytest.raises(TypeError, match=msg):
1569+
obj * True
1570+
with pytest.raises(TypeError, match=msg):
1571+
np.True_ * obj
1572+
with pytest.raises(TypeError, match=msg):
1573+
obj * np.True_
1574+
1575+
@pytest.mark.parametrize(
1576+
"dtype",
1577+
[
1578+
bool,
1579+
"boolean",
1580+
pytest.param("bool[pyarrow]", marks=td.skip_if_no("pyarrow")),
1581+
],
1582+
)
1583+
def test_td64arr_mul_bool_raises(self, dtype, box_with_array):
1584+
# GH#58054
1585+
ser = Series(np.arange(5) * timedelta(hours=1))
1586+
obj = tm.box_expected(ser, box_with_array)
1587+
1588+
other = Series(np.arange(5) < 0.5, dtype=dtype)
1589+
other = tm.box_expected(other, box_with_array)
1590+
1591+
msg = r"Cannot multiply 'timedelta64\[ns\]' by bool"
1592+
with pytest.raises(TypeError, match=msg):
1593+
obj * other
1594+
1595+
msg2 = msg.replace("rmul", "mul")
1596+
if dtype == "bool[pyarrow]":
1597+
# We go through ArrowEA.__mul__ which gives a different message
1598+
msg2 = (
1599+
r"operation 'mul' not supported for dtype 'bool\[pyarrow\]' "
1600+
r"with dtype 'timedelta64\[ns\]'"
1601+
)
1602+
with pytest.raises(TypeError, match=msg2):
1603+
other * obj
1604+
15591605
# ------------------------------------------------------------------
15601606
# __div__, __rdiv__
15611607

pandas/tests/arrays/masked/test_arithmetic.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,3 +246,26 @@ def test_unary_op_does_not_propagate_mask(data, op):
246246
expected = result.copy(deep=True)
247247
ser[0] = None
248248
tm.assert_series_equal(result, expected)
249+
250+
251+
@pytest.mark.parametrize("dtype", ["Int64", "Int32", "Float32", "Float64"])
252+
def test_divmod_pdna(dtype):
253+
# GH#62196
254+
ser = pd.Series([1, 2, 3], dtype=dtype)
255+
res = divmod(pd.NA, ser)
256+
assert isinstance(res, tuple) and len(res) == 2
257+
258+
exp = pd.Series([pd.NA, pd.NA, pd.NA], dtype=dtype)
259+
tm.assert_series_equal(res[0], exp)
260+
tm.assert_series_equal(res[1], exp)
261+
262+
tm.assert_series_equal(res[0], pd.NA // ser)
263+
tm.assert_series_equal(res[1], pd.NA % ser)
264+
265+
res = divmod(ser, pd.NA)
266+
assert isinstance(res, tuple) and len(res) == 2
267+
tm.assert_series_equal(res[0], exp)
268+
tm.assert_series_equal(res[1], exp)
269+
270+
tm.assert_series_equal(res[0], ser // pd.NA)
271+
tm.assert_series_equal(res[1], ser % pd.NA)

0 commit comments

Comments
 (0)