Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/pandas/core/arrays/timedeltas.py: 17%

434 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1from __future__ import annotations 

2 

3from datetime import timedelta 

4from typing import ( 

5 TYPE_CHECKING, 

6 cast, 

7) 

8 

9import numpy as np 

10 

11from pandas._libs import ( 

12 lib, 

13 tslibs, 

14) 

15from pandas._libs.tslibs import ( 

16 BaseOffset, 

17 NaT, 

18 NaTType, 

19 Tick, 

20 Timedelta, 

21 astype_overflowsafe, 

22 iNaT, 

23 periods_per_second, 

24 to_offset, 

25) 

26from pandas._libs.tslibs.conversion import precision_from_unit 

27from pandas._libs.tslibs.fields import get_timedelta_field 

28from pandas._libs.tslibs.timedeltas import ( 

29 array_to_timedelta64, 

30 ints_to_pytimedelta, 

31 parse_timedelta_unit, 

32) 

33from pandas._typing import ( 

34 DtypeObj, 

35 NpDtype, 

36 npt, 

37) 

38from pandas.compat.numpy import function as nv 

39from pandas.util._validators import validate_endpoints 

40 

41from pandas.core.dtypes.astype import astype_td64_unit_conversion 

42from pandas.core.dtypes.common import ( 

43 TD64NS_DTYPE, 

44 is_dtype_equal, 

45 is_float_dtype, 

46 is_integer_dtype, 

47 is_object_dtype, 

48 is_scalar, 

49 is_string_dtype, 

50 is_timedelta64_dtype, 

51 pandas_dtype, 

52) 

53from pandas.core.dtypes.missing import isna 

54 

55from pandas.core import nanops 

56from pandas.core.arrays import datetimelike as dtl 

57from pandas.core.arrays._ranges import generate_regular_range 

58import pandas.core.common as com 

59from pandas.core.ops.common import unpack_zerodim_and_defer 

60 

61if TYPE_CHECKING: 61 ↛ 62line 61 didn't jump to line 62, because the condition on line 61 was never true

62 from pandas import DataFrame 

63 

64 

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

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

67 values = self.asi8 

68 result = get_timedelta_field(values, alias, reso=self._reso) 

69 if self._hasna: 

70 result = self._maybe_mask_results( 

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

72 ) 

73 

74 return result 

75 

76 f.__name__ = name 

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

78 return property(f) 

79 

80 

81class TimedeltaArray(dtl.TimelikeOps): 

82 """ 

83 Pandas ExtensionArray for timedelta data. 

84 

85 .. warning:: 

86 

87 TimedeltaArray is currently experimental, and its API may change 

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

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

90 subclass. 

91 

92 Parameters 

93 ---------- 

94 values : array-like 

95 The timedelta data. 

96 

97 dtype : numpy.dtype 

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

99 freq : Offset, optional 

100 copy : bool, default False 

101 Whether to copy the underlying array of data. 

102 

103 Attributes 

104 ---------- 

105 None 

106 

107 Methods 

108 ------- 

109 None 

110 """ 

111 

112 _typ = "timedeltaarray" 

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

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

115 _is_recognized_dtype = is_timedelta64_dtype 

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

117 

118 @property 

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

120 return Timedelta 

121 

122 __array_priority__ = 1000 

123 # define my properties & methods for delegation 

124 _other_ops: list[str] = [] 

125 _bool_ops: list[str] = [] 

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

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

128 _datetimelike_ops: list[str] = _field_ops + _object_ops + _bool_ops 

129 _datetimelike_methods: list[str] = [ 

130 "to_pytimedelta", 

131 "total_seconds", 

132 "round", 

133 "floor", 

134 "ceil", 

135 ] 

136 

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

138 # operates pointwise. 

139 

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

141 y = x.view("i8") 

142 if y == NaT.value: 

143 return NaT 

144 return Timedelta._from_value_and_reso(y, reso=self._reso) 

145 

146 @property 

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

148 # "ExtensionDtype" in supertype "ExtensionArray" 

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

150 """ 

151 The dtype for the TimedeltaArray. 

152 

153 .. warning:: 

154 

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

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

157 not a ``numpy.dtype``. 

158 

159 Returns 

160 ------- 

161 numpy.dtype 

162 """ 

163 return self._ndarray.dtype 

164 

165 # ---------------------------------------------------------------- 

166 # Constructors 

167 

