From 777a17bbc73332fee3752c79bba0a122c07b3719 Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:04:32 -0700 Subject: [PATCH 1/2] BUG: to_timedelta with ArrowDtype(pa.duration) --- doc/source/whatsnew/v2.1.0.rst | 1 + pandas/core/tools/timedeltas.py | 12 ++++++++++-- pandas/tests/tools/test_to_timedelta.py | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 0fdec3175f635..13963b01b66f8 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -502,6 +502,7 @@ Datetimelike Timedelta ^^^^^^^^^ - :meth:`TimedeltaIndex.map` with ``na_action="ignore"`` now works as expected (:issue:`51644`) +- Bug in :func:`to_timedelta` converting :class:`Series` or :class:`DataFrame` containing :class:`ArrowDtype` of ``pyarrow.duration`` to numpy ``timedelta64`` (:issue:`?`) - Bug in :class:`TimedeltaIndex` division or multiplication leading to ``.freq`` of "0 Days" instead of ``None`` (:issue:`51575`) - Bug in :class:`Timedelta` with Numpy timedelta64 objects not properly raising ``ValueError`` (:issue:`52806`) - Bug in :meth:`Timedelta.__hash__`, raising an ``OutOfBoundsTimedelta`` on certain large values of second resolution (:issue:`54037`) diff --git a/pandas/core/tools/timedeltas.py b/pandas/core/tools/timedeltas.py index 95946f0a159fd..3f2f832c08dc6 100644 --- a/pandas/core/tools/timedeltas.py +++ b/pandas/core/tools/timedeltas.py @@ -23,6 +23,7 @@ from pandas.util._exceptions import find_stack_level from pandas.core.dtypes.common import is_list_like +from pandas.core.dtypes.dtypes import ArrowDtype from pandas.core.dtypes.generic import ( ABCIndex, ABCSeries, @@ -31,6 +32,7 @@ from pandas.core.arrays.timedeltas import sequence_to_td64ns if TYPE_CHECKING: + from collections.abc import Hashable from datetime import timedelta from pandas._libs.tslibs.timedeltas import UnitChoices @@ -242,10 +244,14 @@ def _coerce_scalar_to_timedelta_type( def _convert_listlike( - arg, unit=None, errors: DateTimeErrorChoices = "raise", name=None + arg, + unit: UnitChoices | None = None, + errors: DateTimeErrorChoices = "raise", + name: Hashable | None = None, ): """Convert a list of objects to a timedelta index object.""" - if isinstance(arg, (list, tuple)) or not hasattr(arg, "dtype"): + arg_dtype = getattr(arg, "dtype", None) + if isinstance(arg, (list, tuple)) or arg_dtype is None: # This is needed only to ensure that in the case where we end up # returning arg (errors == "ignore"), and where the input is a # generator, we return a useful list-like instead of a @@ -253,6 +259,8 @@ def _convert_listlike( if not hasattr(arg, "__array__"): arg = list(arg) arg = np.array(arg, dtype=object) + elif isinstance(arg_dtype, ArrowDtype) and arg_dtype.kind == "m": + return arg try: td64arr = sequence_to_td64ns(arg, unit=unit, errors=errors, copy=False)[0] diff --git a/pandas/tests/tools/test_to_timedelta.py b/pandas/tests/tools/test_to_timedelta.py index 828f991eccfa6..57e2f010061fe 100644 --- a/pandas/tests/tools/test_to_timedelta.py +++ b/pandas/tests/tools/test_to_timedelta.py @@ -306,3 +306,12 @@ def test_from_numeric_arrow_dtype(any_numeric_ea_dtype): result = to_timedelta(ser) expected = Series([1, 2], dtype="timedelta64[ns]") tm.assert_series_equal(result, expected) + + +@pytest.mark.parametrize("unit", ["ns", "ms"]) +def test_from_timedelta_arrow_dtype(unit): + # GH + pytest.importorskip("pyarrow") + expected = Series([timedelta(1)], dtype=f"duration[{unit}][pyarrow]") + result = to_timedelta(expected) + tm.assert_series_equal(result, expected) From 25d75454b528491e3f55eaf867a528ac54ce02dc Mon Sep 17 00:00:00 2001 From: Matthew Roeschke <10647082+mroeschke@users.noreply.github.com> Date: Fri, 28 Jul 2023 16:06:02 -0700 Subject: [PATCH 2/2] Add GH number --- doc/source/whatsnew/v2.1.0.rst | 2 +- pandas/tests/tools/test_to_timedelta.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst index 13963b01b66f8..55bef324a0065 100644 --- a/doc/source/whatsnew/v2.1.0.rst +++ b/doc/source/whatsnew/v2.1.0.rst @@ -502,9 +502,9 @@ Datetimelike Timedelta ^^^^^^^^^ - :meth:`TimedeltaIndex.map` with ``na_action="ignore"`` now works as expected (:issue:`51644`) -- Bug in :func:`to_timedelta` converting :class:`Series` or :class:`DataFrame` containing :class:`ArrowDtype` of ``pyarrow.duration`` to numpy ``timedelta64`` (:issue:`?`) - Bug in :class:`TimedeltaIndex` division or multiplication leading to ``.freq`` of "0 Days" instead of ``None`` (:issue:`51575`) - Bug in :class:`Timedelta` with Numpy timedelta64 objects not properly raising ``ValueError`` (:issue:`52806`) +- Bug in :func:`to_timedelta` converting :class:`Series` or :class:`DataFrame` containing :class:`ArrowDtype` of ``pyarrow.duration`` to numpy ``timedelta64`` (:issue:`54298`) - Bug in :meth:`Timedelta.__hash__`, raising an ``OutOfBoundsTimedelta`` on certain large values of second resolution (:issue:`54037`) - Bug in :meth:`Timedelta.round` with values close to the implementation bounds returning incorrect results instead of raising ``OutOfBoundsTimedelta`` (:issue:`51494`) - Bug in :meth:`arrays.TimedeltaArray.map` and :meth:`TimedeltaIndex.map`, where the supplied callable operated array-wise instead of element-wise (:issue:`51977`) diff --git a/pandas/tests/tools/test_to_timedelta.py b/pandas/tests/tools/test_to_timedelta.py index 57e2f010061fe..120b5322adf3e 100644 --- a/pandas/tests/tools/test_to_timedelta.py +++ b/pandas/tests/tools/test_to_timedelta.py @@ -310,7 +310,7 @@ def test_from_numeric_arrow_dtype(any_numeric_ea_dtype): @pytest.mark.parametrize("unit", ["ns", "ms"]) def test_from_timedelta_arrow_dtype(unit): - # GH + # GH 54298 pytest.importorskip("pyarrow") expected = Series([timedelta(1)], dtype=f"duration[{unit}][pyarrow]") result = to_timedelta(expected)