Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/arrays/timedeltas.py: 24%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

461 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5from typing import ( 

6 TYPE_CHECKING, 

7 Self, 

8 cast, 

9) 

10 

11import numpy as np 

12 

13from pandas._libs import ( 

14 lib, 

15 tslibs, 

16) 

17from pandas._libs.tslibs import ( 

18 Day, 

19 NaT, 

20 NaTType, 

21 Tick, 

22 Timedelta, 

23 astype_overflowsafe, 

24 get_supported_dtype, 

25 iNaT, 

26 is_supported_dtype, 

27 periods_per_second, 

28 to_offset, 

29) 

30from pandas._libs.tslibs.conversion import cast_from_unit_vectorized 

31from pandas._libs.tslibs.fields import ( 

32 get_timedelta_days, 

33 get_timedelta_field, 

34) 

35from pandas._libs.tslibs.timedeltas import ( 

36 array_to_timedelta64, 

37 floordiv_object_array, 

38 ints_to_pytimedelta, 

39 parse_timedelta_unit, 

40 truediv_object_array, 

41) 

42from pandas.compat.numpy import function as nv 

43from pandas.util._decorators import set_module 

44from pandas.util._validators import validate_endpoints 

45 

46from pandas.core.dtypes.common import ( 

47 TD64NS_DTYPE, 

48 is_float_dtype, 

49 is_integer_dtype, 

50 is_object_dtype, 

51 is_scalar, 

52 is_string_dtype, 

53 pandas_dtype, 

54) 

55from pandas.core.dtypes.dtypes import ( 

56 ArrowDtype, 

57 BaseMaskedDtype, 

58 ExtensionDtype, 

59) 

60from pandas.core.dtypes.missing import isna 

61 

62from pandas.core import ( 

63 nanops, 

64 roperator, 

65) 

66from pandas.core.array_algos import datetimelike_accumulations 

67from pandas.core.arrays import datetimelike as dtl 

68from pandas.core.arrays._ranges import generate_regular_range 

69import pandas.core.common as com 

70from pandas.core.ops.common import unpack_zerodim_and_defer 

71 

72if TYPE_CHECKING: 

73 from collections.abc import Callable, Iterator 

74 

75 from pandas._typing import ( 

76 AxisInt, 

77 DateTimeErrorChoices, 

78 DtypeObj, 

79 NpDtype, 

80 npt, 

81 TimeUnit, 

82 ) 

83 

84 from pandas import DataFrame 

85 

86import textwrap 

87 

88 

89def _field_accessor(name: str, alias: str, docstring: str): 

90 def f(self) -> np.ndarray: 

91 values = self.asi8 

92 if alias == "days": 

93 result = get_timedelta_days(values, reso=self._creso) 

94 else: 

95 # error: Incompatible types in assignment ( 

96 # expression has type "ndarray[Any, dtype[signedinteger[_32Bit]]]", 

97 # variable has type "ndarray[Any, dtype[signedinteger[_64Bit]]] 

98 result = get_timedelta_field(values, alias, reso=self._creso) # type: ignore[assignment] 

99 if self._hasna: 

100 result = self._maybe_mask_results( 

101 result, fill_value=None, convert="float64" 

102 ) 

103 

104 return result 

105 

106 f.__name__ = name 

107 f.__doc__ = f"\n{docstring}\n" 

108 return property(f) 

109 

110 

111@set_module("pandas.arrays") 

112class TimedeltaArray(dtl.TimelikeOps): 

113 """ 

114 Pandas ExtensionArray for timedelta data. 

115 

116 .. warning:: 

117 

118 TimedeltaArray is currently experimental, and its API may change 

119 without warning. In particular, :attr:`TimedeltaArray.dtype` is 

120 expected to change to be an instance of an ``ExtensionDtype`` 

121 subclass. 

122 

123 Parameters 

124 ---------- 

125 data : array-like 

126 The timedelta data. 

127 dtype : numpy.dtype 

128 Currently, only ``numpy.dtype("timedelta64[ns]")`` is accepted. 

129 freq : Offset, optional 

130 Frequency of the data. 

131 copy : bool, default False 

132 Whether to copy the underlying array of data. 

133 

134 Attributes 

135 ---------- 

136 None 

137 

138 Methods 

139 ------- 

140 None 

141 

142 See Also 

143 -------- 

144 Timedelta : Represents a duration, the difference between two dates or times. 

145 TimedeltaIndex : Immutable Index of timedelta64 data. 

146 to_timedelta : Convert argument to timedelta. 

147 

148 Examples 

149 -------- 

150 >>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(["1h", "2h"])) 

151 <TimedeltaArray> 

152 ['0 days 01:00:00', '0 days 02:00:00'] 

153 Length: 2, dtype: timedelta64[us] 

154 """ 

155 

156 _typ = "timedeltaarray" 

157 _recognized_scalars = (timedelta, np.timedelta64, Tick) 

158 _is_recognized_dtype: Callable[[DtypeObj], bool] = lambda x: lib.is_np_dtype(x, "m") 

159 _infer_matches = ("timedelta", "timedelta64") 

160 

161 @property 

162 def _internal_fill_value(self) -> np.timedelta64: 

163 return np.timedelta64("NaT", self.unit) 

164 

165 @property 

166 def _scalar_type(self) -> type[Timedelta]: 

167 return Timedelta 

168 

169 __array_priority__ = 1000 

170 # define my properties & methods for delegation 

171 _other_ops: list[str] = [] 

172 _bool_ops: list[str] = [] 

173 _field_ops: list[str] = ["days", "seconds", "microseconds", "nanoseconds"] 

174 _datetimelike_ops: list[str] = _field_ops + _bool_ops + ["unit", "freq"] 

175 _datetimelike_methods: list[str] = [ 

176 "to_pytimedelta", 

177 "total_seconds", 

178 "round", 

179 "floor", 

180 "ceil", 

181 "as_unit", 

182 ] 

183 