168 _freq = None 

169 _default_dtype = TD64NS_DTYPE # used in TimeLikeOps.__init__ 

170 

171 @classmethod 

172 def _validate_dtype(cls, values, dtype): 

173 # used in TimeLikeOps.__init__ 

174 _validate_td64_dtype(values.dtype) 

175 dtype = _validate_td64_dtype(dtype) 

176 return dtype 

177 

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

179 @classmethod 

180 def _simple_new( # type: ignore[override] 

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

182 ) -> TimedeltaArray: 

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

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

185 assert not tslibs.is_unitless(dtype) 

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

187 assert dtype == values.dtype 

188 

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

190 result._freq = freq 

191 return result 

192 

193 @classmethod 

194 def _from_sequence( 

195 cls, data, *, dtype=TD64NS_DTYPE, copy: bool = False 

196 ) -> TimedeltaArray: 

197 if dtype: 

198 _validate_td64_dtype(dtype) 

199 

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

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

202 

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

204 

205 @classmethod 

206 def _from_sequence_not_strict( 

207 cls, 

208 data, 

209 dtype=TD64NS_DTYPE, 

210 copy: bool = False, 

211 freq=lib.no_default, 

212 unit=None, 

213 ) -> TimedeltaArray: 

214 if dtype: 

215 _validate_td64_dtype(dtype) 

216 

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

218 

219 explicit_none = freq is None 

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

221 

222 freq, freq_infer = dtl.maybe_infer_freq(freq) 

223 

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

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

226 if explicit_none: 

227 freq = None 

228 

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

230 

231 if inferred_freq is None and freq is not None: 

232 # this condition precludes `freq_infer` 

233 cls._validate_frequency(result, freq) 

234 

235 elif freq_infer: 

236 # Set _freq directly to bypass duplicative _validate_frequency 

237 # check. 

238 result._freq = to_offset(result.inferred_freq) 

239 

240 return result 

241 

242 @classmethod 

243 def _generate_range(cls, start, end, periods, freq, closed=None): 

244 

245 periods = dtl.validate_periods(periods) 

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

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

248 

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

250 raise ValueError( 

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

252 "and freq, exactly three must be specified" 

253 ) 

254 

255 if start is not None: 

256 start = Timedelta(start) 

257 

258 if end is not None: 

259 end = Timedelta(end) 

260 

261 left_closed, right_closed = validate_endpoints(closed) 

262 

263 if freq is not None: 

264 index = generate_regular_range(start, end, periods, freq) 

265 else: 

266 index = np.linspace(start.value, end.value, periods).astype("i8") 

267 

268 if not left_closed: 

269 index = index[1:] 

270 if not right_closed: 

271 index = index[:-1] 

272 

273 td64values = index.view("m8[ns]") 

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

275 

276 # ---------------------------------------------------------------- 

277 # DatetimeLike Interface 

278 

279 def _unbox_scalar(self, value, setitem: bool = False) -> np.timedelta64: 

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

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

282 self._check_compatible_with(value, setitem=setitem) 

283 return np.timedelta64(value.value, "ns") 

284 

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

286 return Timedelta(value) 

287 

288 def _check_compatible_with(self, other, setitem: bool = False) -> None: 

289 # we don't have anything to validate. 

290 pass 

291 

292 # ---------------------------------------------------------------- 

293 # Array-Like / EA-Interface Methods 

294 

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

296 # We handle 

297 # --> timedelta64[ns] 

298 # --> timedelta64 

299 # DatetimeLikeArrayMixin super call handles other cases 

300 dtype = pandas_dtype(dtype) 

301 

302 if dtype.kind == "m": 

303 return astype_td64_unit_conversion(self._ndarray, dtype, copy=copy) 

304 

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

306 

307 def __iter__(self): 

308 if self.ndim > 1: 

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

310 yield self[i] 

311 else: 

312 # convert in chunks of 10k for efficiency 

313 data = self._ndarray 

314 length = len(self) 

315 chunksize = 10000 

