Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pandas/core/arrays/timedeltas.py : 18%

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
4import numpy as np
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
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
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
43from pandas.tseries.frequencies import to_offset
44from pandas.tseries.offsets import Tick
46_BAD_DTYPE = "dtype {dtype} cannot be converted to timedelta64[ns]"
49def _is_convertible_to_td(key):
50 return isinstance(key, (Tick, timedelta, np.timedelta64, str))
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 )
62 return result
64 f.__name__ = name
65 f.__doc__ = f"\n{docstring}\n"
66 return property(f)
69class TimedeltaArray(dtl.DatetimeLikeArrayMixin, dtl.TimelikeOps):
70 """
71 Pandas ExtensionArray for timedelta data.
73 .. versionadded:: 0.24.0
75 .. warning::
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.
82 Parameters
83 ----------
84 values : array-like
85 The timedelta data.
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.
93 Attributes
94 ----------
95 None
97 Methods
98 -------
99 None
100 """
102 _typ = "timedeltaarray"
103 _scalar_type = Timedelta
104 _recognized_scalars = (timedelta, np.timedelta64, Tick)
105 _is_recognized_dtype = is_timedelta64_dtype
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 ]
122 # Note: ndim must be defined to ensure NaT.__richcmp(TimedeltaArray)
123 # operates pointwise.
125 @property
126 def _box_func(self):
127 return lambda x: Timedelta(x, unit="ns")
129 @property
130 def dtype(self):
131 """
132 The dtype for the TimedeltaArray.
134 .. warning::
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``.
140 Returns
141 -------
142 numpy.dtype
143 """
144 return _TD_DTYPE
146 # ----------------------------------------------------------------
147 # Constructors
149 def __init__(self, values, dtype=_TD_DTYPE, freq=None, copy=False):
150 if isinstance(values, (ABCSeries, ABCIndexClass)):
151 values = values._values
153 inferred_freq = getattr(values, "_freq", None)
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
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.")
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)
178 _validate_td64_dtype(values.dtype)
179 dtype = _validate_td64_dtype(dtype)
181 if freq == "infer":
182 msg = (
183 "Frequency inference not allowed in TimedeltaArray.__init__. "
184 "Use 'pd.array()' instead."
185 )
186 raise ValueError(msg)
188 if copy:
189 values = values.copy()
190 if freq:
191 freq = to_offset(freq)
193 self._data = values
194 self._dtype = dtype
195 self._freq = freq
197 if inferred_freq is None and freq is not None:
198 type(self)._validate_frequency(self, freq)
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)
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
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)
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)
220 result = cls._simple_new(data, freq=freq)
222 if inferred_freq is None and freq is not None:
223 # this condition precludes `freq_infer`
224 cls._validate_frequency(result, freq)
226 elif freq_infer:
227 # Set _freq directly to bypass duplicative _validate_frequency
228 # check.
229 result._freq = to_offset(result.inferred_freq)
231 return result
233 @classmethod
234 def _generate_range(cls, start, end, periods, freq, closed=None):
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")
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 )
246 if start is not None:
247 start = Timedelta(start)
249 if end is not None:
250 end = Timedelta(end)
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 )
258 left_closed, right_closed = dtl.validate_endpoints(closed)
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")
265 if not left_closed:
266 index = index[1:]
267 if not right_closed:
268 index = index[:-1]
270 return cls._simple_new(index, freq=freq)
272 # ----------------------------------------------------------------
273 # DatetimeLike Interface
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
281 def _scalar_from_string(self, value):
282 return Timedelta(value)
284 def _check_compatible_with(self, other, setitem: bool = False):
285 # we don't have anything to validate.
286 pass
288 def _maybe_clear_freq(self):
289 self._freq = None
291 # ----------------------------------------------------------------
292 # Array-Like / EA-Interface Methods
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)
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)
320 # ----------------------------------------------------------------
321 # Reductions
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
341 result = nanops.nansum(
342 self._data, axis=axis, skipna=skipna, min_count=min_count
343 )
344 return Timedelta(result)
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
363 result = nanops.nanstd(self._data, axis=axis, skipna=skipna, ddof=ddof)
364 return Timedelta(result)
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)
379 # ----------------------------------------------------------------
380 # Rendering Methods
382 def _formatter(self, boxed=False):
383 from pandas.io.formats.format import _get_format_timedelta64
385 return _get_format_timedelta64(self, box=True)
387 def _format_native_types(self, na_rep="NaT", date_format=None, **kwargs):
388 from pandas.io.formats.format import _get_format_timedelta64
390 formatter = _get_format_timedelta64(self._data, na_rep)
391 return np.array([formatter(x) for x in self._data])
393 # ----------------------------------------------------------------
394 # Arithmetic Methods
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 )
402 def _add_delta(self, delta):
403 """
404 Add a timedelta-like, Tick, or TimedeltaIndex-like object
405 to self, yielding a new TimedeltaArray.
407 Parameters
408 ----------
409 other : {timedelta, np.timedelta64, Tick,
410 TimedeltaIndex, ndarray[timedelta64]}
412 Returns
413 -------
414 result : TimedeltaArray
415 """
416 new_values = super()._add_delta(delta)
417 return type(self)._from_sequence(new_values, freq="infer")
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
427 other = DatetimeArray(other)
429 # defer to implementation in DatetimeArray
430 return other + self
432 def _add_datetimelike_scalar(self, other):
433 # adding a timedeltaindex to a datetimelike
434 from pandas.core.arrays import DatetimeArray
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)
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)
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 )
462 def __mul__(self, other):
463 other = lib.item_from_zerodim(other)
465 if isinstance(other, (ABCDataFrame, ABCSeries, ABCIndexClass)):
466 return NotImplemented
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)
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")
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)
492 # numpy will accept float or int dtype, raise TypeError for others
493 result = self._data * other
494 return type(self)(result)
496 __rmul__ = __mul__
498 def __truediv__(self, other):
499 # timedelta / X is well-defined for timedelta-like or numeric X
500 other = lib.item_from_zerodim(other)
502 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
503 return NotImplemented
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
513 # otherwise, dispatch to Timedelta implementation
514 return self._data / other
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)
525 if not hasattr(other, "dtype"):
526 # e.g. list, tuple
527 other = np.array(other)
529 if len(other) != len(self):
530 raise ValueError("Cannot divide vectors with unequal lengths")
532 elif is_timedelta64_dtype(other.dtype):
533 # let numpy handle it
534 return self._data / other
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
544 else:
545 result = self._data / other
546 return type(self)(result)
548 def __rtruediv__(self, other):
549 # X / timedelta is defined only for timedelta-like X
550 other = lib.item_from_zerodim(other)
552 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
553 return NotImplemented
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
563 # otherwise, dispatch to Timedelta implementation
564 return other / self._data
566 elif lib.is_scalar(other):
567 raise TypeError(
568 f"Cannot divide {type(other).__name__} by {type(self).__name__}"
569 )
571 if not hasattr(other, "dtype"):
572 # e.g. list, tuple
573 other = np.array(other)
575 if len(other) != len(self):
576 raise ValueError("Cannot divide vectors with unequal lengths")
578 elif is_timedelta64_dtype(other.dtype):
579 # let numpy handle it
580 return other / self._data
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)
589 else:
590 raise TypeError(
591 f"Cannot divide {other.dtype} data by {type(self).__name__}"
592 )
594 def __floordiv__(self, other):
595 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
596 return NotImplemented
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
608 # dispatch to Timedelta implementation
609 result = other.__rfloordiv__(self._data)
610 return result
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)
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")
628 elif is_timedelta64_dtype(other.dtype):
629 other = type(self)(other)
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
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
648 elif is_integer_dtype(other.dtype) or is_float_dtype(other.dtype):
649 result = self._data // other
650 return type(self)(result)
652 else:
653 dtype = getattr(other, "dtype", type(other).__name__)
654 raise TypeError(f"Cannot divide {dtype} by {type(self).__name__}")
656 def __rfloordiv__(self, other):
657 if isinstance(other, (ABCSeries, ABCDataFrame, ABCIndexClass)):
658 return NotImplemented
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
670 # dispatch to Timedelta implementation
671 result = other.__floordiv__(self._data)
672 return result
674 raise TypeError(
675 f"Cannot divide {type(other).__name__} by {type(self).__name__}"
676 )
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")
684 elif is_timedelta64_dtype(other.dtype):
685 other = type(self)(other)
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
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
701 else:
702 dtype = getattr(other, "dtype", type(other).__name__)
703 raise TypeError(f"Cannot divide {dtype} by {type(self).__name__}")
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
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
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
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
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
730 other = lib.item_from_zerodim(other)
731 if isinstance(other, (timedelta, np.timedelta64, Tick)):
732 other = Timedelta(other)
734 res1 = self // other
735 res2 = self - res1 * other
736 return res1, res2
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
743 other = lib.item_from_zerodim(other)
744 if isinstance(other, (timedelta, np.timedelta64, Tick)):
745 other = Timedelta(other)
747 res1 = other // self
748 res2 = other - res1 * self
749 return res1, res2
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)
756 def __pos__(self):
757 return type(self)(self._data, freq=self.freq)
759 def __abs__(self):
760 # Note: freq is not preserved
761 return type(self)(np.abs(self._data))
763 # ----------------------------------------------------------------
764 # Conversion Methods - Vectorized analogues of Timedelta methods
766 def total_seconds(self):
767 """
768 Return total duration of each element expressed in seconds.
770 This method is available directly on TimedeltaArray, TimedeltaIndex
771 and on Series containing timedelta values under the ``.dt`` namespace.
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.
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.
789 Examples
790 --------
791 **Series**
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]
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
810 **TimedeltaIndex**
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)
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)
823 def to_pytimedelta(self):
824 """
825 Return Timedelta Array/Index as object ndarray of datetime.timedelta
826 objects.
828 Returns
829 -------
830 datetimes : ndarray
831 """
832 return tslibs.ints_to_pytimedelta(self.asi8)
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 )
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.
857 Returns
858 -------
859 a DataFrame
860 """
861 from pandas import DataFrame
863 columns = [
864 "days",
865 "hours",
866 "minutes",
867 "seconds",
868 "milliseconds",
869 "microseconds",
870 "nanoseconds",
871 ]
872 hasnans = self._hasnans
873 if hasnans:
875 def f(x):
876 if isna(x):
877 return [np.nan] * len(columns)
878 return x.components
880 else:
882 def f(x):
883 return x.components
885 result = DataFrame([f(x) for x in self], columns=columns)
886 if not hasnans:
887 result = result.astype("int64")
888 return result
891# ---------------------------------------------------------------------
892# Constructor Helpers
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.
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.
914 Raises
915 ------
916 ValueError : Data cannot be converted to timedelta64[ns].
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)
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
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
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
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
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
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]")
975 data = np.array(data, copy=copy)
977 assert data.dtype == "m8[ns]", data
978 return data, inferred_freq
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.
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.
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"
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
1006 if unit != "ns":
1007 dtype_str = f"timedelta64[{unit}]"
1008 data = data.view(dtype_str)
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
1015 else:
1016 data = data.view("timedelta64[ns]")
1018 return data, copy_made
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.
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.
1035 Returns
1036 -------
1037 numpy.ndarray : timedelta64[ns] array converted from data
1039 Raises
1040 ------
1041 ValueError : Data cannot be converted to timedelta64[ns].
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)
1052 result = array_to_timedelta64(values, unit=unit, errors=errors)
1053 return result.view("timedelta64[ns]")
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)
1066 if not is_dtype_equal(dtype, _TD_DTYPE):
1067 raise ValueError(_BAD_DTYPE.format(dtype=dtype))
1069 return dtype
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 )
1089 data = np.arange(b, e, stride, dtype=np.int64)
1090 return data