Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pandas/core/resample.py: 26%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import annotations
3import copy
4from typing import (
5 TYPE_CHECKING,
6 Concatenate,
7 Literal,
8 Self,
9 cast,
10 final,
11 no_type_check,
12 overload,
13)
14import warnings
16import numpy as np
18from pandas._libs import lib
19from pandas._libs.tslibs import (
20 BaseOffset,
21 IncompatibleFrequency,
22 NaT,
23 Period,
24 Timedelta,
25 Timestamp,
26 to_offset,
27)
28from pandas._typing import NDFrameT
29from pandas.errors import (
30 AbstractMethodError,
31 Pandas4Warning,
32)
33from pandas.util._decorators import set_module
34from pandas.util._exceptions import find_stack_level
36from pandas.core.dtypes.dtypes import (
37 ArrowDtype,
38 PeriodDtype,
39)
40from pandas.core.dtypes.generic import (
41 ABCDataFrame,
42 ABCSeries,
43)
45import pandas.core.algorithms as algos
46from pandas.core.apply import ResamplerWindowApply
47from pandas.core.arrays import ArrowExtensionArray
48from pandas.core.base import (
49 PandasObject,
50 SelectionMixin,
51)
52from pandas.core.generic import (
53 NDFrame,
54)
55from pandas.core.groupby.groupby import (
56 BaseGroupBy,
57 GroupBy,
58 get_groupby,
59)
60from pandas.core.groupby.grouper import Grouper
61from pandas.core.groupby.ops import BinGrouper
62from pandas.core.indexes.api import MultiIndex
63from pandas.core.indexes.base import Index
64from pandas.core.indexes.datetimes import (
65 DatetimeIndex,
66 date_range,
67)
68from pandas.core.indexes.period import (
69 PeriodIndex,
70 period_range,
71)
72from pandas.core.indexes.timedeltas import (
73 TimedeltaIndex,
74 timedelta_range,
75)
76from pandas.core.reshape.concat import concat
78from pandas.tseries.frequencies import (
79 is_subperiod,
80 is_superperiod,
81)
82from pandas.tseries.offsets import (
83 Day,
84 Tick,
85)
87if TYPE_CHECKING:
88 from collections.abc import (
89 Callable,
90 Hashable,
91 )
93 from pandas._typing import (
94 Any,
95 AnyArrayLike,
96 Axis,
97 FreqIndexT,
98 Frequency,
99 IndexLabel,
100 InterpolateOptions,
101 P,
102 T,
103 TimedeltaConvertibleTypes,
104 TimeGrouperOrigin,
105 TimestampConvertibleTypes,
106 TimeUnit,
107 npt,
108 )
110 from pandas import (
111 DataFrame,
112 Series,
113 )
114 from pandas.core.generic import NDFrame
116_shared_docs_kwargs: dict[str, str] = {}
119@set_module("pandas.api.typing")
120class Resampler(BaseGroupBy, PandasObject):
121 """
122 Class for resampling datetimelike data, a groupby-like operation.
123 See aggregate, transform, and apply functions on this object.
125 It's easiest to use obj.resample(...) to use Resampler.
127 Parameters
128 ----------
129 obj : Series or DataFrame
130 groupby : TimeGrouper
132 Returns
133 -------
134 a Resampler of the appropriate type
136 Notes
137 -----
138 After resampling, see aggregate, apply, and transform functions.
139 """
141 _grouper: BinGrouper
142 _timegrouper: TimeGrouper
143 binner: DatetimeIndex | TimedeltaIndex | PeriodIndex # depends on subclass
144 exclusions: frozenset[Hashable] = frozenset() # for SelectionMixin compat
145 _internal_names_set = set({"obj", "ax", "_indexer"})
147 # to the groupby descriptor
148 _attributes = [
149 "freq",
150 "closed",
151 "label",
152 "convention",
153 "origin",
154 "offset",
155 ]
157 def __init__(
158 self,
159 obj: NDFrame,
160 timegrouper: TimeGrouper,
161 *,
162 gpr_index: Index,
163 group_keys: bool = False,
164 selection=None,
165 include_groups: bool = False,
166 ) -> None:
167 if include_groups:
168 raise ValueError("include_groups=True is no longer allowed.")
169 self._timegrouper = timegrouper
170 self.keys = None
171 self.sort = True
172 self.group_keys = group_keys
173 self.as_index = True
175 self.obj, self.ax, self._indexer = self._timegrouper._set_grouper(
176 self._convert_obj(obj), sort=True, gpr_index=gpr_index
177 )
178 self.binner, self._grouper = self._get_binner()
179 self._selection = selection
180 if self._timegrouper.key is not None:
181 self.exclusions = frozenset([self._timegrouper.key])
182 else:
183 self.exclusions = frozenset()
185 @final
186 def __str__(self) -> str:
187 """
188 Provide a nice str repr of our rolling object.
189 """
190 attrs = (
191 f"{k}={getattr(self._timegrouper, k)}"
192 for k in self._attributes
193 if getattr(self._timegrouper, k, None) is not None
194 )
195 return f"{type(self).__name__} [{', '.join(attrs)}]"
197 @final
198 def __getattr__(self, attr: str):
199 if attr in self._internal_names_set:
200 return object.__getattribute__(self, attr)
201 if attr in self._attributes:
202 return getattr(self._timegrouper, attr)
203 if attr in self.obj:
204 return self[attr]
206 return object.__getattribute__(self, attr)
208 @final
209 @property
210 def _from_selection(self) -> bool:
211 """
212 Is the resampling from a DataFrame column or MultiIndex level.
213 """
214 # upsampling and PeriodIndex resampling do not work
215 # with selection, this state used to catch and raise an error
216 return self._timegrouper is not None and (
217 self._timegrouper.key is not None or self._timegrouper.level is not None
218 )
220 def _convert_obj(self, obj: NDFrameT) -> NDFrameT:
221 """
222 Provide any conversions for the object in order to correctly handle.
224 Parameters
225 ----------
226 obj : Series or DataFrame
228 Returns
229 -------
230 Series or DataFrame
231 """
232 return obj._consolidate()
234 def _get_binner_for_time(self):
235 raise AbstractMethodError(self)
237 @final
238 def _get_binner(self):
239 """
240 Create the BinGrouper, assume that self.set_grouper(obj)
241 has already been called.
242 """
243 binner, bins, binlabels = self._get_binner_for_time()
244 assert len(bins) == len(binlabels)
245 if self._timegrouper._arrow_dtype is not None:
246 binlabels = binlabels.astype(self._timegrouper._arrow_dtype)
247 bin_grouper = BinGrouper(bins, binlabels, indexer=self._indexer)
248 return binner, bin_grouper
250 @overload
251 def pipe(
252 self,
253 func: Callable[Concatenate[Self, P], T],
254 *args: P.args,
255 **kwargs: P.kwargs,
256 ) -> T: ...
258 @overload
259 def pipe(
260 self,
261 func: tuple[Callable[..., T], str],
262 *args: Any,
263 **kwargs: Any,
264 ) -> T: ...
266 @final
267 def pipe(
268 self,
269 func: Callable[Concatenate[Self, P], T] | tuple[Callable[..., T], str],
270 *args: Any,
271 **kwargs: Any,
272 ) -> T:
273 """
274 Apply a ``func`` with arguments to this Resampler object and return its result.
276 Use `.pipe` when you want to improve readability by chaining together
277 functions that expect Series, DataFrames, GroupBy or Resampler objects.
278 Instead of writing
280 >>> h = lambda x, arg2, arg3: x + 1 - arg2 * arg3
281 >>> g = lambda x, arg1: x * 5 / arg1
282 >>> f = lambda x: x**4
283 >>> df = pd.DataFrame([["a", 4], ["b", 5]], columns=["group", "value"])
284 >>> h(g(f(df.groupby("group")), arg1=1), arg2=2, arg3=3) # doctest: +SKIP
286 You can write
288 >>> (
289 ... df.groupby("group").pipe(f).pipe(g, arg1=1).pipe(h, arg2=2, arg3=3)
290 ... ) # doctest: +SKIP
292 which is much more readable.
294 Parameters
295 ----------
296 func : callable or tuple of (callable, str)
297 Function to apply to this Resampler object or, alternatively,
298 a `(callable, data_keyword)` tuple where `data_keyword` is a
299 string indicating the keyword of `callable` that expects the
300 Resampler object.
301 *args : iterable, optional
302 Positional arguments passed into `func`.
303 **kwargs : dict, optional
304 A dictionary of keyword arguments passed into `func`.
306 Returns
307 -------
308 any
309 The result of applying ``func`` to the Resampler object.
311 See Also
312 --------
313 Series.pipe : Apply a function with arguments to a series.
314 DataFrame.pipe: Apply a function with arguments to a dataframe.
315 apply : Apply function to each group instead of to the
316 full Resampler object.
318 Notes
319 -----
320 See more `here
321 <https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#piping-function-calls>`_
323 Examples
324 --------
325 >>> df = pd.DataFrame(
326 ... {"A": [1, 2, 3, 4]}, index=pd.date_range("2012-08-02", periods=4)
327 ... )
328 >>> df
329 A
330 2012-08-02 1
331 2012-08-03 2
332 2012-08-04 3
333 2012-08-05 4
335 To get the difference between each 2-day period's maximum and minimum
336 value in one pass, you can do
338 >>> df.resample("2D").pipe(lambda x: x.max() - x.min())
339 A
340 2012-08-02 1
341 2012-08-04 1
342 """
343 return super().pipe(func, *args, **kwargs)
345 @final
346 def aggregate(self, func=None, *args, **kwargs):
347 """
348 Aggregate using one or more operations over the specified axis.
350 Parameters
351 ----------
352 func : function, str, list or dict
353 Function to use for aggregating the data. If a function, must either
354 work when passed a DataFrame or when passed to DataFrame.apply.
356 Accepted combinations are:
358 - function
359 - string function name
360 - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
361 - dict of axis labels -> functions, function names or list of such.
362 *args
363 Positional arguments to pass to `func`.
364 **kwargs
365 Keyword arguments to pass to `func`.
367 Returns
368 -------
369 scalar, Series or DataFrame
371 The return can be:
373 * scalar : when Series.agg is called with single function
374 * Series : when DataFrame.agg is called with a single function
375 * DataFrame : when DataFrame.agg is called with several functions
377 See Also
378 --------
379 DataFrame.groupby.aggregate : Aggregate using callable, string, dict,
380 or list of string/callables.
381 DataFrame.resample.transform : Transforms the Series on each group
382 based on the given function.
383 DataFrame.aggregate: Aggregate using one or more
384 operations over the specified axis.
386 Notes
387 -----
388 The aggregation operations are always performed over an axis, either the
389 index (default) or the column axis. This behavior is different from
390 `numpy` aggregation functions (`mean`, `median`, `prod`, `sum`, `std`,
391 `var`), where the default is to compute the aggregation of the flattened
392 array, e.g., ``numpy.mean(arr_2d)`` as opposed to
393 ``numpy.mean(arr_2d, axis=0)``.
395 `agg` is an alias for `aggregate`. Use the alias.
397 Functions that mutate the passed object can produce unexpected
398 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
399 for more details.
401 A passed user-defined-function will be passed a Series for evaluation.
403 If ``func`` defines an index relabeling, ``axis`` must be ``0`` or ``index``.
405 Examples
406 --------
407 >>> s = pd.Series(
408 ... [1, 2, 3, 4, 5], index=pd.date_range("20130101", periods=5, freq="s")
409 ... )
410 >>> s
411 2013-01-01 00:00:00 1
412 2013-01-01 00:00:01 2
413 2013-01-01 00:00:02 3
414 2013-01-01 00:00:03 4
415 2013-01-01 00:00:04 5
416 Freq: s, dtype: int64
418 >>> r = s.resample("2s")
420 >>> r.agg("sum")
421 2013-01-01 00:00:00 3
422 2013-01-01 00:00:02 7
423 2013-01-01 00:00:04 5
424 Freq: 2s, dtype: int64
426 >>> r.agg(["sum", "mean", "max"])
427 sum mean max
428 2013-01-01 00:00:00 3 1.5 2
429 2013-01-01 00:00:02 7 3.5 4
430 2013-01-01 00:00:04 5 5.0 5
432 >>> r.agg({"result": lambda x: x.mean() / x.std(), "total": "sum"})
433 result total
434 2013-01-01 00:00:00 2.121320 3
435 2013-01-01 00:00:02 4.949747 7
436 2013-01-01 00:00:04 NaN 5
438 >>> r.agg(average="mean", total="sum")
439 average total
440 2013-01-01 00:00:00 1.5 3
441 2013-01-01 00:00:02 3.5 7
442 2013-01-01 00:00:04 5.0 5
443 """
444 result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg()
445 if result is None:
446 how = func
447 result = self._groupby_and_aggregate(how, *args, **kwargs)
449 return result
451 agg = aggregate
452 apply = aggregate
454 @final
455 def transform(self, arg, *args, **kwargs):
456 """
457 Call function producing a like-indexed Series on each group.
459 Return a Series with the transformed values.
461 Parameters
462 ----------
463 arg : function
464 To apply to each group. Should return a Series with the same index.
465 *args, **kwargs
466 Additional arguments and keywords.
468 Returns
469 -------
470 Series
471 A Series with the transformed values, maintaining the same index as
472 the original object.
474 See Also
475 --------
476 core.resample.Resampler.apply : Apply a function along each group.
477 core.resample.Resampler.aggregate : Aggregate using one or more operations
478 over the specified axis.
480 Examples
481 --------
482 >>> s = pd.Series([1, 2], index=pd.date_range("20180101", periods=2, freq="1h"))
483 >>> s
484 2018-01-01 00:00:00 1
485 2018-01-01 01:00:00 2
486 Freq: h, dtype: int64
488 >>> resampled = s.resample("15min")
489 >>> resampled.transform(lambda x: (x - x.mean()) / x.std())
490 2018-01-01 00:00:00 NaN
491 2018-01-01 01:00:00 NaN
492 Freq: h, dtype: float64
493 """
494 return self._selected_obj.groupby(self._timegrouper).transform(
495 arg, *args, **kwargs
496 )
498 def _downsample(self, how, **kwargs):
499 raise AbstractMethodError(self)
501 def _upsample(self, f, limit: int | None = None, fill_value=None):
502 raise AbstractMethodError(self)
504 def _gotitem(self, key, ndim: int, subset=None):
505 """
506 Sub-classes to define. Return a sliced object.
508 Parameters
509 ----------
510 key : string / list of selections
511 ndim : {1, 2}
512 requested ndim of result
513 subset : object, default None
514 subset to act on
515 """
516 grouper = self._grouper
517 if subset is None:
518 subset = self.obj
519 if key is not None:
520 subset = subset[key]
521 else:
522 # reached via Apply.agg_dict_like with selection=None and ndim=1
523 assert subset.ndim == 1
524 if ndim == 1:
525 assert subset.ndim == 1
527 grouped = get_groupby(
528 subset, by=None, grouper=grouper, group_keys=self.group_keys
529 )
530 return grouped
532 def _groupby_and_aggregate(self, how, *args, **kwargs):
533 """
534 Re-evaluate the obj with a groupby aggregation.
535 """
536 grouper = self._grouper
538 # Excludes `on` column when provided
539 obj = self._obj_with_exclusions
541 grouped = get_groupby(obj, by=None, grouper=grouper, group_keys=self.group_keys)
543 try:
544 if callable(how):
545 # TODO: test_resample_apply_with_additional_args fails if we go
546 # through the non-lambda path, not clear that it should.
547 func = lambda x: how(x, *args, **kwargs)
548 result = grouped.aggregate(func)
549 else:
550 result = grouped.aggregate(how, *args, **kwargs)
551 except (AttributeError, KeyError):
552 # we have a non-reducing function; try to evaluate
553 # alternatively we want to evaluate only a column of the input
555 # test_apply_to_one_column_of_df the function being applied references
556 # a DataFrame column, but aggregate_item_by_item operates column-wise
557 # on Series, raising AttributeError or KeyError
558 # (depending on whether the column lookup uses getattr/__getitem__)
559 result = grouped.apply(how, *args, **kwargs)
561 except ValueError as err:
562 if "Must produce aggregated value" in str(err):
563 # raised in _aggregate_named
564 # see test_apply_without_aggregation, test_apply_with_mutated_index
565 pass
566 else:
567 raise
569 # we have a non-reducing function
570 # try to evaluate
571 result = grouped.apply(how, *args, **kwargs)
573 return self._wrap_result(result)
575 @final
576 def _get_resampler_for_grouping(
577 self,
578 groupby: GroupBy,
579 key,
580 ):
581 """
582 Return the correct class for resampling with groupby.
583 """
584 return self._resampler_for_grouping(
585 groupby=groupby,
586 key=key,
587 parent=self,
588 )
590 def _wrap_result(self, result):
591 """
592 Potentially wrap any results.
593 """
594 if isinstance(result, ABCSeries) and self._selection is not None:
595 result.name = self._selection
597 if isinstance(result, ABCSeries) and result.empty:
598 # When index is all NaT, result is empty but index is not
599 obj = self.obj
600 result.index = _asfreq_compat(obj.index[:0], freq=self.freq)
601 result.name = getattr(obj, "name", None)
603 if self._timegrouper._arrow_dtype is not None:
604 result.index = result.index.astype(self._timegrouper._arrow_dtype)
605 result.index.name = self.obj.index.name
607 return result
609 @final
610 def ffill(self, limit: int | None = None):
611 """
612 Forward fill the values.
614 This method fills missing values by propagating the last valid
615 observation forward, up to the next valid observation. It is commonly
616 used in time series analysis when resampling data to a higher frequency
617 (upsampling) and filling gaps in the resampled output.
619 Parameters
620 ----------
621 limit : int, optional
622 Limit of how many values to fill.
624 Returns
625 -------
626 Series
627 The resampled data with missing values filled forward.
629 See Also
630 --------
631 Series.fillna: Fill NA/NaN values using the specified method.
632 DataFrame.fillna: Fill NA/NaN values using the specified method.
634 Examples
635 --------
636 Here we only create a ``Series``.
638 >>> ser = pd.Series(
639 ... [1, 2, 3, 4],
640 ... index=pd.DatetimeIndex(
641 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
642 ... ),
643 ... )
644 >>> ser
645 2023-01-01 1
646 2023-01-15 2
647 2023-02-01 3
648 2023-02-15 4
649 dtype: int64
651 Example for ``ffill`` with downsampling (we have fewer dates after resampling):
653 >>> ser.resample("MS").ffill()
654 2023-01-01 1
655 2023-02-01 3
656 Freq: MS, dtype: int64
658 Example for ``ffill`` with upsampling (fill the new dates with
659 the previous value):
661 >>> ser.resample("W").ffill()
662 2023-01-01 1
663 2023-01-08 1
664 2023-01-15 2
665 2023-01-22 2
666 2023-01-29 2
667 2023-02-05 3
668 2023-02-12 3
669 2023-02-19 4
670 Freq: W-SUN, dtype: int64
672 With upsampling and limiting (only fill the first new date with the
673 previous value):
675 >>> ser.resample("W").ffill(limit=1)
676 2023-01-01 1.0
677 2023-01-08 1.0
678 2023-01-15 2.0
679 2023-01-22 2.0
680 2023-01-29 NaN
681 2023-02-05 3.0
682 2023-02-12 NaN
683 2023-02-19 4.0
684 Freq: W-SUN, dtype: float64
685 """
686 return self._upsample("ffill", limit=limit)
688 @final
689 def nearest(self, limit: int | None = None):
690 """
691 Resample by using the nearest value.
693 When resampling data, missing values may appear (e.g., when the
694 resampling frequency is higher than the original frequency).
695 The `nearest` method will replace ``NaN`` values that appeared in
696 the resampled data with the value from the nearest member of the
697 sequence, based on the index value.
698 Missing values that existed in the original data will not be modified.
699 If `limit` is given, fill only this many values in each direction for
700 each of the original values.
702 Parameters
703 ----------
704 limit : int, optional
705 Limit of how many values to fill.
707 Returns
708 -------
709 Series or DataFrame
710 An upsampled Series or DataFrame with ``NaN`` values filled with
711 their nearest value.
713 See Also
714 --------
715 bfill : Backward fill the new missing values in the resampled data.
716 ffill : Forward fill ``NaN`` values.
718 Examples
719 --------
720 >>> s = pd.Series([1, 2], index=pd.date_range("20180101", periods=2, freq="1h"))
721 >>> s
722 2018-01-01 00:00:00 1
723 2018-01-01 01:00:00 2
724 Freq: h, dtype: int64
726 >>> s.resample("15min").nearest()
727 2018-01-01 00:00:00 1
728 2018-01-01 00:15:00 1
729 2018-01-01 00:30:00 2
730 2018-01-01 00:45:00 2
731 2018-01-01 01:00:00 2
732 Freq: 15min, dtype: int64
734 Limit the number of upsampled values imputed by the nearest:
736 >>> s.resample("15min").nearest(limit=1)
737 2018-01-01 00:00:00 1.0
738 2018-01-01 00:15:00 1.0
739 2018-01-01 00:30:00 NaN
740 2018-01-01 00:45:00 2.0
741 2018-01-01 01:00:00 2.0
742 Freq: 15min, dtype: float64
743 """
744 return self._upsample("nearest", limit=limit)
746 @final
747 def bfill(self, limit: int | None = None):
748 """
749 Backward fill the new missing values in the resampled data.
751 In statistics, imputation is the process of replacing missing data with
752 substituted values [1]_. When resampling data, missing values may
753 appear (e.g., when the resampling frequency is higher than the original
754 frequency). The backward fill will replace NaN values that appeared in
755 the resampled data with the next value in the original sequence.
756 Missing values that existed in the original data will not be modified.
758 Parameters
759 ----------
760 limit : int, optional
761 Limit of how many values to fill.
763 Returns
764 -------
765 Series, DataFrame
766 An upsampled Series or DataFrame with backward filled NaN values.
768 See Also
769 --------
770 nearest : Fill NaN values with nearest neighbor starting from center.
771 ffill : Forward fill NaN values.
772 Series.fillna : Fill NaN values in the Series using the
773 specified method, which can be 'backfill'.
774 DataFrame.fillna : Fill NaN values in the DataFrame using the
775 specified method, which can be 'backfill'.
777 References
778 ----------
779 .. [1] https://en.wikipedia.org/wiki/Imputation_%28statistics%29
781 Examples
782 --------
783 Resampling a Series:
785 >>> s = pd.Series(
786 ... [1, 2, 3], index=pd.date_range("20180101", periods=3, freq="h")
787 ... )
788 >>> s
789 2018-01-01 00:00:00 1
790 2018-01-01 01:00:00 2
791 2018-01-01 02:00:00 3
792 Freq: h, dtype: int64
794 >>> s.resample("30min").bfill()
795 2018-01-01 00:00:00 1
796 2018-01-01 00:30:00 2
797 2018-01-01 01:00:00 2
798 2018-01-01 01:30:00 3
799 2018-01-01 02:00:00 3
800 Freq: 30min, dtype: int64
802 >>> s.resample("15min").bfill(limit=2)
803 2018-01-01 00:00:00 1.0
804 2018-01-01 00:15:00 NaN
805 2018-01-01 00:30:00 2.0
806 2018-01-01 00:45:00 2.0
807 2018-01-01 01:00:00 2.0
808 2018-01-01 01:15:00 NaN
809 2018-01-01 01:30:00 3.0
810 2018-01-01 01:45:00 3.0
811 2018-01-01 02:00:00 3.0
812 Freq: 15min, dtype: float64
814 Resampling a DataFrame that has missing values:
816 >>> df = pd.DataFrame(
817 ... {"a": [2, np.nan, 6], "b": [1, 3, 5]},
818 ... index=pd.date_range("20180101", periods=3, freq="h"),
819 ... )
820 >>> df
821 a b
822 2018-01-01 00:00:00 2.0 1
823 2018-01-01 01:00:00 NaN 3
824 2018-01-01 02:00:00 6.0 5
826 >>> df.resample("30min").bfill()
827 a b
828 2018-01-01 00:00:00 2.0 1
829 2018-01-01 00:30:00 NaN 3
830 2018-01-01 01:00:00 NaN 3
831 2018-01-01 01:30:00 6.0 5
832 2018-01-01 02:00:00 6.0 5
834 >>> df.resample("15min").bfill(limit=2)
835 a b
836 2018-01-01 00:00:00 2.0 1.0
837 2018-01-01 00:15:00 NaN NaN
838 2018-01-01 00:30:00 NaN 3.0
839 2018-01-01 00:45:00 NaN 3.0
840 2018-01-01 01:00:00 NaN 3.0
841 2018-01-01 01:15:00 NaN NaN
842 2018-01-01 01:30:00 6.0 5.0
843 2018-01-01 01:45:00 6.0 5.0
844 2018-01-01 02:00:00 6.0 5.0
845 """
846 return self._upsample("bfill", limit=limit)
848 @final
849 def interpolate(
850 self,
851 method: InterpolateOptions = "linear",
852 *,
853 axis: Axis = 0,
854 limit: int | None = None,
855 limit_direction: Literal["forward", "backward", "both"] = "forward",
856 limit_area=None,
857 **kwargs,
858 ):
859 """
860 Interpolate values between target timestamps according to different methods.
862 The original index is first reindexed to target timestamps
863 (see :meth:`core.resample.Resampler.asfreq`),
864 then the interpolation of ``NaN`` values via :meth:`DataFrame.interpolate`
865 happens.
867 Parameters
868 ----------
869 method : str, default 'linear'
870 Interpolation technique to use. One of:
872 * 'linear': Ignore the index and treat the values as equally
873 spaced. This is the only method supported on MultiIndexes.
874 * 'time': Works on daily and higher resolution data to interpolate
875 given length of interval.
876 * 'index', 'values': use the actual numerical values of the index.
877 * 'pad': Fill in NaNs using existing values.
878 * 'nearest', 'zero', 'slinear', 'quadratic', 'cubic',
879 'barycentric', 'polynomial': Passed to
880 `scipy.interpolate.interp1d`, whereas 'spline' is passed to
881 `scipy.interpolate.UnivariateSpline`. These methods use the numerical
882 values of the index. Both 'polynomial' and 'spline' require that
883 you also specify an `order` (int), e.g.
884 ``df.interpolate(method='polynomial', order=5)``. Note that,
885 `slinear` method in Pandas refers to the Scipy first order `spline`
886 instead of Pandas first order `spline`.
887 * 'krogh', 'piecewise_polynomial', 'spline', 'pchip', 'akima',
888 'cubicspline': Wrappers around the SciPy interpolation methods of
889 similar names. See `Notes`.
890 * 'from_derivatives': Refers to
891 `scipy.interpolate.BPoly.from_derivatives`.
893 axis : {0 or 'index', 1 or 'columns', None}, default None
894 Axis to interpolate along. For `Series` this parameter is unused
895 and defaults to 0.
896 limit : int, optional
897 Maximum number of consecutive NaNs to fill. Must be greater than
898 0.
899 limit_direction : {'forward', 'backward', 'both'}, Optional
900 Consecutive NaNs will be filled in this direction.
902 limit_area : {`None`, 'inside', 'outside'}, default None
903 If limit is specified, consecutive NaNs will be filled with this
904 restriction.
906 * ``None``: No fill restriction.
907 * 'inside': Only fill NaNs surrounded by valid values
908 (interpolate).
909 * 'outside': Only fill NaNs outside valid values (extrapolate).
911 **kwargs : optional
912 Keyword arguments to pass on to the interpolating function.
914 Returns
915 -------
916 DataFrame or Series
917 Interpolated values at the specified freq.
919 See Also
920 --------
921 core.resample.Resampler.asfreq: Return the values at the new freq,
922 essentially a reindex.
923 DataFrame.interpolate: Fill NaN values using an interpolation method.
924 DataFrame.bfill : Backward fill NaN values in the resampled data.
925 DataFrame.ffill : Forward fill NaN values.
927 Notes
928 -----
929 For high-frequent or non-equidistant time-series with timestamps
930 the reindexing followed by interpolation may lead to information loss
931 as shown in the last example.
933 Examples
934 --------
936 >>> start = "2023-03-01T07:00:00"
937 >>> timesteps = pd.date_range(start, periods=5, freq="s")
938 >>> series = pd.Series(data=[1, -1, 2, 1, 3], index=timesteps)
939 >>> series
940 2023-03-01 07:00:00 1
941 2023-03-01 07:00:01 -1
942 2023-03-01 07:00:02 2
943 2023-03-01 07:00:03 1
944 2023-03-01 07:00:04 3
945 Freq: s, dtype: int64
947 Downsample the dataframe to 0.5Hz by providing the period time of 2s.
949 >>> series.resample("2s").interpolate("linear")
950 2023-03-01 07:00:00 1
951 2023-03-01 07:00:02 2
952 2023-03-01 07:00:04 3
953 Freq: 2s, dtype: int64
955 Upsample the dataframe to 2Hz by providing the period time of 500ms.
957 >>> series.resample("500ms").interpolate("linear")
958 2023-03-01 07:00:00.000 1.0
959 2023-03-01 07:00:00.500 0.0
960 2023-03-01 07:00:01.000 -1.0
961 2023-03-01 07:00:01.500 0.5
962 2023-03-01 07:00:02.000 2.0
963 2023-03-01 07:00:02.500 1.5
964 2023-03-01 07:00:03.000 1.0
965 2023-03-01 07:00:03.500 2.0
966 2023-03-01 07:00:04.000 3.0
967 Freq: 500ms, dtype: float64
969 Internal reindexing with ``asfreq()`` prior to interpolation leads to
970 an interpolated timeseries on the basis of the reindexed timestamps
971 (anchors). It is assured that all available datapoints from original
972 series become anchors, so it also works for resampling-cases that lead
973 to non-aligned timestamps, as in the following example:
975 >>> series.resample("400ms").interpolate("linear")
976 2023-03-01 07:00:00.000 1.000000
977 2023-03-01 07:00:00.400 0.333333
978 2023-03-01 07:00:00.800 -0.333333
979 2023-03-01 07:00:01.200 0.000000
980 2023-03-01 07:00:01.600 1.000000
981 2023-03-01 07:00:02.000 2.000000
982 2023-03-01 07:00:02.400 1.666667
983 2023-03-01 07:00:02.800 1.333333
984 2023-03-01 07:00:03.200 1.666667
985 2023-03-01 07:00:03.600 2.333333
986 2023-03-01 07:00:04.000 3.000000
987 Freq: 400ms, dtype: float64
989 Note that the series correctly decreases between two anchors
990 ``07:00:00`` and ``07:00:02``.
991 """
992 if "inplace" in kwargs:
993 # GH#58690
994 warnings.warn(
995 f"The 'inplace' keyword in {type(self).__name__}.interpolate "
996 "is deprecated and will be removed in a future version. "
997 "resample(...).interpolate is never inplace.",
998 Pandas4Warning,
999 stacklevel=find_stack_level(),
1000 )
1001 inplace = kwargs.pop("inplace")
1002 if inplace:
1003 raise ValueError("Cannot interpolate inplace on a resampled object.")
1005 result = self._upsample("asfreq")
1007 # If the original data has timestamps which are not aligned with the
1008 # target timestamps, we need to add those points back to the data frame
1009 # that is supposed to be interpolated. This does not work with
1010 # PeriodIndex, so we skip this case. GH#21351
1011 obj = self._selected_obj
1012 is_period_index = isinstance(obj.index, PeriodIndex)
1014 # Skip this step for PeriodIndex
1015 if not is_period_index:
1016 final_index = result.index
1017 if isinstance(final_index, MultiIndex):
1018 raise NotImplementedError(
1019 "Direct interpolation of MultiIndex data frames is not "
1020 "supported. If you tried to resample and interpolate on a "
1021 "grouped data frame, please use:\n"
1022 "`df.groupby(...).apply(lambda x: x.resample(...)."
1023 "interpolate(...))`"
1024 "\ninstead, as resampling and interpolation has to be "
1025 "performed for each group independently."
1026 )
1028 missing_data_points_index = obj.index.difference(final_index)
1029 if len(missing_data_points_index) > 0:
1030 result = concat(
1031 [result, obj.loc[missing_data_points_index]]
1032 ).sort_index()
1034 result_interpolated = result.interpolate(
1035 method=method,
1036 axis=axis,
1037 limit=limit,
1038 inplace=False,
1039 limit_direction=limit_direction,
1040 limit_area=limit_area,
1041 **kwargs,
1042 )
1044 # No further steps if the original data has a PeriodIndex
1045 if is_period_index:
1046 return result_interpolated
1048 # Make sure that original data points which do not align with the
1049 # resampled index are removed
1050 result_interpolated = result_interpolated.loc[final_index]
1052 # Make sure frequency indexes are preserved
1053 result_interpolated.index = final_index
1054 return result_interpolated
1056 @final
1057 def asfreq(self, fill_value=None):
1058 """
1059 Return the values at the new freq, essentially a reindex.
1061 Parameters
1062 ----------
1063 fill_value : scalar, optional
1064 Value to use for missing values, applied during upsampling (note
1065 this does not fill NaNs that already were present).
1067 Returns
1068 -------
1069 DataFrame or Series
1070 Values at the specified freq.
1072 See Also
1073 --------
1074 Series.asfreq: Convert TimeSeries to specified frequency.
1075 DataFrame.asfreq: Convert TimeSeries to specified frequency.
1077 Examples
1078 --------
1080 >>> ser = pd.Series(
1081 ... [1, 2, 3, 4],
1082 ... index=pd.DatetimeIndex(
1083 ... ["2023-01-01", "2023-01-31", "2023-02-01", "2023-02-28"]
1084 ... ),
1085 ... )
1086 >>> ser
1087 2023-01-01 1
1088 2023-01-31 2
1089 2023-02-01 3
1090 2023-02-28 4
1091 dtype: int64
1092 >>> ser.resample("MS").asfreq()
1093 2023-01-01 1
1094 2023-02-01 3
1095 Freq: MS, dtype: int64
1096 """
1097 return self._upsample("asfreq", fill_value=fill_value)
1099 @final
1100 def sum(
1101 self,
1102 numeric_only: bool = False,
1103 min_count: int = 0,
1104 ):
1105 """
1106 Compute sum of group values.
1108 This method provides a simple way to compute the sum of values within each
1109 resampled group, particularly useful for aggregating time-based data into
1110 daily, monthly, or yearly sums.
1112 Parameters
1113 ----------
1114 numeric_only : bool, default False
1115 Include only float, int, boolean columns.
1117 .. versionchanged:: 2.0.0
1119 numeric_only no longer accepts ``None``.
1121 min_count : int, default 0
1122 The required number of valid values to perform the operation. If fewer
1123 than ``min_count`` non-NA values are present the result will be NA.
1125 Returns
1126 -------
1127 Series or DataFrame
1128 Computed sum of values within each group.
1130 See Also
1131 --------
1132 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1133 core.resample.Resampler.count : Compute count of group, excluding missing
1134 values.
1135 DataFrame.resample : Resample time-series data.
1136 Series.sum : Return the sum of the values over the requested axis.
1138 Examples
1139 --------
1140 >>> ser = pd.Series(
1141 ... [1, 2, 3, 4],
1142 ... index=pd.DatetimeIndex(
1143 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1144 ... ),
1145 ... )
1146 >>> ser
1147 2023-01-01 1
1148 2023-01-15 2
1149 2023-02-01 3
1150 2023-02-15 4
1151 dtype: int64
1152 >>> ser.resample("MS").sum()
1153 2023-01-01 3
1154 2023-02-01 7
1155 Freq: MS, dtype: int64
1156 """
1157 return self._downsample("sum", numeric_only=numeric_only, min_count=min_count)
1159 @final
1160 def prod(
1161 self,
1162 numeric_only: bool = False,
1163 min_count: int = 0,
1164 ):
1165 """
1166 Compute prod of group values.
1168 Parameters
1169 ----------
1170 numeric_only : bool, default False
1171 Include only float, int, boolean columns.
1173 .. versionchanged:: 2.0.0
1175 numeric_only no longer accepts ``None``.
1177 min_count : int, default 0
1178 The required number of valid values to perform the operation. If fewer
1179 than ``min_count`` non-NA values are present the result will be NA.
1181 Returns
1182 -------
1183 Series or DataFrame
1184 Computed prod of values within each group.
1186 See Also
1187 --------
1188 core.resample.Resampler.sum : Compute sum of groups, excluding missing values.
1189 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1190 core.resample.Resampler.median : Compute median of groups, excluding missing
1191 values.
1193 Examples
1194 --------
1195 >>> ser = pd.Series(
1196 ... [1, 2, 3, 4],
1197 ... index=pd.DatetimeIndex(
1198 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1199 ... ),
1200 ... )
1201 >>> ser
1202 2023-01-01 1
1203 2023-01-15 2
1204 2023-02-01 3
1205 2023-02-15 4
1206 dtype: int64
1207 >>> ser.resample("MS").prod()
1208 2023-01-01 2
1209 2023-02-01 12
1210 Freq: MS, dtype: int64
1211 """
1212 return self._downsample("prod", numeric_only=numeric_only, min_count=min_count)
1214 @final
1215 def min(
1216 self,
1217 numeric_only: bool = False,
1218 min_count: int = 0,
1219 ):
1220 """
1221 Compute min value of group.
1223 Parameters
1224 ----------
1225 numeric_only : bool, default False
1226 Include only float, int, boolean columns.
1228 .. versionchanged:: 2.0.0
1230 numeric_only no longer accepts ``None``.
1232 min_count : int, default 0
1233 The required number of valid values to perform the operation. If fewer
1234 than ``min_count`` non-NA values are present the result will be NA.
1236 Returns
1237 -------
1238 Series or DataFrame
1239 Compute the minimum value in the given Series or DataFrame.
1241 See Also
1242 --------
1243 core.resample.Resampler.max : Compute max value of group.
1244 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1245 core.resample.Resampler.median : Compute median of groups, excluding missing
1246 values.
1248 Examples
1249 --------
1250 >>> ser = pd.Series(
1251 ... [1, 2, 3, 4],
1252 ... index=pd.DatetimeIndex(
1253 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1254 ... ),
1255 ... )
1256 >>> ser
1257 2023-01-01 1
1258 2023-01-15 2
1259 2023-02-01 3
1260 2023-02-15 4
1261 dtype: int64
1262 >>> ser.resample("MS").min()
1263 2023-01-01 1
1264 2023-02-01 3
1265 Freq: MS, dtype: int64
1266 """
1267 return self._downsample("min", numeric_only=numeric_only, min_count=min_count)
1269 @final
1270 def max(
1271 self,
1272 numeric_only: bool = False,
1273 min_count: int = 0,
1274 ):
1275 """
1276 Compute max value of group.
1278 Parameters
1279 ----------
1280 numeric_only : bool, default False
1281 Include only float, int, boolean columns.
1283 .. versionchanged:: 2.0.0
1285 numeric_only no longer accepts ``None``.
1287 min_count : int, default 0
1288 The required number of valid values to perform the operation. If fewer
1289 than ``min_count`` non-NA values are present the result will be NA.
1291 Returns
1292 -------
1293 Series or DataFrame
1294 Computes the maximum value in the given Series or Dataframe.
1296 See Also
1297 --------
1298 core.resample.Resampler.min : Compute min value of group.
1299 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1300 core.resample.Resampler.median : Compute median of groups, excluding missing
1301 values.
1303 Examples
1304 --------
1305 >>> ser = pd.Series(
1306 ... [1, 2, 3, 4],
1307 ... index=pd.DatetimeIndex(
1308 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1309 ... ),
1310 ... )
1311 >>> ser
1312 2023-01-01 1
1313 2023-01-15 2
1314 2023-02-01 3
1315 2023-02-15 4
1316 dtype: int64
1317 >>> ser.resample("MS").max()
1318 2023-01-01 2
1319 2023-02-01 4
1320 Freq: MS, dtype: int64
1321 """
1322 return self._downsample("max", numeric_only=numeric_only, min_count=min_count)
1324 @final
1325 def first(
1326 self,
1327 numeric_only: bool = False,
1328 min_count: int = 0,
1329 skipna: bool = True,
1330 ):
1331 """
1332 Compute the first non-null entry of each column.
1334 Parameters
1335 ----------
1336 numeric_only : bool, default False
1337 Include only float, int, boolean columns.
1338 min_count : int, default 0
1339 The required number of valid values to perform the operation. If fewer
1340 than ``min_count`` non-NA values are present the result will be NA.
1341 skipna : bool, default True
1342 Exclude NA/null values. If an entire group is NA, the result will be NA.
1344 Returns
1345 -------
1346 Series or DataFrame
1347 First values within each group.
1349 See Also
1350 --------
1351 core.resample.Resampler.last : Compute the last non-null value in each group.
1352 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1354 Examples
1355 --------
1356 >>> s = pd.Series(
1357 ... [1, 2, 3, 4],
1358 ... index=pd.DatetimeIndex(
1359 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1360 ... ),
1361 ... )
1362 >>> s
1363 2023-01-01 1
1364 2023-01-15 2
1365 2023-02-01 3
1366 2023-02-15 4
1367 dtype: int64
1368 >>> s.resample("MS").first()
1369 2023-01-01 1
1370 2023-02-01 3
1371 Freq: MS, dtype: int64
1372 """
1373 return self._downsample(
1374 "first", numeric_only=numeric_only, min_count=min_count, skipna=skipna
1375 )
1377 @final
1378 def last(
1379 self,
1380 numeric_only: bool = False,
1381 min_count: int = 0,
1382 skipna: bool = True,
1383 ):
1384 """
1385 Compute the last non-null entry of each column.
1387 Parameters
1388 ----------
1389 numeric_only : bool, default False
1390 Include only float, int, boolean columns.
1391 min_count : int, default 0
1392 The required number of valid values to perform the operation. If fewer
1393 than ``min_count`` non-NA values are present the result will be NA.
1394 skipna : bool, default True
1395 Exclude NA/null values. If an entire group is NA, the result will be NA.
1397 Returns
1398 -------
1399 Series or DataFrame
1400 Last of values within each group.
1402 See Also
1403 --------
1404 core.resample.Resampler.first : Compute the first non-null value in each group.
1405 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1407 Examples
1408 --------
1409 >>> s = pd.Series(
1410 ... [1, 2, 3, 4],
1411 ... index=pd.DatetimeIndex(
1412 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1413 ... ),
1414 ... )
1415 >>> s
1416 2023-01-01 1
1417 2023-01-15 2
1418 2023-02-01 3
1419 2023-02-15 4
1420 dtype: int64
1421 >>> s.resample("MS").last()
1422 2023-01-01 2
1423 2023-02-01 4
1424 Freq: MS, dtype: int64
1425 """
1426 return self._downsample(
1427 "last", numeric_only=numeric_only, min_count=min_count, skipna=skipna
1428 )
1430 @final
1431 def median(self, numeric_only: bool = False):
1432 """
1433 Compute median of groups, excluding missing values.
1435 For multiple groupings, the result index will be a MultiIndex
1437 Parameters
1438 ----------
1439 numeric_only : bool, default False
1440 Include only float, int, boolean columns.
1442 .. versionchanged:: 2.0.0
1444 numeric_only no longer accepts ``None`` and defaults to False.
1446 Returns
1447 -------
1448 Series or DataFrame
1449 Median of values within each group.
1451 See Also
1452 --------
1453 Series.groupby : Apply a function groupby to a Series.
1454 DataFrame.groupby : Apply a function groupby to each row or column of a
1455 DataFrame.
1457 Examples
1458 --------
1460 >>> ser = pd.Series(
1461 ... [1, 2, 3, 3, 4, 5],
1462 ... index=pd.DatetimeIndex(
1463 ... [
1464 ... "2023-01-01",
1465 ... "2023-01-10",
1466 ... "2023-01-15",
1467 ... "2023-02-01",
1468 ... "2023-02-10",
1469 ... "2023-02-15",
1470 ... ]
1471 ... ),
1472 ... )
1473 >>> ser.resample("MS").median()
1474 2023-01-01 2.0
1475 2023-02-01 4.0
1476 Freq: MS, dtype: float64
1477 """
1478 return self._downsample("median", numeric_only=numeric_only)
1480 @final
1481 def mean(
1482 self,
1483 numeric_only: bool = False,
1484 ):
1485 """
1486 Compute mean of groups, excluding missing values.
1488 Parameters
1489 ----------
1490 numeric_only : bool, default False
1491 Include only `float`, `int` or `boolean` data.
1493 .. versionchanged:: 2.0.0
1495 numeric_only now defaults to ``False``.
1497 Returns
1498 -------
1499 DataFrame or Series
1500 Mean of values within each group.
1502 See Also
1503 --------
1504 core.resample.Resampler.median : Compute median of groups, excluding missing
1505 values.
1506 core.resample.Resampler.sum : Compute sum of groups, excluding missing values.
1507 core.resample.Resampler.std : Compute standard deviation of groups, excluding
1508 missing values.
1509 core.resample.Resampler.var : Compute variance of groups, excluding missing
1510 values.
1512 Examples
1513 --------
1515 >>> ser = pd.Series(
1516 ... [1, 2, 3, 4],
1517 ... index=pd.DatetimeIndex(
1518 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1519 ... ),
1520 ... )
1521 >>> ser
1522 2023-01-01 1
1523 2023-01-15 2
1524 2023-02-01 3
1525 2023-02-15 4
1526 dtype: int64
1527 >>> ser.resample("MS").mean()
1528 2023-01-01 1.5
1529 2023-02-01 3.5
1530 Freq: MS, dtype: float64
1531 """
1532 return self._downsample("mean", numeric_only=numeric_only)
1534 @final
1535 def std(
1536 self,
1537 ddof: int = 1,
1538 numeric_only: bool = False,
1539 ):
1540 """
1541 Compute standard deviation of groups, excluding missing values.
1543 Parameters
1544 ----------
1545 ddof : int, default 1
1546 Degrees of freedom.
1547 numeric_only : bool, default False
1548 Include only `float`, `int` or `boolean` data.
1550 .. versionchanged:: 2.0.0
1552 numeric_only now defaults to ``False``.
1554 Returns
1555 -------
1556 DataFrame or Series
1557 Standard deviation of values within each group.
1559 See Also
1560 --------
1561 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1562 core.resample.Resampler.median : Compute median of groups, excluding missing
1563 values.
1564 core.resample.Resampler.var : Compute variance of groups, excluding missing
1565 values.
1567 Examples
1568 --------
1570 >>> ser = pd.Series(
1571 ... [1, 3, 2, 4, 3, 8],
1572 ... index=pd.DatetimeIndex(
1573 ... [
1574 ... "2023-01-01",
1575 ... "2023-01-10",
1576 ... "2023-01-15",
1577 ... "2023-02-01",
1578 ... "2023-02-10",
1579 ... "2023-02-15",
1580 ... ]
1581 ... ),
1582 ... )
1583 >>> ser.resample("MS").std()
1584 2023-01-01 1.000000
1585 2023-02-01 2.645751
1586 Freq: MS, dtype: float64
1587 """
1588 return self._downsample("std", ddof=ddof, numeric_only=numeric_only)
1590 @final
1591 def var(
1592 self,
1593 ddof: int = 1,
1594 numeric_only: bool = False,
1595 ):
1596 """
1597 Compute variance of groups, excluding missing values.
1599 Parameters
1600 ----------
1601 ddof : int, default 1
1602 Degrees of freedom.
1604 numeric_only : bool, default False
1605 Include only `float`, `int` or `boolean` data.
1607 .. versionchanged:: 2.0.0
1609 numeric_only now defaults to ``False``.
1611 Returns
1612 -------
1613 DataFrame or Series
1614 Variance of values within each group.
1616 See Also
1617 --------
1618 core.resample.Resampler.std : Compute standard deviation of groups, excluding
1619 missing values.
1620 core.resample.Resampler.mean : Compute mean of groups, excluding missing values.
1621 core.resample.Resampler.median : Compute median of groups, excluding missing
1622 values.
1624 Examples
1625 --------
1627 >>> ser = pd.Series(
1628 ... [1, 3, 2, 4, 3, 8],
1629 ... index=pd.DatetimeIndex(
1630 ... [
1631 ... "2023-01-01",
1632 ... "2023-01-10",
1633 ... "2023-01-15",
1634 ... "2023-02-01",
1635 ... "2023-02-10",
1636 ... "2023-02-15",
1637 ... ]
1638 ... ),
1639 ... )
1640 >>> ser.resample("MS").var()
1641 2023-01-01 1.0
1642 2023-02-01 7.0
1643 Freq: MS, dtype: float64
1645 >>> ser.resample("MS").var(ddof=0)
1646 2023-01-01 0.666667
1647 2023-02-01 4.666667
1648 Freq: MS, dtype: float64
1649 """
1650 return self._downsample("var", ddof=ddof, numeric_only=numeric_only)
1652 @final
1653 def sem(
1654 self,
1655 ddof: int = 1,
1656 numeric_only: bool = False,
1657 ):
1658 """
1659 Compute standard error of the mean of groups, excluding missing values.
1661 For multiple groupings, the result index will be a MultiIndex.
1663 Parameters
1664 ----------
1665 ddof : int, default 1
1666 Degrees of freedom.
1668 numeric_only : bool, default False
1669 Include only `float`, `int` or `boolean` data.
1671 .. versionchanged:: 2.0.0
1673 numeric_only now defaults to ``False``.
1675 Returns
1676 -------
1677 Series or DataFrame
1678 Standard error of the mean of values within each group.
1680 See Also
1681 --------
1682 DataFrame.sem : Return unbiased standard error of the mean over requested axis.
1683 Series.sem : Return unbiased standard error of the mean over requested axis.
1685 Examples
1686 --------
1688 >>> ser = pd.Series(
1689 ... [1, 3, 2, 4, 3, 8],
1690 ... index=pd.DatetimeIndex(
1691 ... [
1692 ... "2023-01-01",
1693 ... "2023-01-10",
1694 ... "2023-01-15",
1695 ... "2023-02-01",
1696 ... "2023-02-10",
1697 ... "2023-02-15",
1698 ... ]
1699 ... ),
1700 ... )
1701 >>> ser.resample("MS").sem()
1702 2023-01-01 0.577350
1703 2023-02-01 1.527525
1704 Freq: MS, dtype: float64
1705 """
1706 return self._downsample("sem", ddof=ddof, numeric_only=numeric_only)
1708 @final
1709 def ohlc(self):
1710 """
1711 Compute open, high, low and close values of a group, excluding missing values.
1713 Returns
1714 -------
1715 DataFrame
1716 Open, high, low and close values within each group.
1718 See Also
1719 --------
1720 DataFrame.agg : Aggregate using one or more operations over the specified axis.
1721 DataFrame.resample : Resample time-series data.
1722 DataFrame.groupby : Group DataFrame using a mapper or by a Series of columns.
1724 Examples
1725 --------
1726 >>> ser = pd.Series(
1727 ... [1, 3, 2, 4, 3, 5],
1728 ... index=pd.DatetimeIndex(
1729 ... [
1730 ... "2023-01-01",
1731 ... "2023-01-10",
1732 ... "2023-01-15",
1733 ... "2023-02-01",
1734 ... "2023-02-10",
1735 ... "2023-02-15",
1736 ... ]
1737 ... ),
1738 ... )
1739 >>> ser.resample("MS").ohlc()
1740 open high low close
1741 2023-01-01 1 3 1 2
1742 2023-02-01 4 5 3 5
1743 """
1744 ax = self.ax
1745 obj = self._obj_with_exclusions
1746 if len(ax) == 0:
1747 # GH#42902
1748 obj = obj.copy()
1749 obj.index = _asfreq_compat(obj.index, self.freq)
1750 if obj.ndim == 1:
1751 obj = obj.to_frame()
1752 obj = obj.reindex(["open", "high", "low", "close"], axis=1)
1753 else:
1754 mi = MultiIndex.from_product(
1755 [obj.columns, ["open", "high", "low", "close"]]
1756 )
1757 obj = obj.reindex(mi, axis=1)
1758 return obj
1760 return self._downsample("ohlc")
1762 @final
1763 def nunique(self):
1764 """
1765 Return number of unique elements in the group.
1767 Returns
1768 -------
1769 Series
1770 Number of unique values within each group.
1772 See Also
1773 --------
1774 core.groupby.SeriesGroupBy.nunique : Method nunique for SeriesGroupBy.
1776 Examples
1777 --------
1778 >>> ser = pd.Series(
1779 ... [1, 2, 3, 3],
1780 ... index=pd.DatetimeIndex(
1781 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1782 ... ),
1783 ... )
1784 >>> ser
1785 2023-01-01 1
1786 2023-01-15 2
1787 2023-02-01 3
1788 2023-02-15 3
1789 dtype: int64
1790 >>> ser.resample("MS").nunique()
1791 2023-01-01 2
1792 2023-02-01 1
1793 Freq: MS, dtype: int64
1794 """
1795 return self._downsample("nunique")
1797 @final
1798 def size(self):
1799 """
1800 Compute group sizes.
1802 Returns
1803 -------
1804 Series
1805 Number of rows in each group.
1807 See Also
1808 --------
1809 Series.groupby : Apply a function groupby to a Series.
1810 DataFrame.groupby : Apply a function groupby to each row
1811 or column of a DataFrame.
1813 Examples
1814 --------
1815 >>> ser = pd.Series(
1816 ... [1, 2, 3],
1817 ... index=pd.DatetimeIndex(["2023-01-01", "2023-01-15", "2023-02-01"]),
1818 ... )
1819 >>> ser
1820 2023-01-01 1
1821 2023-01-15 2
1822 2023-02-01 3
1823 dtype: int64
1824 >>> ser.resample("MS").size()
1825 2023-01-01 2
1826 2023-02-01 1
1827 Freq: MS, dtype: int64
1828 """
1829 result = self._downsample("size")
1831 # If the result is a non-empty DataFrame we stack to get a Series
1832 # GH 46826
1833 if isinstance(result, ABCDataFrame) and not result.empty:
1834 result = result.stack()
1836 if not len(self.ax):
1837 from pandas import Series
1839 if self._selected_obj.ndim == 1:
1840 name = self._selected_obj.name
1841 else:
1842 name = None
1843 result = Series([], index=result.index, dtype="int64", name=name)
1844 return result
1846 @final
1847 def count(self):
1848 """
1849 Compute count of group, excluding missing values.
1851 Returns
1852 -------
1853 Series or DataFrame
1854 Count of values within each group.
1856 See Also
1857 --------
1858 Series.groupby : Apply a function groupby to a Series.
1859 DataFrame.groupby : Apply a function groupby to each row
1860 or column of a DataFrame.
1862 Examples
1863 --------
1864 >>> ser = pd.Series(
1865 ... [1, 2, 3, 4],
1866 ... index=pd.DatetimeIndex(
1867 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1868 ... ),
1869 ... )
1870 >>> ser
1871 2023-01-01 1
1872 2023-01-15 2
1873 2023-02-01 3
1874 2023-02-15 4
1875 dtype: int64
1876 >>> ser.resample("MS").count()
1877 2023-01-01 2
1878 2023-02-01 2
1879 Freq: MS, dtype: int64
1880 """
1881 result = self._downsample("count")
1882 if not len(self.ax):
1883 if self._selected_obj.ndim == 1:
1884 result = type(self._selected_obj)(
1885 [], index=result.index, dtype="int64", name=self._selected_obj.name
1886 )
1887 else:
1888 from pandas import DataFrame
1890 result = DataFrame(
1891 [], index=result.index, columns=result.columns, dtype="int64"
1892 )
1894 return result
1896 @final
1897 def quantile(self, q: float | list[float] | AnyArrayLike = 0.5, **kwargs):
1898 """
1899 Return value at the given quantile.
1901 Computes the quantile of values within each resampled group.
1903 Parameters
1904 ----------
1905 q : float or array-like, default 0.5 (50% quantile)
1906 Value between 0 <= q <= 1, the quantile(s) to compute.
1907 **kwargs
1908 Additional keyword arguments to be passed to the function.
1910 Returns
1911 -------
1912 DataFrame or Series
1913 Quantile of values within each group.
1915 See Also
1916 --------
1917 Series.quantile
1918 Return a series, where the index is q and the values are the quantiles.
1919 DataFrame.quantile
1920 Return a DataFrame, where the columns are the columns of self,
1921 and the values are the quantiles.
1922 DataFrameGroupBy.quantile
1923 Return a DataFrame, where the columns are groupby columns,
1924 and the values are its quantiles.
1926 Examples
1927 --------
1929 >>> ser = pd.Series(
1930 ... [1, 3, 2, 4, 3, 8],
1931 ... index=pd.DatetimeIndex(
1932 ... [
1933 ... "2023-01-01",
1934 ... "2023-01-10",
1935 ... "2023-01-15",
1936 ... "2023-02-01",
1937 ... "2023-02-10",
1938 ... "2023-02-15",
1939 ... ]
1940 ... ),
1941 ... )
1942 >>> ser.resample("MS").quantile()
1943 2023-01-01 2.0
1944 2023-02-01 4.0
1945 Freq: MS, dtype: float64
1947 >>> ser.resample("MS").quantile(0.25)
1948 2023-01-01 1.5
1949 2023-02-01 3.5
1950 Freq: MS, dtype: float64
1951 """
1952 return self._downsample("quantile", q=q, **kwargs)
1955class _GroupByMixin(PandasObject, SelectionMixin):
1956 """
1957 Provide the groupby facilities.
1958 """
1960 _attributes: list[str] # in practice the same as Resampler._attributes
1961 _selection: IndexLabel | None = None
1962 _groupby: GroupBy
1963 _timegrouper: TimeGrouper
1965 def __init__(
1966 self,
1967 *,
1968 parent: Resampler,
1969 groupby: GroupBy,
1970 key=None,
1971 selection: IndexLabel | None = None,
1972 ) -> None:
1973 # reached via ._gotitem and _get_resampler_for_grouping
1975 assert isinstance(groupby, GroupBy), type(groupby)
1977 # parent is always a Resampler, sometimes a _GroupByMixin
1978 assert isinstance(parent, Resampler), type(parent)
1980 # initialize our GroupByMixin object with
1981 # the resampler attributes
1982 for attr in self._attributes:
1983 setattr(self, attr, getattr(parent, attr))
1984 self._selection = selection
1986 self.binner = parent.binner
1987 self.key = key
1989 self._groupby = groupby
1990 self._timegrouper = copy.copy(parent._timegrouper)
1992 self.ax = parent.ax
1993 self.obj = parent.obj
1995 @no_type_check
1996 def _apply(self, f, *args, **kwargs):
1997 """
1998 Dispatch to _upsample; we are stripping all of the _upsample kwargs and
1999 performing the original function call on the grouped object.
2000 """
2002 def func(x):
2003 x = self._resampler_cls(x, timegrouper=self._timegrouper, gpr_index=self.ax)
2005 if isinstance(f, str):
2006 return getattr(x, f)(**kwargs)
2008 return x.apply(f, *args, **kwargs)
2010 result = self._groupby.apply(func)
2012 # GH 47705
2013 if (
2014 isinstance(result, ABCDataFrame)
2015 and len(result) == 0
2016 and not isinstance(result.index, PeriodIndex)
2017 ):
2018 result = result.set_index(
2019 _asfreq_compat(self.obj.index[:0], freq=self.freq), append=True
2020 )
2022 return self._wrap_result(result)
2024 _upsample = _apply
2025 _downsample = _apply
2026 _groupby_and_aggregate = _apply
2028 @final
2029 def _gotitem(self, key, ndim, subset=None):
2030 """
2031 Sub-classes to define. Return a sliced object.
2033 Parameters
2034 ----------
2035 key : string / list of selections
2036 ndim : {1, 2}
2037 requested ndim of result
2038 subset : object, default None
2039 subset to act on
2040 """
2041 # create a new object to prevent aliasing
2042 if subset is None:
2043 subset = self.obj
2044 if key is not None:
2045 subset = subset[key]
2046 else:
2047 # reached via Apply.agg_dict_like with selection=None, ndim=1
2048 assert subset.ndim == 1
2050 # Try to select from a DataFrame, falling back to a Series
2051 try:
2052 if isinstance(key, list) and self.key not in key and self.key is not None:
2053 key.append(self.key)
2054 groupby = self._groupby[key]
2055 except IndexError:
2056 groupby = self._groupby
2058 selection = self._infer_selection(key, subset)
2060 new_rs = type(self)(
2061 groupby=groupby,
2062 parent=cast(Resampler, self),
2063 selection=selection,
2064 )
2065 return new_rs
2068class DatetimeIndexResampler(Resampler):
2069 ax: DatetimeIndex
2071 @property
2072 def _resampler_for_grouping(self) -> type[DatetimeIndexResamplerGroupby]:
2073 return DatetimeIndexResamplerGroupby
2075 def _get_binner_for_time(self):
2076 # this is how we are actually creating the bins
2077 return self._timegrouper._get_time_bins(self.ax)
2079 def _downsample(self, how, **kwargs):
2080 """
2081 Downsample the cython defined function.
2083 Parameters
2084 ----------
2085 how : string / cython mapped function
2086 **kwargs : kw args passed to how function
2087 """
2088 ax = self.ax
2090 # Excludes `on` column when provided
2091 obj = self._obj_with_exclusions
2093 if not len(ax):
2094 # reset to the new freq
2095 obj = obj.copy()
2096 obj.index = obj.index._with_freq(self.freq)
2097 assert obj.index.freq == self.freq, (obj.index.freq, self.freq)
2098 return obj
2100 # we are downsampling
2101 # we want to call the actual grouper method here
2102 result = obj.groupby(self._grouper).aggregate(how, **kwargs)
2103 return self._wrap_result(result)
2105 def _adjust_binner_for_upsample(self, binner):
2106 """
2107 Adjust our binner when upsampling.
2109 The range of a new index should not be outside specified range
2110 """
2111 if self.closed == "right":
2112 binner = binner[1:]
2113 else:
2114 binner = binner[:-1]
2115 return binner
2117 def _upsample(self, method, limit: int | None = None, fill_value=None):
2118 """
2119 Parameters
2120 ----------
2121 method : string {'backfill', 'bfill', 'pad',
2122 'ffill', 'asfreq'} method for upsampling
2123 limit : int, default None
2124 Maximum size gap to fill when reindexing
2125 fill_value : scalar, default None
2126 Value to use for missing values
2127 """
2128 if self._from_selection:
2129 raise ValueError(
2130 "Upsampling from level= or on= selection "
2131 "is not supported, use .set_index(...) "
2132 "to explicitly set index to datetime-like"
2133 )
2135 ax = self.ax
2136 obj = self._selected_obj
2137 binner = self.binner
2138 res_index = self._adjust_binner_for_upsample(binner)
2140 # if index exactly matches target grid (same freq & alignment), use fast path
2141 if (
2142 limit is None
2143 and to_offset(ax.inferred_freq) == self.freq
2144 and len(obj) == len(res_index)
2145 and obj.index.equals(res_index)
2146 ):
2147 result = obj.copy()
2148 result.index = res_index
2149 else:
2150 if method == "asfreq":
2151 method = None
2152 result = obj.reindex(
2153 res_index, method=method, limit=limit, fill_value=fill_value
2154 )
2156 return self._wrap_result(result)
2158 def _wrap_result(self, result):
2159 result = super()._wrap_result(result)
2161 # we may have a different kind that we were asked originally
2162 # convert if needed
2163 if isinstance(self.ax, PeriodIndex) and not isinstance(
2164 result.index, PeriodIndex
2165 ):
2166 if isinstance(result.index, MultiIndex):
2167 # GH 24103 - e.g. groupby resample
2168 if not isinstance(result.index.levels[-1], PeriodIndex):
2169 new_level = result.index.levels[-1].to_period(self.freq)
2170 result.index = result.index.set_levels(new_level, level=-1)
2171 else:
2172 result.index = result.index.to_period(self.freq)
2173 return result
2176@set_module("pandas.api.typing")
2177# error: Definition of "ax" in base class "_GroupByMixin" is incompatible
2178# with definition in base class "DatetimeIndexResampler"
2179class DatetimeIndexResamplerGroupby( # type: ignore[misc]
2180 _GroupByMixin, DatetimeIndexResampler
2181):
2182 """
2183 Provides a resample of a groupby implementation
2184 """
2186 @property
2187 def _resampler_cls(self):
2188 return DatetimeIndexResampler
2191class PeriodIndexResampler(DatetimeIndexResampler):
2192 # error: Incompatible types in assignment (expression has type "PeriodIndex", base
2193 # class "DatetimeIndexResampler" defined the type as "DatetimeIndex")
2194 ax: PeriodIndex # type: ignore[assignment]
2196 @property
2197 def _resampler_for_grouping(self):
2198 return PeriodIndexResamplerGroupby
2200 def _get_binner_for_time(self):
2201 return self._timegrouper._get_period_bins(self.ax)
2203 def _convert_obj(self, obj: NDFrameT) -> NDFrameT:
2204 obj = super()._convert_obj(obj)
2206 if self._from_selection:
2207 # see GH 14008, GH 12871
2208 msg = (
2209 "Resampling from level= or on= selection "
2210 "with a PeriodIndex is not currently supported, "
2211 "use .set_index(...) to explicitly set index"
2212 )
2213 raise NotImplementedError(msg)
2215 return obj
2217 def _downsample(self, how, **kwargs):
2218 """
2219 Downsample the cython defined function.
2221 Parameters
2222 ----------
2223 how : string / cython mapped function
2224 **kwargs : kw args passed to how function
2225 """
2226 ax = self.ax
2228 if is_subperiod(ax.freq, self.freq):
2229 # Downsampling
2230 return self._groupby_and_aggregate(how, **kwargs)
2231 elif is_superperiod(ax.freq, self.freq):
2232 if how == "ohlc":
2233 # GH #13083
2234 # upsampling to subperiods is handled as an asfreq, which works
2235 # for pure aggregating/reducing methods
2236 # OHLC reduces along the time dimension, but creates multiple
2237 # values for each period -> handle by _groupby_and_aggregate()
2238 return self._groupby_and_aggregate(how)
2239 return self.asfreq()
2240 elif ax.freq == self.freq:
2241 return self.asfreq()
2243 raise IncompatibleFrequency(
2244 f"Frequency {ax.freq} cannot be resampled to {self.freq}, "
2245 "as they are not sub or super periods"
2246 )
2248 def _upsample(self, method, limit: int | None = None, fill_value=None):
2249 """
2250 Parameters
2251 ----------
2252 method : {'backfill', 'bfill', 'pad', 'ffill'}
2253 Method for upsampling.
2254 limit : int, default None
2255 Maximum size gap to fill when reindexing.
2256 fill_value : scalar, default None
2257 Value to use for missing values.
2258 """
2259 ax = self.ax
2260 obj = self.obj
2261 new_index = self.binner
2263 # Start vs. end of period
2264 memb = ax.asfreq(self.freq, how=self.convention)
2266 # Get the fill indexer
2267 if method == "asfreq":
2268 method = None
2269 indexer = memb.get_indexer(new_index, method=method, limit=limit)
2270 new_obj = _take_new_index(
2271 obj,
2272 indexer,
2273 new_index,
2274 )
2275 return self._wrap_result(new_obj)
2278@set_module("pandas.api.typing")
2279# error: Definition of "ax" in base class "_GroupByMixin" is incompatible with
2280# definition in base class "PeriodIndexResampler"
2281class PeriodIndexResamplerGroupby( # type: ignore[misc]
2282 _GroupByMixin, PeriodIndexResampler
2283):
2284 """
2285 Provides a resample of a groupby implementation.
2286 """
2288 @property
2289 def _resampler_cls(self):
2290 return PeriodIndexResampler
2293class TimedeltaIndexResampler(DatetimeIndexResampler):
2294 # error: Incompatible types in assignment (expression has type "TimedeltaIndex",
2295 # base class "DatetimeIndexResampler" defined the type as "DatetimeIndex")
2296 ax: TimedeltaIndex # type: ignore[assignment]
2298 @property
2299 def _resampler_for_grouping(self):
2300 return TimedeltaIndexResamplerGroupby
2302 def _get_binner_for_time(self):
2303 return self._timegrouper._get_time_delta_bins(self.ax)
2305 def _adjust_binner_for_upsample(self, binner):
2306 """
2307 Adjust our binner when upsampling.
2309 The range of a new index is allowed to be greater than original range
2310 so we don't need to change the length of a binner, GH 13022
2311 """
2312 return binner
2315@set_module("pandas.api.typing")
2316# error: Definition of "ax" in base class "_GroupByMixin" is incompatible with
2317# definition in base class "DatetimeIndexResampler"
2318class TimedeltaIndexResamplerGroupby( # type: ignore[misc]
2319 _GroupByMixin, TimedeltaIndexResampler
2320):
2321 """
2322 Provides a resample of a groupby implementation.
2323 """
2325 @property
2326 def _resampler_cls(self):
2327 return TimedeltaIndexResampler
2330def get_resampler(obj: Series | DataFrame, **kwds) -> Resampler:
2331 """
2332 Create a TimeGrouper and return our resampler.
2333 """
2334 tg = TimeGrouper(obj, **kwds) # type: ignore[arg-type]
2335 return tg._get_resampler(obj)
2338get_resampler.__doc__ = Resampler.__doc__
2341def get_resampler_for_grouping(
2342 groupby: GroupBy,
2343 rule,
2344 how=None,
2345 fill_method=None,
2346 limit: int | None = None,
2347 on=None,
2348 **kwargs,
2349) -> Resampler:
2350 """
2351 Return our appropriate resampler when grouping as well.
2352 """
2353 # .resample uses 'on' similar to how .groupby uses 'key'
2354 tg = TimeGrouper(freq=rule, key=on, **kwargs)
2355 resampler = tg._get_resampler(groupby.obj)
2356 return resampler._get_resampler_for_grouping(groupby=groupby, key=tg.key)
2359@set_module("pandas.api.typing")
2360class TimeGrouper(Grouper):
2361 """
2362 Custom groupby class for time-interval grouping.
2364 Parameters
2365 ----------
2366 freq : pandas date offset or offset alias for identifying bin edges
2367 closed : closed end of interval; 'left' or 'right'
2368 label : interval boundary to use for labeling; 'left' or 'right'
2369 convention : {'start', 'end', 'e', 's'}
2370 If axis is PeriodIndex
2371 """
2373 _attributes = (
2374 *Grouper._attributes,
2375 "closed",
2376 "label",
2377 "how",
2378 "convention",
2379 "origin",
2380 "offset",
2381 )
2383 origin: TimeGrouperOrigin
2385 def __init__(
2386 self,
2387 obj: Grouper | None = None,
2388 freq: Frequency = "Min",
2389 key: str | None = None,
2390 closed: Literal["left", "right"] | None = None,
2391 label: Literal["left", "right"] | None = None,
2392 how: str = "mean",
2393 fill_method=None,
2394 limit: int | None = None,
2395 convention: Literal["start", "end", "e", "s"] | None = None,
2396 origin: (
2397 Literal["epoch", "start", "start_day", "end", "end_day"]
2398 | TimestampConvertibleTypes
2399 ) = "start_day",
2400 offset: TimedeltaConvertibleTypes | None = None,
2401 group_keys: bool = False,
2402 **kwargs,
2403 ) -> None:
2404 # Check for correctness of the keyword arguments which would
2405 # otherwise silently use the default if misspelled
2406 if label not in {None, "left", "right"}:
2407 raise ValueError(f"Unsupported value {label} for `label`")
2408 if closed not in {None, "left", "right"}:
2409 raise ValueError(f"Unsupported value {closed} for `closed`")
2410 if convention not in {None, "start", "end", "e", "s"}:
2411 raise ValueError(f"Unsupported value {convention} for `convention`")
2413 if (key is None and obj is not None and isinstance(obj.index, PeriodIndex)) or ( # type: ignore[attr-defined]
2414 key is not None
2415 and obj is not None
2416 and getattr(obj[key], "dtype", None) == "period" # type: ignore[index]
2417 ):
2418 freq = to_offset(freq, is_period=True)
2419 else:
2420 freq = to_offset(freq)
2422 if not isinstance(freq, Tick):
2423 if offset is not None:
2424 warnings.warn(
2425 "The 'offset' keyword does not take effect when resampling "
2426 "with a 'freq' that is not Tick-like (h, m, s, ms, us, ns)",
2427 RuntimeWarning,
2428 stacklevel=find_stack_level(),
2429 )
2430 if origin != "start_day":
2431 warnings.warn(
2432 "The 'origin' keyword does not take effect when resampling "
2433 "with a 'freq' that is not Tick-like (h, m, s, ms, us, ns)",
2434 RuntimeWarning,
2435 stacklevel=find_stack_level(),
2436 )
2438 end_types = {"ME", "YE", "QE", "BME", "BYE", "BQE", "W"}
2439 rule = freq.rule_code
2440 if rule in end_types or ("-" in rule and rule[: rule.find("-")] in end_types):
2441 if closed is None:
2442 closed = "right"
2443 if label is None:
2444 label = "right"
2445 # The backward resample sets ``closed`` to ``'right'`` by default
2446 # since the last value should be considered as the edge point for
2447 # the last bin. When origin in "end" or "end_day", the value for a
2448 # specific ``Timestamp`` index stands for the resample result from
2449 # the current ``Timestamp`` minus ``freq`` to the current
2450 # ``Timestamp`` with a right close.
2451 elif origin in ["end", "end_day"]:
2452 if closed is None:
2453 closed = "right"
2454 if label is None:
2455 label = "right"
2456 else:
2457 if closed is None:
2458 closed = "left"
2459 if label is None:
2460 label = "left"
2462 self.closed = closed
2463 self.label = label
2464 self.convention = convention if convention is not None else "e"
2465 self.how = how
2466 self.fill_method = fill_method
2467 self.limit = limit
2468 self.group_keys = group_keys
2469 self._arrow_dtype: ArrowDtype | None = None
2471 if origin in ("epoch", "start", "start_day", "end", "end_day"):
2472 # error: Incompatible types in assignment (expression has type "Union[Union[
2473 # Timestamp, datetime, datetime64, signedinteger[_64Bit], float, str],
2474 # Literal['epoch', 'start', 'start_day', 'end', 'end_day']]", variable has
2475 # type "Union[Timestamp, Literal['epoch', 'start', 'start_day', 'end',
2476 # 'end_day']]")
2477 self.origin = origin # type: ignore[assignment]
2478 else:
2479 try:
2480 self.origin = Timestamp(origin)
2481 except (ValueError, TypeError) as err:
2482 raise ValueError(
2483 "'origin' should be equal to 'epoch', 'start', 'start_day', "
2484 "'end', 'end_day' or "
2485 f"should be a Timestamp convertible type. Got '{origin}' instead."
2486 ) from err
2488 try:
2489 self.offset = Timedelta(offset) if offset is not None else None
2490 except (ValueError, TypeError) as err:
2491 raise ValueError(
2492 "'offset' should be a Timedelta convertible type. "
2493 f"Got '{offset}' instead."
2494 ) from err
2496 # always sort time groupers
2497 kwargs["sort"] = True
2499 super().__init__(freq=freq, key=key, **kwargs)
2501 def _get_resampler(self, obj: NDFrame) -> Resampler:
2502 """
2503 Return my resampler or raise if we have an invalid axis.
2505 Parameters
2506 ----------
2507 obj : Series or DataFrame
2509 Returns
2510 -------
2511 Resampler
2513 Raises
2514 ------
2515 TypeError if incompatible axis
2517 """
2518 _, ax, _ = self._set_grouper(obj, gpr_index=None)
2519 if isinstance(ax, DatetimeIndex):
2520 return DatetimeIndexResampler(
2521 obj,
2522 timegrouper=self,
2523 group_keys=self.group_keys,
2524 gpr_index=ax,
2525 )
2526 elif isinstance(ax, PeriodIndex):
2527 return PeriodIndexResampler(
2528 obj,
2529 timegrouper=self,
2530 group_keys=self.group_keys,
2531 gpr_index=ax,
2532 )
2533 elif isinstance(ax, TimedeltaIndex):
2534 return TimedeltaIndexResampler(
2535 obj,
2536 timegrouper=self,
2537 group_keys=self.group_keys,
2538 gpr_index=ax,
2539 )
2541 raise TypeError(
2542 "Only valid with DatetimeIndex, "
2543 "TimedeltaIndex or PeriodIndex, "
2544 f"but got an instance of '{type(ax).__name__}'"
2545 )
2547 def _get_grouper(
2548 self, obj: NDFrameT, validate: bool = True, observed: bool = True
2549 ) -> tuple[BinGrouper, NDFrameT]:
2550 """
2551 Parameters
2552 ----------
2553 obj : Series or DataFrame
2554 Object being grouped.
2555 validate : bool, default True
2556 Unused. Only for compatibility with ``Grouper._get_grouper``.
2557 observed : bool, default True
2558 Unused. Only for compatibility with ``Grouper._get_grouper``.
2560 Returns
2561 -------
2562 A tuple of grouper, obj (possibly sorted)
2563 """
2564 # create the resampler and return our binner
2565 r = self._get_resampler(obj)
2566 return r._grouper, cast(NDFrameT, r.obj)
2568 def _get_time_bins(self, ax: DatetimeIndex):
2569 if not isinstance(ax, DatetimeIndex):
2570 raise TypeError(
2571 "axis must be a DatetimeIndex, but got "
2572 f"an instance of {type(ax).__name__}"
2573 )
2575 if len(ax) == 0:
2576 binner = labels = DatetimeIndex(
2577 data=[], freq=self.freq, name=ax.name, dtype=ax.dtype
2578 )
2579 return binner, [], labels
2581 first, last = _get_timestamp_range_edges(
2582 ax.min(),
2583 ax.max(),
2584 self.freq,
2585 unit=ax.unit,
2586 closed=self.closed,
2587 origin=self.origin,
2588 offset=self.offset,
2589 )
2590 # GH #12037
2591 # use first/last directly instead of call replace() on them
2592 # because replace() will swallow the nanosecond part
2593 # thus last bin maybe slightly before the end if the end contains
2594 # nanosecond part and lead to `Values falls after last bin` error
2595 # GH 25758: If DST lands at midnight (e.g. 'America/Havana'), user feedback
2596 # has noted that ambiguous=True provides the most sensible result
2597 binner = labels = date_range(
2598 freq=self.freq,
2599 start=first,
2600 end=last,
2601 tz=ax.tz,
2602 name=ax.name,
2603 ambiguous=True,
2604 nonexistent="shift_forward",
2605 unit=ax.unit,
2606 )
2608 ax_values = ax.asi8
2609 binner, bin_edges = self._adjust_bin_edges(binner, ax_values)
2611 # general version, knowing nothing about relative frequencies
2612 bins = lib.generate_bins_dt64(
2613 ax_values, bin_edges, self.closed, hasnans=ax.hasnans
2614 )
2616 if self.closed == "right":
2617 labels = binner
2618 if self.label == "right":
2619 labels = labels[1:]
2620 elif self.label == "right":
2621 labels = labels[1:]
2623 if ax.hasnans:
2624 binner = binner.insert(0, NaT)
2625 labels = labels.insert(0, NaT)
2627 # if we end up with more labels than bins
2628 # adjust the labels
2629 # GH4076
2630 if len(bins) < len(labels):
2631 labels = labels[: len(bins)]
2633 return binner, bins, labels
2635 def _adjust_bin_edges(
2636 self, binner: DatetimeIndex, ax_values: npt.NDArray[np.int64]
2637 ) -> tuple[DatetimeIndex, npt.NDArray[np.int64]]:
2638 # Some hacks for > daily data, see #1471, #1458, #1483
2640 if self.freq.name in ("BME", "ME", "W") or self.freq.name.split("-")[0] in (
2641 "BQE",
2642 "BYE",
2643 "QE",
2644 "YE",
2645 "W",
2646 ):
2647 # If the right end-point is on the last day of the month, roll forwards
2648 # until the last moment of that day. Note that we only do this for offsets
2649 # which correspond to the end of a super-daily period - "month start", for
2650 # example, is excluded.
2651 if self.closed == "right":
2652 # GH 21459, GH 9119: Adjust the bins relative to the wall time
2653 edges_dti = binner.tz_localize(None)
2654 edges_dti = (
2655 edges_dti
2656 + Timedelta(days=1).as_unit(edges_dti.unit)
2657 - Timedelta(1, unit=edges_dti.unit).as_unit(edges_dti.unit)
2658 )
2659 bin_edges = edges_dti.tz_localize(binner.tz).asi8
2660 else:
2661 bin_edges = binner.asi8
2663 # intraday values on last day
2664 if bin_edges[-2] > ax_values.max():
2665 bin_edges = bin_edges[:-1]
2666 binner = binner[:-1]
2667 else:
2668 bin_edges = binner.asi8
2669 return binner, bin_edges
2671 def _get_time_delta_bins(self, ax: TimedeltaIndex):
2672 if not isinstance(ax, TimedeltaIndex):
2673 raise TypeError(
2674 "axis must be a TimedeltaIndex, but got "
2675 f"an instance of {type(ax).__name__}"
2676 )
2678 if not isinstance(self.freq, (Tick, Day)):
2679 # GH#51896
2680 raise ValueError(
2681 "Resampling on a TimedeltaIndex requires fixed-duration `freq`, "
2682 f"e.g. '24h' or '3D', not {self.freq}"
2683 )
2685 if not len(ax):
2686 binner = labels = TimedeltaIndex(data=[], freq=self.freq, name=ax.name)
2687 return binner, [], labels
2689 start, end = ax.min(), ax.max()
2691 if self.closed == "right":
2692 end += self.freq
2694 labels = binner = timedelta_range(
2695 start=start, end=end, freq=self.freq, name=ax.name
2696 )
2698 end_stamps = labels
2699 if self.closed == "left":
2700 end_stamps += self.freq
2702 bins = ax.searchsorted(end_stamps, side=self.closed)
2704 if self.offset:
2705 # GH 10530 & 31809
2706 labels += self.offset
2708 return binner, bins, labels
2710 def _get_time_period_bins(self, ax: DatetimeIndex):
2711 if not isinstance(ax, DatetimeIndex):
2712 raise TypeError(
2713 "axis must be a DatetimeIndex, but got "
2714 f"an instance of {type(ax).__name__}"
2715 )
2717 freq = self.freq
2719 if len(ax) == 0:
2720 binner = labels = PeriodIndex(
2721 data=[], freq=freq, name=ax.name, dtype=ax.dtype
2722 )
2723 return binner, [], labels
2725 labels = binner = period_range(start=ax[0], end=ax[-1], freq=freq, name=ax.name)
2727 end_stamps = (labels + freq).asfreq(freq, "s").to_timestamp()
2728 if ax.tz:
2729 end_stamps = end_stamps.tz_localize(ax.tz)
2730 bins = ax.searchsorted(end_stamps, side="left")
2732 return binner, bins, labels
2734 def _get_period_bins(self, ax: PeriodIndex):
2735 if not isinstance(ax, PeriodIndex):
2736 raise TypeError(
2737 "axis must be a PeriodIndex, but got "
2738 f"an instance of {type(ax).__name__}"
2739 )
2741 memb = ax.asfreq(self.freq, how=self.convention)
2743 # NaT handling as in pandas._lib.lib.generate_bins_dt64()
2744 nat_count = 0
2745 if memb.hasnans:
2746 # error: Incompatible types in assignment (expression has type
2747 # "bool_", variable has type "int") [assignment]
2748 nat_count = np.sum(memb._isnan) # type: ignore[assignment]
2749 memb = memb[~memb._isnan]
2751 if not len(memb):
2752 # index contains no valid (non-NaT) values
2753 bins = np.array([], dtype=np.int64)
2754 binner = labels = PeriodIndex(data=[], freq=self.freq, name=ax.name)
2755 if len(ax) > 0:
2756 # index is all NaT
2757 binner, bins, labels = _insert_nat_bin(binner, bins, labels, len(ax))
2758 return binner, bins, labels
2760 freq_mult = self.freq.n
2762 start = ax.min().asfreq(self.freq, how=self.convention)
2763 end = ax.max().asfreq(self.freq, how="end")
2764 bin_shift = 0
2766 if isinstance(self.freq, Tick):
2767 # GH 23882 & 31809: get adjusted bin edge labels with 'origin'
2768 # and 'origin' support. This call only makes sense if the freq is a
2769 # Tick since offset and origin are only used in those cases.
2770 # Not doing this check could create an extra empty bin.
2771 p_start, end = _get_period_range_edges(
2772 start,
2773 end,
2774 self.freq,
2775 closed=self.closed,
2776 origin=self.origin,
2777 offset=self.offset,
2778 )
2780 # Get offset for bin edge (not label edge) adjustment
2781 start_offset = Period(start, self.freq) - Period(p_start, self.freq)
2782 # error: Item "Period" of "Union[Period, Any]" has no attribute "n"
2783 bin_shift = start_offset.n % freq_mult # type: ignore[union-attr]
2784 start = p_start
2786 labels = binner = period_range(
2787 start=start, end=end, freq=self.freq, name=ax.name
2788 )
2790 i8 = memb.asi8
2792 # when upsampling to subperiods, we need to generate enough bins
2793 expected_bins_count = len(binner) * freq_mult
2794 i8_extend = expected_bins_count - (i8[-1] - i8[0])
2795 rng = np.arange(i8[0], i8[-1] + i8_extend, freq_mult)
2796 rng += freq_mult
2797 # adjust bin edge indexes to account for base
2798 rng -= bin_shift
2800 # Wrap in PeriodArray for PeriodArray.searchsorted
2801 prng = type(memb._data)(rng, dtype=memb.dtype)
2802 bins = memb.searchsorted(prng, side="left")
2804 if nat_count > 0:
2805 binner, bins, labels = _insert_nat_bin(binner, bins, labels, nat_count)
2807 return binner, bins, labels
2809 def _set_grouper(
2810 self, obj: NDFrameT, sort: bool = False, *, gpr_index: Index | None = None
2811 ) -> tuple[NDFrameT, Index, npt.NDArray[np.intp] | None]:
2812 obj, ax, indexer = super()._set_grouper(obj, sort, gpr_index=gpr_index)
2813 if isinstance(ax.dtype, ArrowDtype) and ax.dtype.kind in "Mm":
2814 self._arrow_dtype = ax.dtype
2815 ax = Index(
2816 cast(ArrowExtensionArray, ax.array)._maybe_convert_datelike_array()
2817 )
2818 return obj, ax, indexer
2821@overload
2822def _take_new_index(
2823 obj: DataFrame, indexer: npt.NDArray[np.intp], new_index: Index
2824) -> DataFrame: ...
2827@overload
2828def _take_new_index(
2829 obj: Series, indexer: npt.NDArray[np.intp], new_index: Index
2830) -> Series: ...
2833def _take_new_index(
2834 obj: DataFrame | Series,
2835 indexer: npt.NDArray[np.intp],
2836 new_index: Index,
2837) -> DataFrame | Series:
2838 if isinstance(obj, ABCSeries):
2839 new_values = algos.take_nd(obj._values, indexer)
2840 return obj._constructor(new_values, index=new_index, name=obj.name)
2841 elif isinstance(obj, ABCDataFrame):
2842 new_mgr = obj._mgr.reindex_indexer(new_axis=new_index, indexer=indexer, axis=1)
2843 return obj._constructor_from_mgr(new_mgr, axes=new_mgr.axes)
2844 else:
2845 raise ValueError("'obj' should be either a Series or a DataFrame")
2848def _get_timestamp_range_edges(
2849 first: Timestamp,
2850 last: Timestamp,
2851 freq: BaseOffset,
2852 unit: TimeUnit,
2853 closed: Literal["right", "left"] = "left",
2854 origin: TimeGrouperOrigin = "start_day",
2855 offset: Timedelta | None = None,
2856) -> tuple[Timestamp, Timestamp]:
2857 """
2858 Adjust the `first` Timestamp to the preceding Timestamp that resides on
2859 the provided offset. Adjust the `last` Timestamp to the following
2860 Timestamp that resides on the provided offset. Input Timestamps that
2861 already reside on the offset will be adjusted depending on the type of
2862 offset and the `closed` parameter.
2864 Parameters
2865 ----------
2866 first : pd.Timestamp
2867 The beginning Timestamp of the range to be adjusted.
2868 last : pd.Timestamp
2869 The ending Timestamp of the range to be adjusted.
2870 freq : pd.DateOffset
2871 The dateoffset to which the Timestamps will be adjusted.
2872 closed : {'right', 'left'}, default "left"
2873 Which side of bin interval is closed.
2874 origin : {'epoch', 'start', 'start_day'} or Timestamp, default 'start_day'
2875 The timestamp on which to adjust the grouping. The timezone of origin must
2876 match the timezone of the index.
2877 If a timestamp is not used, these values are also supported:
2879 - 'epoch': `origin` is 1970-01-01
2880 - 'start': `origin` is the first value of the timeseries
2881 - 'start_day': `origin` is the first day at midnight of the timeseries
2882 offset : pd.Timedelta, default is None
2883 An offset timedelta added to the origin.
2885 Returns
2886 -------
2887 A tuple of length 2, containing the adjusted pd.Timestamp objects.
2888 """
2889 if isinstance(freq, Tick):
2890 index_tz = first.tz
2891 if isinstance(origin, Timestamp) and (origin.tz is None) != (index_tz is None):
2892 raise ValueError("The origin must have the same timezone as the index.")
2893 if origin == "epoch":
2894 # set the epoch based on the timezone to have similar bins results when
2895 # resampling on the same kind of indexes on different timezones
2896 origin = Timestamp("1970-01-01", tz=index_tz)
2898 first, last = _adjust_dates_anchored(
2899 first,
2900 last,
2901 freq,
2902 closed=closed,
2903 origin=origin,
2904 offset=offset,
2905 unit=unit,
2906 )
2907 else:
2908 first = first.normalize()
2909 last = last.normalize()
2911 if closed == "left":
2912 first = Timestamp(freq.rollback(first))
2913 else:
2914 first = Timestamp(first - freq)
2916 last = Timestamp(last + freq)
2918 return first, last
2921def _get_period_range_edges(
2922 first: Period,
2923 last: Period,
2924 freq: BaseOffset,
2925 closed: Literal["right", "left"] = "left",
2926 origin: TimeGrouperOrigin = "start_day",
2927 offset: Timedelta | None = None,
2928) -> tuple[Period, Period]:
2929 """
2930 Adjust the provided `first` and `last` Periods to the respective Period of
2931 the given offset that encompasses them.
2933 Parameters
2934 ----------
2935 first : pd.Period
2936 The beginning Period of the range to be adjusted.
2937 last : pd.Period
2938 The ending Period of the range to be adjusted.
2939 freq : pd.DateOffset
2940 The freq to which the Periods will be adjusted.
2941 closed : {'right', 'left'}, default "left"
2942 Which side of bin interval is closed.
2943 origin : {'epoch', 'start', 'start_day'}, Timestamp, default 'start_day'
2944 The timestamp on which to adjust the grouping. The timezone of origin must
2945 match the timezone of the index.
2947 If a timestamp is not used, these values are also supported:
2949 - 'epoch': `origin` is 1970-01-01
2950 - 'start': `origin` is the first value of the timeseries
2951 - 'start_day': `origin` is the first day at midnight of the timeseries
2952 offset : pd.Timedelta, default is None
2953 An offset timedelta added to the origin.
2955 Returns
2956 -------
2957 A tuple of length 2, containing the adjusted pd.Period objects.
2958 """
2959 if not all(isinstance(obj, Period) for obj in [first, last]):
2960 raise TypeError("'first' and 'last' must be instances of type Period")
2962 # GH 23882
2963 first_ts = first.to_timestamp()
2964 last_ts = last.to_timestamp()
2965 adjust_first = not freq.is_on_offset(first_ts)
2966 adjust_last = freq.is_on_offset(last_ts)
2968 first_ts, last_ts = _get_timestamp_range_edges(
2969 first_ts, last_ts, freq, unit="ns", closed=closed, origin=origin, offset=offset
2970 )
2972 first = (first_ts + int(adjust_first) * freq).to_period(freq)
2973 last = (last_ts - int(adjust_last) * freq).to_period(freq)
2974 return first, last
2977def _insert_nat_bin(
2978 binner: PeriodIndex, bins: np.ndarray, labels: PeriodIndex, nat_count: int
2979) -> tuple[PeriodIndex, np.ndarray, PeriodIndex]:
2980 # NaT handling as in pandas._lib.lib.generate_bins_dt64()
2981 # shift bins by the number of NaT
2982 assert nat_count > 0
2983 bins += nat_count
2984 bins = np.insert(bins, 0, nat_count)
2986 # Incompatible types in assignment (expression has type "Index", variable
2987 # has type "PeriodIndex")
2988 binner = binner.insert(0, NaT) # type: ignore[assignment]
2989 # Incompatible types in assignment (expression has type "Index", variable
2990 # has type "PeriodIndex")
2991 labels = labels.insert(0, NaT) # type: ignore[assignment]
2992 return binner, bins, labels
2995def _adjust_dates_anchored(
2996 first: Timestamp,
2997 last: Timestamp,
2998 freq: Tick,
2999 closed: Literal["right", "left"] = "right",
3000 origin: TimeGrouperOrigin = "start_day",
3001 offset: Timedelta | None = None,
3002 unit: TimeUnit = "ns",
3003) -> tuple[Timestamp, Timestamp]:
3004 # First and last offsets should be calculated from the start day to fix an
3005 # error cause by resampling across multiple days when a one day period is
3006 # not a multiple of the frequency. See GH 8683
3007 # To handle frequencies that are not multiple or divisible by a day we let
3008 # the possibility to define a fixed origin timestamp. See GH 31809
3009 first = first.as_unit(unit)
3010 last = last.as_unit(unit)
3011 if offset is not None:
3012 offset = offset.as_unit(unit)
3014 freq_value = Timedelta(freq).as_unit(unit)._value
3016 origin_timestamp = 0 # origin == "epoch"
3017 if origin == "start_day":
3018 origin_timestamp = first.normalize()._value
3019 elif origin == "start":
3020 origin_timestamp = first._value
3021 elif isinstance(origin, Timestamp):
3022 origin_timestamp = origin.as_unit(unit)._value
3023 elif origin in ["end", "end_day"]:
3024 origin_last = last if origin == "end" else last.ceil("D")
3025 sub_freq_times = (origin_last._value - first._value) // freq_value
3026 if closed == "left":
3027 sub_freq_times += 1
3028 first = origin_last - sub_freq_times * freq
3029 origin_timestamp = first._value
3030 origin_timestamp += offset._value if offset else 0
3032 # GH 10117 & GH 19375. If first and last contain timezone information,
3033 # Perform the calculation in UTC in order to avoid localizing on an
3034 # Ambiguous or Nonexistent time.
3035 first_tzinfo = first.tzinfo
3036 last_tzinfo = last.tzinfo
3037 if first_tzinfo is not None:
3038 first = first.tz_convert("UTC")
3039 if last_tzinfo is not None:
3040 last = last.tz_convert("UTC")
3042 foffset = (first._value - origin_timestamp) % freq_value
3043 loffset = (last._value - origin_timestamp) % freq_value
3045 if closed == "right":
3046 if foffset > 0:
3047 # roll back
3048 fresult_int = first._value - foffset
3049 else:
3050 fresult_int = first._value - freq_value
3052 if loffset > 0:
3053 # roll forward
3054 lresult_int = last._value + (freq_value - loffset)
3055 else:
3056 # already the end of the road
3057 lresult_int = last._value
3058 else: # closed == 'left'
3059 if foffset > 0:
3060 fresult_int = first._value - foffset
3061 else:
3062 # start of the road
3063 fresult_int = first._value
3065 if loffset > 0:
3066 # roll forward
3067 lresult_int = last._value + (freq_value - loffset)
3068 else:
3069 lresult_int = last._value + freq_value
3070 fresult = Timestamp(fresult_int, unit=unit)
3071 lresult = Timestamp(lresult_int, unit=unit)
3072 if first_tzinfo is not None:
3073 fresult = fresult.tz_localize("UTC").tz_convert(first_tzinfo)
3074 if last_tzinfo is not None:
3075 lresult = lresult.tz_localize("UTC").tz_convert(last_tzinfo)
3076 return fresult, lresult
3079def asfreq(
3080 obj: NDFrameT,
3081 freq,
3082 method=None,
3083 how=None,
3084 normalize: bool = False,
3085 fill_value=None,
3086) -> NDFrameT:
3087 """
3088 Utility frequency conversion method for Series/DataFrame.
3090 See :meth:`pandas.NDFrame.asfreq` for full documentation.
3091 """
3092 if isinstance(obj.index, PeriodIndex):
3093 if method is not None:
3094 raise NotImplementedError("'method' argument is not supported")
3096 if how is None:
3097 how = "E"
3099 if isinstance(freq, BaseOffset):
3100 if hasattr(freq, "_period_dtype_code"):
3101 freq = PeriodDtype(freq)._freqstr
3103 new_obj = obj.copy()
3104 new_obj.index = obj.index.asfreq(freq, how=how)
3106 elif len(obj.index) == 0:
3107 new_obj = obj.copy()
3109 new_obj.index = _asfreq_compat(obj.index, freq)
3110 else:
3111 unit: TimeUnit = "ns"
3112 if isinstance(obj.index, DatetimeIndex):
3113 # TODO: should we disallow non-DatetimeIndex?
3114 unit = obj.index.unit
3115 dti = date_range(obj.index.min(), obj.index.max(), freq=freq, unit=unit)
3116 dti.name = obj.index.name
3117 new_obj = obj.reindex(dti, method=method, fill_value=fill_value)
3118 if normalize:
3119 new_obj.index = new_obj.index.normalize()
3121 return new_obj
3124def _asfreq_compat(index: FreqIndexT, freq) -> FreqIndexT:
3125 """
3126 Helper to mimic asfreq on (empty) DatetimeIndex and TimedeltaIndex.
3128 Parameters
3129 ----------
3130 index : PeriodIndex, DatetimeIndex, or TimedeltaIndex
3131 freq : DateOffset
3133 Returns
3134 -------
3135 same type as index
3136 """
3137 if len(index) != 0:
3138 # This should never be reached, always checked by the caller
3139 raise ValueError(
3140 "Can only set arbitrary freq for empty DatetimeIndex or TimedeltaIndex"
3141 )
3142 if isinstance(index, PeriodIndex):
3143 new_index = index.asfreq(freq=freq)
3144 elif isinstance(index, DatetimeIndex):
3145 new_index = DatetimeIndex([], dtype=index.dtype, freq=freq, name=index.name)
3146 elif isinstance(index, TimedeltaIndex):
3147 new_index = TimedeltaIndex([], dtype=index.dtype, freq=freq, name=index.name)
3148 else: # pragma: no cover
3149 raise TypeError(type(index))
3150 return new_index