184 # Note: ndim must be defined to ensure NaT.__richcmp__(TimedeltaArray) 

185 # operates pointwise. 

186 

187 def _box_func(self, x: np.timedelta64) -> Timedelta | NaTType: 

188 y = x.view("i8") 

189 if y == NaT._value: 

190 return NaT 

191 return Timedelta._from_value_and_reso(y, reso=self._creso) 

192 

193 @property 

194 # error: Return type "dtype" of "dtype" incompatible with return type 

195 # "ExtensionDtype" in supertype "ExtensionArray" 

196 def dtype(self) -> np.dtype[np.timedelta64]: # type: ignore[override] 

197 """ 

198 The dtype for the TimedeltaArray. 

199 

200 .. warning:: 

201 

202 A future version of pandas will change dtype to be an instance 

203 of a :class:`pandas.api.extensions.ExtensionDtype` subclass, 

204 not a ``numpy.dtype``. 

205 

206 Returns 

207 ------- 

208 numpy.dtype 

209 """ 

210 return self._ndarray.dtype 

211 

212 # ---------------------------------------------------------------- 

213 # Constructors 

214 

215 _freq: Tick | Day | None = None 

216 

217 @classmethod 

218 def _validate_dtype(cls, values, dtype): 

219 # used in TimeLikeOps.__init__ 

220 dtype = _validate_td64_dtype(dtype) 

221 _validate_td64_dtype(values.dtype) 

222 if dtype != values.dtype: 

223 raise ValueError("Values resolution does not match dtype.") 

224 return dtype 

225 

226 # error: Signature of "_simple_new" incompatible with supertype "NDArrayBacked" 

227 @classmethod 

228 def _simple_new( # type: ignore[override] 

229 cls, 

230 values: npt.NDArray[np.timedelta64], 

231 freq: Tick | Day | None = None, 

232 dtype: np.dtype[np.timedelta64] = TD64NS_DTYPE, 

233 ) -> Self: 

234 # Require td64 dtype, not unit-less, matching values.dtype 

235 assert lib.is_np_dtype(dtype, "m") 

236 assert not tslibs.is_unitless(dtype) 

237 assert isinstance(values, np.ndarray), type(values) 

238 assert dtype == values.dtype 

239 assert freq is None or isinstance(freq, (Tick, Day)) 

240 

241 result = super()._simple_new(values=values, dtype=dtype) 

242 result._freq = freq 

243 return result 

244 

245 @classmethod 

246 def _from_sequence(cls, data, *, dtype=None, copy: bool = False) -> Self: 

247 unit = None 

248 if dtype: 

249 dtype = _validate_td64_dtype(dtype) 

250 if lib.infer_dtype(data) == "integer": 

251 unit = np.datetime_data(dtype)[0] 

252 

253 data, freq = sequence_to_td64ns(data, copy=copy, unit=unit) 

254 

255 if dtype is not None: 

256 data = astype_overflowsafe(data, dtype=dtype, copy=False) 

257 

258 return cls._simple_new(data, dtype=data.dtype, freq=freq) 

259 

260 @classmethod 

261 def _from_sequence_not_strict( 

262 cls, 

263 data, 

264 *, 

265 dtype=None, 

266 copy: bool = False, 

267 freq=lib.no_default, 

268 unit=None, 

269 ) -> Self: 

270 """ 

271 _from_sequence_not_strict but without responsibility for finding the 

272 result's `freq`. 

273 """ 

274 if dtype: 

275 dtype = _validate_td64_dtype(dtype) 

276 if unit is None and lib.infer_dtype(data) == "integer": 

277 unit = np.datetime_data(dtype)[0] 

278 

279 assert unit not in ["Y", "y", "M"] # caller is responsible for checking 

280 

281 data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=unit) 

282 

283 if dtype is not None: 

284 data = astype_overflowsafe(data, dtype=dtype, copy=False) 

285 

286 result = cls._simple_new(data, dtype=data.dtype, freq=inferred_freq) 

287 

288 result._maybe_pin_freq(freq, {}) 

289 return result 

290 

291 @classmethod 

292 def _generate_range( 

293 cls, start, end, periods, freq, closed=None, *, unit: TimeUnit 

294 ) -> Self: 

295 periods = dtl.validate_periods(periods) 

296 if freq is None and any(x is None for x in [periods, start, end]): 

297 raise ValueError("Must provide freq argument if no data is supplied") 

298 

299 if com.count_not_none(start, end, periods, freq) != 3: 

300 raise ValueError( 

301 "Of the four parameters: start, end, periods, " 

302 "and freq, exactly three must be specified" 

303 ) 

304 

305 if start is not None: 

306 start = Timedelta(start).as_unit("ns") 

307 

308 if end is not None: 

309 end = Timedelta(end).as_unit("ns") 

310 

311 if unit not in ["s", "ms", "us", "ns"]: 

312 raise ValueError("'unit' must be one of 's', 'ms', 'us', 'ns'") 

313 

314 if start is not None and unit is not None: 

315 start = start.as_unit(unit, round_ok=False) 

316 if end is not None and unit is not None: 

317 end = end.as_unit(unit, round_ok=False) 

318 

319 left_closed, right_closed = validate_endpoints(closed) 

320 

321 if freq is not None: 

322 index = generate_regular_range(start, end, periods, freq, unit=unit) 

323 else: 

324 index = np.linspace(start._value, end._value, periods).astype("i8") 

325 

326 if not left_closed: 

327 index = index[1:] 

328 if not right_closed: 

329 index = index[:-1] 

330 

331 td64values = index.view(f"m8[{unit}]") 

332 return cls._simple_new(td64values, dtype=td64values.dtype, freq=freq) 

333 

334 # ---------------------------------------------------------------- 

335 # DatetimeLike Interface 

336 

337 def _unbox_scalar(self, value) -> np.timedelta64: 

338 if not isinstance(value, self._scalar_type) and value is not NaT: 

339 raise ValueError("'value' should be a Timedelta.") 

