Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1from datetime import timedelta 

2from typing import List 

3 

4import numpy as np 

5 

6from pandas._libs import lib, tslibs 

7from pandas._libs.tslibs import NaT, Timedelta, Timestamp, iNaT 

8from pandas._libs.tslibs.fields import get_timedelta_field 

9from pandas._libs.tslibs.timedeltas import ( 

10 array_to_timedelta64, 

11 parse_timedelta_unit, 

12 precision_from_unit, 

13) 

14from pandas.compat.numpy import function as nv 

15 

16from pandas.core.dtypes.common import ( 

17 _NS_DTYPE, 

18 _TD_DTYPE, 

19 is_dtype_equal, 

20 is_float_dtype, 

21 is_integer_dtype, 

22 is_object_dtype, 

23 is_scalar, 

24 is_string_dtype, 

25 is_timedelta64_dtype, 

26 is_timedelta64_ns_dtype, 

27 pandas_dtype, 

28) 

29from pandas.core.dtypes.dtypes import DatetimeTZDtype 

30from pandas.core.dtypes.generic import ( 

31 ABCDataFrame, 

32 ABCIndexClass, 

33 ABCSeries, 

34 ABCTimedeltaIndex, 

35) 

36from pandas.core.dtypes.missing import isna 

37 

38from pandas.core import nanops 

39from pandas.core.algorithms import checked_add_with_arr 

40from pandas.core.arrays import datetimelike as dtl 

41import pandas.core.common as com 

42 

43from pandas.tseries.frequencies import to_offset 

44from pandas.tseries.offsets import Tick 

45 

46_BAD_DTYPE = "dtype {dtype} cannot be converted to timedelta64[ns]" 

47 

48 

49def _is_convertible_to_td(key): 

50 return isinstance(key, (Tick, timedelta, np.timedelta64, str)) 

51 

52 

53def _field_accessor(name, alias, docstring=None): 

54 def f(self): 

55 values = self.asi8 

56 result = get_timedelta_field(values, alias) 

57 if self._hasnans: 

58 result = self._maybe_mask_results( 

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

60 ) 

61 

62 return result 

63 

64 f.__name__ = name 

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

66 return property(f) 

67 

68 

69class TimedeltaArray(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps): 

70 """ 

71 Pandas ExtensionArray for timedelta data. 

72 

73 .. versionadded:: 0.24.0 

74 

75 .. warning:: 

76 

77 TimedeltaArray is currently experimental, and its API may change 

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

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

80 subclass. 

81 

82 Parameters 

83 ---------- 

84 values : array-like 

85 The timedelta data. 

86 

87 dtype : numpy.dtype 

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

89 freq : Offset, optional 

90 copy : bool, default False 

91 Whether to copy the underlying array of data. 

92 

93 Attributes 

94 ---------- 

95 None 

96 

97 Methods 

98 ------- 

99 None 

100 """ 

101 

102 _typ = "timedeltaarray" 

103 _scalar_type = Timedelta 

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

105 _is_recognized_dtype = is_timedelta64_dtype 

106 

107 __array_priority__ = 1000 

108 # define my properties & methods for delegation 

109 _other_ops: List[str] = [] 

110 _bool_ops: List[str] = [] 

111 _object_ops = ["freq"] 

112 _field_ops = ["days", "seconds", "microseconds", "nanoseconds"] 

113 _datetimelike_ops = _field_ops + _object_ops + _bool_ops 

114 _datetimelike_methods = [ 

115 "to_pytimedelta", 

116 "total_seconds", 

117 "round", 

118 "floor", 

119 "ceil", 

120 ] 

121 

122 # Note: ndim must be defined to ensure NaT.__richcmp(TimedeltaArray) 

123 # operates pointwise. 

124 

125 @property 

126 def _box_func(self): 

127 return lambda x: Timedelta(x, unit="ns") 

128 

129 @property 

130 def dtype(self): 

131 """ 

132 The dtype for the TimedeltaArray. 

133 

134 .. warning:: 

135 

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

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

138 not a ``numpy.dtype``. 

139 

140 Returns 

141 ------- 

142 numpy.dtype 

143 """ 

144 return _TD_DTYPE 

145 

146 # ---------------------------------------------------------------- 

147 # Constructors 

148 

149 def __init__(self, values, dtype=_TD_DTYPE, freq=None, copy=False): 