316 chunks = (length // chunksize) + 1 

317 for i in range(chunks): 

318 start_i = i * chunksize 

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

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

321 yield from converted 

322 

323 # ---------------------------------------------------------------- 

324 # Reductions 

325 

326 def sum( 

327 self, 

328 *, 

329 axis: int | None = None, 

330 dtype: NpDtype | None = None, 

331 out=None, 

332 keepdims: bool = False, 

333 initial=None, 

334 skipna: bool = True, 

335 min_count: int = 0, 

336 ): 

337 nv.validate_sum( 

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

339 ) 

340 

341 result = nanops.nansum( 

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

343 ) 

344 return self._wrap_reduction_result(axis, result) 

345 

346 def std( 

347 self, 

348 *, 

349 axis: int | None = None, 

350 dtype: NpDtype | None = None, 

351 out=None, 

352 ddof: int = 1, 

353 keepdims: bool = False, 

354 skipna: bool = True, 

355 ): 

356 nv.validate_stat_ddof_func( 

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

358 ) 

359 

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

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

362 return self._box_func(result) 

363 return self._from_backing_data(result) 

364 

365 # ---------------------------------------------------------------- 

366 # Rendering Methods 

367 

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

369 from pandas.io.formats.format import get_format_timedelta64 

370 

371 return get_format_timedelta64(self, box=True) 

372 

373 def _format_native_types( 

374 self, *, na_rep="NaT", date_format=None, **kwargs 

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

376 from pandas.io.formats.format import get_format_timedelta64 

377 

378 # Relies on TimeDelta._repr_base 

379 formatter = get_format_timedelta64(self._ndarray, na_rep) 

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

381 # but independent of dimension 

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

383 

384 # ---------------------------------------------------------------- 

385 # Arithmetic Methods 

386 

387 def _add_offset(self, other): 

388 assert not isinstance(other, Tick) 

389 raise TypeError( 

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

391 ) 

392 

393 @unpack_zerodim_and_defer("__mul__") 

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

395 if is_scalar(other): 

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

397 result = self._ndarray * other 

398 freq = None 

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

400 freq = self.freq * other 

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

402 

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

404 # list, tuple 

405 other = np.array(other) 

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

407 # Exclude timedelta64 here so we correctly raise TypeError 

408 # for that instead of ValueError 

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

410 

411 if is_object_dtype(other.dtype): 

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

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

414 # timedelta64[ns]-dtyped result 

415 arr = self._ndarray 

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

417 result = np.array(result) 

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

419 

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

421 result = self._ndarray * other 

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

423 

424 __rmul__ = __mul__ 

425 

426 @unpack_zerodim_and_defer("__truediv__") 

427 def __truediv__(self, other): 

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

429 

430 if isinstance(other, self._recognized_scalars): 

431 other = Timedelta(other) 

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

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

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

435 # specifically timedelta64-NaT 

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

437 result.fill(np.nan) 

438 return result 

439 

440 # otherwise, dispatch to Timedelta implementation 

441 return self._ndarray / other 

442 

443 elif lib.is_scalar(other): 

444 # assume it is numeric 

445 result = self._ndarray / other 

446 freq = None 

447 if self.freq is not None: 

448 # Tick division is not implemented, so operate on Timedelta 

449 freq = self.freq.delta / other 

450 freq = to_offset(freq) 

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

452 

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

454 # e.g. list, tuple 

455 other = np.array(other) 

456 

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

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

459 

460 elif is_timedelta64_dtype(other.dtype): 

461 # let numpy handle it 

462 return self._ndarray / other 

463 

464 elif is_object_dtype(other.dtype): 

465 # We operate on raveled arrays to avoid problems in inference 

466 # on NaT 

467 # TODO: tests with non-nano 

468 srav = self.ravel() 

469 orav = other.ravel() 

470 result_list = [srav[n] / orav[n] for n in range(len(srav))] 

471 result = np.array(result_list).reshape(self.shape) 

472 

473 # We need to do dtype inference in order to keep DataFrame ops 

474 # behavior consistent with Series behavior 

475 inferred = lib.infer_dtype(result, skipna=False) 

476 if inferred == "timedelta": 

477 flat = result.ravel() 

478 result = type(self)._from_sequence(flat).reshape(result.shape) 

479 elif inferred == "floating": 

480 result = result.astype(float) 

481 elif inferred == "datetime": 

482 # GH#39750 this occurs when result is all-NaT, in which case 

483 # we want to interpret these NaTs as td64. 

484 # We construct an all-td64NaT result. 

485 # error: Incompatible types in assignment (expression has type 

486 # "TimedeltaArray", variable has type "ndarray[Any, 

487 # dtype[floating[_64Bit]]]") 

488 result = self * np.nan # type: ignore[assignment] 

489 

490 return result 

491 

492 else: 

493 result = self._ndarray / other 

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

495 

496 @unpack_zerodim_and_defer("__rtruediv__") 

497 def __rtruediv__(self, other): 

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

499 if isinstance(other, self._recognized_scalars): 

500 other = Timedelta(other) 

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

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

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

504 # specifically timedelta64-NaT 

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

506 result.fill(np.nan) 

507 return result 

508 

509 # otherwise, dispatch to Timedelta implementation 

510 return other / self._ndarray 

511 

512 elif lib.is_scalar(other): 

513 raise TypeError( 

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

515 ) 

516 

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

518 # e.g. list, tuple 

519 other = np.array(other) 

520 

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

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

523 

524 elif is_timedelta64_dtype(other.dtype): 

525 # let numpy handle it 

526 return other / self._ndarray 

527 

528 elif is_object_dtype(other.dtype): 

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

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

531 # is returned. GH#23829 

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

533 return np.array(result_list) 

534 

535 else: 

536 raise TypeError( 

537 f"Cannot divide {other.dtype} data by {type(self).__name__}" 

538 ) 

539 

540 @unpack_zerodim_and_defer("__floordiv__") 

541 def __floordiv__(self, other): 

542 

543 if is_scalar(other): 

544 if isinstance(other, self._recognized_scalars): 

545 other = Timedelta(other) 

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

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

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

549 # treat this specifically as timedelta-NaT 

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

551 result.fill(np.nan) 

552 return result 

553 

554 # dispatch to Timedelta implementation 

555 return other.__rfloordiv__(self._ndarray) 

556 

557 # at this point we should only have numeric scalars; anything 

558 # else will raise 

559 result = self._ndarray // other 

560 freq = None 

561 if self.freq is not None: 

562 # Note: freq gets division, not floor-division 

563 freq = self.freq / other 

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

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

566 # rounds down to zero 

567 freq = None 

568 return type(self)(result, freq=freq) 

569 

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

571 # list, tuple 

572 other = np.array(other) 

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

574 raise ValueError("Cannot divide with unequal lengths") 

575 

576 elif is_timedelta64_dtype(other.dtype): 

577 other = type(self)(other) 

578 

579 # numpy timedelta64 does not natively support floordiv, so operate 

580 # on the i8 values 

581 result = self.asi8 // other.asi8 

582 mask = self._isnan | other._isnan 

583 if mask.any(): 

584 result = result.astype(np.float64) 

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

586 return result 

587 

588 elif is_object_dtype(other.dtype): 

589 # error: Incompatible types in assignment (expression has type 

590 # "List[Any]", variable has type "ndarray") 

591 srav = self.ravel() 

592 orav = other.ravel() 

593 res_list = [srav[n] // orav[n] for n in range(len(srav))] 

594 result_flat = np.asarray(res_list) 

595 inferred = lib.infer_dtype(result_flat, skipna=False) 

596 

597 result = result_flat.reshape(self.shape) 

598 

599 if inferred == "timedelta": 

600 result, _ = sequence_to_td64ns(result) 

601 return type(self)(result) 

602 if inferred == "datetime": 

603 # GH#39750 occurs when result is all-NaT, which in this 

604 # case should be interpreted as td64nat. This can only 

605 # occur when self is all-td64nat 

606 return self * np.nan 

607 return result 

608 

609 elif is_integer_dtype(other.dtype) or is_float_dtype(other.dtype): 

610 result = self._ndarray // other 

611 return type(self)(result) 

612 

613 else: 

614 dtype = getattr(other, "dtype", type(other).__name__) 

615 raise TypeError(f"Cannot divide {dtype} by {type(self).__name__}") 

616 

617 @unpack_zerodim_and_defer("__rfloordiv__") 

618 def __rfloordiv__(self, other): 

619 

620 if is_scalar(other): 

621 if isinstance(other, self._recognized_scalars): 

622 other = Timedelta(other) 

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

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

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

626 # treat this specifically as timedelta-NaT 

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

628 result.fill(np.nan) 

629 return result 

630 

631 # dispatch to Timedelta implementation 

632 return other.__floordiv__(self._ndarray) 

633 

634 raise TypeError( 

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

636 ) 

637 

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

639 # list, tuple 

640 other = np.array(other) 

641 

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

643 raise ValueError("Cannot divide with unequal lengths") 

644 

645 elif is_timedelta64_dtype(other.dtype): 

646 other = type(self)(other) 

647 # numpy timedelta64 does not natively support floordiv, so operate 

648 # on the i8 values 

649 result = other.asi8 // self.asi8 

650 mask = self._isnan | other._isnan 

651 if mask.any(): 

652 result = result.astype(np.float64) 

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

654 return result 

655 

656 elif is_object_dtype(other.dtype): 

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

658 result = np.array(result_list) 

659 return result 

660 

661 else: 

662 dtype = getattr(other, "dtype", type(other).__name__) 

663 raise TypeError(f"Cannot divide {dtype} by {type(self).__name__}") 

664 

665 @unpack_zerodim_and_defer("__mod__") 

666 def __mod__(self, other): 

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

668 if isinstance(other, self._recognized_scalars): 

669 other = Timedelta(other) 

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

671 

672 @unpack_zerodim_and_defer("__rmod__") 

673 def __rmod__(self, other): 

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

675 if isinstance(other, self._recognized_scalars): 

676 other = Timedelta(other) 

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

678 

679 @unpack_zerodim_and_defer("__divmod__") 

680 def __divmod__(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 

685 res1 = self // other 

686 res2 = self - res1 * other 

687 return res1, res2 

688 

689 @unpack_zerodim_and_defer("__rdivmod__") 

690 def __rdivmod__(self, other): 

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

692 if isinstance(other, self._recognized_scalars): 

693 other = Timedelta(other) 

694 

695 res1 = other // self 

696 res2 = other - res1 * self 

697 return res1, res2 

698 

699 def __neg__(self) -> TimedeltaArray: 

700 if self.freq is not None: 

701 return type(self)(-self._ndarray, freq=-self.freq) 

702 return type(self)(-self._ndarray) 

703 

704 def __pos__(self) -> TimedeltaArray: 

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

706 

707 def __abs__(self) -> TimedeltaArray: 

708 # Note: freq is not preserved 

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

710 

711 # ---------------------------------------------------------------- 

712 # Conversion Methods - Vectorized analogues of Timedelta methods 

713 

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

715 """ 

716 Return total duration of each element expressed in seconds. 

717 

718 This method is available directly on TimedeltaArray, TimedeltaIndex 

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

720 

721 Returns 

722 ------- 

723 seconds : [ndarray, Float64Index, Series] 

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

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

726 the return type is a Float64Index. When the calling object 

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

728 index is the same as the original. 

729 

730 See Also 

731 -------- 

732 datetime.timedelta.total_seconds : Standard library version 

733 of this method. 

734 TimedeltaIndex.components : Return a DataFrame with components of 

735 each Timedelta. 

736 

737 Examples 

738 -------- 

739 **Series** 

740 

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

742 >>> s 

743 0 0 days 

744 1 1 days 

745 2 2 days 

746 3 3 days 

747 4 4 days 

748 dtype: timedelta64[ns] 

749 

750 >>> s.dt.total_seconds() 

751 0 0.0 

752 1 86400.0 

753 2 172800.0 

754 3 259200.0 

755 4 345600.0 

756 dtype: float64 

757 

758 **TimedeltaIndex** 

759 

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

761 >>> idx 

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

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

764 

765 >>> idx.total_seconds() 

766 Float64Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], 

767 dtype='float64') 

768 """ 

769 pps = periods_per_second(self._reso) 

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

771 

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

773 """ 

774 Return an ndarray of datetime.timedelta objects. 

775 

776 Returns 

777 ------- 

778 timedeltas : ndarray[object] 

779 """ 

780 return ints_to_pytimedelta(self._ndarray) 

781 

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

783 seconds = _field_accessor( 

784 "seconds", 

785 "seconds", 

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

787 ) 

788 microseconds = _field_accessor( 

789 "microseconds", 

790 "microseconds", 

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

792 ) 

793 nanoseconds = _field_accessor( 

794 "nanoseconds", 

795 "nanoseconds", 

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

797 ) 

798 

799 @property 

800 def components(self) -> DataFrame: 

801 """ 

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

803 

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

805 nanoseconds) are returned as columns in a DataFrame. 

806 

807 Returns 

808 ------- 

809 DataFrame 

810 """ 

811 from pandas import DataFrame 

812 

813 columns = [ 

814 "days", 

815 "hours", 

816 "minutes", 

817 "seconds", 

818 "milliseconds", 

819 "microseconds", 

820 "nanoseconds", 

821 ] 

822 hasnans = self._hasna 

823 if hasnans: 

824 

825 def f(x): 

826 if isna(x): 

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

828 return x.components 

829 

830 else: 

831 

832 def f(x): 

833 return x.components 

834 

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

836 if not hasnans: 

837 result = result.astype("int64") 

838 return result 

839 

840 

841# --------------------------------------------------------------------- 

842# Constructor Helpers 

843 

844 

845def sequence_to_td64ns( 

846 data, copy: bool = False, unit=None, errors="raise" 

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

848 """ 

849 Parameters 

850 ---------- 

851 data : list-like 

852 copy : bool, default False 

853 unit : str, optional 

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

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

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

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

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

859 See ``pandas.to_timedelta`` for details. 

860 

861 Returns 

862 ------- 

863 converted : numpy.ndarray 

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

865 inferred_freq : Tick or None 

866 The inferred frequency of the sequence. 

867 

868 Raises 

869 ------ 

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

871 

872 Notes 

873 ----- 

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

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

876 higher level. 

877 """ 

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

879 

880 inferred_freq = None 

881 if unit is not None: 

882 unit = parse_timedelta_unit(unit) 

883 

884 data, copy = dtl.ensure_arraylike_for_datetimelike( 

885 data, copy, cls_name="TimedeltaArray" 

886 ) 

887 

888 if isinstance(data, TimedeltaArray): 

889 inferred_freq = data.freq 

890 

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

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

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

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

895 copy = False 

896 

897 elif is_integer_dtype(data.dtype): 

898 # treat as multiples of the given unit 

899 data, copy_made = ints_to_td64ns(data, unit=unit) 

900 copy = copy and not copy_made 

901 

902 elif is_float_dtype(data.dtype): 

903 # cast the unit, multiply base/frac separately 

904 # to avoid precision issues from float -> int 

905 mask = np.isnan(data) 

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

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

908 base = data.astype(np.int64) 

909 frac = data - base 

910 if p: 

911 frac = np.round(frac, p) 

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

913 data[mask] = iNaT 

914 copy = False 

915 

916 elif is_timedelta64_dtype(data.dtype): 

917 if data.dtype != TD64NS_DTYPE: 

918 # non-nano unit 

919 data = astype_overflowsafe(data, dtype=TD64NS_DTYPE) 

920 copy = False 

921 

922 else: 

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

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

925 

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

927 

928 assert data.dtype == "m8[ns]", data 

929 return data, inferred_freq 

930 

931 

932def ints_to_td64ns(data, unit="ns"): 

933 """ 

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

935 the integers as multiples of the given timedelta unit. 

936 

937 Parameters 

938 ---------- 

939 data : numpy.ndarray with integer-dtype 

940 unit : str, default "ns" 

941 The timedelta unit to treat integers as multiples of. 

942 

943 Returns 

944 ------- 

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

946 bool : whether a copy was made 

947 """ 

948 copy_made = False 

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

950 

951 if data.dtype != np.int64: 

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

953 # re-copying later 

954 data = data.astype(np.int64) 

955 copy_made = True 

956 

957 if unit != "ns": 

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

959 data = data.view(dtype_str) 

960 

961 data = astype_overflowsafe(data, dtype=TD64NS_DTYPE) 

962 

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

964 copy_made = True 

965 

966 else: 

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

968 

969 return data, copy_made 

970 

971 

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

973 """ 

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

975 timedelta64[ns]-dtyped array. 

976 

977 Parameters 

978 ---------- 

979 data : ndarray or Index 

980 unit : str, default "ns" 

981 The timedelta unit to treat integers as multiples of. 

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

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

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

985 See ``pandas.to_timedelta`` for details. 

986 

987 Returns 

988 ------- 

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

990 

991 Raises 

992 ------ 

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

994 

995 Notes 

996 ----- 

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

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

999 higher level. 

1000 """ 

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

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

1003 

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

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

1006 

1007 

1008def _validate_td64_dtype(dtype) -> DtypeObj: 

1009 dtype = pandas_dtype(dtype) 

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

1011 # no precision disallowed GH#24806 

1012 msg = ( 

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

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

1015 ) 

1016 raise ValueError(msg) 

1017 

1018 if not is_dtype_equal(dtype, TD64NS_DTYPE): 

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

1020 

1021 return dtype