Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/pandas/core/arrays/timedeltas.py: 25%

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

436 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5from typing import ( 

6 TYPE_CHECKING, 

7 cast, 

8) 

9 

10import numpy as np 

11 

12from pandas._libs import ( 

13 lib, 

14 tslibs, 

15) 

16from pandas._libs.tslibs import ( 

17 NaT, 

18 NaTType, 

19 Tick, 

20 Timedelta, 

21 astype_overflowsafe, 

22 get_supported_dtype, 

23 iNaT, 

24 is_supported_dtype, 

25 periods_per_second, 

26) 

27from pandas._libs.tslibs.conversion import cast_from_unit_vectorized 

28from pandas._libs.tslibs.fields import ( 

29 get_timedelta_days, 

30 get_timedelta_field, 

31) 

32from pandas._libs.tslibs.timedeltas import ( 

33 array_to_timedelta64, 

34 floordiv_object_array, 

35 ints_to_pytimedelta, 

36 parse_timedelta_unit, 

37 truediv_object_array, 

38) 

39from pandas.compat.numpy import function as nv 

40from pandas.util._validators import validate_endpoints 

41 

42from pandas.core.dtypes.common import ( 

43 TD64NS_DTYPE, 

44 is_float_dtype, 

45 is_integer_dtype, 

46 is_object_dtype, 

47 is_scalar, 

48 is_string_dtype, 

49 pandas_dtype, 

50) 

51from pandas.core.dtypes.dtypes import ExtensionDtype 

52from pandas.core.dtypes.missing import isna 

53 

54from pandas.core import ( 

55 nanops, 

56 roperator, 

57) 

58from pandas.core.array_algos import datetimelike_accumulations 

59from pandas.core.arrays import datetimelike as dtl 

60from pandas.core.arrays._ranges import generate_regular_range 

61import pandas.core.common as com 

62from pandas.core.ops.common import unpack_zerodim_and_defer 

63 

64if TYPE_CHECKING: 

65 from collections.abc import Iterator 

66 

67 from pandas._typing import ( 

68 AxisInt, 

69 DateTimeErrorChoices, 

70 DtypeObj, 

71 NpDtype, 

72 Self, 

73 npt, 

74 ) 

75 

76 from pandas import DataFrame 

77 

78import textwrap 

79 

80 

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

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

83 values = self.asi8 

84 if alias == "days": 

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

86 else: 

87 # error: Incompatible types in assignment ( 

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

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

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

91 if self._hasna: 

92 result = self._maybe_mask_results( 

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

94 ) 

95 

96 return result 

97 

98 f.__name__ = name 

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

100 return property(f) 

101 

102 

103class TimedeltaArray(dtl.TimelikeOps): 

104 """ 

105 Pandas ExtensionArray for timedelta data. 

106 

107 .. warning:: 

108 

109 TimedeltaArray is currently experimental, and its API may change 

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

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

112 subclass. 

113 

114 Parameters 

115 ---------- 

116 values : array-like 

117 The timedelta data. 

118 

119 dtype : numpy.dtype 

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

121 freq : Offset, optional 

122 copy : bool, default False 

123 Whether to copy the underlying array of data. 

124 

125 Attributes 

126 ---------- 

127 None 

128 

129 Methods 

130 ------- 

131 None 

132 

133 Examples 

134 -------- 

135 >>> pd.arrays.TimedeltaArray._from_sequence(pd.TimedeltaIndex(['1h', '2h'])) 

136 <TimedeltaArray> 

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

138 Length: 2, dtype: timedelta64[ns] 

139 """ 

140 

141 _typ = "timedeltaarray" 

142 _internal_fill_value = np.timedelta64("NaT", "ns") 

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

144 _is_recognized_dtype = lambda x: lib.is_np_dtype(x, "m") 

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

146 

147 @property 

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

149 return Timedelta 

150 

151 __array_priority__ = 1000 

152 # define my properties & methods for delegation 

153 _other_ops: list[str] = [] 

