From 4d488eb95d4a771e0a521fc6fa939f4613a692eb Mon Sep 17 00:00:00 2001 From: Brock Date: Sun, 19 Jun 2022 08:07:41 -0700 Subject: [PATCH 1/3] ENH: TDA.total_seconds support non-nano --- pandas/_libs/tslibs/__init__.py | 2 ++ pandas/_libs/tslibs/dtypes.pxd | 2 +- pandas/_libs/tslibs/dtypes.pyx | 2 +- pandas/core/arrays/timedeltas.py | 6 ++++-- pandas/tests/arrays/test_timedeltas.py | 24 +++++++++++++++++++++++- pandas/tests/tslibs/test_api.py | 1 + 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/pandas/_libs/tslibs/__init__.py b/pandas/_libs/tslibs/__init__.py index 68452ce011f9d..599ddfec5a268 100644 --- a/pandas/_libs/tslibs/__init__.py +++ b/pandas/_libs/tslibs/__init__.py @@ -29,6 +29,7 @@ "astype_overflowsafe", "get_unit_from_dtype", "periods_per_day", + "periods_per_second", ] from pandas._libs.tslibs import dtypes @@ -36,6 +37,7 @@ from pandas._libs.tslibs.dtypes import ( Resolution, periods_per_day, + periods_per_second, ) from pandas._libs.tslibs.nattype import ( NaT, diff --git a/pandas/_libs/tslibs/dtypes.pxd b/pandas/_libs/tslibs/dtypes.pxd index e16a389bc5459..98a64068eab94 100644 --- a/pandas/_libs/tslibs/dtypes.pxd +++ b/pandas/_libs/tslibs/dtypes.pxd @@ -6,7 +6,7 @@ from pandas._libs.tslibs.np_datetime cimport NPY_DATETIMEUNIT cdef str npy_unit_to_abbrev(NPY_DATETIMEUNIT unit) cdef NPY_DATETIMEUNIT freq_group_code_to_npy_unit(int freq) nogil cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=*) except? -1 -cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1 +cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1 cdef int64_t get_conversion_factor(NPY_DATETIMEUNIT from_unit, NPY_DATETIMEUNIT to_unit) except? -1 cdef dict attrname_to_abbrevs diff --git a/pandas/_libs/tslibs/dtypes.pyx b/pandas/_libs/tslibs/dtypes.pyx index f843f6ccdfc58..ea8499d6f6506 100644 --- a/pandas/_libs/tslibs/dtypes.pyx +++ b/pandas/_libs/tslibs/dtypes.pyx @@ -370,7 +370,7 @@ cpdef int64_t periods_per_day(NPY_DATETIMEUNIT reso=NPY_DATETIMEUNIT.NPY_FR_ns) return day_units -cdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1: +cpdef int64_t periods_per_second(NPY_DATETIMEUNIT reso) except? -1: if reso == NPY_DATETIMEUNIT.NPY_FR_ns: return 1_000_000_000 elif reso == NPY_DATETIMEUNIT.NPY_FR_us: diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index e08518a54fe6b..4d4ddc51e3e1b 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -21,6 +21,7 @@ Timedelta, astype_overflowsafe, iNaT, + periods_per_second, to_offset, ) from pandas._libs.tslibs.conversion import precision_from_unit @@ -818,7 +819,8 @@ def total_seconds(self) -> npt.NDArray[np.float64]: Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0], dtype='float64') """ - return self._maybe_mask_results(1e-9 * self.asi8, fill_value=None) + pps = periods_per_second(self._reso) + return self._maybe_mask_results(self.asi8 / pps, fill_value=None) def to_pytimedelta(self) -> npt.NDArray[np.object_]: """ @@ -829,7 +831,7 @@ def to_pytimedelta(self) -> npt.NDArray[np.object_]: ------- timedeltas : ndarray[object] """ - return tslibs.ints_to_pytimedelta(self._ndarray) + return ints_to_pytimedelta(self._ndarray) days = _field_accessor("days", "days", "Number of days for each element.") seconds = _field_accessor( diff --git a/pandas/tests/arrays/test_timedeltas.py b/pandas/tests/arrays/test_timedeltas.py index c8b850d35035a..5983c2f644949 100644 --- a/pandas/tests/arrays/test_timedeltas.py +++ b/pandas/tests/arrays/test_timedeltas.py @@ -33,7 +33,7 @@ def test_non_nano(self, unit, reso): assert tda[0]._reso == reso @pytest.mark.parametrize("field", TimedeltaArray._field_ops) - def test_fields(self, unit, reso, field): + def test_fields(self, unit, field): arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]") tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype) @@ -44,6 +44,28 @@ def test_fields(self, unit, reso, field): expected = getattr(tda_nano, field) tm.assert_numpy_array_equal(result, expected) + def test_to_pytimedelta(self, unit): + arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]") + tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype) + + as_nano = arr.astype("m8[ns]") + tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype) + + result = tda.to_pytimedelta() + expected = tda_nano.to_pytimedelta() + tm.assert_numpy_array_equal(result, expected) + + def test_total_seconds(self, unit): + arr = np.arange(5, dtype=np.int64).view(f"m8[{unit}]") + tda = TimedeltaArray._simple_new(arr, dtype=arr.dtype) + + as_nano = arr.astype("m8[ns]") + tda_nano = TimedeltaArray._simple_new(as_nano, dtype=as_nano.dtype) + + result = tda.total_seconds() + expected = tda_nano.total_seconds() + tm.assert_numpy_array_equal(result, expected) + class TestTimedeltaArray: @pytest.mark.parametrize("dtype", [int, np.int32, np.int64, "uint32", "uint64"]) diff --git a/pandas/tests/tslibs/test_api.py b/pandas/tests/tslibs/test_api.py index d61a2fca33f56..5891c28c11a68 100644 --- a/pandas/tests/tslibs/test_api.py +++ b/pandas/tests/tslibs/test_api.py @@ -54,6 +54,7 @@ def test_namespace(): "astype_overflowsafe", "get_unit_from_dtype", "periods_per_day", + "periods_per_second", ] expected = set(submodules + api) From 951dad733f52c45e9e5ed3df149174a01e90d138 Mon Sep 17 00:00:00 2001 From: Brock Date: Thu, 23 Jun 2022 10:41:48 -0700 Subject: [PATCH 2/3] fix doctest --- pandas/core/arrays/timedeltas.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pandas/core/arrays/timedeltas.py b/pandas/core/arrays/timedeltas.py index 4d4ddc51e3e1b..31387b2cf85b9 100644 --- a/pandas/core/arrays/timedeltas.py +++ b/pandas/core/arrays/timedeltas.py @@ -816,7 +816,7 @@ def total_seconds(self) -> npt.NDArray[np.float64]: dtype='timedelta64[ns]', freq=None) >>> idx.total_seconds() - Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0], + Float64Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64') """ pps = periods_per_second(self._reso) From 2f6116f259b88f9a6684fdf0ffe0e45078439602 Mon Sep 17 00:00:00 2001 From: Brock Date: Mon, 27 Jun 2022 10:26:14 -0700 Subject: [PATCH 3/3] mypy fixup --- pandas/_libs/tslibs/dtypes.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/pandas/_libs/tslibs/dtypes.pyi b/pandas/_libs/tslibs/dtypes.pyi index 5c343f89f38ea..f8c6a36a63c7e 100644 --- a/pandas/_libs/tslibs/dtypes.pyi +++ b/pandas/_libs/tslibs/dtypes.pyi @@ -6,6 +6,7 @@ _attrname_to_abbrevs: dict[str, str] _period_code_map: dict[str, int] def periods_per_day(reso: int) -> int: ... +def periods_per_second(reso: int) -> int: ... class PeriodDtypeBase: _dtype_code: int # PeriodDtypeCode