150 if isinstance(values, (ABCSeries, ABCIndexClass)): 

151 values = values._values 

152 

153 inferred_freq = getattr(values, "_freq", None) 

154 

155 if isinstance(values, type(self)): 

156 if freq is None: 

157 freq = values.freq 

158 elif freq and values.freq: 

159 freq = to_offset(freq) 

160 freq, _ = dtl.validate_inferred_freq(freq, values.freq, False) 

161 values = values._data 

162 

163 if not isinstance(values, np.ndarray): 

164 msg = ( 

165 f"Unexpected type '{type(values).__name__}'. 'values' must be a " 

166 "TimedeltaArray ndarray, or Series or Index containing one of those." 

167 ) 

168 raise ValueError(msg) 

169 if values.ndim not in [1, 2]: 

170 raise ValueError("Only 1-dimensional input arrays are supported.") 

171 

172 if values.dtype == "i8": 

173 # for compat with datetime/timedelta/period shared methods, 

174 # we can sometimes get here with int64 values. These represent 

175 # nanosecond UTC (or tz-naive) unix timestamps 

176 values = values.view(_TD_DTYPE) 

177 

178 _validate_td64_dtype(values.dtype) 

179 dtype = _validate_td64_dtype(dtype) 

180 

181 if freq == "infer": 

182 msg = ( 

183 "Frequency inference not allowed in TimedeltaArray.__init__. " 

184 "Use 'pd.array()' instead." 

185 ) 

186 raise ValueError(msg) 

187 

188 if copy: 

189 values = values.copy() 

190 if freq: 

191 freq = to_offset(freq) 

192 

193 self._data = values 

194 self._dtype = dtype 

195 self._freq = freq 

196 

197 if inferred_freq is None and freq is not None: 

198 type(self)._validate_frequency(self, freq) 

199 

200 @classmethod 

201 def _simple_new(cls, values, freq=None, dtype=_TD_DTYPE): 

202 assert dtype == _TD_DTYPE, dtype 

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

204 

205 result = object.__new__(cls) 

206 result._data = values.view(_TD_DTYPE) 

207 result._freq = to_offset(freq) 

208 result._dtype = _TD_DTYPE 

209 return result 

210 

211 @classmethod 

212 def _from_sequence(cls, data, dtype=_TD_DTYPE, copy=False, freq=None, unit=None): 

213 if dtype: 

214 _validate_td64_dtype(dtype) 

215 freq, freq_infer = dtl.maybe_infer_freq(freq) 

216 

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

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

219 

220 result = cls._simple_new(data, freq=freq) 

221 

222 if inferred_freq is None and freq is not None: 

223 # this condition precludes `freq_infer` 

224 cls._validate_frequency(result, freq) 

225 

226 elif freq_infer: 

227 # Set _freq directly to bypass duplicative _validate_frequency 

228 # check. 

229 result._freq = to_offset(result.inferred_freq) 

230 

231 return result 

232 

233 @classmethod 

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

235 

236 periods = dtl.validate_periods(periods) 

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

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

239 

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

241 raise ValueError( 

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

243 "and freq, exactly three must be specified" 

244 ) 

245 

246 if start is not None: 

247 start = Timedelta(start) 

248 

249 if end is not None: 

250 end = Timedelta(end) 

251 

252 if start is None and end is None: 

253 if closed is not None: 

254 raise ValueError( 

255 "Closed has to be None if not both of startand end are defined" 

256 ) 

257 

258 left_closed, right_closed = dtl.validate_endpoints(closed) 

259 

260 if freq is not None: 

261 index = _generate_regular_range(start, end, periods, freq) 

262 else: 

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

264 

265 if not left_closed: 

266 index = index[1:] 

267 if not right_closed: 

268 index = index[:-1] 

269 

270 return cls._simple_new(index, freq=freq) 

271 

272 # ---------------------------------------------------------------- 

273 # DatetimeLike Interface 

274 

275 def _unbox_scalar(self, value): 

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

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

278 self._check_compatible_with(value) 

279 return value.value 

280 

281 def _scalar_from_string(self, value): 

282 return Timedelta(value) 

283 

284 def _check_compatible_with(self, other, setitem: bool = False): 

285 # we don't have anything to validate. 

286 pass 

287 