154 _bool_ops: list[str] = [] 

155 _object_ops: list[str] = ["freq"] 

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

157 _datetimelike_ops: list[str] = _field_ops + _object_ops + _bool_ops + ["unit"] 

158 _datetimelike_methods: list[str] = [ 

159 "to_pytimedelta", 

160 "total_seconds", 

161 "round", 

162 "floor", 

163 "ceil", 

164 "as_unit", 

165 ] 

166 

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

168 # operates pointwise. 

169 

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

171 y = x.view("i8") 

172 if y == NaT._value: 

173 return NaT 

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

175 

176 @property 

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

178 # "ExtensionDtype" in supertype "ExtensionArray" 

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

180 """ 

181 The dtype for the TimedeltaArray. 

182 

183 .. warning:: 

184 

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

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

187 not a ``numpy.dtype``. 

188 

189 Returns 

190 ------- 

191 numpy.dtype 

192 """ 

193 return self._ndarray.dtype 

194 

195 # ---------------------------------------------------------------- 

196 # Constructors 

197 

198 _freq = None 

199 _default_dtype = TD64NS_DTYPE # used in TimeLikeOps.__init__ 

200 

201 @classmethod 

202 def _validate_dtype(cls, values, dtype): 

203 # used in TimeLikeOps.__init__ 

204 dtype = _validate_td64_dtype(dtype) 

205 _validate_td64_dtype(values.dtype) 

206 if dtype != values.dtype: 

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

208 return dtype 

209 

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

211 @classmethod 

212 def _simple_new( # type: ignore[override] 

213 cls, 

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

215 freq: Tick | None = None, 

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

217 ) -> Self: 

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

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

220 assert not tslibs.is_unitless(dtype) 

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

222 assert dtype == values.dtype 

223 assert freq is None or isinstance(freq, Tick) 

224 

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

226 result._freq = freq 

227 return result 

228 

229 @classmethod 

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

231 if dtype: 

232 dtype = _validate_td64_dtype(dtype) 

233 

234 data, freq = sequence_to_td64ns(data, copy=copy, unit=None) 

235 

236 if dtype is not None: 

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

238 

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

240 

241 @classmethod 

242 def _from_sequence_not_strict( 

243 cls, 

244 data, 

245 *, 

246 dtype=None, 

247 copy: bool = False, 

248 freq=lib.no_default, 

249 unit=None, 

250 ) -> Self: 

251 """ 

252 _from_sequence_not_strict but without responsibility for finding the 

253 result's `freq`. 

254 """ 

255 if dtype: 

256 dtype = _validate_td64_dtype(dtype) 

257 

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

259 

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

261 

262 if dtype is not None: 

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

264 

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

266 

267 result._maybe_pin_freq(freq, {}) 

268 return result 

269 

270 @classmethod 

271 def _generate_range( 

272 cls, start, end, periods, freq, closed=None, *, unit: str | None = None 

273 ) -> Self: 

274 periods = dtl.validate_periods(periods) 

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

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

277 

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

279 raise ValueError( 

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

281 "and freq, exactly three must be specified" 

282 ) 

283 

284 if start is not None: 

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

286 

287 if end is not None: 

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

289 

290 if unit is not None: 

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

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

293 else: 

294 unit = "ns" 

295 

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

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

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

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

300 

301 left_closed, right_closed = validate_endpoints(closed) 

302 

303 if freq is not None: 

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

305 else: 

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

307 

308 if not left_closed: 

309 index = index[1:] 

310 if not right_closed: 

311 index = index[:-1] 

312 

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

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

315 

316 # ---------------------------------------------------------------- 

317 # DatetimeLike Interface 

318 

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

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

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

322 self._check_compatible_with(value) 

323 if value is NaT: 

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

325 else: 

326 return value.as_unit(self.unit).asm8 

327 

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

329 return Timedelta(value) 

330 

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

332 # we don't have anything to validate. 

333 pass 

334 

335 # ---------------------------------------------------------------- 