340 self._check_compatible_with(value) 

341 if value is NaT: 

342 return np.timedelta64(value._value, self.unit) 

343 else: 

344 return value.as_unit(self.unit, round_ok=False).asm8 

345 

346 def _scalar_from_string(self, value) -> Timedelta | NaTType: 

347 return Timedelta(value) 

348 

349 def _check_compatible_with(self, other) -> None: 

350 # we don't have anything to validate. 

351 pass 

352 

353 # ---------------------------------------------------------------- 

354 # Array-Like / EA-Interface Methods 

355 

356 def astype(self, dtype, copy: bool = True): 

357 # We handle 

358 # --> timedelta64[ns] 

359 # --> timedelta64 

360 # DatetimeLikeArrayMixin super call handles other cases 

361 dtype = pandas_dtype(dtype) 

362 

363 if lib.is_np_dtype(dtype, "m"): 

364 if dtype == self.dtype: 

365 if copy: 

366 return self.copy() 

367 return self 

368 

369 if is_supported_dtype(dtype): 

370 # unit conversion e.g. timedelta64[s] 

371 res_values = astype_overflowsafe(self._ndarray, dtype, copy=False) 

372 return type(self)._simple_new( 

373 res_values, dtype=res_values.dtype, freq=self.freq 

374 ) 

375 else: 

376 raise ValueError( 

377 f"Cannot convert from {self.dtype} to {dtype}. " 

378 "Supported resolutions are 's', 'ms', 'us', 'ns'" 

379 ) 

380 

381 return dtl.DatetimeLikeArrayMixin.astype(self, dtype, copy=copy) 

382 

383 def __iter__(self) -> Iterator: 

384 if self.ndim > 1: 

385 for i in range(len(self)): 

386 yield self[i] 

387 else: 

388 # convert in chunks of 10k for efficiency 

389 data = self._ndarray 

390 length = len(self) 

391 chunksize = 10000 