288 def _maybe_clear_freq(self): 

289 self._freq = None 

290 

291 # ---------------------------------------------------------------- 

292 # Array-Like / EA-Interface Methods 

293 

294 def astype(self, dtype, copy=True): 

295 # We handle 

296 # --> timedelta64[ns] 

297 # --> timedelta64 

298 # DatetimeLikeArrayMixin super call handles other cases 

299 dtype = pandas_dtype(dtype) 

300 

301 if is_timedelta64_dtype(dtype) and not is_timedelta64_ns_dtype(dtype): 

302 # by pandas convention, converting to non-nano timedelta64 

303 # returns an int64-dtyped array with ints representing multiples 

304 # of the desired timedelta unit. This is essentially division 

305 if self._hasnans: 

306 # avoid double-copying 

307 result = self._data.astype(dtype, copy=False) 

308 values = self._maybe_mask_results( 

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

310 ) 

311 return values 

312 result = self._data.astype(dtype, copy=copy) 

313 return result.astype("i8") 

314 elif is_timedelta64_ns_dtype(dtype): 

315 if copy: 

316 return self.copy() 

317 return self 

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

319 

320 # ---------------------------------------------------------------- 

321 # Reductions 

322 

323 def sum( 

324 self, 

325 axis=None, 

326 dtype=None, 

327 out=None, 

328 keepdims: bool = False, 

329 initial=None, 

330 skipna: bool = True, 

331 min_count: int = 0, 

332 ): 

333 nv.validate_sum( 

334 (), dict(dtype=dtype, out=out, keepdims=keepdims, initial=initial) 

335 ) 

336 if not len(self): 

337 return NaT 

338 if not skipna and self._hasnans: 

339 return NaT 

340 

341 result = nanops.nansum( 

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

343 ) 

344 return Timedelta(result) 

345 

346 def std( 

347 self, 

348 axis=None, 

349 dtype=None, 

350 out=None, 

351 ddof: int = 1, 

352 keepdims: bool = False, 

353 skipna: bool = True, 

354 ): 

355 nv.validate_stat_ddof_func( 

356 (), dict(dtype=dtype, out=out, keepdims=keepdims), fname="std" 

357 ) 

358 if not len(self): 

359 return NaT 

360 if not skipna and self._hasnans: 

361 return NaT 

362 

363 result = nanops.nanstd(self._data, axis=axis, skipna=skipna, ddof=ddof) 

364 return Timedelta(result) 

365 

366 def median( 

367 self, 

368 axis=None, 

369 out=None, 

370 overwrite_input: bool = False, 

371 keepdims: bool = False, 

372 skipna: bool = True, 

373 ): 

374 nv.validate_median( 

375 (), dict(out=out, overwrite_input=overwrite_input, keepdims=keepdims) 

376 ) 

377 return nanops.nanmedian(self._data, axis=axis, skipna=skipna) 

378 

379 # ---------------------------------------------------------------- 

380 # Rendering Methods 

381 

382 def _formatter(self, boxed=False): 

383 from pandas.io.formats.format import _get_format_timedelta64 

384 

385 return _get_format_timedelta64(self, box=True) 

386 

387 def _format_native_types(self, na_rep="NaT", date_format=None, **kwargs): 

388 from pandas.io.formats.format import _get_format_timedelta64 

389 

390 formatter = _get_format_timedelta64(self._data, na_rep) 

391 return np.array([formatter(x) for x in self._data]) 

392 

393 # ---------------------------------------------------------------- 

394 # Arithmetic Methods 

395 

396 def _add_offset(self, other): 

397 assert not isinstance(other, Tick) 

398 raise TypeError( 

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

400 ) 

401 

402 def _add_delta(self, delta): 

403 """ 

404 Add a timedelta-like, Tick, or TimedeltaIndex-like object 

405 to self, yielding a new TimedeltaArray. 

406 

407 Parameters 

408 ---------- 

409 other : {timedelta, np.timedelta64, Tick, 

410 TimedeltaIndex, ndarray[timedelta64]} 

411 

412 Returns 

413 ------- 

414 result : TimedeltaArray 

415 """ 

416 new_values = super()._add_delta(delta) 

417 return type(self)._from_sequence(new_values, freq="infer") 

418 

419 def _add_datetime_arraylike(self, other): 

