Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/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

440 statements  

1from __future__ import annotations 

2 

3from datetime import timedelta 

4import operator 

5from typing import ( 

6 TYPE_CHECKING, 

7 Iterator, 

8 cast, 

9) 

10import warnings 

11 

12import numpy as np 

13 

14from pandas._libs import ( 

15 lib, 

16 tslibs, 

17) 

18from pandas._libs.tslibs import ( 

19 BaseOffset, 

20 NaT, 

21 NaTType, 

22 Tick, 

23 Timedelta, 

24 astype_overflowsafe, 

25 get_supported_reso, 

26 get_unit_from_dtype, 

27 iNaT, 

28 is_supported_unit, 

29 npy_unit_to_abbrev, 

30 periods_per_second, 

31 to_offset, 

32) 

33from pandas._libs.tslibs.conversion import precision_from_unit 

34from pandas._libs.tslibs.fields import ( 

35 get_timedelta_days, 

36 get_timedelta_field, 

37) 

38from pandas._libs.tslibs.timedeltas import ( 

39 array_to_timedelta64, 

40 floordiv_object_array, 

41 ints_to_pytimedelta, 

42 parse_timedelta_unit, 

43 truediv_object_array, 

44) 

45from pandas._typing import ( 

46 AxisInt, 

47 DateTimeErrorChoices, 

48 DtypeObj, 

49 NpDtype, 

50 npt, 

51) 

52from pandas.compat.numpy import function as nv 

53from pandas.util._validators import validate_endpoints 

54 

55from pandas.core.dtypes.common import ( 

56 TD64NS_DTYPE, 

57 is_dtype_equal, 

58 is_extension_array_dtype, 

59 is_float_dtype, 

60 is_integer_dtype, 

61 is_object_dtype, 

62 is_scalar, 

63 is_string_dtype, 

64 is_timedelta64_dtype, 

65 pandas_dtype, 

66) 

67from pandas.core.dtypes.missing import isna 

68 

69from pandas.core import nanops 

70from pandas.core.array_algos import datetimelike_accumulations 

71from pandas.core.arrays import datetimelike as dtl 

72from pandas.core.arrays._ranges import generate_regular_range 

73import pandas.core.common as com 

74from pandas.core.ops import roperator 

75from pandas.core.ops.common import unpack_zerodim_and_defer 

76 

77if TYPE_CHECKING: 

78 from pandas import DataFrame 

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] # noqa: E501 

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 

134 _typ = "timedeltaarray" 

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

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

137 _is_recognized_dtype = is_timedelta64_dtype 

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

139 

140 @property 

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

142 return Timedelta 

143 

144 __array_priority__ = 1000 

145 # define my properties & methods for delegation 

146 _other_ops: list[str] = [] 

147 _bool_ops: list[str] = [] 

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

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

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

151 _datetimelike_methods: list[str] = [ 

152 "to_pytimedelta", 

153 "total_seconds", 

154 "round", 

155 "floor", 

156 "ceil", 

157 "as_unit", 

158 ] 

159 

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

161 # operates pointwise. 

162 

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

164 y = x.view("i8") 

165 if y == NaT._value: 

166 return NaT 

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

168 

169 @property 

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

171 # "ExtensionDtype" in supertype "ExtensionArray" 

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

173 """ 

174 The dtype for the TimedeltaArray. 

175 

176 .. warning:: 

177 

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

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

180 not a ``numpy.dtype``. 

181 

182 Returns 

183 ------- 

184 numpy.dtype 

185 """ 

186 return self._ndarray.dtype 

187 

188 # ---------------------------------------------------------------- 

189 # Constructors 

190 

191 _freq = None 

192 _default_dtype = TD64NS_DTYPE # used in TimeLikeOps.__init__ 

193 

194 @classmethod 

195 def _validate_dtype(cls, values, dtype): 

196 # used in TimeLikeOps.__init__ 

197 _validate_td64_dtype(values.dtype) 

198 dtype = _validate_td64_dtype(dtype) 

199 return dtype 

200 

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

202 @classmethod 

203 def _simple_new( # type: ignore[override] 

204 cls, values: np.ndarray, freq: BaseOffset | None = None, dtype=TD64NS_DTYPE 

205 ) -> TimedeltaArray: 

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