392 chunks = (length // chunksize) + 1 

393 for i in range(chunks): 

394 start_i = i * chunksize 

395 end_i = min((i + 1) * chunksize, length) 

396 converted = ints_to_pytimedelta(data[start_i:end_i], box=True) 

397 yield from converted 

398 

399 # ---------------------------------------------------------------- 

400 # Reductions 

401 

402 def sum( 

403 self, 

404 *, 

405 axis: AxisInt | None = None, 

406 dtype: NpDtype | None = None, 

407 out=None, 

408 keepdims: bool = False, 

409 initial=None, 

410 skipna: bool = True, 

411 min_count: int = 0, 

412 ): 

413 nv.validate_sum( 

414 (), {"dtype": dtype, "out": out, "keepdims": keepdims, "initial": initial} 

415 ) 

416 

417 result = nanops.nansum( 

418 self._ndarray, axis=axis, skipna=skipna, min_count=min_count 

419 ) 

420 return self._wrap_reduction_result(axis, result) 

421 

422 def std( 

423 self, 

424 *, 

425 axis: AxisInt | None = None, 

426 dtype: NpDtype | None = None, 

427 out=None, 

428 ddof: int = 1, 

429 keepdims: bool = False, 

430 skipna: bool = True, 

431 ): 

432 nv.validate_stat_ddof_func( 

433 (), {"dtype": dtype, "out": out, "keepdims": keepdims}, fname="std" 

434 ) 

435 

436 result = nanops.nanstd(self._ndarray, axis=axis, skipna=skipna, ddof=ddof) 

437 if axis is None or self.ndim == 1: 

438 return self._box_func(result) 

439 return self._from_backing_data(result) 

440 

441 # ---------------------------------------------------------------- 

442 # Accumulations 

443 

444 def _accumulate(self, name: str, *, skipna: bool = True, **kwargs): 

445 if name == "cumsum": 

446 op = getattr(datetimelike_accumulations, name) 

447 result = op(self._ndarray.copy(), skipna=skipna, **kwargs) 

448 

449 return type(self)._simple_new(result, freq=None, dtype=self.dtype) 

450 elif name == "cumprod": 

451 raise TypeError("cumprod not supported for Timedelta.") 

452 

453 else: 

454 return super()._accumulate(name, skipna=skipna, **kwargs) 

455 

456 # ---------------------------------------------------------------- 

457 # Rendering Methods 

458 

459 def _formatter(self, boxed: bool = False): 

460 from pandas.io.formats.format import get_format_timedelta64 

461 

462 return get_format_timedelta64(self, box=True) 

463 

464 def _format_native_types( 

465 self, *, na_rep: str | float = "NaT", date_format=None, **kwargs 

466 ) -> npt.NDArray[np.object_]: 

467 from pandas.io.formats.format import get_format_timedelta64 

468 

469 # Relies on TimeDelta._repr_base 

470 formatter = get_format_timedelta64(self, na_rep) 

471 # equiv: np.array([formatter(x) for x in self._ndarray]) 

472 # but independent of dimension 

473 return np.frompyfunc(formatter, 1, 1)(self._ndarray) 

474 

475 # ---------------------------------------------------------------- 

476 # Arithmetic Methods 

477 

478 def _add_offset(self, other): 

479 assert not isinstance(other, (Tick, Day)) 

480 raise TypeError( 

481 f"cannot add the type {type(other).__name__} to a {type(self).__name__}" 

482 ) 

483 

484 @unpack_zerodim_and_defer("__mul__") 

485 def __mul__(self, other) -> Self: 

486 if is_scalar(other): 

487 if lib.is_bool(other): 

488 raise TypeError( 

489 f"Cannot multiply '{self.dtype}' by bool, explicitly cast to " 

490 "integers instead" 

491 ) 

492 # numpy will accept float and int, raise TypeError for others 

493 result = self._ndarray * other 

494 if result.dtype.kind != "m": 

495 # numpy >= 2.1 may not raise a TypeError 

496 # and seems to dispatch to others.__rmul__? 

497 raise TypeError(f"Cannot multiply with {type(other).__name__}") 

498 freq = None 

499 if self.freq is not None and not isna(other): 

500 freq = self.freq * other 

501 if freq.n == 0: 

502 # GH#51575 Better to have no freq than an incorrect one 

503 freq = None 

504 return type(self)._simple_new(result, dtype=result.dtype, freq=freq) 

505 

506 if not hasattr(other, "dtype"): 

507 # list, tuple 

508 other = np.array(other) 

509 

510 if other.dtype.kind == "b": 

511 # GH#58054 

512 raise TypeError( 

513 f"Cannot multiply '{self.dtype}' by bool, explicitly cast to " 

514 "integers instead" 

515 ) 

516 if isinstance(other.dtype, (ArrowDtype, BaseMaskedDtype)): 

517 # GH#58054 

518 return NotImplemented 

519 

520 if len(other) != len(self) and not lib.is_np_dtype(other.dtype, "m"): 

521 # Exclude timedelta64 here so we correctly raise TypeError 

522 # for that instead of ValueError 

523 raise ValueError("Cannot multiply with unequal lengths") 

524 

525 if is_object_dtype(other.dtype): 

526 # this multiplication will succeed only if all elements of other 

527 # are int or float scalars, so we will end up with 

528 # timedelta64[ns]-dtyped result 

529 arr = self._ndarray 

530 result = [arr[n] * other[n] for n in range(len(self))] 

531 result = np.array(result) 

532 return type(self)._simple_new(result, dtype=result.dtype) 

533 

534 # numpy will accept float or int dtype, raise TypeError for others 

535 result = self._ndarray * other 

536 if result.dtype.kind != "m": 

537 # numpy >= 2.1 may not raise a TypeError 

538 # and seems to dispatch to others.__rmul__? 

539 raise TypeError(f"Cannot multiply with {type(other).__name__}") 

540 return type(self)._simple_new(result, dtype=result.dtype) 

541 

542 __rmul__ = __mul__ 

543 

544 def _scalar_divlike_op(self, other, op): 

545 """ 

546 Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__ 

547 with scalar 'other'. 

548 """ 

549 if isinstance(other, self._recognized_scalars): 

550 other = Timedelta(other) 

551 # mypy assumes that __new__ returns an instance of the class 

552 # github.com/python/mypy/issues/1020 

553 if cast("Timedelta | NaTType", other) is NaT: 

554 # specifically timedelta64-NaT 

555 res = np.empty(self.shape, dtype=np.float64) 

556 res.fill(np.nan) 

557 return res 

558 

559 # otherwise, dispatch to Timedelta implementation 

560 return op(self._ndarray, other) 

561 

562 else: 

563 # caller is responsible for checking lib.is_scalar(other) 

564 # assume other is numeric, otherwise numpy will raise 

565 

566 if op in [roperator.rtruediv, roperator.rfloordiv]: 

567 raise TypeError( 

568 f"Cannot divide {type(other).__name__} by {type(self).__name__}" 

569 ) 

570 

571 result = op(self._ndarray, other) 

572 freq = None 

573 

574 if self.freq is not None: 

575 # Note: freq gets division, not floor-division, even if op 

576 # is floordiv. 

577 if isinstance(self.freq, Day): 

578 if self.freq.n % other == 0: 

579 freq = Day(self.freq.n // other) 

580 else: 

581 freq = to_offset(Timedelta(days=self.freq.n)) / other 

582 else: 

583 freq = self.freq / other 

584 if freq.nanos == 0 and self.freq.nanos != 0: 

585 # e.g. if self.freq is Nano(1) then dividing by 2 

586 # rounds down to zero 

587 freq = None 

588 

589 return type(self)._simple_new(result, dtype=result.dtype, freq=freq) 

590 

591 def _cast_divlike_op(self, other): 

592 if not hasattr(other, "dtype"): 

593 # e.g. list, tuple 

594 other = np.array(other) 

595 

596 if len(other) != len(self): 

597 raise ValueError("Cannot divide vectors with unequal lengths") 

598 return other 

599 

600 def _vector_divlike_op(self, other, op) -> np.ndarray | Self: 

601 """ 

602 Shared logic for __truediv__, __floordiv__, and their reversed versions 

603 with timedelta64-dtype ndarray other. 

604 """ 

605 # Let numpy handle it 

606 result = op(self._ndarray, np.asarray(other)) 

607 

608 if (is_integer_dtype(other.dtype) or is_float_dtype(other.dtype)) and op in [ 

609 operator.truediv, 

610 operator.floordiv, 

611 ]: 

612 return type(self)._simple_new(result, dtype=result.dtype) 

613 

614 if op in [operator.floordiv, roperator.rfloordiv]: 

615 mask = self.isna() | isna(other) 

616 if mask.any(): 

617 result = result.astype(np.float64) 

618 np.putmask(result, mask, np.nan) 

619 

620 return result 

621 

622 @unpack_zerodim_and_defer("__truediv__") 

623 def __truediv__(self, other): 

624 # timedelta / X is well-defined for timedelta-like or numeric X 

625 op = operator.truediv 

626 if is_scalar(other): 

627 return self._scalar_divlike_op(other, op) 

628 

629 other = self._cast_divlike_op(other) 

630 if ( 

631 lib.is_np_dtype(other.dtype, "m") 

632 or is_integer_dtype(other.dtype) 

633 or is_float_dtype(other.dtype) 

634 ): 

635 return self._vector_divlike_op(other, op) 

636 

637 if is_object_dtype(other.dtype): 

638 other = np.asarray(other) 

639 if self.ndim > 1: 

640 res_cols = [ 

641 left / right for left, right in zip(self, other, strict=True) 

642 ] 

643 res_cols2 = [x.reshape(1, -1) for x in res_cols] 

644 result = np.concatenate(res_cols2, axis=0) 

645 else: 

646 result = truediv_object_array(self._ndarray, other) 

647 

648 return result 

649 

650 else: 

651 return NotImplemented 

652 

653 @unpack_zerodim_and_defer("__rtruediv__") 

654 def __rtruediv__(self, other): 

655 # X / timedelta is defined only for timedelta-like X 

656 op = roperator.rtruediv 

657 if is_scalar(other): 

658 return self._scalar_divlike_op(other, op) 

659 

660 other = self._cast_divlike_op(other) 

661 if lib.is_np_dtype(other.dtype, "m"): 

662 return self._vector_divlike_op(other, op) 

663 

664 elif is_object_dtype(other.dtype): 

665 # Note: unlike in __truediv__, we do not _need_ to do type 

666 # inference on the result. It does not raise, a numeric array 

667 # is returned. GH#23829 

668 result_list = [other[n] / self[n] for n in range(len(self))] 

669 return np.array(result_list) 

670 

671 else: 

672 return NotImplemented 

673 

674 @unpack_zerodim_and_defer("__floordiv__") 

675 def __floordiv__(self, other): 

676 op = operator.floordiv 

677 if is_scalar(other): 

678 return self._scalar_divlike_op(other, op) 

679 

680 other = self._cast_divlike_op(other) 

681 if ( 

682 lib.is_np_dtype(other.dtype, "m") 

683 or is_integer_dtype(other.dtype) 

684 or is_float_dtype(other.dtype) 

685 ): 

686 return self._vector_divlike_op(other, op) 

687 

688 elif is_object_dtype(other.dtype): 

689 other = np.asarray(other) 

690 if self.ndim > 1: 

691 res_cols = [ 

692 left // right for left, right in zip(self, other, strict=True) 

693 ] 

694 res_cols2 = [x.reshape(1, -1) for x in res_cols] 

695 result = np.concatenate(res_cols2, axis=0) 

696 else: 

697 result = floordiv_object_array(self._ndarray, other) 

698 

699 assert result.dtype == object 

700 return result 

701 

702 else: 

703 return NotImplemented 

704 

705 @unpack_zerodim_and_defer("__rfloordiv__") 

706 def __rfloordiv__(self, other): 

707 op = roperator.rfloordiv 

708 if is_scalar(other): 

709 return self._scalar_divlike_op(other, op) 

710 

711 other = self._cast_divlike_op(other) 

712 if lib.is_np_dtype(other.dtype, "m"): 

713 return self._vector_divlike_op(other, op) 

714 

715 elif is_object_dtype(other.dtype): 

716 result_list = [other[n] // self[n] for n in range(len(self))] 

717 result = np.array(result_list) 

718 return result 

719 

720 else: 

721 return NotImplemented 

722 

723 @unpack_zerodim_and_defer("__mod__") 

724 def __mod__(self, other): 

725 # Note: This is a naive implementation, can likely be optimized 

726 if isinstance(other, self._recognized_scalars): 

727 other = Timedelta(other) 

728 return self - (self // other) * other 

729 

730 @unpack_zerodim_and_defer("__rmod__") 

731 def __rmod__(self, other): 

732 # Note: This is a naive implementation, can likely be optimized 

733 if isinstance(other, self._recognized_scalars): 

734 other = Timedelta(other) 

735 return other - (other // self) * self 

736 

737 @unpack_zerodim_and_defer("__divmod__") 

738 def __divmod__(self, other): 

739 # Note: This is a naive implementation, can likely be optimized 

740 if isinstance(other, self._recognized_scalars): 

741 other = Timedelta(other) 

742 

743 res1 = self // other 

744 res2 = self - res1 * other 

745 return res1, res2 

746 

747 @unpack_zerodim_and_defer("__rdivmod__") 

748 def __rdivmod__(self, other): 

749 # Note: This is a naive implementation, can likely be optimized 

750 if isinstance(other, self._recognized_scalars): 

751 other = Timedelta(other) 

752 

753 res1 = other // self 

754 res2 = other - res1 * self 

755 return res1, res2 

756 

757 def __neg__(self) -> TimedeltaArray: 

758 freq = None 

759 if self.freq is not None: 

760 freq = -self.freq 

761 return type(self)._simple_new(-self._ndarray, dtype=self.dtype, freq=freq) 

762 

763 def __pos__(self) -> TimedeltaArray: 

764 return type(self)._simple_new( 

765 self._ndarray.copy(), dtype=self.dtype, freq=self.freq 

766 ) 

767 

768 def __abs__(self) -> TimedeltaArray: 

769 # Note: freq is not preserved 

770 return type(self)._simple_new(np.abs(self._ndarray), dtype=self.dtype) 

771 

772 # ---------------------------------------------------------------- 

773 # Conversion Methods - Vectorized analogues of Timedelta methods 

774 

775 def total_seconds(self) -> npt.NDArray[np.float64]: 

776 """ 

777 Return total duration of each element expressed in seconds. 

778 

779 This method is available directly on TimedeltaArray, TimedeltaIndex 

780 and on Series containing timedelta values under the ``.dt`` namespace. 

781 

782 Returns 

783 ------- 

784 ndarray, Index or Series 

785 When the calling object is a TimedeltaArray, the return type 

786 is ndarray. When the calling object is a TimedeltaIndex, 

787 the return type is an Index with a float64 dtype. When the calling object 

788 is a Series, the return type is Series of type `float64` whose 

789 index is the same as the original. 

790 

791 See Also 

792 -------- 

793 datetime.timedelta.total_seconds : Standard library version 

794 of this method. 

795 TimedeltaIndex.components : Return a DataFrame with components of 

796 each Timedelta. 

797 

798 Examples 

799 -------- 

800 **Series** 

801 

802 >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit="D")) 

803 >>> s 

804 0 0 days 

805 1 1 days 

806 2 2 days 

807 3 3 days 

808 4 4 days 

809 dtype: timedelta64[s] 

810 

811 >>> s.dt.total_seconds() 

812 0 0.0 

813 1 86400.0 

814 2 172800.0 

815 3 259200.0 

816 4 345600.0 

817 dtype: float64 

818 

819 **TimedeltaIndex** 

820 

821 >>> idx = pd.to_timedelta(np.arange(5), unit="D") 

822 >>> idx 

823 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'], 

824 dtype='timedelta64[s]', freq=None) 

825 

826 >>> idx.total_seconds() 

827 Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64') 

828 """ 

829 pps = periods_per_second(self._creso) 

830 return self._maybe_mask_results(self.asi8 / pps, fill_value=None) 

831 

832 def to_pytimedelta(self) -> npt.NDArray[np.object_]: 

833 """ 

834 Return an ndarray of datetime.timedelta objects. 

835 

836 Returns 

837 ------- 

838 numpy.ndarray 

839 A NumPy ``timedelta64`` object representing the same duration as the 

840 original pandas ``Timedelta`` object. The precision of the resulting 

841 object is in nanoseconds, which is the default 

842 time resolution used by pandas for ``Timedelta`` objects, ensuring 

843 high precision for time-based calculations. 

844 

845 See Also 

846 -------- 

847 to_timedelta : Convert argument to timedelta format. 

848 Timedelta : Represents a duration between two dates or times. 

849 DatetimeIndex: Index of datetime64 data. 

850 Timedelta.components : Return a components namedtuple-like 

851 of a single timedelta. 

852 

853 Examples 

854 -------- 

855 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D") 

856 >>> tdelta_idx 

857 TimedeltaIndex(['1 days', '2 days', '3 days'], 

858 dtype='timedelta64[s]', freq=None) 

859 >>> tdelta_idx.to_pytimedelta() 

860 array([datetime.timedelta(days=1), datetime.timedelta(days=2), 

861 datetime.timedelta(days=3)], dtype=object) 

862 

863 >>> tidx = pd.TimedeltaIndex(data=["1 days 02:30:45", "3 days 04:15:10"]) 

864 >>> tidx 

865 TimedeltaIndex(['1 days 02:30:45', '3 days 04:15:10'], 

866 dtype='timedelta64[us]', freq=None) 

867 >>> tidx.to_pytimedelta() 

868 array([datetime.timedelta(days=1, seconds=9045), 

869 datetime.timedelta(days=3, seconds=15310)], dtype=object) 

870 """ 

871 return ints_to_pytimedelta(self._ndarray) 

872 

873 days_docstring = textwrap.dedent( 

874 """Number of days for each element. 

875 

876 See Also 

877 -------- 

878 Series.dt.seconds : Return number of seconds for each element. 

879 Series.dt.microseconds : Return number of microseconds for each element. 

880 Series.dt.nanoseconds : Return number of nanoseconds for each element. 

881 

882 Examples 

883 -------- 

884 For Series: 

885 

886 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='D')) 

887 >>> ser 

888 0 1 days 

889 1 2 days 

890 2 3 days 

891 dtype: timedelta64[s] 

892 >>> ser.dt.days 

893 0 1 

894 1 2 

895 2 3 

896 dtype: int64 

897 

898 For TimedeltaIndex: 

899 

900 >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"]) 

901 >>> tdelta_idx 

902 TimedeltaIndex(['0 days', '10 days', '20 days'], 

903 dtype='timedelta64[us]', freq=None) 

904 >>> tdelta_idx.days 

905 Index([0, 10, 20], dtype='int64')""" 

906 ) 

907 days = _field_accessor("days", "days", days_docstring) 

908 

909 seconds_docstring = textwrap.dedent( 

910 """Number of seconds (>= 0 and less than 1 day) for each element. 

911 

912 See Also 

913 -------- 

914 Series.dt.seconds : Return number of seconds for each element. 

915 Series.dt.nanoseconds : Return number of nanoseconds for each element. 

916 

917 Examples 

918 -------- 

919 For Series: 

920 

921 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='s')) 

922 >>> ser 

923 0 0 days 00:00:01 

924 1 0 days 00:00:02 

925 2 0 days 00:00:03 

926 dtype: timedelta64[s] 

927 >>> ser.dt.seconds 

928 0 1 

929 1 2 

930 2 3 

931 dtype: int32 

932 

933 For TimedeltaIndex: 

934 

935 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='s') 

936 >>> tdelta_idx 

937 TimedeltaIndex(['0 days 00:00:01', '0 days 00:00:02', '0 days 00:00:03'], 

938 dtype='timedelta64[s]', freq=None) 

939 >>> tdelta_idx.seconds 

940 Index([1, 2, 3], dtype='int32')""" 

941 ) 

942 seconds = _field_accessor( 

943 "seconds", 

944 "seconds", 

945 seconds_docstring, 

946 ) 

947 

948 microseconds_docstring = textwrap.dedent( 

949 """Number of microseconds (>= 0 and less than 1 second) for each element. 

950 

951 See Also 

952 -------- 

953 pd.Timedelta.microseconds : Number of microseconds (>= 0 and less than 1 second). 

954 pd.Timedelta.to_pytimedelta.microseconds : Number of microseconds (>= 0 and less 

955 than 1 second) of a datetime.timedelta. 

956 

957 Examples 

958 -------- 

959 For Series: 

960 

961 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='us')) 

962 >>> ser 

963 0 0 days 00:00:00.000001 

964 1 0 days 00:00:00.000002 

965 2 0 days 00:00:00.000003 

966 dtype: timedelta64[us] 

967 >>> ser.dt.microseconds 

968 0 1 

969 1 2 

970 2 3 

971 dtype: int32 

972 

973 For TimedeltaIndex: 

974 

975 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='us') 

976 >>> tdelta_idx 

977 TimedeltaIndex(['0 days 00:00:00.000001', '0 days 00:00:00.000002', 

978 '0 days 00:00:00.000003'], 

979 dtype='timedelta64[us]', freq=None) 

980 >>> tdelta_idx.microseconds 

981 Index([1, 2, 3], dtype='int32')""" 

982 ) 

983 microseconds = _field_accessor( 

984 "microseconds", 

985 "microseconds", 

986 microseconds_docstring, 

987 ) 

988 

989 nanoseconds_docstring = textwrap.dedent( 

990 """Number of nanoseconds (>= 0 and less than 1 microsecond) for each element. 

991 

992 See Also 

993 -------- 

994 Series.dt.seconds : Return number of seconds for each element. 

995 Series.dt.microseconds : Return number of nanoseconds for each element. 

996 

997 Examples 

998 -------- 

999 For Series: 

1000 

1001 >>> ser = pd.Series(pd.to_timedelta([1, 2, 3], unit='ns')) 

1002 >>> ser 

1003 0 0 days 00:00:00.000000001 

1004 1 0 days 00:00:00.000000002 

1005 2 0 days 00:00:00.000000003 

1006 dtype: timedelta64[ns] 

1007 >>> ser.dt.nanoseconds 

1008 0 1 

1009 1 2 

1010 2 3 

1011 dtype: int32 

1012 

1013 For TimedeltaIndex: 

1014 

1015 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit='ns') 

1016 >>> tdelta_idx 

1017 TimedeltaIndex(['0 days 00:00:00.000000001', '0 days 00:00:00.000000002', 

1018 '0 days 00:00:00.000000003'], 

1019 dtype='timedelta64[ns]', freq=None) 

1020 >>> tdelta_idx.nanoseconds 

1021 Index([1, 2, 3], dtype='int32')""" 

1022 ) 

1023 nanoseconds = _field_accessor( 

1024 "nanoseconds", 

1025 "nanoseconds", 

1026 nanoseconds_docstring, 

1027 ) 

1028 

1029 @property 

1030 def components(self) -> DataFrame: 

1031 """ 

1032 Return a DataFrame of the individual resolution components of the Timedeltas. 

1033 

1034 The components (days, hours, minutes seconds, milliseconds, microseconds, 

1035 nanoseconds) are returned as columns in a DataFrame. 

1036 

1037 Returns 

1038 ------- 

1039 DataFrame 

1040 

1041 See Also 

1042 -------- 

1043 TimedeltaIndex.total_seconds : Return total duration expressed in seconds. 

1044 Timedelta.components : Return a components namedtuple-like of a single 

1045 timedelta. 

1046 

1047 Examples 

1048 -------- 

1049 >>> tdelta_idx = pd.to_timedelta(["1 day 3 min 2 us 42 ns"]) 

1050 >>> tdelta_idx 

1051 TimedeltaIndex(['1 days 00:03:00.000002042'], 

1052 dtype='timedelta64[ns]', freq=None) 

1053 >>> tdelta_idx.components 

1054 days hours minutes seconds milliseconds microseconds nanoseconds 

1055 0 1 0 3 0 0 2 42 

1056 """ 

1057 from pandas import DataFrame 

1058 

1059 columns = [ 

1060 "days", 

1061 "hours", 

1062 "minutes", 

1063 "seconds", 

1064 "milliseconds", 

1065 "microseconds", 

1066 "nanoseconds", 

1067 ] 

1068 hasnans = self._hasna 

1069 if hasnans: 

1070 

1071 def f(x): 

1072 if isna(x): 

1073 return [np.nan] * len(columns) 

1074 return x.components 

1075 

1076 else: 

1077 

1078 def f(x): 

1079 return x.components 

1080 

1081 result = DataFrame([f(x) for x in self], columns=columns) 

1082 if not hasnans: 

1083 result = result.astype("int64") 

1084 return result 

1085 

1086 

1087# --------------------------------------------------------------------- 

1088# Constructor Helpers 

1089 

1090 

1091def sequence_to_td64ns( 

1092 data, 

1093 copy: bool = False, 

1094 unit=None, 

1095 errors: DateTimeErrorChoices = "raise", 

1096) -> tuple[np.ndarray, Tick | Day | None]: 

1097 """ 

1098 Parameters 

1099 ---------- 

1100 data : list-like 

1101 copy : bool, default False 

1102 unit : str, optional 

1103 The timedelta unit to treat integers as multiples of. For numeric 

1104 data this defaults to ``'ns'``. 

1105 Must be un-specified if the data contains a str and ``errors=="raise"``. 

1106 errors : {"raise", "coerce", "ignore"}, default "raise" 

1107 How to handle elements that cannot be converted to timedelta64[ns]. 

1108 See ``pandas.to_timedelta`` for details. 

1109 

1110 Returns 

1111 ------- 

1112 converted : numpy.ndarray 

1113 The sequence converted to a numpy array with dtype ``timedelta64[ns]``. 

1114 inferred_freq : Tick, Day, or None 

1115 The inferred frequency of the sequence. 

1116 

1117 Raises 

1118 ------ 

1119 ValueError : Data cannot be converted to timedelta64[ns]. 

1120 

1121 Notes 

1122 ----- 

1123 Unlike `pandas.to_timedelta`, if setting ``errors=ignore`` will not cause 

1124 errors to be ignored; they are caught and subsequently ignored at a 

1125 higher level. 

1126 """ 

1127 assert unit not in ["Y", "y", "M"] # caller is responsible for checking 

1128 

1129 inferred_freq = None 

1130 if unit is not None: 

1131 unit = parse_timedelta_unit(unit) 

1132 

1133 data, copy = dtl.ensure_arraylike_for_datetimelike( 

1134 data, copy, cls_name="TimedeltaArray" 

1135 ) 

1136 

1137 if isinstance(data, TimedeltaArray): 

1138 inferred_freq = data.freq 

1139 

1140 # Convert whatever we have into timedelta64[ns] dtype 

1141 if data.dtype == object or is_string_dtype(data.dtype): 

1142 # no need to make a copy, need to convert if string-dtyped 

1143 data = _objects_to_td64ns(data, unit=unit, errors=errors) 

1144 copy = False 

1145 

1146 elif is_integer_dtype(data.dtype): 

1147 # treat as multiples of the given unit 

1148 data, copy_made = _ints_to_td64ns(data, unit=unit) 

1149 copy = copy and not copy_made 

1150 

1151 elif is_float_dtype(data.dtype): 

1152 # cast the unit, multiply base/frac separately 

1153 # to avoid precision issues from float -> int 

1154 if isinstance(data.dtype, ExtensionDtype): 

1155 mask = data._mask 

1156 data = data._data 

1157 else: 

1158 mask = np.isnan(data) 

1159 

1160 if unit is not None and unit != "ns": 

1161 # if all non-NaN entries are round, treat these like ints and give 

1162 # back the requested unit (or closest-supported) 

1163 with np.errstate(invalid="ignore"): 

1164 int_data = data.astype(np.int64) 

1165 all_round = (mask | (data == int_data)).all() 

1166 if all_round: 

1167 result, _ = sequence_to_td64ns( 

1168 int_data, copy=False, unit=unit, errors=errors 

1169 ) 

1170 result[mask] = iNaT 

1171 return result, inferred_freq 

1172 

1173 data = cast_from_unit_vectorized(data, unit or "ns") 

1174 data[mask] = iNaT 

1175 data = data.view("m8[ns]") 

1176 copy = False 

1177 

1178 elif lib.is_np_dtype(data.dtype, "m"): 

1179 if not is_supported_dtype(data.dtype): 

1180 # cast to closest supported unit, i.e. s or ns 

1181 new_dtype = get_supported_dtype(data.dtype) 

1182 data = astype_overflowsafe(data, dtype=new_dtype, copy=False) 

1183 copy = False 

1184 

1185 else: 

1186 # This includes datetime64-dtype, see GH#23539, GH#29794 

1187 raise TypeError(f"dtype {data.dtype} cannot be converted to timedelta64[ns]") 

1188 

1189 if not copy: 

1190 data = np.asarray(data) 

1191 else: 

1192 data = np.array(data, copy=copy) 

1193 

1194 assert data.dtype.kind == "m" 

1195 assert data.dtype != "m8" # i.e. not unit-less 

1196 

1197 return data, inferred_freq 

1198 

1199 

1200def _ints_to_td64ns(data, unit: str = "ns") -> tuple[np.ndarray, bool]: 

1201 """ 

1202 Convert an ndarray with integer-dtype to timedelta64[ns] dtype, treating 

1203 the integers as multiples of the given timedelta unit. 

1204 

1205 Parameters 

1206 ---------- 

1207 data : numpy.ndarray with integer-dtype 

1208 unit : str, default "ns" 

1209 The timedelta unit to treat integers as multiples of. 

1210 

1211 Returns 

1212 ------- 

1213 numpy.ndarray : timedelta64[ns] array converted from data 

1214 bool : whether a copy was made 

1215 """ 

1216 copy_made = False 

1217 unit = unit if unit is not None else "ns" 

1218 

1219 if data.dtype != np.int64: 

1220 # converting to int64 makes a copy, so we can avoid 

1221 # re-copying later 

1222 data = data.astype(np.int64) 

1223 copy_made = True 

1224 

1225 if unit != "ns": 

1226 dtype_str = f"timedelta64[{unit}]" 

1227 data = data.view(dtype_str) 

1228 

1229 new_dtype = get_supported_dtype(data.dtype) 

1230 if new_dtype != data.dtype: 

1231 data = astype_overflowsafe(data, dtype=new_dtype) 

1232 

1233 # the astype conversion makes a copy, so we can avoid re-copying later 

1234 copy_made = True 

1235 

1236 else: 

1237 data = data.view("timedelta64[ns]") 

1238 

1239 return data, copy_made 

1240 

1241 

1242def _objects_to_td64ns( 

1243 data, unit=None, errors: DateTimeErrorChoices = "raise" 

1244) -> np.ndarray: 

1245 """ 

1246 Convert an object-dtyped or string-dtyped array into a 

1247 timedelta64[ns]-dtyped array. 

1248 

1249 Parameters 

1250 ---------- 

1251 data : ndarray or Index 

1252 unit : str, default "ns" 

1253 The timedelta unit to treat integers as multiples of. 

1254 Must not be specified if the data contains a str. 

1255 errors : {"raise", "coerce", "ignore"}, default "raise" 

1256 How to handle elements that cannot be converted to timedelta64[ns]. 

1257 See ``pandas.to_timedelta`` for details. 

1258 

1259 Returns 

1260 ------- 

1261 numpy.ndarray : timedelta64[ns] array converted from data 

1262 

1263 Raises 

1264 ------ 

1265 ValueError : Data cannot be converted to timedelta64[ns]. 

1266 

1267 Notes 

1268 ----- 

1269 Unlike `pandas.to_timedelta`, if setting `errors=ignore` will not cause 

1270 errors to be ignored; they are caught and subsequently ignored at a 

1271 higher level. 

1272 """ 

1273 # coerce Index to np.ndarray, converting string-dtype if necessary 

1274 values = np.asarray(data, dtype=np.object_) 

1275 

1276 result = array_to_timedelta64(values, unit=unit, errors=errors) 

1277 return result 

1278 

1279 

1280def _validate_td64_dtype(dtype) -> DtypeObj: 

1281 dtype = pandas_dtype(dtype) 

1282 if dtype == np.dtype("m8"): 

1283 # no precision disallowed GH#24806 

1284 msg = ( 

1285 "Passing in 'timedelta' dtype with no precision is not allowed. " 

1286 "Please pass in 'timedelta64[ns]' instead." 

1287 ) 

1288 raise ValueError(msg) 

1289 

1290 if not lib.is_np_dtype(dtype, "m"): 

1291 raise ValueError(f"dtype '{dtype}' is invalid, should be np.timedelta64 dtype") 

1292 elif not is_supported_dtype(dtype): 

1293 raise ValueError("Supported timedelta64 resolutions are 's', 'ms', 'us', 'ns'") 

1294 

1295 return dtype