420 """ 

421 Add DatetimeArray/Index or ndarray[datetime64] to TimedeltaArray. 

422 """ 

423 if isinstance(other, np.ndarray): 

424 # At this point we have already checked that dtype is datetime64 

425 from pandas.core.arrays import DatetimeArray 

426 

427 other = DatetimeArray(other) 

428 

429 # defer to implementation in DatetimeArray 

430 return other + self 

431 

432 def _add_datetimelike_scalar(self, other): 

433 # adding a timedeltaindex to a datetimelike 

434 from pandas.core.arrays import DatetimeArray 

435 

436 assert other is not NaT 

437 other = Timestamp(other) 

438 if other is NaT: 

439 # In this case we specifically interpret NaT as a datetime, not 

440 # the timedelta interpretation we would get by returning self + NaT 

441 result = self.asi8.view("m8[ms]") + NaT.to_datetime64() 

442 return DatetimeArray(result) 

443 

444 i8 = self.asi8 

445 result = checked_add_with_arr(i8, other.value, arr_mask=self._isnan) 

446 result = self._maybe_mask_results(result) 

447 dtype = DatetimeTZDtype(tz=other.tz) if other.tz else _NS_DTYPE 

448 return DatetimeArray(result, dtype=dtype, freq=self.freq) 

449 

450 def _addsub_object_array(self, other, op): 

451 # Add or subtract Array-like of objects 

452 try: 

453 # TimedeltaIndex can only operate with a subset of DateOffset 

454 # subclasses. Incompatible classes will raise AttributeError, 

455 # which we re-raise as TypeError 

456 return super()._addsub_object_array(other, op) 

457 except AttributeError: 

458 raise TypeError( 

459 f"Cannot add/subtract non-tick DateOffset to {type(self).__name__}" 

460 ) 

461 

462 def __mul__(self, other): 

463 other = lib.item_from_zerodim(other) 

464 

465 if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)): 

466 return NotImplemented 

467 

468 if is_scalar(other): 

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

470 result = self._data * other 

471 freq = None 

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

473 freq = self.freq * other 

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

475 

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

477 # list, tuple 

478 other = np.array(other) 

479 if len(other) != len(self) and not is_timedelta64_dtype(other): 

480 # Exclude timedelta64 here so we correctly raise TypeError 

481 # for that instead of ValueError 

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

483 

484 if is_object_dtype(other.dtype): 

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

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

487 # timedelta64[ns]-dtyped result 

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

489 result = np.array(result) 

490 return type(self)(result) 

491 

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

493 result = self._data * other 

494 return type(self)(result) 

495 

496 __rmul__ = __mul__ 

497 

498 def __truediv__(self, other): 

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

500 other = lib.item_from_zerodim(other) 

501 

502 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

503 return NotImplemented 

504 

505 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

506 other = Timedelta(other) 

507 if other is NaT: 

508 # specifically timedelta64-NaT 

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

510 result.fill(np.nan) 

511 return result 

512 

513 # otherwise, dispatch to Timedelta implementation 

514 return self._data / other 

515 

516 elif lib.is_scalar(other): 

517 # assume it is numeric 

518 result = self._data / other 

519 freq = None 

520 if self.freq is not None: 

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

522 freq = self.freq.delta / other 

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

524 

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

526 # e.g. list, tuple 

527 other = np.array(other) 

528 

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

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

531 

532 elif is_timedelta64_dtype(other.dtype): 

533 # let numpy handle it 

534 return self._data / other 

535 

536 elif is_object_dtype(other.dtype): 

537 # Note: we do not do type inference on the result, so either 

538 # an object array or numeric-dtyped (if numpy does inference) 

539 # will be returned. GH#23829 

540 result = [self[n] / other[n] for n in range(len(self))] 

541 result = np.array(result) 

542 return result 

543 

544 else: 

545 result = self._data / other 

546 return type(self)(result) 

547 

548 def __rtruediv__(self, other): 

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

550 other = lib.item_from_zerodim(other) 

551 

552 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

553 return NotImplemented 

554 

555 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

556 other = Timedelta(other) 

557 if other is NaT: 

558 # specifically timedelta64-NaT 

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

560 result.fill(np.nan) 

561 return result 

562 

563 # otherwise, dispatch to Timedelta implementation 

564 return other / self._data 

565 

566 elif lib.is_scalar(other): 