336 # Array-Like / EA-Interface Methods 

337 

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

339 # We handle 

340 # --> timedelta64[ns] 

341 # --> timedelta64 

342 # DatetimeLikeArrayMixin super call handles other cases 

343 dtype = pandas_dtype(dtype) 

344 

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

346 if dtype == self.dtype: 

347 if copy: 

348 return self.copy() 

349 return self 

350 

351 if is_supported_dtype(dtype): 

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

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

354 return type(self)._simple_new( 

355 res_values, dtype=res_values.dtype, freq=self.freq 

356 ) 

357 else: 

358 raise ValueError( 

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

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

361 ) 

362 

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

364 

365 def __iter__(self) -> Iterator: 

366 if self.ndim > 1: 

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

368 yield self[i] 

369 else: 

370 # convert in chunks of 10k for efficiency 

371 data = self._ndarray 

372 length = len(self) 

373 chunksize = 10000 

374 chunks = (length // chunksize) + 1 

375 for i in range(chunks): 

376 start_i = i * chunksize 

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

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

379 yield from converted 

380 

381 # ---------------------------------------------------------------- 

382 # Reductions 

383 

384 def sum( 

385 self, 

386 *, 

387 axis: AxisInt | None = None, 

388 dtype: NpDtype | None = None, 

389 out=None, 

390 keepdims: bool = False, 

391 initial=None, 

392 skipna: bool = True, 

393 min_count: int = 0, 

394 ): 

395 nv.validate_sum( 

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

397 ) 

398 

399 result = nanops.nansum( 

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

401 ) 

402 return self._wrap_reduction_result(axis, result) 

403 

404 def std( 

405 self, 

406 *, 

407 axis: AxisInt | None = None, 

408 dtype: NpDtype | None = None, 

409 out=None, 

410 ddof: int = 1, 

411 keepdims: bool = False, 

412 skipna: bool = True, 

413 ): 

414 nv.validate_stat_ddof_func( 

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

416 ) 

417 

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

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

420 return self._box_func(result) 

421 return self._from_backing_data(result) 

422 

423 # ---------------------------------------------------------------- 

424 # Accumulations 

425 

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

427 if name == "cumsum": 

428 op = getattr(datetimelike_accumulations, name) 

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

430 

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

432 elif name == "cumprod": 

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

434 

435 else: 

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

437 

438 # ---------------------------------------------------------------- 

439 # Rendering Methods 

440 

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

442 from pandas.io.formats.format import get_format_timedelta64 

443 

444 return get_format_timedelta64(self, box=True) 

445 

446 def _format_native_types( 

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

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

449 from pandas.io.formats.format import get_format_timedelta64 

450 

451 # Relies on TimeDelta._repr_base 

452 formatter = get_format_timedelta64(self, na_rep) 

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

454 # but independent of dimension 

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

456 

457 # ---------------------------------------------------------------- 

458 # Arithmetic Methods 

459 

460 def _add_offset(self, other): 

461 assert not isinstance(other, Tick) 

462 raise TypeError( 

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

464 ) 

465 

466 @unpack_zerodim_and_defer("__mul__") 

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

468 if is_scalar(other): 

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

470 result = self._ndarray * other 

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

472 # numpy >= 2.1 may not raise a TypeError 

473 # and seems to dispatch to others.__rmul__? 

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

475 freq = None 

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

477 freq = self.freq * other 

478 if freq.n == 0: 

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

480 freq = None 

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

482 

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

484 # list, tuple 

485 other = np.array(other) 

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

487 # Exclude timedelta64 here so we correctly raise TypeError 

488 # for that instead of ValueError 

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

490 

491 if is_object_dtype(other.dtype): 

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

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

494 # timedelta64[ns]-dtyped result 

495 arr = self._ndarray 

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

497 result = np.array(result) 

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

499 

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

501 result = self._ndarray * other 

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

503 # numpy >= 2.1 may not raise a TypeError 

504 # and seems to dispatch to others.__rmul__? 

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

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

507 

508 __rmul__ = __mul__ 

509 

510 def _scalar_divlike_op(self, other, op): 

511 """ 

512 Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__ 

513 with scalar 'other'. 

514 """ 

515 if isinstance(other, self._recognized_scalars): 

516 other = Timedelta(other) 

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

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

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

520 # specifically timedelta64-NaT 

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

522 res.fill(np.nan) 

523 return res 

524 

525 # otherwise, dispatch to Timedelta implementation 

526 return op(self._ndarray, other) 

527 

528 else: 

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

530 # assume other is numeric, otherwise numpy will raise 

531 

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

533 raise TypeError( 

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

535 ) 

536 

537 result = op(self._ndarray, other) 

538 freq = None 

539 

540 if self.freq is not None: 

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

542 # is floordiv. 

543 freq = self.freq / other 

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

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

546 # rounds down to zero 

547 freq = None 

548 

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

550 

551 def _cast_divlike_op(self, other): 

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

553 # e.g. list, tuple 

554 other = np.array(other) 

555 

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

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

558 return other 

559 

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

561 """ 

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

563 with timedelta64-dtype ndarray other. 

564 """ 

565 # Let numpy handle it 

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

567 

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

569 operator.truediv, 

570 operator.floordiv, 

571 ]: 

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

573 

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

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

576 if mask.any(): 

577 result = result.astype(np.float64) 

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

579 

580 return result 

581 

582 @unpack_zerodim_and_defer("__truediv__") 

583 def __truediv__(self, other): 

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

585 op = operator.truediv 

586 if is_scalar(other): 

587 return self._scalar_divlike_op(other, op) 

588 

589 other = self._cast_divlike_op(other) 

590 if ( 

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

592 or is_integer_dtype(other.dtype) 

593 or is_float_dtype(other.dtype) 

594 ): 

595 return self._vector_divlike_op(other, op) 

596 

597 if is_object_dtype(other.dtype): 

598 other = np.asarray(other) 

599 if self.ndim > 1: 

600 res_cols = [left / right for left, right in zip(self, other)] 

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

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

603 else: 

604 result = truediv_object_array(self._ndarray, other) 

605 

606 return result 

607 

608 else: 

609 return NotImplemented 

610 

611 @unpack_zerodim_and_defer("__rtruediv__") 

612 def __rtruediv__(self, other): 

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

614 op = roperator.rtruediv 

615 if is_scalar(other): 

616 return self._scalar_divlike_op(other, op) 

617 

618 other = self._cast_divlike_op(other) 

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

620 return self._vector_divlike_op(other, op) 

621 

622 elif is_object_dtype(other.dtype): 

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

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

625 # is returned. GH#23829 

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

627 return np.array(result_list) 

628 

629 else: 

630 return NotImplemented 

631 

632 @unpack_zerodim_and_defer("__floordiv__") 

633 def __floordiv__(self, other): 

634 op = operator.floordiv 

635 if is_scalar(other): 

636 return self._scalar_divlike_op(other, op) 

637 

638 other = self._cast_divlike_op(other) 

639 if ( 

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

641 or is_integer_dtype(other.dtype) 

642 or is_float_dtype(other.dtype) 

643 ): 

644 return self._vector_divlike_op(other, op) 

645 

646 elif is_object_dtype(other.dtype): 

647 other = np.asarray(other) 

648 if self.ndim > 1: 

649 res_cols = [left // right for left, right in zip(self, other)] 

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

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

652 else: 

653 result = floordiv_object_array(self._ndarray, other) 

654 

655 assert result.dtype == object 

656 return result 

657 

658 else: 

659 return NotImplemented 

660 

661 @unpack_zerodim_and_defer("__rfloordiv__") 

662 def __rfloordiv__(self, other): 

663 op = roperator.rfloordiv 

664 if is_scalar(other): 

665 return self._scalar_divlike_op(other, op) 

666 

667 other = self._cast_divlike_op(other) 

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

669 return self._vector_divlike_op(other, op) 

670 

671 elif is_object_dtype(other.dtype): 

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

673 result = np.array(result_list) 

674 return result 

675 

676 else: 

677 return NotImplemented 

678 

679 @unpack_zerodim_and_defer("__mod__") 

680 def __mod__(self, other): 

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

682 if isinstance(other, self._recognized_scalars): 

683 other = Timedelta(other) 

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

685 

686 @unpack_zerodim_and_defer("__rmod__") 

687 def __rmod__(self, other): 

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

689 if isinstance(other, self._recognized_scalars): 

690 other = Timedelta(other) 

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

692 

693 @unpack_zerodim_and_defer("__divmod__") 

694 def __divmod__(self, other): 

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

696 if isinstance(other, self._recognized_scalars): 

697 other = Timedelta(other) 

698 

699 res1 = self // other 

700 res2 = self - res1 * other 

701 return res1, res2 

702 

703 @unpack_zerodim_and_defer("__rdivmod__") 

704 def __rdivmod__(self, other): 

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

706 if isinstance(other, self._recognized_scalars): 

707 other = Timedelta(other) 

708 

709 res1 = other // self 

710 res2 = other - res1 * self 

711 return res1, res2 

712 

713 def __neg__(self) -> TimedeltaArray: 

714 freq = None 

715 if self.freq is not None: 

716 freq = -self.freq 

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

718 

719 def __pos__(self) -> TimedeltaArray: 

720 return type(self)._simple_new( 

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

722 ) 

723 

724 def __abs__(self) -> TimedeltaArray: 

725 # Note: freq is not preserved 

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

727 

728 # ---------------------------------------------------------------- 

729 # Conversion Methods - Vectorized analogues of Timedelta methods 

730 

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

732 """ 

733 Return total duration of each element expressed in seconds. 

734 

735 This method is available directly on TimedeltaArray, TimedeltaIndex 

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

737 

738 Returns 

739 ------- 

740 ndarray, Index or Series 

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

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

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

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

745 index is the same as the original. 

746 

747 See Also 

748 -------- 

749 datetime.timedelta.total_seconds : Standard library version 

750 of this method. 

751 TimedeltaIndex.components : Return a DataFrame with components of 

752 each Timedelta. 

753 

754 Examples 

755 -------- 

756 **Series** 

757 

758 >>> s = pd.Series(pd.to_timedelta(np.arange(5), unit='d')) 

759 >>> s 

760 0 0 days 

761 1 1 days 

762 2 2 days 

763 3 3 days 

764 4 4 days 

765 dtype: timedelta64[ns] 

766 

767 >>> s.dt.total_seconds() 

768 0 0.0 

769 1 86400.0 

770 2 172800.0 

771 3 259200.0 

772 4 345600.0 

773 dtype: float64 

774 

775 **TimedeltaIndex** 

776 

777 >>> idx = pd.to_timedelta(np.arange(5), unit='d') 

778 >>> idx 

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

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

781 

782 >>> idx.total_seconds() 

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

784 """ 

785 pps = periods_per_second(self._creso) 

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

787 

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

789 """ 

790 Return an ndarray of datetime.timedelta objects. 

791 

792 Returns 

793 ------- 

794 numpy.ndarray 

795 

796 Examples 

797 -------- 

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

799 >>> tdelta_idx 

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

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

802 >>> tdelta_idx.to_pytimedelta() 

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

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

805 """ 

806 return ints_to_pytimedelta(self._ndarray) 

807 

808 days_docstring = textwrap.dedent( 

809 """Number of days for each element. 

810 

811 Examples 

812 -------- 

813 For Series: 

814 

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

816 >>> ser 

817 0 1 days 

818 1 2 days 

819 2 3 days 

820 dtype: timedelta64[ns] 

821 >>> ser.dt.days 

822 0 1 

823 1 2 

824 2 3 

825 dtype: int64 

826 

827 For TimedeltaIndex: 

828 

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

830 >>> tdelta_idx 

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

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

833 >>> tdelta_idx.days 

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

835 ) 

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

837 

838 seconds_docstring = textwrap.dedent( 

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

840 

841 Examples 

842 -------- 

843 For Series: 

844 

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

846 >>> ser 

847 0 0 days 00:00:01 

848 1 0 days 00:00:02 

849 2 0 days 00:00:03 

850 dtype: timedelta64[ns] 

851 >>> ser.dt.seconds 

852 0 1 

853 1 2 

854 2 3 

855 dtype: int32 

856 

857 For TimedeltaIndex: 

858 

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

860 >>> tdelta_idx 

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

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

863 >>> tdelta_idx.seconds 

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

865 ) 

866 seconds = _field_accessor( 

867 "seconds", 

868 "seconds", 

869 seconds_docstring, 

870 ) 

871 

872 microseconds_docstring = textwrap.dedent( 

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

874 

875 Examples 

876 -------- 

877 For Series: 

878 

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

880 >>> ser 

881 0 0 days 00:00:00.000001 

882 1 0 days 00:00:00.000002 

883 2 0 days 00:00:00.000003 

884 dtype: timedelta64[ns] 

885 >>> ser.dt.microseconds 

886 0 1 

887 1 2 

888 2 3 

889 dtype: int32 

890 

891 For TimedeltaIndex: 

892 

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

894 >>> tdelta_idx 

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

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

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

898 >>> tdelta_idx.microseconds 

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

900 ) 

901 microseconds = _field_accessor( 

902 "microseconds", 

903 "microseconds", 

904 microseconds_docstring, 

905 ) 

906 

907 nanoseconds_docstring = textwrap.dedent( 

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

909 

910 Examples 

911 -------- 

912 For Series: 

913 

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

915 >>> ser 

916 0 0 days 00:00:00.000000001 

917 1 0 days 00:00:00.000000002 

918 2 0 days 00:00:00.000000003 

919 dtype: timedelta64[ns] 

920 >>> ser.dt.nanoseconds 

921 0 1 

922 1 2 

923 2 3 

924 dtype: int32 

925 

926 For TimedeltaIndex: 

927 

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

929 >>> tdelta_idx 

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

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

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

933 >>> tdelta_idx.nanoseconds 

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

935 ) 

936 nanoseconds = _field_accessor( 

937 "nanoseconds", 

938 "nanoseconds", 

939 nanoseconds_docstring, 

940 ) 

941 

942 @property 

943 def components(self) -> DataFrame: 

944 """ 

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

946 

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

948 nanoseconds) are returned as columns in a DataFrame. 

949 

950 Returns 

951 ------- 

952 DataFrame 

953 

954 Examples 

955 -------- 

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

957 >>> tdelta_idx 

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

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

960 >>> tdelta_idx.components 

961 days hours minutes seconds milliseconds microseconds nanoseconds 

962 0 1 0 3 0 0 2 42 

963 """ 

964 from pandas import DataFrame 

965 

966 columns = [ 

967 "days", 

968 "hours", 

969 "minutes", 

970 "seconds", 

971 "milliseconds", 

972 "microseconds", 

973 "nanoseconds", 

974 ] 

975 hasnans = self._hasna 

976 if hasnans: 

977 

978 def f(x): 

979 if isna(x): 

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

981 return x.components 

982 

983 else: 

984 

985 def f(x): 

986 return x.components 

987 

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

989 if not hasnans: 

990 result = result.astype("int64") 

991 return result 

992 

993 

994# --------------------------------------------------------------------- 

995# Constructor Helpers 

996 

997 

998def sequence_to_td64ns( 

999 data, 

1000 copy: bool = False, 

1001 unit=None, 

1002 errors: DateTimeErrorChoices = "raise", 

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

1004 """ 

1005 Parameters 

1006 ---------- 

1007 data : list-like 

1008 copy : bool, default False 

1009 unit : str, optional 

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

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

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

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

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

1015 See ``pandas.to_timedelta`` for details. 

1016 

1017 Returns 

1018 ------- 

1019 converted : numpy.ndarray 

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

1021 inferred_freq : Tick or None 

1022 The inferred frequency of the sequence. 

1023 

1024 Raises 

1025 ------ 

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

1027 

1028 Notes 

1029 ----- 

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

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

1032 higher level. 

1033 """ 

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

1035 

1036 inferred_freq = None 

1037 if unit is not None: 

1038 unit = parse_timedelta_unit(unit) 

1039 

1040 data, copy = dtl.ensure_arraylike_for_datetimelike( 

1041 data, copy, cls_name="TimedeltaArray" 

1042 ) 

1043 

1044 if isinstance(data, TimedeltaArray): 

1045 inferred_freq = data.freq 

1046 

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

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

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

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

1051 copy = False 

1052 

1053 elif is_integer_dtype(data.dtype): 

1054 # treat as multiples of the given unit 

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

1056 copy = copy and not copy_made 

1057 

1058 elif is_float_dtype(data.dtype): 

1059 # cast the unit, multiply base/frac separately 

1060 # to avoid precision issues from float -> int 

1061 if isinstance(data.dtype, ExtensionDtype): 

1062 mask = data._mask 

1063 data = data._data 

1064 else: 

1065 mask = np.isnan(data) 

1066 

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

1068 data[mask] = iNaT 

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

1070 copy = False 

1071 

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

1073 if not is_supported_dtype(data.dtype): 

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

1075 new_dtype = get_supported_dtype(data.dtype) 

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

1077 copy = False 

1078 

1079 else: 

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

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

1082 

1083 if not copy: 

1084 data = np.asarray(data) 

1085 else: 

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

1087 

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

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

1090 

1091 return data, inferred_freq 

1092 

1093 

1094def _ints_to_td64ns(data, unit: str = "ns"): 

1095 """ 

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

1097 the integers as multiples of the given timedelta unit. 

1098 

1099 Parameters 

1100 ---------- 

1101 data : numpy.ndarray with integer-dtype 

1102 unit : str, default "ns" 

1103 The timedelta unit to treat integers as multiples of. 

1104 

1105 Returns 

1106 ------- 

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

1108 bool : whether a copy was made 

1109 """ 

1110 copy_made = False 

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

1112 

1113 if data.dtype != np.int64: 

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

1115 # re-copying later 

1116 data = data.astype(np.int64) 

1117 copy_made = True 

1118 

1119 if unit != "ns": 

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

1121 data = data.view(dtype_str) 

1122 

1123 data = astype_overflowsafe(data, dtype=TD64NS_DTYPE) 

1124 

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

1126 copy_made = True 

1127 

1128 else: 

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

1130 

1131 return data, copy_made 

1132 

1133 

1134def _objects_to_td64ns(data, unit=None, errors: DateTimeErrorChoices = "raise"): 

1135 """ 

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

1137 timedelta64[ns]-dtyped array. 

1138 

1139 Parameters 

1140 ---------- 

1141 data : ndarray or Index 

1142 unit : str, default "ns" 

1143 The timedelta unit to treat integers as multiples of. 

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

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

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

1147 See ``pandas.to_timedelta`` for details. 

1148 

1149 Returns 

1150 ------- 

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

1152 

1153 Raises 

1154 ------ 

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

1156 

1157 Notes 

1158 ----- 

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

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

1161 higher level. 

1162 """ 

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

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

1165 

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

1167 return result.view("timedelta64[ns]") 

1168 

1169 

1170def _validate_td64_dtype(dtype) -> DtypeObj: 

1171 dtype = pandas_dtype(dtype) 

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

1173 # no precision disallowed GH#24806 

1174 msg = ( 

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

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

1177 ) 

1178 raise ValueError(msg) 

1179 

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

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

1182 elif not is_supported_dtype(dtype): 

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

1184 

1185 return dtype