207 assert isinstance(dtype, np.dtype) and dtype.kind == "m" 

208 assert not tslibs.is_unitless(dtype) 

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

210 assert dtype == values.dtype 

211 

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

213 result._freq = freq 

214 return result 

215 

216 @classmethod 

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

218 if dtype: 

219 dtype = _validate_td64_dtype(dtype) 

220 

221 data, inferred_freq = sequence_to_td64ns(data, copy=copy, unit=None) 

222 freq, _ = dtl.validate_inferred_freq(None, inferred_freq, False) 

223 

224 if dtype is not None: 

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

226 

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

228 

229 @classmethod 

230 def _from_sequence_not_strict( 

231 cls, 

232 data, 

233 *, 

234 dtype=None, 

235 copy: bool = False, 

236 freq=lib.no_default, 

237 unit=None, 

238 ) -> TimedeltaArray: 

239 """ 

240 A non-strict version of _from_sequence, called from TimedeltaIndex.__new__. 

241 """ 

242 if dtype: 

243 dtype = _validate_td64_dtype(dtype) 

244 

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

246 

247 explicit_none = freq is None 

248 freq = freq if freq is not lib.no_default else None 

249 

250 freq, freq_infer = dtl.maybe_infer_freq(freq) 

251 

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

253 freq, freq_infer = dtl.validate_inferred_freq(freq, inferred_freq, freq_infer) 

254 if explicit_none: 

255 freq = None 

256 

257 if dtype is not None: 

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

259 

260 result = cls._simple_new(data, dtype=data.dtype, freq=freq) 

261 

262 if inferred_freq is None and freq is not None: 

263 # this condition precludes `freq_infer` 

264 cls._validate_frequency(result, freq) 

265 

266 elif freq_infer: 

267 # Set _freq directly to bypass duplicative _validate_frequency 

268 # check. 

269 result._freq = to_offset(result.inferred_freq) 

270 

271 return result 

272 

273 # Signature of "_generate_range" incompatible with supertype 

274 # "DatetimeLikeArrayMixin" 

275 @classmethod 

276 def _generate_range( # type: ignore[override] 

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

278 ): 

279 periods = dtl.validate_periods(periods) 

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

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

282 

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

284 raise ValueError( 

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

286 "and freq, exactly three must be specified" 

287 ) 

288 

289 if start is not None: 

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

291 

292 if end is not None: 

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

294 

295 if unit is not None: 

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

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

298 else: 

299 unit = "ns" 

300 

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

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

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

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

305 

306 left_closed, right_closed = validate_endpoints(closed) 

307 

308 if freq is not None: 

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

310 else: 

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

312 

313 if not left_closed: 

314 index = index[1:] 

315 if not right_closed: 

316 index = index[:-1] 

317 

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

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

320 

321 # ---------------------------------------------------------------- 

322 # DatetimeLike Interface 

323 

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

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

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

327 self._check_compatible_with(value) 

328 if value is NaT: 

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

330 else: 

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

332 

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

334 return Timedelta(value) 

335 

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

337 # we don't have anything to validate. 

338 pass 

339 

340 # ---------------------------------------------------------------- 

341 # Array-Like / EA-Interface Methods 

342 

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

344 # We handle 

345 # --> timedelta64[ns] 

346 # --> timedelta64 

347 # DatetimeLikeArrayMixin super call handles other cases 

348 dtype = pandas_dtype(dtype) 

349 

350 if isinstance(dtype, np.dtype) and dtype.kind == "m": 

351 if dtype == self.dtype: 

352 if copy: 

353 return self.copy() 

354 return self 

355 

356 if is_supported_unit(get_unit_from_dtype(dtype)): 

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

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

359 return type(self)._simple_new( 

360 res_values, dtype=res_values.dtype, freq=self.freq 

361 ) 

362 else: 

363 raise ValueError( 

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

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

366 ) 

367 

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

369 

370 def __iter__(self) -> Iterator: 

371 if self.ndim > 1: 

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

373 yield self[i] 

374 else: 

375 # convert in chunks of 10k for efficiency 

376 data = self._ndarray 

377 length = len(self) 

378 chunksize = 10000 