567 raise TypeError( 

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

569 ) 

570 

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

572 # e.g. list, tuple 

573 other = np.array(other) 

574 

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

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

577 

578 elif is_timedelta64_dtype(other.dtype): 

579 # let numpy handle it 

580 return other / self._data 

581 

582 elif is_object_dtype(other.dtype): 

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

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

585 # is returned. GH#23829 

586 result = [other[n] / self[n] for n in range(len(self))] 

587 return np.array(result) 

588 

589 else: 

590 raise TypeError( 

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

592 ) 

593 

594 def __floordiv__(self, other): 

595 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

596 return NotImplemented 

597 

598 other = lib.item_from_zerodim(other) 

599 if is_scalar(other): 

600 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

601 other = Timedelta(other) 

602 if other is NaT: 

603 # treat this specifically as timedelta-NaT 

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

605 result.fill(np.nan) 

606 return result 

607 

608 # dispatch to Timedelta implementation 

609 result = other.__rfloordiv__(self._data) 

610 return result 

611 

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

613 # else will raise 

614 result = self.asi8 // other 

615 result[self._isnan] = iNaT 

616 freq = None 

617 if self.freq is not None: 

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

619 freq = self.freq / other 

620 return type(self)(result.view("m8[ns]"), freq=freq) 

621 

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

623 # list, tuple 

624 other = np.array(other) 

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

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

627 

628 elif is_timedelta64_dtype(other.dtype): 

629 other = type(self)(other) 

630 

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

632 # on the i8 values 

633 result = self.asi8 // other.asi8 

634 mask = self._isnan | other._isnan 

635 if mask.any(): 

636 result = result.astype(np.int64) 

637 result[mask] = np.nan 

638 return result 

639 

640 elif is_object_dtype(other.dtype): 

641 result = [self[n] // other[n] for n in range(len(self))] 

642 result = np.array(result) 

643 if lib.infer_dtype(result, skipna=False) == "timedelta": 

644 result, _ = sequence_to_td64ns(result) 

645 return type(self)(result) 

646 return result 

647 

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

649 result = self._data // other 

650 return type(self)(result) 

651 

652 else: 

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

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

655 

656 def __rfloordiv__(self, other): 

657 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

658 return NotImplemented 

659 

660 other = lib.item_from_zerodim(other) 

661 if is_scalar(other): 

662 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

663 other = Timedelta(other) 

664 if other is NaT: 

665 # treat this specifically as timedelta-NaT 

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

667 result.fill(np.nan) 

668 return result 

669 

670 # dispatch to Timedelta implementation 

671 result = other.__floordiv__(self._data) 

672 return result 

673 

674 raise TypeError( 

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

676 ) 

677 

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

679 # list, tuple 

680 other = np.array(other) 

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

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

683 

684 elif is_timedelta64_dtype(other.dtype): 

685 other = type(self)(other) 

686 

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

688 # on the i8 values 

689 result = other.asi8 // self.asi8 

690 mask = self._isnan | other._isnan 

691 if mask.any(): 

692 result = result.astype(np.int64) 

693 result[mask] = np.nan 

694 return result 

695 

696 elif is_object_dtype(other.dtype): 

697 result = [other[n] // self[n] for n in range(len(self))] 

698 result = np.array(result) 

699 return result 

700 

701 else: 

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

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

704 

705 def __mod__(self, other): 

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

707 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

708 return NotImplemented 

709 

710 other = lib.item_from_zerodim(other) 

711 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

712 other = Timedelta(other) 

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

714 

715 def __rmod__(self, other): 

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

717 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

718 return NotImplemented 

719 

720 other = lib.item_from_zerodim(other) 

721 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

722 other = Timedelta(other) 

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

724 

725 def __divmod__(self, other): 

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

727 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

728 return NotImplemented 

729 

730 other = lib.item_from_zerodim(other) 

731 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

732 other = Timedelta(other) 

733 

734 res1 = self // other 

735 res2 = self - res1 * other 

736 return res1, res2 

737 

738 def __rdivmod__(self, other): 

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

740 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)): 

741 return NotImplemented 

742 

743 other = lib.item_from_zerodim(other) 

744 if isinstance(other, (timedelta, np.timedelta64, Tick)): 

745 other = Timedelta(other) 

746 

747 res1 = other // self 

