1"""implement the TimedeltaIndex"""
2
3from __future__ import annotations
4
5import operator
6from typing import (
7 TYPE_CHECKING,
8 Self,
9 cast,
10)
11import warnings
12
13from pandas._libs import (
14 index as libindex,
15 lib,
16)
17from pandas._libs.tslibs import (
18 Day,
19 Resolution,
20 Tick,
21 Timedelta,
22 Timestamp,
23 timezones,
24 to_offset,
25)
26from pandas._libs.tslibs.dtypes import abbrev_to_npy_unit
27from pandas._libs.tslibs.timedeltas import parse_timedelta_string_reso
28from pandas.util._decorators import set_module
29
30from pandas.core.dtypes.common import (
31 is_scalar,
32 pandas_dtype,
33)
34from pandas.core.dtypes.dtypes import ArrowDtype
35from pandas.core.dtypes.generic import ABCSeries
36from pandas.core.dtypes.missing import isna
37
38from pandas.core.arrays.timedeltas import TimedeltaArray
39import pandas.core.common as com
40from pandas.core.indexes.base import (
41 Index,
42 maybe_extract_name,
43)
44from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
45from pandas.core.roperator import (
46 rmul,
47 rsub,
48)
49
50if TYPE_CHECKING:
51 from collections.abc import Callable
52 from typing import Any
53
54 import numpy as np
55
56 from pandas._libs import NaTType
57 from pandas._typing import (
58 AxisInt,
59 DtypeObj,
60 NpDtype,
61 TimeAmbiguous,
62 TimeNonexistent,
63 TimeUnit,
64 npt,
65 )
66
67 from pandas import DataFrame
68
69
70def _new_TimedeltaIndex(cls, d):
71 """
72 This is called upon unpickling, rather than the default which doesn't
73 have arguments and breaks __new__
74 """
75 if "data" in d and not isinstance(d["data"], TimedeltaIndex):
76 data = d.pop("data")
77 if isinstance(data, TimedeltaArray):
78 tdarr = data
79 else:
80 tdarr = TimedeltaArray._simple_new(data, dtype=data.dtype)
81 # Legacy pickles stored freq on the TimedeltaArray; current pickles
82 # include it in ``d``. Migrate either up onto the Index.
83 legacy_freq = vars(tdarr).pop("_freq", None)
84 freq = d.pop("freq", legacy_freq)
85 result = cls._simple_new(tdarr, **d)
86 result._freq = freq
87 else:
88 with warnings.catch_warnings():
89 warnings.simplefilter("ignore")
90 result = cls.__new__(cls, **d)
91 return result
92
93
94@set_module("pandas")
95class TimedeltaIndex(DatetimeTimedeltaMixin):
96 """
97 Immutable Index of timedelta64 data.
98
99 Represented internally as int64, and scalars returned Timedelta objects.
100
101 Parameters
102 ----------
103 data : array-like (1-dimensional), optional
104 Optional timedelta-like data to construct index with.
105 freq : str or pandas offset object, optional
106 One of pandas date offset strings or corresponding objects. The string
107 ``'infer'`` can be passed in order to set the frequency of the index as
108 the inferred frequency upon creation.
109 dtype : numpy.dtype or str, default None
110 Valid ``numpy`` dtypes are ``timedelta64[ns]``, ``timedelta64[us]``,
111 ``timedelta64[ms]``, and ``timedelta64[s]``.
112 copy : bool, default None
113 Whether to copy input data, only relevant for array, Series, and Index
114 inputs (for other input, e.g. a list, a new array is created anyway).
115 Defaults to True for array input and False for Index/Series.
116 Set to False to avoid copying array input at your own risk (if you
117 know the input data won't be modified elsewhere).
118 Set to True to force copying Series/Index input up front.
119 name : object
120 Name to be stored in the index.
121
122 Attributes
123 ----------
124 days
125 seconds
126 microseconds
127 nanoseconds
128 components
129 inferred_freq
130
131 Methods
132 -------
133 to_pytimedelta
134 to_series
135 round
136 floor
137 ceil
138 to_frame
139 mean
140
141 See Also
142 --------
143 Index : The base pandas Index type.
144 Timedelta : Represents a duration between two dates or times.
145 DatetimeIndex : Index of datetime64 data.
146 PeriodIndex : Index of Period data.
147 timedelta_range : Create a fixed-frequency TimedeltaIndex.
148
149 Notes
150 -----
151 To learn more about the frequency strings, please see
152 :ref:`this link<timeseries.offset_aliases>`.
153
154 Examples
155 --------
156 >>> pd.TimedeltaIndex(["0 days", "1 days", "2 days", "3 days", "4 days"])
157 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
158 dtype='timedelta64[us]', freq=None)
159
160 We can also let pandas infer the frequency when possible.
161
162 >>> pd.TimedeltaIndex(np.arange(5) * 24 * 3600 * 1e9, freq="infer")
163 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
164 dtype='timedelta64[ns]', freq='D')
165 """
166
167 _typ = "timedeltaindex"
168
169 _data_cls = TimedeltaArray
170
171 @property
172 def _engine_type(self) -> type[libindex.TimedeltaEngine]:
173 return libindex.TimedeltaEngine
174
175 _data: TimedeltaArray
176
177 # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice
178 _get_string_slice = Index._get_string_slice
179
180 # -------------------------------------------------------------------
181 # Methods that dispatch to TimedeltaArray and wrap the result
182
183 def _wrap_field_result(self, result: np.ndarray) -> Index:
184 """Wrap an ndarray result computed from ``self._data`` in an Index."""
185 return Index(result, name=self.name, dtype=result.dtype, copy=False)
186
187 def _wrap_td_result(self, result: TimedeltaArray) -> Self:
188 """Wrap a TimedeltaArray result computed from ``self._data``."""
189 return type(self)._simple_new(result, name=self.name)
190
191 def total_seconds(self) -> Index:
192 """
193 Return total duration of each element expressed in seconds.
194
195 This method is available directly on TimedeltaArray, TimedeltaIndex
196 and on Series containing timedelta values under the ``.dt`` namespace.
197
198 Returns
199 -------
200 Index
201 An Index with a float64 dtype.
202
203 See Also
204 --------
205 datetime.timedelta.total_seconds : Standard library version
206 of this method.
207 TimedeltaIndex.components : Return a DataFrame with components of
208 each Timedelta.
209
210 Examples
211 --------
212 >>> idx = pd.to_timedelta(np.arange(5), unit="D")
213 >>> idx
214 TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'],
215 dtype='timedelta64[us]', freq=None)
216
217 >>> idx.total_seconds()
218 Index([0.0, 86400.0, 172800.0, 259200.0, 345600.0], dtype='float64')
219 """
220 return self._wrap_field_result(self._data.total_seconds())
221
222 # error: Signature of "round" incompatible with supertype "Index"
223 # TimedeltaIndex.round rounds to a freq like Index.floor/ceil, unlike
224 # Index.round which rounds numeric values to a number of decimals.
225 def round( # type: ignore[override]
226 self,
227 freq,
228 ambiguous: TimeAmbiguous = "raise",
229 nonexistent: TimeNonexistent = "raise",
230 ) -> Self:
231 """
232 Perform round operation on the data to the specified `freq`.
233
234 This method rounds each timedelta value in the Series/Index to the
235 nearest specified frequency using standard rounding rules (round half
236 to even).
237
238 Parameters
239 ----------
240 freq : str or Offset
241 The frequency level to round the index to. Must be a fixed
242 frequency like 's' (second) not 'ME' (month end). See
243 :ref:`frequency aliases <timeseries.offset_aliases>` for
244 a list of possible `freq` values.
245 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
246 Accepted for compatibility with :meth:`DatetimeIndex.round`;
247 has no effect since TimedeltaIndex values are never tz-aware.
248 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
249 default 'raise'
250 Accepted for compatibility with :meth:`DatetimeIndex.round`;
251 has no effect since TimedeltaIndex values are never tz-aware.
252
253 Returns
254 -------
255 TimedeltaIndex
256 Values rounded to the given `freq`.
257
258 Raises
259 ------
260 ValueError if the `freq` cannot be converted.
261
262 See Also
263 --------
264 TimedeltaIndex.floor : Perform floor operation on the data to the
265 specified `freq`.
266 TimedeltaIndex.ceil : Perform ceil operation on the data to the
267 specified `freq`.
268
269 Examples
270 --------
271 >>> tdelta_idx = pd.timedelta_range("1 day", periods=3, freq="6h20min")
272 >>> tdelta_idx
273 TimedeltaIndex(['1 days 00:00:00', '1 days 06:20:00', '1 days 12:40:00'],
274 dtype='timedelta64[us]', freq='380min')
275 >>> tdelta_idx.round("h")
276 TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 13:00:00'],
277 dtype='timedelta64[us]', freq=None)
278 """
279 return self._wrap_td_result(self._data.round(freq, ambiguous, nonexistent))
280
281 def floor(
282 self,
283 freq,
284 ambiguous: TimeAmbiguous = "raise",
285 nonexistent: TimeNonexistent = "raise",
286 ) -> Self:
287 """
288 Perform floor operation on the data to the specified `freq`.
289
290 This method rounds each timedelta value in the Series/Index down to
291 the specified frequency (i.e., towards negative infinity).
292
293 Parameters
294 ----------
295 freq : str or Offset
296 The frequency level to floor the index to. Must be a fixed
297 frequency like 's' (second) not 'ME' (month end). See
298 :ref:`frequency aliases <timeseries.offset_aliases>` for
299 a list of possible `freq` values.
300 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
301 Accepted for compatibility with :meth:`DatetimeIndex.floor`;
302 has no effect since TimedeltaIndex values are never tz-aware.
303 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
304 default 'raise'
305 Accepted for compatibility with :meth:`DatetimeIndex.floor`;
306 has no effect since TimedeltaIndex values are never tz-aware.
307
308 Returns
309 -------
310 TimedeltaIndex
311 Values rounded down to the given `freq`.
312
313 Raises
314 ------
315 ValueError if the `freq` cannot be converted.
316
317 See Also
318 --------
319 TimedeltaIndex.round : Perform round operation on the data to the
320 specified `freq`.
321 TimedeltaIndex.ceil : Perform ceil operation on the data to the
322 specified `freq`.
323
324 Examples
325 --------
326 >>> tdelta_idx = pd.timedelta_range("1 day", periods=3, freq="6h20min")
327 >>> tdelta_idx
328 TimedeltaIndex(['1 days 00:00:00', '1 days 06:20:00', '1 days 12:40:00'],
329 dtype='timedelta64[us]', freq='380min')
330 >>> tdelta_idx.floor("h")
331 TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 12:00:00'],
332 dtype='timedelta64[us]', freq=None)
333 """
334 return self._wrap_td_result(self._data.floor(freq, ambiguous, nonexistent))
335
336 def ceil(
337 self,
338 freq,
339 ambiguous: TimeAmbiguous = "raise",
340 nonexistent: TimeNonexistent = "raise",
341 ) -> Self:
342 """
343 Perform ceil operation on the data to the specified `freq`.
344
345 This method rounds each timedelta value in the Series/Index up to
346 the specified frequency (i.e., towards positive infinity).
347
348 Parameters
349 ----------
350 freq : str or Offset
351 The frequency level to ceil the index to. Must be a fixed
352 frequency like 's' (second) not 'ME' (month end). See
353 :ref:`frequency aliases <timeseries.offset_aliases>` for
354 a list of possible `freq` values.
355 ambiguous : 'infer', bool-ndarray, 'NaT', default 'raise'
356 Accepted for compatibility with :meth:`DatetimeIndex.ceil`;
357 has no effect since TimedeltaIndex values are never tz-aware.
358 nonexistent : 'shift_forward', 'shift_backward', 'NaT', timedelta, \
359 default 'raise'
360 Accepted for compatibility with :meth:`DatetimeIndex.ceil`;
361 has no effect since TimedeltaIndex values are never tz-aware.
362
363 Returns
364 -------
365 TimedeltaIndex
366 Values rounded up to the given `freq`.
367
368 Raises
369 ------
370 ValueError if the `freq` cannot be converted.
371
372 See Also
373 --------
374 TimedeltaIndex.round : Perform round operation on the data to the
375 specified `freq`.
376 TimedeltaIndex.floor : Perform floor operation on the data to the
377 specified `freq`.
378
379 Examples
380 --------
381 >>> tdelta_idx = pd.timedelta_range("1 day", periods=3, freq="6h20min")
382 >>> tdelta_idx
383 TimedeltaIndex(['1 days 00:00:00', '1 days 06:20:00', '1 days 12:40:00'],
384 dtype='timedelta64[us]', freq='380min')
385 >>> tdelta_idx.ceil("h")
386 TimedeltaIndex(['1 days 00:00:00', '1 days 07:00:00', '1 days 13:00:00'],
387 dtype='timedelta64[us]', freq=None)
388 """
389 return self._wrap_td_result(self._data.ceil(freq, ambiguous, nonexistent))
390
391 @property
392 def days(self) -> Index:
393 """
394 Number of days for each element.
395
396 This attribute returns the number of whole days in each timedelta value.
397 It represents the days component of the duration, not the total duration
398 expressed in days.
399
400 See Also
401 --------
402 Series.dt.seconds : Return number of seconds for each element.
403 Series.dt.microseconds : Return number of microseconds for each element.
404 Series.dt.nanoseconds : Return number of nanoseconds for each element.
405
406 Examples
407 --------
408 >>> tdelta_idx = pd.to_timedelta(["0 days", "10 days", "20 days"])
409 >>> tdelta_idx
410 TimedeltaIndex(['0 days', '10 days', '20 days'],
411 dtype='timedelta64[us]', freq=None)
412 >>> tdelta_idx.days
413 Index([0, 10, 20], dtype='int64')
414 """
415 return self._wrap_field_result(self._data.days)
416
417 @property
418 def seconds(self) -> Index:
419 """
420 Number of seconds (>= 0 and less than 1 day) for each element.
421
422 This attribute returns the seconds component of each timedelta value,
423 which is the number of seconds remaining after subtracting whole days.
424 Values range from 0 to 86399.
425
426 See Also
427 --------
428 Series.dt.seconds : Return number of seconds for each element.
429 Series.dt.nanoseconds : Return number of nanoseconds for each element.
430
431 Examples
432 --------
433 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="s")
434 >>> tdelta_idx
435 TimedeltaIndex(['0 days 00:00:01', '0 days 00:00:02', '0 days 00:00:03'],
436 dtype='timedelta64[us]', freq=None)
437 >>> tdelta_idx.seconds
438 Index([1, 2, 3], dtype='int32')
439 """
440 return self._wrap_field_result(self._data.seconds)
441
442 @property
443 def microseconds(self) -> Index:
444 """
445 Number of microseconds (>= 0 and less than 1 second) for each element.
446
447 This attribute returns the microseconds component of each timedelta value,
448 which is the number of microseconds remaining after subtracting whole
449 seconds. Values range from 0 to 999999.
450
451 See Also
452 --------
453 Timedelta.microseconds : Number of microseconds (>= 0 and less than
454 1 second).
455
456 Examples
457 --------
458 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="us")
459 >>> tdelta_idx
460 TimedeltaIndex(['0 days 00:00:00.000001', '0 days 00:00:00.000002',
461 '0 days 00:00:00.000003'],
462 dtype='timedelta64[us]', freq=None)
463 >>> tdelta_idx.microseconds
464 Index([1, 2, 3], dtype='int32')
465 """
466 return self._wrap_field_result(self._data.microseconds)
467
468 @property
469 def nanoseconds(self) -> Index:
470 """
471 Number of nanoseconds (>= 0 and less than 1 microsecond) for each element.
472
473 This attribute returns the nanoseconds component of each timedelta value,
474 which is the number of nanoseconds remaining after subtracting whole
475 microseconds. Values range from 0 to 999.
476
477 See Also
478 --------
479 Series.dt.seconds : Return number of seconds for each element.
480 Series.dt.microseconds : Return number of microseconds for each element.
481
482 Examples
483 --------
484 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="ns")
485 >>> tdelta_idx
486 TimedeltaIndex(['0 days 00:00:00.000000001', '0 days 00:00:00.000000002',
487 '0 days 00:00:00.000000003'],
488 dtype='timedelta64[ns]', freq=None)
489 >>> tdelta_idx.nanoseconds
490 Index([1, 2, 3], dtype='int32')
491 """
492 return self._wrap_field_result(self._data.nanoseconds)
493
494 @property
495 def components(self) -> DataFrame:
496 """
497 Return a DataFrame of the individual resolution components of the Timedeltas.
498
499 The components (days, hours, minutes seconds, milliseconds, microseconds,
500 nanoseconds) are returned as columns in a DataFrame.
501
502 Returns
503 -------
504 DataFrame
505
506 See Also
507 --------
508 TimedeltaIndex.total_seconds : Return total duration expressed in seconds.
509 Timedelta.components : Return a components namedtuple-like of a single
510 timedelta.
511
512 Examples
513 --------
514 >>> tdelta_idx = pd.to_timedelta(["1 day 3 min 2 us 42 ns"])
515 >>> tdelta_idx
516 TimedeltaIndex(['1 days 00:03:00.000002042'],
517 dtype='timedelta64[ns]', freq=None)
518 >>> tdelta_idx.components
519 days hours minutes seconds milliseconds microseconds nanoseconds
520 0 1 0 3 0 0 2 42
521 """
522 return self._data.components
523
524 def to_pytimedelta(self) -> npt.NDArray[np.object_]:
525 """
526 Return an ndarray of datetime.timedelta objects.
527
528 Each element of the :class:`TimedeltaIndex` is converted to the
529 corresponding native Python :class:`datetime.timedelta` object.
530
531 Returns
532 -------
533 numpy.ndarray
534 Object-dtype array of :class:`datetime.timedelta`.
535
536 See Also
537 --------
538 to_timedelta : Convert argument to timedelta format.
539 Timedelta : Represents a duration between two dates or times.
540 DatetimeIndex: Index of datetime64 data.
541 Timedelta.components : Return a components namedtuple-like
542 of a single timedelta.
543
544 Examples
545 --------
546 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
547 >>> tdelta_idx
548 TimedeltaIndex(['1 days', '2 days', '3 days'],
549 dtype='timedelta64[us]', freq=None)
550 >>> tdelta_idx.to_pytimedelta()
551 array([datetime.timedelta(days=1), datetime.timedelta(days=2),
552 datetime.timedelta(days=3)], dtype=object)
553 """
554 return self._data.to_pytimedelta()
555
556 def sum(
557 self,
558 *,
559 axis: AxisInt | None = None,
560 dtype: NpDtype | None = None,
561 out=None,
562 keepdims: bool = False,
563 initial=None,
564 skipna: bool = True,
565 min_count: int = 0,
566 ) -> Timedelta | NaTType:
567 """
568 Return the sum of the values over the requested axis.
569
570 Parameters
571 ----------
572 axis : int, optional
573 Axis for the function to be applied on.
574 dtype, out, keepdims, initial
575 Not implemented; kept for compatibility with :func:`numpy.sum`,
576 which calls this method when ``numpy.sum(tdi)`` is used. Must be
577 left at their default values.
578 skipna : bool, default True
579 Whether to ignore any NaT elements.
580 min_count : int, default 0
581 The required number of valid values to perform the operation. If fewer
582 than ``min_count`` non-NaT values are present the result is NaT.
583
584 Returns
585 -------
586 Timedelta
587 The sum, or NaT if it cannot be computed.
588
589 See Also
590 --------
591 numpy.ndarray.sum : Returns the sum of array elements along a given axis.
592 Series.sum : Return the sum of the values in a Series.
593
594 Examples
595 --------
596 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
597 >>> tdelta_idx
598 TimedeltaIndex(['1 days', '2 days', '3 days'],
599 dtype='timedelta64[us]', freq=None)
600 >>> tdelta_idx.sum()
601 Timedelta('6 days 00:00:00')
602 """
603 return self._data.sum(
604 axis=axis,
605 dtype=dtype,
606 out=out,
607 keepdims=keepdims,
608 initial=initial,
609 skipna=skipna,
610 min_count=min_count,
611 )
612
613 def std(
614 self,
615 *,
616 axis: AxisInt | None = None,
617 dtype: NpDtype | None = None,
618 out=None,
619 ddof: int = 1,
620 keepdims: bool = False,
621 skipna: bool = True,
622 ) -> Timedelta | NaTType:
623 """
624 Return the standard deviation of the values over the requested axis.
625
626 Parameters
627 ----------
628 axis : int, optional
629 Axis for the function to be applied on.
630 dtype, out, keepdims
631 Not implemented; kept for compatibility with :func:`numpy.std`,
632 which calls this method when ``numpy.std(tdi)`` is used. Must be
633 left at their default values.
634 ddof : int, default 1
635 Delta degrees of freedom. The divisor used in calculations is
636 ``N - ddof``, where ``N`` represents the number of elements.
637 skipna : bool, default True
638 Whether to ignore any NaT elements.
639
640 Returns
641 -------
642 Timedelta
643 The standard deviation, or NaT if it cannot be computed.
644
645 See Also
646 --------
647 numpy.ndarray.std : Returns the standard deviation of array elements
648 along a given axis.
649 Series.std : Return sample standard deviation over requested axis.
650
651 Examples
652 --------
653 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
654 >>> tdelta_idx
655 TimedeltaIndex(['1 days', '2 days', '3 days'],
656 dtype='timedelta64[us]', freq=None)
657 >>> tdelta_idx.std()
658 Timedelta('1 days 00:00:00')
659 """
660 return self._data.std(
661 axis=axis, dtype=dtype, out=out, ddof=ddof, keepdims=keepdims, skipna=skipna
662 )
663
664 def median(
665 self,
666 *,
667 axis: AxisInt | None = None,
668 skipna: bool = True,
669 **kwargs,
670 ) -> Timedelta | NaTType:
671 """
672 Return the median of the values over the requested axis.
673
674 Parameters
675 ----------
676 axis : int, optional
677 Axis for the function to be applied on.
678 skipna : bool, default True
679 Whether to ignore any NaT elements.
680 **kwargs
681 Additional keywords have no effect but might be accepted for
682 compatibility with NumPy.
683
684 Returns
685 -------
686 Timedelta
687 The median, or NaT if it cannot be computed.
688
689 See Also
690 --------
691 numpy.median : Compute the median along the specified axis.
692 Series.median : Return the median of the values in a Series.
693
694 Examples
695 --------
696 >>> tdelta_idx = pd.to_timedelta([1, 2, 3], unit="D")
697 >>> tdelta_idx
698 TimedeltaIndex(['1 days', '2 days', '3 days'],
699 dtype='timedelta64[us]', freq=None)
700 >>> tdelta_idx.median()
701 Timedelta('2 days 00:00:00')
702 """
703 return self._data.median(axis=axis, skipna=skipna, **kwargs)
704
705 # -------------------------------------------------------------------
706 # Constructors
707
708 def __new__(
709 cls,
710 data=None,
711 freq=lib.no_default,
712 dtype=None,
713 copy: bool | None = None,
714 name=None,
715 ):
716 name = maybe_extract_name(name, data, cls)
717
718 # GH#63388
719 data, copy = cls._maybe_copy_array_input(data, copy, dtype)
720
721 if is_scalar(data):
722 cls._raise_scalar_data_error(data)
723
724 if dtype is not None:
725 dtype = pandas_dtype(dtype)
726
727 if (
728 isinstance(data, TimedeltaArray)
729 and freq is lib.no_default
730 and (dtype is None or dtype == data.dtype)
731 ):
732 if copy:
733 data = data.copy()
734 return cls._simple_new(data, name=name)
735
736 if (
737 isinstance(data, TimedeltaIndex)
738 and freq is lib.no_default
739 and name is None
740 and (dtype is None or dtype == data.dtype)
741 ):
742 if copy:
743 return data.copy()
744 else:
745 return data._view()
746
747 # - Cases checked above all return/raise before reaching here - #
748
749 inferred_freq = data.freq if isinstance(data, TimedeltaIndex) else None
750
751 tdarr = TimedeltaArray._from_sequence(data, dtype=dtype, copy=copy)
752 refs = None
753 if not copy and isinstance(data, (ABCSeries, Index)):
754 refs = data._references
755
756 result = cls._simple_new(tdarr, name=name, refs=refs)
757 result._pin_freq(freq, inferred_freq)
758 return result
759
760 def __reduce__(self):
761 d = {"data": self._data, "name": self.name, "freq": self._freq}
762 return _new_TimedeltaIndex, (type(self), d), None
763
764 # -------------------------------------------------------------------
765
766 def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
767 """
768 Can we compare values of the given dtype to our own?
769 """
770 if isinstance(dtype, ArrowDtype):
771 return dtype.kind == "m"
772 return lib.is_np_dtype(dtype, "m") # aka self._data._is_recognized_dtype
773
774 # -------------------------------------------------------------------
775 # Arithmetic Methods
776
777 def __neg__(self) -> TimedeltaIndex:
778 result = self._data.__neg__()
779 idx = type(self)._simple_new(result, name=self.name)
780 if self.freq is not None:
781 idx._freq = -self.freq
782 return idx
783
784 def __pos__(self) -> TimedeltaIndex:
785 result = self._data.__pos__()
786 idx = type(self)._simple_new(result, name=self.name)
787 if self.freq is not None:
788 idx._freq = self.freq
789 return idx
790
791 def _arith_method(self, other: object, op: Callable[..., Any]) -> Index:
792 result = super()._arith_method(other, op)
793 if self.freq is None or not is_scalar(other):
794 return result
795
796 new_freq = None
797 if isinstance(result, type(self)):
798 new_freq = self._get_arith_result_freq(other, op)
799 elif (
800 getattr(getattr(result, "dtype", None), "kind", None) == "M" and op is rsub
801 ):
802 # Timestamp/datetime - TDI produces a DatetimeIndex.
803 # The array __rsub__ does (-self) + other, losing freq in
804 # negation. Compute the freq at the Index level.
805 new_freq = self._get_rsub_datetime_result_freq(other)
806
807 if new_freq is not None:
808 result._freq = new_freq
809 return result
810
811 def _get_arith_result_freq(
812 self, other: object, op: Callable[..., Any]
813 ) -> Day | Tick | None:
814 """
815 Compute the result freq for arithmetic operations whose result
816 is also a TimedeltaIndex.
817
818 Caller is responsible for checking self.freq is not None.
819 """
820 freq = self.freq
821 assert freq is not None # caller ensures this
822 if op in (operator.mul, rmul):
823 if bool(isna(other)):
824 return None
825 # error: No overload variant of "__mul__" of "BaseOffset"
826 # matches argument type "object"
827 new_freq = freq * other # type: ignore[operator]
828 if new_freq.n == 0:
829 # GH#51575 Better to have no freq than an incorrect one
830 return None
831 return new_freq
832
833 if op in (operator.truediv, operator.floordiv):
834 # Note: freq gets division, not floor-division, even if op
835 # is floordiv.
836 if isinstance(freq, Day):
837 if freq.n % other == 0: # type: ignore[operator]
838 new_freq = Day(freq.n // other) # type: ignore[operator]
839 else:
840 new_freq = to_offset(Timedelta(days=freq.n)) / other # type: ignore[operator]
841 else:
842 new_freq = freq / other # type: ignore[operator]
843 if new_freq.nanos == 0 and freq.nanos != 0:
844 # e.g. if self.freq is Nano(1) then dividing by 2
845 # rounds down to zero
846 return None
847 return new_freq
848
849 if op is rsub:
850 # scalar_timedelta - TDI: the array uses (-self) + other,
851 # losing freq in negation. Result freq is -self.freq.
852 return -freq # type: ignore[return-value]
853
854 return None
855
856 def _get_rsub_datetime_result_freq(self, other: object) -> Day | Tick | None:
857 """
858 Compute the result freq for Timestamp/datetime - TimedeltaIndex.
859
860 Mirrors the logic of _get_arithmetic_result_freq for the negated
861 array case.
862 """
863 freq = self.freq
864 assert freq is not None # caller ensures this
865 if isinstance(freq, Tick):
866 return -freq
867
868 # freq is a Day; only preserve with tz-naive or UTC
869 if isinstance(other, Timestamp):
870 tz = other.tz
871 else:
872 tz = Timestamp(other).tz # type: ignore[arg-type]
873 if tz is None or timezones.is_utc(tz):
874 return -freq # type: ignore[return-value]
875 return None
876
877 # -------------------------------------------------------------------
878 # Indexing Methods
879
880 def get_loc(self, key):
881 """
882 Get integer location for requested label
883
884 Returns
885 -------
886 loc : int, slice, or ndarray[int]
887 """
888 self._check_indexing_error(key)
889
890 try:
891 key = self._data._validate_scalar(key, unbox=False)
892 except TypeError as err:
893 raise KeyError(key) from err
894
895 return Index.get_loc(self, key)
896
897 # error: Return type "tuple[Timedelta | NaTType, Resolution]" of
898 # "_parse_with_reso" incompatible with return type
899 # "tuple[datetime, Resolution]" in supertype
900 # "pandas.core.indexes.datetimelike.DatetimeIndexOpsMixin"
901 def _parse_with_reso(self, label: str) -> tuple[Timedelta | NaTType, Resolution]: # type: ignore[override]
902 # Resolution comes from the string text (GH#33603), not the value's
903 # components: "720s" resolves to "s", not "min" (even though
904 # 720s == 12 minutes). The parser reports both in a single pass.
905 parsed, string_reso_code = parse_timedelta_string_reso(label)
906 if isinstance(parsed, Timedelta):
907 string_reso = Resolution(string_reso_code)
908 # Fold in sub-unit precision the written unit doesn't capture,
909 # e.g. "1.5min" is minute-written but second-resolution.
910 value_reso = Resolution.get_reso_from_freqstr(parsed.resolution_string)
911 reso = min(string_reso, value_reso)
912 else:
913 # i.e. pd.NaT
914 reso = Resolution.RESO_SEC
915 return parsed, reso
916
917 def _parsed_string_to_bounds(self, reso: Resolution, parsed: Timedelta):
918 reso_str = reso.attr_abbrev
919 lbound = parsed.floor(reso_str)
920 rbound = (
921 lbound
922 + to_offset(reso_str)
923 - Timedelta(1, unit=self.unit).as_unit(self.unit)
924 )
925 # If reso is finer than the index unit, the window [lbound, rbound]
926 # collapses to lbound alone; without the clamp rbound < lbound.
927 return lbound, max(lbound, rbound)
928
929 # -------------------------------------------------------------------
930
931 @property
932 def inferred_type(self) -> str:
933 return "timedelta64"
934
935
936@set_module("pandas")
937def timedelta_range(
938 start=None,
939 end=None,
940 periods: int | None = None,
941 freq=None,
942 name=None,
943 closed=None,
944 *,
945 unit: TimeUnit | None = None,
946) -> TimedeltaIndex:
947 """
948 Return a fixed frequency TimedeltaIndex with day as the default.
949
950 This function generates a sequence of evenly spaced timedelta values
951 between the specified bounds, using day as the default frequency.
952
953 Parameters
954 ----------
955 start : str or timedelta-like, default None
956 Left bound for generating timedeltas.
957 end : str or timedelta-like, default None
958 Right bound for generating timedeltas.
959 periods : int, default None
960 Number of periods to generate.
961 freq : str, Timedelta, datetime.timedelta, or DateOffset, default 'D'
962 Frequency strings can have multiples, e.g. '5h'.
963 name : Hashable, default None
964 Name of the resulting TimedeltaIndex.
965 closed : str, default None
966 Make the interval closed with respect to the given frequency to
967 the 'left', 'right', or both sides (None).
968 unit : {'s', 'ms', 'us', 'ns', None}, default None
969 Specify the desired resolution of the result.
970 If not specified, this is inferred from the 'start', 'end', and 'freq'
971 using the same inference as :class:`Timedelta` taking the highest
972 resolution of the three that are provided.
973
974 .. versionadded:: 2.0.0
975
976 Returns
977 -------
978 TimedeltaIndex
979 Fixed frequency, with day as the default.
980
981 See Also
982 --------
983 date_range : Return a fixed frequency DatetimeIndex.
984 period_range : Return a fixed frequency PeriodIndex.
985
986 Notes
987 -----
988 Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
989 a maximum of three can be specified at once. Of the three parameters
990 ``start``, ``end``, and ``periods``, at least two must be specified.
991 If ``freq`` is omitted, the resulting ``DatetimeIndex`` will have
992 ``periods`` linearly spaced elements between ``start`` and ``end``
993 (closed on both sides).
994
995 To learn more about the frequency strings, please see
996 :ref:`this link<timeseries.offset_aliases>`.
997
998 Examples
999 --------
1000 >>> pd.timedelta_range(start="1 day", periods=4)
1001 TimedeltaIndex(['1 days', '2 days', '3 days', '4 days'],
1002 dtype='timedelta64[us]', freq='D')
1003
1004 The ``closed`` parameter specifies which endpoint is included. The default
1005 behavior is to include both endpoints.
1006
1007 >>> pd.timedelta_range(start="1 day", periods=4, closed="right")
1008 TimedeltaIndex(['2 days', '3 days', '4 days'],
1009 dtype='timedelta64[us]', freq='D')
1010
1011 The ``freq`` parameter specifies the frequency of the TimedeltaIndex.
1012 Only fixed frequencies can be passed, non-fixed frequencies such as
1013 'M' (month end) will raise.
1014
1015 >>> pd.timedelta_range(start="1 day", end="2 days", freq="6h")
1016 TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 12:00:00',
1017 '1 days 18:00:00', '2 days 00:00:00'],
1018 dtype='timedelta64[us]', freq='6h')
1019
1020 Specify ``start``, ``end``, and ``periods``; the frequency is generated
1021 automatically (linearly spaced).
1022
1023 >>> pd.timedelta_range(start="1 day", end="5 days", periods=4)
1024 TimedeltaIndex(['1 days 00:00:00', '2 days 08:00:00', '3 days 16:00:00',
1025 '5 days 00:00:00'],
1026 dtype='timedelta64[us]', freq=None)
1027
1028 **Specify a unit**
1029
1030 >>> pd.timedelta_range("1 Day", periods=3, freq="100000D", unit="s")
1031 TimedeltaIndex(['1 days', '100001 days', '200001 days'],
1032 dtype='timedelta64[s]', freq='100000D')
1033 """
1034 if freq is None and com.any_none(periods, start, end):
1035 freq = "D"
1036 freq = to_offset(freq)
1037
1038 if com.count_not_none(start, end, periods, freq) != 3:
1039 # This check needs to come before the `unit = start.unit` line below
1040 raise ValueError(
1041 "Of the four parameters: start, end, periods, "
1042 "and freq, exactly three must be specified"
1043 )
1044
1045 if unit is None:
1046 # Infer the unit based on the inputs
1047
1048 if start is not None and end is not None:
1049 start = Timedelta(start)
1050 end = Timedelta(end)
1051 start = cast("Timedelta", start)
1052 end = cast("Timedelta", end)
1053 if abbrev_to_npy_unit(start.unit) > abbrev_to_npy_unit(end.unit):
1054 unit = cast("TimeUnit", start.unit)
1055 else:
1056 unit = cast("TimeUnit", end.unit)
1057 elif start is not None:
1058 start = Timedelta(start)
1059 start = cast("Timedelta", start)
1060 unit = cast("TimeUnit", start.unit)
1061 else:
1062 end = Timedelta(end)
1063 end = cast("Timedelta", end)
1064 unit = cast("TimeUnit", end.unit)
1065
1066 # Last we need to watch out for cases where the 'freq' implies a higher
1067 # unit than either start or end
1068 if freq is not None:
1069 freq = cast("Tick | Day", freq)
1070 creso = abbrev_to_npy_unit(unit)
1071 if freq._creso > creso: # pyright: ignore[reportAttributeAccessIssue]
1072 unit = cast("TimeUnit", freq.base.freqstr)
1073
1074 tdarr = TimedeltaArray._generate_range(
1075 start, end, periods, freq, closed=closed, unit=unit
1076 )
1077 result = TimedeltaIndex._simple_new(tdarr, name=name)
1078 result._freq = freq
1079 return result