379 chunks = (length // chunksize) + 1 

380 for i in range(chunks): 

381 start_i = i * chunksize 

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

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

384 yield from converted 

385 

386 # ---------------------------------------------------------------- 

387 # Reductions 

388 

389 def sum( 

390 self, 

391 *, 

392 axis: AxisInt | None = None, 

393 dtype: NpDtype | None = None, 

394 out=None, 

395 keepdims: bool = False, 

396 initial=None, 

397 skipna: bool = True, 

398 min_count: int = 0, 

399 ): 

400 nv.validate_sum( 

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

402 ) 

403 

404 result = nanops.nansum( 

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

406 ) 

407 return self._wrap_reduction_result(axis, result) 

408 

409 def std( 

410 self, 

411 *, 

412 axis: AxisInt | None = None, 

413 dtype: NpDtype | None = None, 

414 out=None, 

415 ddof: int = 1, 

416 keepdims: bool = False, 

417 skipna: bool = True, 

418 ): 

419 nv.validate_stat_ddof_func( 

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

421 ) 

422 

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

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

425 return self._box_func(result) 

426 return self._from_backing_data(result) 

427 

428 # ---------------------------------------------------------------- 

429 # Accumulations 

430 

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

432 if name == "cumsum": 

433 op = getattr(datetimelike_accumulations, name) 

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

435 

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

437 elif name == "cumprod": 

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

439 

440 else: 

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

442 

443 # ---------------------------------------------------------------- 

444 # Rendering Methods 

445 

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

447 from pandas.io.formats.format import get_format_timedelta64 

448 

449 return get_format_timedelta64(self, box=True) 

450 

451 def _format_native_types( 

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

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

454 from pandas.io.formats.format import get_format_timedelta64 

455 

456 # Relies on TimeDelta._repr_base 

457 formatter = get_format_timedelta64(self._ndarray, na_rep) 

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

459 # but independent of dimension 

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

461 

462 # ---------------------------------------------------------------- 

463 # Arithmetic Methods 

464 

465 def _add_offset(self, other): 

466 assert not isinstance(other, Tick) 

467 raise TypeError( 

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

469 ) 

470 

471 @unpack_zerodim_and_defer("__mul__") 

472 def __mul__(self, other) -> TimedeltaArray: 

473 if is_scalar(other): 

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

475 result = self._ndarray * other 

476 freq = None 

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

478 freq = self.freq * other 

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

480 

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

482 # list, tuple 

483 other = np.array(other) 

484 if len(other) != len(self) and not is_timedelta64_dtype(other.dtype): 

485 # Exclude timedelta64 here so we correctly raise TypeError 

486 # for that instead of ValueError 

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

488 

489 if is_object_dtype(other.dtype): 

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

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

492 # timedelta64[ns]-dtyped result 

493 arr = self._ndarray 

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

495 result = np.array(result) 

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

497 

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

499 result = self._ndarray * other 

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

501 

502 __rmul__ = __mul__ 

503 

504 def _scalar_divlike_op(self, other, op): 

505 """ 

506 Shared logic for __truediv__, __rtruediv__, __floordiv__, __rfloordiv__ 

507 with scalar 'other'. 

508 """ 

509 if isinstance(other, self._recognized_scalars): 

510 other = Timedelta(other) 

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

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

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

514 # specifically timedelta64-NaT 

515 result = np.empty(self.shape, dtype=np.float64) 

516 result.fill(np.nan) 

517 return result 

518 

519 # otherwise, dispatch to Timedelta implementation 

520 return op(self._ndarray, other) 

521 

522 else: 

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

524 # assume other is numeric, otherwise numpy will raise 

525 

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

527 raise TypeError( 

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

529 ) 

530 

531 result = op(self._ndarray, other) 

532 freq = None 

533 

534 if self.freq is not None: 

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

536 # is floordiv. 

537 freq = self.freq / other 

538 

539 # TODO: 2022-12-24 test_ufunc_coercions, test_tdi_ops_attributes 

540 # get here for truediv, no tests for floordiv 

541 

542 if op is operator.floordiv: 

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

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

545 # rounds down to zero 

546 # TODO: 2022-12-24 should implement the same check 

547 # for truediv case 

548 freq = None 

549 

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

551 

552 def _cast_divlike_op(self, other): 

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

554 # e.g. list, tuple 

555 other = np.array(other) 

556 

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

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

559 return other 

560 

561 def _vector_divlike_op(self, other, op) -> np.ndarray | TimedeltaArray: 

562 """ 

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

564 with timedelta64-dtype ndarray other. 

565 """ 

566 # Let numpy handle it 

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

568 

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

570 operator.truediv, 

571 operator.floordiv, 

572 ]: 

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

574 

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

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

577 if mask.any(): 

578 result = result.astype(np.float64) 

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

580 

581 return result 

582 

583 @unpack_zerodim_and_defer("__truediv__") 

584 def __truediv__(self, other): 

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

586 op = operator.truediv 

587 if is_scalar(other): 

588 return self._scalar_divlike_op(other, op) 

589 

590 other = self._cast_divlike_op(other) 

591 if ( 

592 is_timedelta64_dtype(other.dtype) 

593 or is_integer_dtype(other.dtype) 

594 or is_float_dtype(other.dtype) 

595 ): 

596 return self._vector_divlike_op(other, op) 

597 

598 if is_object_dtype(other.dtype): 

599 other = np.asarray(other) 

600 if self.ndim > 1: 

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

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

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

604 else: 

605 result = truediv_object_array(self._ndarray, other) 

606 

607 return result 

608 

609 else: 

610 return NotImplemented 

611 

612 @unpack_zerodim_and_defer("__rtruediv__") 

613 def __rtruediv__(self, other): 

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

615 op = roperator.rtruediv 

616 if is_scalar(other): 

617 return self._scalar_divlike_op(other, op) 

618 

619 other = self._cast_divlike_op(other) 

620 if is_timedelta64_dtype(other.dtype): 

621 return self._vector_divlike_op(other, op) 

622 

623 elif is_object_dtype(other.dtype): 

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

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

626 # is returned. GH#23829 

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

628 return np.array(result_list) 

629 

630 else: 

631 return NotImplemented 

632 

633 @unpack_zerodim_and_defer("__floordiv__") 

634 def __floordiv__(self, other): 

635 op = operator.floordiv 

636 if is_scalar(other): 

637 return self._scalar_divlike_op(other, op) 

638 

639 other = self._cast_divlike_op(other) 

640 if ( 

641 is_timedelta64_dtype(other.dtype) 

642 or is_integer_dtype(other.dtype) 

643 or is_float_dtype(other.dtype) 

644 ): 

645 return self._vector_divlike_op(other, op) 

646 

647 elif is_object_dtype(other.dtype): 

648 other = np.asarray(other) 

649 if self.ndim > 1: 

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

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

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

653 else: 

654 result = floordiv_object_array(self._ndarray, other) 

655 

656 assert result.dtype == object 

657 return result 

658 

659 else: 

660 return NotImplemented 

661 

662 @unpack_zerodim_and_defer("__rfloordiv__") 

663 def __rfloordiv__(self, other): 

664 op = roperator.rfloordiv 

665 if is_scalar(other): 

666 return self._scalar_divlike_op(other, op) 

667 

668 other = self._cast_divlike_op(other) 

669 if is_timedelta64_dtype(other.dtype): 

670 return self._vector_divlike_op(other, op) 

671 

672 elif is_object_dtype(other.dtype): 

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

674 result = np.array(result_list) 

675 return result 

676 

677 else: 

678 return NotImplemented 

679 

680 @unpack_zerodim_and_defer("__mod__") 

681 def __mod__(self, other): 

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

683 if isinstance(other, self._recognized_scalars): 

684 other = Timedelta(other) 

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

686 

687 @unpack_zerodim_and_defer("__rmod__") 

688 def __rmod__(self, other): 

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

690 if isinstance(other, self._recognized_scalars): 

691 other = Timedelta(other) 

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

693 

694 @unpack_zerodim_and_defer("__divmod__") 

695 def __divmod__(self, other): 

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

697 if isinstance(other, self._recognized_scalars): 

698 other = Timedelta(other) 

699 

700 res1 = self // other 

701 res2 = self - res1 * other 

702 return res1, res2 

703 

704 @unpack_zerodim_and_defer("__rdivmod__") 

705 def __rdivmod__(self, other): 

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

707 if isinstance(other, self._recognized_scalars): 

708 other = Timedelta(other) 

709 

710 res1 = other // self 

711 res2 = other - res1 * self 

712 return res1, res2 

713 

714 def __neg__(self) -> TimedeltaArray: 

715 freq = None 

716 if self.freq is not None: 

717 freq = -self.freq 

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

719 

720 def __pos__(self) -> TimedeltaArray: 

721 return type(self)(self._ndarray.copy(), freq=self.freq) 

722 

723 def __abs__(self) -> TimedeltaArray: 

724 # Note: freq is not preserved 

725 return type(self)(np.abs(self._ndarray)) 

726 

727 # ---------------------------------------------------------------- 

728 # Conversion Methods - Vectorized analogues of Timedelta methods 

729 

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

731 """ 

732 Return total duration of each element expressed in seconds. 

733 

734 This method is available directly on TimedeltaArray, TimedeltaIndex 

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

736 

737 Returns 

738 ------- 

739 ndarray, Index or Series 

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

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

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

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

744 index is the same as the original. 

745 

746 See Also 

747 -------- 

748 datetime.timedelta.total_seconds : Standard library version 

749 of this method. 

750 TimedeltaIndex.components : Return a DataFrame with components of 

751 each Timedelta. 

752 

753 Examples 

754 -------- 

755 **Series** 

756 

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

758 >>> s 

759 0 0 days 

760 1 1 days 

761 2 2 days 

762 3 3 days 

763 4 4 days 

764 dtype: timedelta64[ns] 

765 

766 >>> s.dt.total_seconds() 

767 0 0.0 

768 1 86400.0 

769 2 172800.0 

770 3 259200.0 

771 4 345600.0 

772 dtype: float64 

773 

774 **TimedeltaIndex** 

775 

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

777 >>> idx 

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

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

780 

781 >>> idx.total_seconds() 

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

783 """ 

784 pps = periods_per_second(self._creso) 

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

786 

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

788 """ 

789 Return an ndarray of datetime.timedelta objects. 

790 

791 Returns 

792 ------- 

793 numpy.ndarray 

794 """ 

795 return ints_to_pytimedelta(self._ndarray) 

796 

797 days = _field_accessor("days", "days", "Number of days for each element.") 

798 seconds = _field_accessor( 

799 "seconds", 

800 "seconds", 

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

802 ) 

803 microseconds = _field_accessor( 

804 "microseconds", 

805 "microseconds", 

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

807 ) 

808 nanoseconds = _field_accessor( 

809 "nanoseconds", 

810 "nanoseconds", 

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

812 ) 

813 

814 @property 

815 def components(self) -> DataFrame: 

816 """ 

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

818 

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

820 nanoseconds) are returned as columns in a DataFrame. 

821 

822 Returns 

823 ------- 

824 DataFrame 

825 """ 

826 from pandas import DataFrame 

827 

828 columns = [ 

829 "days", 

830 "hours", 

831 "minutes", 

832 "seconds", 

833 "milliseconds", 

834 "microseconds", 

835 "nanoseconds", 

836 ] 

837 hasnans = self._hasna 

838 if hasnans: 

839 

840 def f(x): 

841 if isna(x): 

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

843 return x.components 

844 

845 else: 

846 

847 def f(x): 

848 return x.components 

849 

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

851 if not hasnans: 

852 result = result.astype("int64") 

853 return result 

854 

855 

856# --------------------------------------------------------------------- 

857# Constructor Helpers 

858 

859 

860def sequence_to_td64ns( 

861 data, 

862 copy: bool = False, 

863 unit=None, 

864 errors: DateTimeErrorChoices = "raise", 

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

866 """ 

867 Parameters 

868 ---------- 

869 data : list-like 

870 copy : bool, default False 

871 unit : str, optional 

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

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

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

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

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

877 See ``pandas.to_timedelta`` for details. 

878 

879 Returns 

880 ------- 

881 converted : numpy.ndarray 

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

883 inferred_freq : Tick or None 

884 The inferred frequency of the sequence. 

885 

886 Raises 

887 ------ 

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

889 

890 Notes 

891 ----- 

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

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

894 higher level. 

895 """ 

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

897 

898 inferred_freq = None 

899 if unit is not None: 

900 unit = parse_timedelta_unit(unit) 

901 

902 data, copy = dtl.ensure_arraylike_for_datetimelike( 

903 data, copy, cls_name="TimedeltaArray" 

904 ) 

905 

906 if isinstance(data, TimedeltaArray): 

907 inferred_freq = data.freq 

908 

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

910 if is_object_dtype(data.dtype) or is_string_dtype(data.dtype): 

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

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

913 copy = False 

914 

915 elif is_integer_dtype(data.dtype): 

916 # treat as multiples of the given unit 

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

918 copy = copy and not copy_made 

919 

920 elif is_float_dtype(data.dtype): 

921 # cast the unit, multiply base/frac separately 

922 # to avoid precision issues from float -> int 

923 if is_extension_array_dtype(data): 

924 mask = data._mask 

925 data = data._data 

926 else: 

927 mask = np.isnan(data) 

928 # The next few lines are effectively a vectorized 'cast_from_unit' 

929 m, p = precision_from_unit(unit or "ns") 

930 with warnings.catch_warnings(): 

931 # Suppress RuntimeWarning about All-NaN slice 

932 warnings.filterwarnings( 

933 "ignore", "invalid value encountered in cast", RuntimeWarning 

934 ) 

935 base = data.astype(np.int64) 

936 frac = data - base 

937 if p: 

938 frac = np.round(frac, p) 

939 with warnings.catch_warnings(): 

940 warnings.filterwarnings( 

941 "ignore", "invalid value encountered in cast", RuntimeWarning 

942 ) 

943 data = (base * m + (frac * m).astype(np.int64)).view("timedelta64[ns]") 

944 data[mask] = iNaT 

945 copy = False 

946 

947 elif is_timedelta64_dtype(data.dtype): 

948 data_unit = get_unit_from_dtype(data.dtype) 

949 if not is_supported_unit(data_unit): 

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

951 new_reso = get_supported_reso(data_unit) 

952 new_unit = npy_unit_to_abbrev(new_reso) 

953 new_dtype = np.dtype(f"m8[{new_unit}]") 

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

955 copy = False 

956 

957 else: 

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

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

960 

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

962 

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

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

965 

966 return data, inferred_freq 

967 

968 

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

970 """ 

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

972 the integers as multiples of the given timedelta unit. 

973 

974 Parameters 

975 ---------- 

976 data : numpy.ndarray with integer-dtype 

977 unit : str, default "ns" 

978 The timedelta unit to treat integers as multiples of. 

979 

980 Returns 

981 ------- 

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

983 bool : whether a copy was made 

984 """ 

985 copy_made = False 

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

987 

988 if data.dtype != np.int64: 

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

990 # re-copying later 

991 data = data.astype(np.int64) 

992 copy_made = True 

993 

994 if unit != "ns": 

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

996 data = data.view(dtype_str) 

997 

998 data = astype_overflowsafe(data, dtype=TD64NS_DTYPE) 

999 

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

1001 copy_made = True 

1002 

1003 else: 

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

1005 

1006 return data, copy_made 

1007 

1008 

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

1010 """ 

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

1012 timedelta64[ns]-dtyped array. 

1013 

1014 Parameters 

1015 ---------- 

1016 data : ndarray or Index 

1017 unit : str, default "ns" 

1018 The timedelta unit to treat integers as multiples of. 

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

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

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

1022 See ``pandas.to_timedelta`` for details. 

1023 

1024 Returns 

1025 ------- 

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

1027 

1028 Raises 

1029 ------ 

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

1031 

1032 Notes 

1033 ----- 

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

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

1036 higher level. 

1037 """ 

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

1039 values = np.array(data, dtype=np.object_, copy=False) 

1040 

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

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

1043 

1044 

1045def _validate_td64_dtype(dtype) -> DtypeObj: 

1046 dtype = pandas_dtype(dtype) 

1047 if is_dtype_equal(dtype, np.dtype("timedelta64")): 

1048 # no precision disallowed GH#24806 

1049 msg = ( 

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

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

1052 ) 

1053 raise ValueError(msg) 

1054 

1055 if ( 

1056 not isinstance(dtype, np.dtype) 

1057 or dtype.kind != "m" 

1058 or not is_supported_unit(get_unit_from_dtype(dtype)) 

1059 ): 

1060 raise ValueError(f"dtype {dtype} cannot be converted to timedelta64[ns]") 

1061 

1062 return dtype