748 res2 = other - res1 * self 

749 return res1, res2 

750 

751 def __neg__(self): 

752 if self.freq is not None: 

753 return type(self)(-self._data, freq=-self.freq) 

754 return type(self)(-self._data) 

755 

756 def __pos__(self): 

757 return type(self)(self._data, freq=self.freq) 

758 

759 def __abs__(self): 

760 # Note: freq is not preserved 

761 return type(self)(np.abs(self._data)) 

762 

763 # ---------------------------------------------------------------- 

764 # Conversion Methods - Vectorized analogues of Timedelta methods 

765 

766 def total_seconds(self): 

767 """ 

768 Return total duration of each element expressed in seconds. 

769 

770 This method is available directly on TimedeltaArray, TimedeltaIndex 

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

772 

773 Returns 

774 ------- 

775 seconds : [ndarray, Float64Index, Series] 

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

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

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

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

780 index is the same as the original. 

781 

782 See Also 

783 -------- 

784 datetime.timedelta.total_seconds : Standard library version 

785 of this method. 

786 TimedeltaIndex.components : Return a DataFrame with components of 

787 each Timedelta. 

788 

789 Examples 

790 -------- 

791 **Series** 

792 

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

794 >>> s 

795 0 0 days 

796 1 1 days 

797 2 2 days 

798 3 3 days 

799 4 4 days 

800 dtype: timedelta64[ns] 

801 

802 >>> s.dt.total_seconds() 

803 0 0.0 

804 1 86400.0 

805 2 172800.0 

806 3 259200.0 

807 4 345600.0 

808 dtype: float64 

809 

810 **TimedeltaIndex** 

811 

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

813 >>> idx 

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

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

816 

817 >>> idx.total_seconds() 

818 Float64Index([0.0, 86400.0, 172800.0, 259200.00000000003, 345600.0], 

819 dtype='float64') 

820 """ 

821 return self._maybe_mask_results(1e-9 * self.asi8, fill_value=None) 

822 

823 def to_pytimedelta(self): 

824 """ 

825 Return Timedelta Array/Index as object ndarray of datetime.timedelta 

826 objects. 

827 

828 Returns 

829 ------- 

830 datetimes : ndarray 

831 """ 

832 return tslibs.ints_to_pytimedelta(self.asi8) 

833 

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

835 seconds = _field_accessor( 

836 "seconds", 

837 "seconds", 

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

839 ) 

840 microseconds = _field_accessor( 

841 "microseconds", 

842 "microseconds", 

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

844 ) 

845 nanoseconds = _field_accessor( 

846 "nanoseconds", 

847 "nanoseconds", 

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

849 ) 

850 

851 @property 

852 def components(self): 

853 """ 

854 Return a dataframe of the components (days, hours, minutes, 

855 seconds, milliseconds, microseconds, nanoseconds) of the Timedeltas. 

856 

857 Returns 

858 ------- 

859 a DataFrame 

860 """ 

861 from pandas import DataFrame 

862 

863 columns = [ 

864 "days", 

865 "hours", 

866 "minutes", 

867 "seconds", 

868 "milliseconds", 

869 "microseconds", 

870 "nanoseconds", 

871 ] 

872 hasnans = self._hasnans 

873 if hasnans: 

874 

875 def f(x): 

876 if isna(x): 

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

878 return x.components 

879 

880 else: 

881 

882 def f(x): 

883 return x.components 

884 

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

886 if not hasnans: 

887 result = result.astype("int64") 

888 return result 

889 

890 

891# --------------------------------------------------------------------- 

892# Constructor Helpers 

893 

894 

895def sequence_to_td64ns(data, copy=False, unit="ns", errors="raise"): 

896 """ 

897 Parameters 

898 ---------- 

899 array : list-like 

900 copy : bool, default False 

901 unit : str, default "ns" 

902 The timedelta unit to treat integers as multiples of. 

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

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

905 See ``pandas.to_timedelta`` for details. 

906 

907 Returns 

908 ------- 

909 converted : numpy.ndarray 

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

911 inferred_freq : Tick or None 

912 The inferred frequency of the sequence. 

913 

914 Raises 

915 ------ 

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

917 

918 Notes 

919 ----- 

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

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

922 higher level. 

923 """ 

924 inferred_freq = None 

925 unit = parse_timedelta_unit(unit) 

