1""" implement the TimedeltaIndex """
2from __future__ import annotations
3
4from pandas._libs import (
5 index as libindex,
6 lib,
7)
8from pandas._libs.tslibs import (
9 Resolution,
10 Timedelta,
11 to_offset,
12)
13from pandas._typing import DtypeObj
14
15from pandas.core.dtypes.common import (
16 is_dtype_equal,
17 is_scalar,
18 is_timedelta64_dtype,
19)
20from pandas.core.dtypes.generic import ABCSeries
21
22from pandas.core.arrays import datetimelike as dtl
23from pandas.core.arrays.timedeltas import TimedeltaArray
24import pandas.core.common as com
25from pandas.core.indexes.base import (
26 Index,
27 maybe_extract_name,
28)
29from pandas.core.indexes.datetimelike import DatetimeTimedeltaMixin
30from pandas.core.indexes.extension import inherit_names
31
32
33@inherit_names(
34 ["__neg__", "__pos__", "__abs__", "total_seconds", "round", "floor", "ceil"]
35 + TimedeltaArray._field_ops,
36 TimedeltaArray,
37 wrap=True,
38)
39@inherit_names(
40 [
41 "components",
42 "to_pytimedelta",
43 "sum",
44 "std",
45 "median",
46 "_format_native_types",
47 ],
48 TimedeltaArray,
49)
50class TimedeltaIndex(DatetimeTimedeltaMixin):
51 """
52 Immutable Index of timedelta64 data.
53
54 Represented internally as int64, and scalars returned Timedelta objects.
55
56 Parameters
57 ----------
58 data : array-like (1-dimensional), optional
59 Optional timedelta-like data to construct index with.
60 unit : unit of the arg (D,h,m,s,ms,us,ns) denote the unit, optional
61 Which is an integer/float number.
62 freq : str or pandas offset object, optional
63 One of pandas date offset strings or corresponding objects. The string
64 'infer' can be passed in order to set the frequency of the index as the
65 inferred frequency upon creation.
66 copy : bool
67 Make a copy of input ndarray.
68 name : object
69 Name to be stored in the index.
70
71 Attributes
72 ----------
73 days
74 seconds
75 microseconds
76 nanoseconds
77 components
78 inferred_freq
79
80 Methods
81 -------
82 to_pytimedelta
83 to_series
84 round
85 floor
86 ceil
87 to_frame
88 mean
89
90 See Also
91 --------
92 Index : The base pandas Index type.
93 Timedelta : Represents a duration between two dates or times.
94 DatetimeIndex : Index of datetime64 data.
95 PeriodIndex : Index of Period data.
96 timedelta_range : Create a fixed-frequency TimedeltaIndex.
97
98 Notes
99 -----
100 To learn more about the frequency strings, please see `this link
101 <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`__.
102 """
103
104 _typ = "timedeltaindex"
105
106 _data_cls = TimedeltaArray
107
108 @property
109 def _engine_type(self) -> type[libindex.TimedeltaEngine]:
110 return libindex.TimedeltaEngine
111
112 _data: TimedeltaArray
113
114 # Use base class method instead of DatetimeTimedeltaMixin._get_string_slice
115 _get_string_slice = Index._get_string_slice
116
117 # error: Signature of "_resolution_obj" incompatible with supertype
118 # "DatetimeIndexOpsMixin"
119 @property
120 def _resolution_obj(self) -> Resolution | None: # type: ignore[override]
121 return self._data._resolution_obj
122
123 # -------------------------------------------------------------------
124 # Constructors
125
126 def __new__(
127 cls,
128 data=None,
129 unit=None,
130 freq=lib.no_default,
131 closed=None,
132 dtype=None,
133 copy: bool = False,
134 name=None,
135 ):
136 name = maybe_extract_name(name, data, cls)
137
138 if is_scalar(data):
139 cls._raise_scalar_data_error(data)
140
141 if unit in {"Y", "y", "M"}:
142 raise ValueError(
143 "Units 'M', 'Y', and 'y' are no longer supported, as they do not "
144 "represent unambiguous timedelta values durations."
145 )
146
147 if (
148 isinstance(data, TimedeltaArray)
149 and freq is lib.no_default
150 and (dtype is None or is_dtype_equal(dtype, data.dtype))
151 ):
152 if copy:
153 data = data.copy()
154 return cls._simple_new(data, name=name)
155
156 if (
157 isinstance(data, TimedeltaIndex)
158 and freq is lib.no_default
159 and name is None
160 and (dtype is None or is_dtype_equal(dtype, data.dtype))
161 ):
162 if copy:
163 return data.copy()
164 else:
165 return data._view()
166
167 # - Cases checked above all return/raise before reaching here - #
168
169 tdarr = TimedeltaArray._from_sequence_not_strict(
170 data, freq=freq, unit=unit, dtype=dtype, copy=copy
171 )
172 refs = None
173 if not copy and isinstance(data, (ABCSeries, Index)):
174 refs = data._references
175
176 return cls._simple_new(tdarr, name=name, refs=refs)
177
178 # -------------------------------------------------------------------
179
180 def _is_comparable_dtype(self, dtype: DtypeObj) -> bool:
181 """
182 Can we compare values of the given dtype to our own?
183 """
184 return is_timedelta64_dtype(dtype) # aka self._data._is_recognized_dtype
185
186 # -------------------------------------------------------------------
187 # Indexing Methods
188
189 def get_loc(self, key):
190 """
191 Get integer location for requested label
192
193 Returns
194 -------
195 loc : int, slice, or ndarray[int]
196 """
197 self._check_indexing_error(key)
198
199 try:
200 key = self._data._validate_scalar(key, unbox=False)
201 except TypeError as err:
202 raise KeyError(key) from err
203
204 return Index.get_loc(self, key)
205
206 def _parse_with_reso(self, label: str):
207 # the "with_reso" is a no-op for TimedeltaIndex
208 parsed = Timedelta(label)
209 return parsed, None
210
211 def _parsed_string_to_bounds(self, reso, parsed: Timedelta):
212 # reso is unused, included to match signature of DTI/PI
213 lbound = parsed.round(parsed.resolution_string)
214 rbound = lbound + to_offset(parsed.resolution_string) - Timedelta(1, "ns")
215 return lbound, rbound
216
217 # -------------------------------------------------------------------
218
219 @property
220 def inferred_type(self) -> str:
221 return "timedelta64"
222
223
224def timedelta_range(
225 start=None,
226 end=None,
227 periods: int | None = None,
228 freq=None,
229 name=None,
230 closed=None,
231 *,
232 unit: str | None = None,
233) -> TimedeltaIndex:
234 """
235 Return a fixed frequency TimedeltaIndex with day as the default.
236
237 Parameters
238 ----------
239 start : str or timedelta-like, default None
240 Left bound for generating timedeltas.
241 end : str or timedelta-like, default None
242 Right bound for generating timedeltas.
243 periods : int, default None
244 Number of periods to generate.
245 freq : str or DateOffset, default 'D'
246 Frequency strings can have multiples, e.g. '5H'.
247 name : str, default None
248 Name of the resulting TimedeltaIndex.
249 closed : str, default None
250 Make the interval closed with respect to the given frequency to
251 the 'left', 'right', or both sides (None).
252 unit : str, default None
253 Specify the desired resolution of the result.
254
255 .. versionadded:: 2.0.0
256
257 Returns
258 -------
259 TimedeltaIndex
260
261 Notes
262 -----
263 Of the four parameters ``start``, ``end``, ``periods``, and ``freq``,
264 exactly three must be specified. If ``freq`` is omitted, the resulting
265 ``TimedeltaIndex`` will have ``periods`` linearly spaced elements between
266 ``start`` and ``end`` (closed on both sides).
267
268 To learn more about the frequency strings, please see `this link
269 <https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases>`__.
270
271 Examples
272 --------
273 >>> pd.timedelta_range(start='1 day', periods=4)
274 TimedeltaIndex(['1 days', '2 days', '3 days', '4 days'],
275 dtype='timedelta64[ns]', freq='D')
276
277 The ``closed`` parameter specifies which endpoint is included. The default
278 behavior is to include both endpoints.
279
280 >>> pd.timedelta_range(start='1 day', periods=4, closed='right')
281 TimedeltaIndex(['2 days', '3 days', '4 days'],
282 dtype='timedelta64[ns]', freq='D')
283
284 The ``freq`` parameter specifies the frequency of the TimedeltaIndex.
285 Only fixed frequencies can be passed, non-fixed frequencies such as
286 'M' (month end) will raise.
287
288 >>> pd.timedelta_range(start='1 day', end='2 days', freq='6H')
289 TimedeltaIndex(['1 days 00:00:00', '1 days 06:00:00', '1 days 12:00:00',
290 '1 days 18:00:00', '2 days 00:00:00'],
291 dtype='timedelta64[ns]', freq='6H')
292
293 Specify ``start``, ``end``, and ``periods``; the frequency is generated
294 automatically (linearly spaced).
295
296 >>> pd.timedelta_range(start='1 day', end='5 days', periods=4)
297 TimedeltaIndex(['1 days 00:00:00', '2 days 08:00:00', '3 days 16:00:00',
298 '5 days 00:00:00'],
299 dtype='timedelta64[ns]', freq=None)
300
301 **Specify a unit**
302
303 >>> pd.timedelta_range("1 Day", periods=3, freq="100000D", unit="s")
304 TimedeltaIndex(['1 days 00:00:00', '100001 days 00:00:00',
305 '200001 days 00:00:00'],
306 dtype='timedelta64[s]', freq='100000D')
307 """
308 if freq is None and com.any_none(periods, start, end):
309 freq = "D"
310
311 freq, _ = dtl.maybe_infer_freq(freq)
312 tdarr = TimedeltaArray._generate_range(
313 start, end, periods, freq, closed=closed, unit=unit
314 )
315 return TimedeltaIndex._simple_new(tdarr, name=name)