Skip to content

Commit 8b8e0b9

Browse files
committed
update with array_to_timedelta64 cases
1 parent 723cba8 commit 8b8e0b9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+239
-191
lines changed

pandas/_libs/tslibs/timedeltas.pyx

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -449,11 +449,16 @@ def array_to_timedelta64(
449449
ival = parse_iso_format_string(item)
450450
else:
451451
ival = parse_timedelta_string(item)
452+
if not needs_nano_unit(ival, item):
453+
item_reso = NPY_DATETIMEUNIT.NPY_FR_us
454+
ival = ival // 1000
455+
else:
456+
item_reso = NPY_FR_ns
452457

453-
item_reso = NPY_FR_ns
454-
state.update_creso(item_reso)
455-
if infer_reso:
456-
creso = state.creso
458+
if ival != NPY_NAT:
459+
state.update_creso(item_reso)
460+
if infer_reso:
461+
creso = state.creso
457462

458463
elif is_tick_object(item):
459464
item_reso = get_supported_reso(item._creso)
@@ -738,7 +743,7 @@ cdef bint needs_nano_unit(int64_t ival, str item):
738743
# TODO: more performant way of doing this check?
739744
if ival % 1000 != 0:
740745
return True
741-
return re.search(r"\.\d{7}", item) or "ns" in item or "nano" in item
746+
return re.search(r"\.\d{7}", item) or "ns" in item or "nano" in item.lower()
742747

743748

744749
cpdef inline str parse_timedelta_unit(str unit):

pandas/core/dtypes/astype.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _astype_nansafe(
117117
# bc we know arr.dtype == object, this is equivalent to
118118
# `np.asarray(to_timedelta(arr))`, but using a lower-level API that
119119
# does not require a circular import.
120-
tdvals = array_to_timedelta64(arr).view("m8[ns]")
120+
tdvals = array_to_timedelta64(arr)
121121

122122
tda = ensure_wrapped_if_datetimelike(tdvals)
123123
return tda.astype(dtype, copy=False)._ndarray

pandas/core/frame.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8657,7 +8657,8 @@ def _maybe_align_series_as_frame(self, series: Series, axis: AxisInt):
86578657
rvalues = series._values
86588658
if not isinstance(rvalues, np.ndarray):
86598659
# TODO(EA2D): no need to special-case with 2D EAs
8660-
if rvalues.dtype in ("datetime64[ns]", "timedelta64[ns]"):
8660+
if lib.is_np_dtype(rvalues.dtype, "mM"):
8661+
# i.e. DatetimeArray[tznaive] or TimedeltaArray
86618662
# We can losslessly+cheaply cast to ndarray
86628663
rvalues = np.asarray(rvalues)
86638664
else:

pandas/core/indexes/timedeltas.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -230,18 +230,19 @@ def get_loc(self, key):
230230

231231
return Index.get_loc(self, key)
232232

233-
# error: Return type "tuple[Timedelta | NaTType, None]" of "_parse_with_reso"
234-
# incompatible with return type "tuple[datetime, Resolution]" in supertype
235-
# "DatetimeIndexOpsMixin"
236-
def _parse_with_reso(self, label: str) -> tuple[Timedelta | NaTType, None]: # type: ignore[override]
237-
# the "with_reso" is a no-op for TimedeltaIndex
233+
def _parse_with_reso(self, label: str) -> tuple[Timedelta | NaTType, Resolution]:
238234
parsed = Timedelta(label)
239-
return parsed, None
235+
reso = Resolution.get_reso_from_freqstr(parsed.unit)
236+
return parsed, reso
240237

241-
def _parsed_string_to_bounds(self, reso, parsed: Timedelta):
238+
def _parsed_string_to_bounds(self, reso: Resolution, parsed: Timedelta):
242239
# reso is unused, included to match signature of DTI/PI
243240
lbound = parsed.round(parsed.resolution_string)
244-
rbound = lbound + to_offset(parsed.resolution_string) - Timedelta(1, "ns")
241+
rbound = (
242+
lbound
243+
+ to_offset(parsed.resolution_string)
244+
- Timedelta(1, unit=self.unit).as_unit(self.unit)
245+
)
245246
return lbound, rbound
246247

247248
# -------------------------------------------------------------------

pandas/core/nanops.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -944,8 +944,9 @@ def nanstd(
944944
>>> nanops.nanstd(s.values)
945945
1.0
946946
"""
947-
if values.dtype == "M8[ns]":
948-
values = values.view("m8[ns]")
947+
if values.dtype.kind == "M":
948+
unit = np.datetime_data(values.dtype)[0]
949+
values = values.view(f"m8[{unit}]")
949950

950951
orig_dtype = values.dtype
951952
values, mask = _get_values(values, skipna, mask=mask)

pandas/core/ops/array_ops.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -545,7 +545,9 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape):
545545
# Avoid possible ambiguities with pd.NaT
546546
# GH 52295
547547
if is_unitless(obj.dtype):
548-
obj = obj.astype("datetime64[ns]")
548+
# Use second resolution to ensure that the result of e.g.
549+
# `left - np.datetime64("NaT")` retains the unit of left.unit
550+
obj = obj.astype("datetime64[s]")
549551
elif not is_supported_dtype(obj.dtype):
550552
new_dtype = get_supported_dtype(obj.dtype)
551553
obj = obj.astype(new_dtype)
@@ -563,7 +565,9 @@ def maybe_prepare_scalar_for_op(obj, shape: Shape):
563565
# we broadcast and wrap in a TimedeltaArray
564566
# GH 52295
565567
if is_unitless(obj.dtype):
566-
obj = obj.astype("timedelta64[ns]")
568+
# Use second resolution to ensure that the result of e.g.
569+
# `left + np.timedelta64("NaT")` retains the unit of left.unit
570+
obj = obj.astype("timedelta64[s]")
567571
elif not is_supported_dtype(obj.dtype):
568572
new_dtype = get_supported_dtype(obj.dtype)
569573
obj = obj.astype(new_dtype)

pandas/plotting/_matplotlib/converter.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@
6161
from matplotlib.axis import Axis
6262

6363
from pandas._libs.tslibs.offsets import BaseOffset
64+
from pandas._typing import TimeUnit
6465

6566

6667
_mpl_units: dict = {} # Cache for units overwritten by us
@@ -1099,18 +1100,22 @@ class TimeSeries_TimedeltaFormatter(mpl.ticker.Formatter): # pyright: ignore[re
10991100
Formats the ticks along an axis controlled by a :class:`TimedeltaIndex`.
11001101
"""
11011102

1103+
def __init__(self, unit: TimeUnit = "ns"):
1104+
self.unit = unit
1105+
super().__init__()
1106+
11021107
axis: Axis
11031108

11041109
@staticmethod
1105-
def format_timedelta_ticks(x, pos, n_decimals: int) -> str:
1110+
def format_timedelta_ticks(x, pos, n_decimals: int, exp: int) -> str:
11061111
"""
11071112
Convert seconds to 'D days HH:MM:SS.F'
11081113
"""
1109-
s, ns = divmod(x, 10**9) # TODO(non-nano): this looks like it assumes ns
1114+
s, ns = divmod(x, 10**exp)
11101115
m, s = divmod(s, 60)
11111116
h, m = divmod(m, 60)
11121117
d, h = divmod(h, 24)
1113-
decimals = int(ns * 10 ** (n_decimals - 9))
1118+
decimals = int(ns * 10 ** (n_decimals - exp))
11141119
s = f"{int(h):02d}:{int(m):02d}:{int(s):02d}"
11151120
if n_decimals > 0:
11161121
s += f".{decimals:0{n_decimals}d}"
@@ -1119,6 +1124,7 @@ def format_timedelta_ticks(x, pos, n_decimals: int) -> str:
11191124
return s
11201125

11211126
def __call__(self, x, pos: int | None = 0) -> str:
1127+
exp = {"ns": 9, "us": 6, "ms": 3, "s": 0}[self.unit]
11221128
(vmin, vmax) = tuple(self.axis.get_view_interval())
1123-
n_decimals = min(int(np.ceil(np.log10(100 * 10**9 / abs(vmax - vmin)))), 9)
1124-
return self.format_timedelta_ticks(x, pos, n_decimals)
1129+
n_decimals = min(int(np.ceil(np.log10(100 * 10**exp / abs(vmax - vmin)))), exp)
1130+
return self.format_timedelta_ticks(x, pos, n_decimals, exp)

pandas/plotting/_matplotlib/timeseries.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ def format_dateaxis(
341341
subplot.format_coord = functools.partial(_format_coord, freq)
342342

343343
elif isinstance(index, ABCTimedeltaIndex):
344-
subplot.xaxis.set_major_formatter(TimeSeries_TimedeltaFormatter())
344+
subplot.xaxis.set_major_formatter(TimeSeries_TimedeltaFormatter(index.unit))
345345
else:
346346
raise TypeError("index type not supported")
347347

pandas/tests/apply/test_frame_apply.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ def test_apply_non_numpy_dtype():
659659

660660
result = df.apply(lambda x: x + pd.Timedelta("1day"))
661661
expected = DataFrame(
662-
{"dt": date_range("2015-01-02", periods=3, tz="Europe/Brussels", unit="ns")}
662+
{"dt": date_range("2015-01-02", periods=3, tz="Europe/Brussels")}
663663
)
664664
tm.assert_frame_equal(result, expected)
665665

pandas/tests/apply/test_series_apply.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ def test_apply_box_td64():
158158
# timedelta
159159
vals = [pd.Timedelta("1 days"), pd.Timedelta("2 days")]
160160
ser = Series(vals)
161-
assert ser.dtype == "timedelta64[ns]"
161+
assert ser.dtype == "timedelta64[us]"
162162
res = ser.apply(lambda x: f"{type(x).__name__}_{x.days}", by_row="compat")
163163
exp = Series(["Timedelta_1", "Timedelta_2"])
164164
tm.assert_series_equal(res, exp)

0 commit comments

Comments
 (0)