926 

927 # Unwrap whatever we have into a np.ndarray 

928 if not hasattr(data, "dtype"): 

929 # e.g. list, tuple 

930 if np.ndim(data) == 0: 

931 # i.e. generator 

932 data = list(data) 

933 data = np.array(data, copy=False) 

934 elif isinstance(data, ABCSeries): 

935 data = data._values 

936 elif isinstance(data, (ABCTimedeltaIndex, TimedeltaArray)): 

937 inferred_freq = data.freq 

938 data = data._data 

939 

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

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

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

943 data = objects_to_td64ns(data, unit=unit, errors=errors) 

944 copy = False 

945 

946 elif is_integer_dtype(data.dtype): 

947 # treat as multiples of the given unit 

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

949 copy = copy and not copy_made 

950 

951 elif is_float_dtype(data.dtype): 

952 # cast the unit, multiply base/frace separately 

953 # to avoid precision issues from float -> int 

954 mask = np.isnan(data) 

955 m, p = precision_from_unit(unit) 

956 base = data.astype(np.int64) 

957 frac = data - base 

958 if p: 

959 frac = np.round(frac, p) 

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

961 data[mask] = iNaT 

962 copy = False 

963 

964 elif is_timedelta64_dtype(data.dtype): 

965 if data.dtype != _TD_DTYPE: 

966 # non-nano unit 

967 # TODO: watch out for overflows 

968 data = data.astype(_TD_DTYPE) 

969 copy = False 

970 

971 else: 

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

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

974 

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

976 

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

978 return data, inferred_freq 

979 

980 

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

982 """ 

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

984 the integers as multiples of the given timedelta unit. 

985 

986 Parameters 

987 ---------- 

988 data : numpy.ndarray with integer-dtype 

989 unit : str, default "ns" 

990 The timedelta unit to treat integers as multiples of. 

991 

992 Returns 

993 ------- 

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

995 bool : whether a copy was made 

996 """ 

997 copy_made = False 

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

999 

1000 if data.dtype != np.int64: 

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

1002 # re-copying later 

1003 data = data.astype(np.int64) 

1004 copy_made = True 

1005 

1006 if unit != "ns": 

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

1008 data = data.view(dtype_str) 

1009 

1010 # TODO: watch out for overflows when converting from lower-resolution 

1011 data = data.astype("timedelta64[ns]") 

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

1013 copy_made = True 

1014 

1015 else: 

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

1017 

1018 return data, copy_made 

1019 

1020 

1021def objects_to_td64ns(data, unit="ns", errors="raise"): 

1022 """ 

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

1024 timedelta64[ns]-dtyped array. 

1025 

1026 Parameters 

1027 ---------- 

1028 data : ndarray or Index 

1029 unit : str, default "ns" 

1030 The timedelta unit to treat integers as multiples of. 

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

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

1033 See ``pandas.to_timedelta`` for details. 

1034 

1035 Returns 

1036 ------- 

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

1038 

1039 Raises 

1040 ------ 

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

1042 

1043 Notes 

1044 ----- 

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

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

1047 higher level. 

1048 """ 

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

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

1051 

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

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

1054 

1055 

1056def _validate_td64_dtype(dtype): 

1057 dtype = pandas_dtype(dtype) 

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

1059 # no precision disallowed GH#24806 

1060 msg = ( 

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

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

1063 ) 

1064 raise ValueError(msg) 

1065 

1066 if not is_dtype_equal(dtype, _TD_DTYPE): 

1067 raise ValueError(_BAD_DTYPE.format(dtype=dtype)) 

1068 

1069 return dtype 

1070 

1071 

1072def _generate_regular_range(start, end, periods, offset): 

1073 stride = offset.nanos 

1074 if periods is None: 

1075 b = Timedelta(start).value 

1076 e = Timedelta(end).value 

1077 e += stride - e % stride 

1078 elif start is not None: 

1079 b = Timedelta(start).value 

1080 e = b + periods * stride 

1081 elif end is not None: 

1082 e = Timedelta(end).value + stride 

1083 b = e - periods * stride 

1084 else: 

1085 raise ValueError( 

1086 "at least 'start' or 'end' should be specified if a 'period' is given." 

1087 ) 

1088 

1089 data = np.arange(b, e, stride, dtype=np.int64) 

1090 return data