1"""
2Provide a generic structure to support window functions,
3similar to how we have a Groupby object.
4"""
5
6from __future__ import annotations
7
8import copy
9from datetime import timedelta
10from functools import partial
11import inspect
12from typing import (
13 TYPE_CHECKING,
14 Any,
15 Concatenate,
16 Literal,
17 Self,
18 cast,
19 final,
20 overload,
21)
22
23import numpy as np
24
25from pandas._libs.tslibs import (
26 BaseOffset,
27 Timedelta,
28 to_offset,
29)
30import pandas._libs.window.aggregations as window_aggregations
31from pandas.compat._optional import import_optional_dependency
32from pandas.errors import DataError
33from pandas.util._decorators import set_module
34
35from pandas.core.dtypes.common import (
36 ensure_float64,
37 is_bool,
38 is_integer,
39 is_numeric_dtype,
40 needs_i8_conversion,
41)
42from pandas.core.dtypes.dtypes import ArrowDtype
43from pandas.core.dtypes.generic import (
44 ABCDataFrame,
45 ABCSeries,
46)
47from pandas.core.dtypes.missing import notna
48
49from pandas.core._numba import executor
50from pandas.core.algorithms import factorize
51from pandas.core.apply import (
52 ResamplerWindowApply,
53 reconstruct_func,
54)
55from pandas.core.arrays import ExtensionArray
56from pandas.core.base import SelectionMixin
57import pandas.core.common as com
58from pandas.core.indexers.objects import (
59 BaseIndexer,
60 FixedWindowIndexer,
61 GroupbyIndexer,
62 VariableWindowIndexer,
63)
64from pandas.core.indexes.api import (
65 DatetimeIndex,
66 Index,
67 MultiIndex,
68 PeriodIndex,
69 TimedeltaIndex,
70)
71from pandas.core.reshape.concat import concat
72from pandas.core.util.numba_ import (
73 get_jit_arguments,
74 maybe_use_numba,
75 prepare_function_arguments,
76)
77from pandas.core.window.common import (
78 flex_binary_moment,
79 zsqrt,
80)
81from pandas.core.window.numba_ import (
82 generate_manual_numpy_nan_agg_with_axis,
83 generate_numba_apply_func,
84 generate_numba_table_func,
85)
86
87if TYPE_CHECKING:
88 from collections.abc import Callable
89 from collections.abc import (
90 Hashable,
91 Iterator,
92 Sized,
93 )
94
95 from pandas._typing import (
96 ArrayLike,
97 NDFrameT,
98 QuantileInterpolation,
99 P,
100 TimeUnit,
101 T,
102 WindowingRankType,
103 npt,
104 )
105
106 from pandas import (
107 DataFrame,
108 Series,
109 )
110 from pandas.core.generic import NDFrame
111 from pandas.core.groupby.ops import BaseGrouper
112
113from pandas.core.arrays.datetimelike import dtype_to_unit
114
115
116class BaseWindow(SelectionMixin):
117 """Provides utilities for performing windowing operations."""
118
119 _attributes: list[str] = []
120 exclusions: frozenset[Hashable] = frozenset()
121 _on: Index
122
123 def __init__(
124 self,
125 obj: NDFrame,
126 window=None,
127 min_periods: int | None = None,
128 center: bool | None = False,
129 win_type: str | None = None,
130 on: str | Index | None = None,
131 closed: str | None = None,
132 step: int | None = None,
133 method: str = "single",
134 *,
135 selection=None,
136 ) -> None:
137 self.obj = obj
138 self.on = on
139 self.closed = closed
140 self.step = step
141 self.window = window
142 self.min_periods = min_periods
143 self.center = center
144 self.win_type = win_type
145 self.method = method
146 self._win_freq_i8: int | None = None
147 if self.on is None:
148 self._on = self.obj.index
149 elif isinstance(self.on, Index):
150 self._on = self.on
151 elif isinstance(self.obj, ABCDataFrame) and self.on in self.obj.columns:
152 self._on = Index(self.obj[self.on])
153 else:
154 raise ValueError(
155 f"invalid on specified as {self.on}, "
156 "must be a column (of DataFrame), an Index or None"
157 )
158
159 self._selection = selection
160 self._validate()
161
162 def _validate(self) -> None:
163 if self.center is not None and not is_bool(self.center):
164 raise ValueError("center must be a boolean")
165 if self.min_periods is not None:
166 if not is_integer(self.min_periods):
167 raise ValueError("min_periods must be an integer")
168 if self.min_periods < 0:
169 raise ValueError("min_periods must be >= 0")
170 if is_integer(self.window) and self.min_periods > self.window:
171 raise ValueError(
172 f"min_periods {self.min_periods} must be <= window {self.window}"
173 )
174 if self.closed is not None and self.closed not in [
175 "right",
176 "both",
177 "left",
178 "neither",
179 ]:
180 raise ValueError("closed must be 'right', 'left', 'both' or 'neither'")
181 if not isinstance(self.obj, (ABCSeries, ABCDataFrame)):
182 raise TypeError(f"invalid type: {type(self)}")
183 if isinstance(self.window, BaseIndexer):
184 # Validate that the passed BaseIndexer subclass has
185 # a get_window_bounds with the correct signature.
186 get_window_bounds_signature = inspect.signature(
187 self.window.get_window_bounds
188 ).parameters.keys()
189 expected_signature = inspect.signature(
190 BaseIndexer().get_window_bounds
191 ).parameters.keys()
192 if get_window_bounds_signature != expected_signature:
193 raise ValueError(
194 f"{type(self.window).__name__} does not implement "
195 f"the correct signature for get_window_bounds"
196 )
197 if self.method not in ["table", "single"]:
198 raise ValueError("method must be 'table' or 'single")
199 if self.step is not None:
200 if not is_integer(self.step):
201 raise ValueError("step must be an integer")
202 if self.step < 0:
203 raise ValueError("step must be >= 0")
204
205 def _check_window_bounds(
206 self, start: np.ndarray, end: np.ndarray, num_vals: int
207 ) -> None:
208 if len(start) != len(end):
209 raise ValueError(
210 f"start ({len(start)}) and end ({len(end)}) bounds must be the "
211 f"same length"
212 )
213 if len(start) != (num_vals + (self.step or 1) - 1) // (self.step or 1):
214 raise ValueError(
215 f"start and end bounds ({len(start)}) must be the same length "
216 f"as the object ({num_vals}) divided by the step ({self.step}) "
217 f"if given and rounded up"
218 )
219
220 def _slice_axis_for_step(self, index: Index, result: Sized | None = None) -> Index:
221 """
222 Slices the index for a given result and the preset step.
223 """
224 return (
225 index
226 if result is None or len(result) == len(index)
227 else index[:: self.step]
228 )
229
230 def _validate_numeric_only(self, name: str, numeric_only: bool) -> None:
231 """
232 Validate numeric_only argument, raising if invalid for the input.
233
234 Parameters
235 ----------
236 name : str
237 Name of the operator (kernel).
238 numeric_only : bool
239 Value passed by user.
240 """
241 if (
242 self._selected_obj.ndim == 1
243 and numeric_only
244 and not is_numeric_dtype(self._selected_obj.dtype)
245 ):
246 raise NotImplementedError(
247 f"{type(self).__name__}.{name} does not implement numeric_only"
248 )
249
250 def _make_numeric_only(self, obj: NDFrameT) -> NDFrameT:
251 """Subset DataFrame to numeric columns.
252
253 Parameters
254 ----------
255 obj : DataFrame
256
257 Returns
258 -------
259 obj subset to numeric-only columns.
260 """
261 result = obj.select_dtypes(include=["number"], exclude=["timedelta"])
262 return result
263
264 def _create_data(self, obj: NDFrameT, numeric_only: bool = False) -> NDFrameT:
265 """
266 Split data into blocks & return conformed data.
267 """
268 # filter out the on from the object
269 if self.on is not None and not isinstance(self.on, Index) and obj.ndim == 2:
270 obj = obj.reindex(columns=obj.columns.difference([self.on], sort=False))
271 if obj.ndim > 1 and numeric_only:
272 obj = self._make_numeric_only(obj)
273 return obj
274
275 def _gotitem(self, key, ndim, subset=None):
276 """
277 Sub-classes to define. Return a sliced object.
278
279 Parameters
280 ----------
281 key : str / list of selections
282 ndim : {1, 2}
283 requested ndim of result
284 subset : object, default None
285 subset to act on
286 """
287 # create a new object to prevent aliasing
288 if subset is None:
289 subset = self.obj
290
291 # we need to make a shallow copy of ourselves
292 # with the same groupby
293 kwargs = {attr: getattr(self, attr) for attr in self._attributes}
294
295 selection = self._infer_selection(key, subset)
296 new_win = type(self)(subset, selection=selection, **kwargs)
297 return new_win
298
299 def __getattr__(self, attr: str):
300 if attr in self._internal_names_set:
301 return object.__getattribute__(self, attr)
302 if attr in self.obj:
303 return self[attr]
304
305 raise AttributeError(
306 f"'{type(self).__name__}' object has no attribute '{attr}'"
307 )
308
309 def _dir_additions(self):
310 return self.obj._dir_additions()
311
312 def __repr__(self) -> str:
313 """
314 Provide a nice str repr of our rolling object.
315 """
316 attrs_list = (
317 f"{attr_name}={getattr(self, attr_name)}"
318 for attr_name in self._attributes
319 if getattr(self, attr_name, None) is not None and attr_name[0] != "_"
320 )
321 attrs = ",".join(attrs_list)
322 return f"{type(self).__name__} [{attrs}]"
323
324 def __iter__(self) -> Iterator:
325 obj = self._selected_obj.set_axis(self._on)
326 obj = self._create_data(obj)
327 indexer = self._get_window_indexer()
328
329 start, end = indexer.get_window_bounds(
330 num_values=len(obj),
331 min_periods=self.min_periods,
332 center=self.center,
333 closed=self.closed,
334 step=self.step,
335 )
336 self._check_window_bounds(start, end, len(obj))
337
338 for s, e in zip(start, end, strict=True):
339 result = obj.iloc[slice(s, e)]
340 yield result
341
342 def _prep_values(self, values: ArrayLike) -> np.ndarray:
343 """Convert input to numpy arrays for Cython routines"""
344 if needs_i8_conversion(values.dtype):
345 raise NotImplementedError(
346 f"ops for {type(self).__name__} for this "
347 f"dtype {values.dtype} are not implemented"
348 )
349 # GH #12373 : rolling functions error on float32 data
350 # make sure the data is coerced to float64
351 try:
352 if isinstance(values, ExtensionArray):
353 values = values.to_numpy(np.float64, na_value=np.nan)
354 else:
355 values = ensure_float64(values)
356 except (ValueError, TypeError) as err:
357 raise TypeError(f"cannot handle this type -> {values.dtype}") from err
358
359 # Convert inf to nan for C funcs
360 inf = np.isinf(values)
361 if inf.any():
362 values = np.where(inf, np.nan, values)
363
364 return values
365
366 def _insert_on_column(self, result: DataFrame, obj: DataFrame) -> None:
367 # if we have an 'on' column we want to put it back into
368 # the results in the same location
369 from pandas import Series
370
371 if self.on is not None and not self._on.equals(obj.index):
372 name = self._on.name
373 extra_col = Series(self._on, index=self.obj.index, name=name, copy=False)
374 if name in result.columns:
375 # TODO: sure we want to overwrite results?
376 result[name] = extra_col
377 elif name in result.index.names:
378 pass
379 elif name in self._selected_obj.columns:
380 # insert in the same location as we had in _selected_obj
381 old_cols = self._selected_obj.columns
382 new_cols = result.columns
383 old_loc = old_cols.get_loc(name)
384 overlap = new_cols.intersection(old_cols[:old_loc])
385 new_loc = len(overlap)
386 result.insert(new_loc, name, extra_col)
387 else:
388 # insert at the end
389 result[name] = extra_col
390
391 @property
392 def _index_array(self) -> npt.NDArray[np.int64] | None:
393 # TODO: why do we get here with e.g. MultiIndex?
394 if isinstance(self._on, (PeriodIndex, DatetimeIndex, TimedeltaIndex)):
395 return self._on.asi8
396 elif isinstance(self._on.dtype, ArrowDtype) and self._on.dtype.kind in "mM":
397 return self._on.to_numpy(dtype=np.int64)
398 return None
399
400 def _resolve_output(self, out: DataFrame, obj: DataFrame) -> DataFrame:
401 """Validate and finalize result."""
402 if out.shape[1] == 0 and obj.shape[1] > 0:
403 raise DataError("No numeric types to aggregate")
404 if out.shape[1] == 0:
405 return obj.astype("float64")
406
407 self._insert_on_column(out, obj)
408 return out
409
410 def _get_window_indexer(self) -> BaseIndexer:
411 """
412 Return an indexer class that will compute the window start and end bounds
413 """
414 if isinstance(self.window, BaseIndexer):
415 return self.window
416 if self._win_freq_i8 is not None:
417 return VariableWindowIndexer(
418 index_array=self._index_array,
419 window_size=self._win_freq_i8,
420 center=self.center,
421 )
422 return FixedWindowIndexer(window_size=self.window)
423
424 def _apply_series(
425 self, homogeneous_func: Callable[..., ArrayLike], name: str | None = None
426 ) -> Series:
427 """
428 Series version of _apply_columnwise
429 """
430 obj = self._create_data(self._selected_obj)
431
432 if name == "count":
433 # GH 12541: Special case for count where we support date-like types
434 obj = notna(obj).astype(int)
435 try:
436 values = self._prep_values(obj._values)
437 except (TypeError, NotImplementedError) as err:
438 raise DataError("No numeric types to aggregate") from err
439
440 result = homogeneous_func(values)
441 index = self._slice_axis_for_step(obj.index, result)
442 return obj._constructor(result, index=index, name=obj.name)
443
444 def _apply_columnwise(
445 self,
446 homogeneous_func: Callable[..., ArrayLike],
447 name: str,
448 numeric_only: bool = False,
449 ) -> DataFrame | Series:
450 """
451 Apply the given function to the DataFrame broken down into homogeneous
452 sub-frames.
453 """
454 self._validate_numeric_only(name, numeric_only)
455 if self._selected_obj.ndim == 1:
456 return self._apply_series(homogeneous_func, name)
457
458 obj = self._create_data(self._selected_obj, numeric_only)
459 if name == "count":
460 # GH 12541: Special case for count where we support date-like types
461 obj = notna(obj).astype(int)
462 obj._mgr = obj._mgr.consolidate()
463
464 taker = []
465 res_values = []
466 for i, arr in enumerate(obj._iter_column_arrays()):
467 # GH#42736 operate column-wise instead of block-wise
468 # As of 2.0, hfunc will raise for nuisance columns
469 try:
470 arr = self._prep_values(arr)
471 except (TypeError, NotImplementedError) as err:
472 raise DataError(
473 f"Cannot aggregate non-numeric type: {arr.dtype}"
474 ) from err
475 res = homogeneous_func(arr)
476 res_values.append(res)
477 taker.append(i)
478
479 index = self._slice_axis_for_step(
480 obj.index, res_values[0] if len(res_values) > 0 else None
481 )
482 df = type(obj)._from_arrays(
483 res_values,
484 index=index,
485 columns=obj.columns.take(taker),
486 verify_integrity=False,
487 )
488
489 return self._resolve_output(df, obj)
490
491 def _apply_tablewise(
492 self,
493 homogeneous_func: Callable[..., ArrayLike],
494 name: str | None = None,
495 numeric_only: bool = False,
496 ) -> DataFrame | Series:
497 """
498 Apply the given function to the DataFrame across the entire object
499 """
500 if self._selected_obj.ndim == 1:
501 raise ValueError("method='table' not applicable for Series objects.")
502 obj = self._create_data(self._selected_obj, numeric_only)
503 values = self._prep_values(obj.to_numpy())
504 result = homogeneous_func(values)
505 index = self._slice_axis_for_step(obj.index, result)
506 columns = (
507 obj.columns
508 if result.shape[1] == len(obj.columns)
509 else obj.columns[:: self.step]
510 )
511 out = obj._constructor(result, index=index, columns=columns)
512
513 return self._resolve_output(out, obj)
514
515 def _apply_pairwise(
516 self,
517 target: DataFrame | Series,
518 other: DataFrame | Series | None,
519 pairwise: bool | None,
520 func: Callable[[DataFrame | Series, DataFrame | Series], DataFrame | Series],
521 numeric_only: bool,
522 ) -> DataFrame | Series:
523 """
524 Apply the given pairwise function given 2 pandas objects (DataFrame/Series)
525 """
526 target = self._create_data(target, numeric_only)
527 if other is None:
528 other = target
529 # only default unset
530 pairwise = True if pairwise is None else pairwise
531 elif not isinstance(other, (ABCDataFrame, ABCSeries)):
532 raise ValueError("other must be a DataFrame or Series")
533 elif other.ndim == 2 and numeric_only:
534 other = self._make_numeric_only(other)
535
536 return flex_binary_moment(target, other, func, pairwise=bool(pairwise))
537
538 def _apply(
539 self,
540 func: Callable[..., Any],
541 name: str,
542 numeric_only: bool = False,
543 numba_args: tuple[Any, ...] = (),
544 **kwargs,
545 ):
546 """
547 Rolling statistical measure using supplied function.
548
549 Designed to be used with passed-in Cython array-based functions.
550
551 Parameters
552 ----------
553 func : callable function to apply
554 name : str,
555 numba_args : tuple
556 args to be passed when func is a numba func
557 **kwargs
558 additional arguments for rolling function and window function
559
560 Returns
561 -------
562 y : type of input
563 """
564 window_indexer = self._get_window_indexer()
565 min_periods = (
566 self.min_periods
567 if self.min_periods is not None
568 else window_indexer.window_size
569 )
570
571 def homogeneous_func(values: np.ndarray):
572 # calculation function
573
574 if values.size == 0:
575 return values.copy()
576
577 def calc(x):
578 start, end = window_indexer.get_window_bounds(
579 num_values=len(x),
580 min_periods=min_periods,
581 center=self.center,
582 closed=self.closed,
583 step=self.step,
584 )
585 self._check_window_bounds(start, end, len(x))
586
587 return func(x, start, end, min_periods, *numba_args)
588
589 with np.errstate(all="ignore"):
590 result = calc(values)
591
592 return result
593
594 if self.method == "single":
595 return self._apply_columnwise(homogeneous_func, name, numeric_only)
596 else:
597 return self._apply_tablewise(homogeneous_func, name, numeric_only)
598
599 def _numba_apply(
600 self,
601 func: Callable[..., Any],
602 engine_kwargs: dict[str, bool] | None = None,
603 **func_kwargs,
604 ):
605 window_indexer = self._get_window_indexer()
606 min_periods = (
607 self.min_periods
608 if self.min_periods is not None
609 else window_indexer.window_size
610 )
611 obj = self._create_data(self._selected_obj)
612 values = self._prep_values(obj.to_numpy())
613 if values.ndim == 1:
614 values = values.reshape(-1, 1)
615 start, end = window_indexer.get_window_bounds(
616 num_values=len(values),
617 min_periods=min_periods,
618 center=self.center,
619 closed=self.closed,
620 step=self.step,
621 )
622 self._check_window_bounds(start, end, len(values))
623 # For now, map everything to float to match the Cython impl
624 # even though it is wrong
625 # TODO: Could preserve correct dtypes in future
626 # xref #53214
627 dtype_mapping = executor.float_dtype_mapping
628 aggregator = executor.generate_shared_aggregator(
629 func,
630 dtype_mapping,
631 is_grouped_kernel=False,
632 **get_jit_arguments(engine_kwargs),
633 )
634 result = aggregator(
635 values.T, start=start, end=end, min_periods=min_periods, **func_kwargs
636 ).T
637 index = self._slice_axis_for_step(obj.index, result)
638 if obj.ndim == 1:
639 result = result.squeeze()
640 out = obj._constructor(result, index=index, name=obj.name)
641 return out
642 else:
643 columns = self._slice_axis_for_step(obj.columns, result.T)
644 out = obj._constructor(result, index=index, columns=columns)
645 return self._resolve_output(out, obj)
646
647 def aggregate(self, func=None, *args, **kwargs):
648 relabeling, func, columns, order = reconstruct_func(func, **kwargs)
649 result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg()
650 if isinstance(result, ABCDataFrame) and relabeling:
651 result = result.iloc[:, order]
652 result.columns = columns
653 if result is None:
654 return self.apply(func, raw=False, args=args, kwargs=kwargs)
655 return result
656
657 agg = aggregate
658
659
660class BaseWindowGroupby(BaseWindow):
661 """
662 Provide the groupby windowing facilities.
663 """
664
665 _grouper: BaseGrouper
666 _as_index: bool
667 _attributes: list[str] = ["_grouper"]
668
669 def __init__(
670 self,
671 obj: DataFrame | Series,
672 *args,
673 _grouper: BaseGrouper,
674 _as_index: bool = True,
675 **kwargs,
676 ) -> None:
677 from pandas.core.groupby.ops import BaseGrouper
678
679 if not isinstance(_grouper, BaseGrouper):
680 raise ValueError("Must pass a BaseGrouper object.")
681 self._grouper = _grouper
682 self._as_index = _as_index
683 # GH 32262: It's convention to keep the grouping column in
684 # groupby.<agg_func>, but unexpected to users in
685 # groupby.rolling.<agg_func>
686 obj = obj.drop(columns=self._grouper.names, errors="ignore")
687 # GH 15354
688 if kwargs.get("step") is not None:
689 raise NotImplementedError("step not implemented for groupby")
690 super().__init__(obj, *args, **kwargs)
691
692 def _apply(
693 self,
694 func: Callable[..., Any],
695 name: str,
696 numeric_only: bool = False,
697 numba_args: tuple[Any, ...] = (),
698 **kwargs,
699 ) -> DataFrame | Series:
700 result = super()._apply(
701 func,
702 name,
703 numeric_only,
704 numba_args,
705 **kwargs,
706 )
707 # Reconstruct the resulting MultiIndex
708 # 1st set of levels = group by labels
709 # 2nd set of levels = original DataFrame/Series index
710 grouped_object_index = self.obj.index
711 grouped_index_name = [*grouped_object_index.names]
712 groupby_keys = copy.copy(self._grouper.names)
713 result_index_names = groupby_keys + grouped_index_name
714
715 drop_columns = [
716 key
717 for key in self._grouper.names
718 if key not in self.obj.index.names or key is None
719 ]
720
721 if len(drop_columns) != len(groupby_keys):
722 # Our result will have still kept the column in the result
723 result = result.drop(columns=drop_columns, errors="ignore")
724
725 codes = self._grouper.codes
726 levels = copy.copy(self._grouper.levels)
727
728 group_indices = self._grouper.indices.values()
729 if group_indices:
730 indexer = np.concatenate(list(group_indices))
731 else:
732 indexer = np.array([], dtype=np.intp)
733 codes = [c.take(indexer) for c in codes]
734
735 # if the index of the original dataframe needs to be preserved, append
736 # this index (but reordered) to the codes/levels from the groupby
737 if grouped_object_index is not None:
738 idx = grouped_object_index.take(indexer)
739 if not isinstance(idx, MultiIndex):
740 idx = MultiIndex.from_arrays([idx])
741 codes.extend(list(idx.codes))
742 levels.extend(list(idx.levels))
743
744 result_index = MultiIndex(
745 levels, codes, names=result_index_names, verify_integrity=False
746 )
747
748 result.index = result_index
749 if not self._as_index:
750 result = result.reset_index(level=list(range(len(groupby_keys))))
751 return result
752
753 def _apply_pairwise(
754 self,
755 target: DataFrame | Series,
756 other: DataFrame | Series | None,
757 pairwise: bool | None,
758 func: Callable[[DataFrame | Series, DataFrame | Series], DataFrame | Series],
759 numeric_only: bool,
760 ) -> DataFrame | Series:
761 """
762 Apply the given pairwise function given 2 pandas objects (DataFrame/Series)
763 """
764 # Manually drop the grouping column first
765 target = target.drop(columns=self._grouper.names, errors="ignore")
766 result = super()._apply_pairwise(target, other, pairwise, func, numeric_only)
767 # 1) Determine the levels + codes of the groupby levels
768 if other is not None and not all(
769 len(group) == len(other) for group in self._grouper.indices.values()
770 ):
771 # GH 42915
772 # len(other) != len(any group), so must reindex (expand) the result
773 # from flex_binary_moment to a "transform"-like result
774 # per groupby combination
775 old_result_len = len(result)
776 result = concat(
777 [
778 result.take(gb_indices).reindex(result.index)
779 for gb_indices in self._grouper.indices.values()
780 ]
781 )
782
783 gb_pairs = (
784 com.maybe_make_list(pair) for pair in self._grouper.indices.keys()
785 )
786 groupby_codes = []
787 groupby_levels = []
788 # e.g. [[1, 2], [4, 5]] as [[1, 4], [2, 5]]
789 for gb_level_pair in map(list, zip(*gb_pairs, strict=True)):
790 labels = np.repeat(np.array(gb_level_pair), old_result_len)
791 codes, levels = factorize(labels)
792 groupby_codes.append(codes)
793 groupby_levels.append(levels)
794 else:
795 # pairwise=True or len(other) == len(each group), so repeat
796 # the groupby labels by the number of columns in the original object
797 groupby_codes = self._grouper.codes
798 # error: Incompatible types in assignment (expression has type
799 # "List[Index]", variable has type "List[Union[ndarray, Index]]")
800 groupby_levels = self._grouper.levels # type: ignore[assignment]
801
802 group_indices = self._grouper.indices.values()
803 if group_indices:
804 indexer = np.concatenate(list(group_indices))
805 else:
806 indexer = np.array([], dtype=np.intp)
807
808 if target.ndim == 1:
809 repeat_by = 1
810 else:
811 repeat_by = len(target.columns)
812 groupby_codes = [
813 np.repeat(c.take(indexer), repeat_by) for c in groupby_codes
814 ]
815 # 2) Determine the levels + codes of the result from super()._apply_pairwise
816 if isinstance(result.index, MultiIndex):
817 result_codes = list(result.index.codes)
818 result_levels = list(result.index.levels)
819 result_names = list(result.index.names)
820 else:
821 idx_codes, idx_levels = factorize(result.index)
822 result_codes = [idx_codes]
823 result_levels = [idx_levels]
824 result_names = [result.index.name]
825
826 # 3) Create the resulting index by combining 1) + 2)
827 result_codes = groupby_codes + result_codes
828 result_levels = groupby_levels + result_levels
829 result_names = self._grouper.names + result_names
830
831 result_index = MultiIndex(
832 result_levels, result_codes, names=result_names, verify_integrity=False
833 )
834 result.index = result_index
835 return result
836
837 def _create_data(self, obj: NDFrameT, numeric_only: bool = False) -> NDFrameT:
838 """
839 Split data into blocks & return conformed data.
840 """
841 # Ensure the object we're rolling over is monotonically sorted relative
842 # to the groups
843 # GH 36197
844 if not obj.empty:
845 groupby_order = np.concatenate(list(self._grouper.indices.values())).astype(
846 np.int64
847 )
848 obj = obj.take(groupby_order)
849 return super()._create_data(obj, numeric_only)
850
851 def _gotitem(self, key, ndim, subset=None):
852 # we are setting the index on the actual object
853 # here so our index is carried through to the selected obj
854 # when we do the splitting for the groupby
855 if self.on is not None:
856 # GH 43355
857 subset = self.obj.set_index(self._on)
858 return super()._gotitem(key, ndim, subset=subset)
859
860
861@set_module("pandas.api.typing")
862class Window(BaseWindow):
863 """
864 Provide rolling window calculations.
865
866 Parameters
867 ----------
868 window : int, timedelta, str, offset, or BaseIndexer subclass
869 Interval of the moving window.
870
871 If an integer, the delta between the start and end of each window.
872 The number of points in the window depends on the ``closed`` argument.
873
874 If a timedelta, str, or offset, the time period of each window. Each
875 window will be a variable sized based on the observations included in
876 the time-period. This is only valid for datetimelike indexes.
877 To learn more about the offsets & frequency strings, please see
878 :ref:`this link<timeseries.offset_aliases>`.
879
880 If a BaseIndexer subclass, the window boundaries
881 based on the defined ``get_window_bounds`` method. Additional rolling
882 keyword arguments, namely ``min_periods``, ``center``, ``closed`` and
883 ``step`` will be passed to ``get_window_bounds``.
884
885 min_periods : int, default None
886 Minimum number of observations in window required to have a value;
887 otherwise, result is ``np.nan``.
888
889 For a window that is specified by an offset, ``min_periods`` will default to 1.
890
891 For a window that is specified by an integer, ``min_periods`` will default
892 to the size of the window.
893
894 center : bool, default False
895 If False, set the window labels as the right edge of the window index.
896
897 If True, set the window labels as the center of the window index.
898
899 win_type : str, default None
900 If ``None``, all points are evenly weighted.
901
902 If a string, it must be a valid `scipy.signal window function
903 <https://docs.scipy.org/doc/scipy/reference/signal.windows.html#module-scipy.signal.windows>`__.
904
905 Certain Scipy window types require additional parameters to be passed
906 in the aggregation function. The additional parameters must match
907 the keywords specified in the Scipy window type method signature.
908
909 on : str, optional
910 For a DataFrame, a column label or Index level on which
911 to calculate the rolling window, rather than the DataFrame's index.
912
913 Provided integer column is ignored and excluded from result since
914 an integer index is not used to calculate the rolling window.
915
916 closed : str, default None
917 Determines the inclusivity of points in the window
918
919 If ``'right'``, uses the window (first, last] meaning the last point
920 is included in the calculations.
921
922 If ``'left'``, uses the window [first, last) meaning the first point
923 is included in the calculations.
924
925 If ``'both'``, uses the window [first, last] meaning all points in
926 the window are included in the calculations.
927
928 If ``'neither'``, uses the window (first, last) meaning the first
929 and last points in the window are excluded from calculations.
930
931 () and [] are referencing open and closed set
932 notation respetively.
933
934 Default ``None`` (``'right'``).
935
936 step : int, default None
937 Evaluate the window at every ``step`` result, equivalent to slicing as
938 ``[::step]``. ``window`` must be an integer. Using a step argument other
939 than None or 1 will produce a result with a different shape than the input.
940
941 method : str {'single', 'table'}, default 'single'
942
943 Execute the rolling operation per single column or row (``'single'``)
944 or over the entire object (``'table'``).
945
946 This argument is only implemented when specifying ``engine='numba'``
947 in the method call.
948
949 Returns
950 -------
951 pandas.api.typing.Window or pandas.api.typing.Rolling
952 An instance of Window is returned if ``win_type`` is passed. Otherwise,
953 an instance of Rolling is returned.
954
955 See Also
956 --------
957 expanding : Provides expanding transformations.
958 ewm : Provides exponential weighted functions.
959
960 Notes
961 -----
962 See :ref:`Windowing Operations <window.generic>` for further usage details
963 and examples.
964
965 Examples
966 --------
967 >>> df = pd.DataFrame({"B": [0, 1, 2, np.nan, 4]})
968 >>> df
969 B
970 0 0.0
971 1 1.0
972 2 2.0
973 3 NaN
974 4 4.0
975
976 **window**
977
978 Rolling sum with a window length of 2 observations.
979
980 >>> df.rolling(2).sum()
981 B
982 0 NaN
983 1 1.0
984 2 3.0
985 3 NaN
986 4 NaN
987
988 Rolling sum with a window span of 2 seconds.
989
990 >>> df_time = pd.DataFrame(
991 ... {"B": [0, 1, 2, np.nan, 4]},
992 ... index=[
993 ... pd.Timestamp("20130101 09:00:00"),
994 ... pd.Timestamp("20130101 09:00:02"),
995 ... pd.Timestamp("20130101 09:00:03"),
996 ... pd.Timestamp("20130101 09:00:05"),
997 ... pd.Timestamp("20130101 09:00:06"),
998 ... ],
999 ... )
1000
1001 >>> df_time
1002 B
1003 2013-01-01 09:00:00 0.0
1004 2013-01-01 09:00:02 1.0
1005 2013-01-01 09:00:03 2.0
1006 2013-01-01 09:00:05 NaN
1007 2013-01-01 09:00:06 4.0
1008
1009 >>> df_time.rolling("2s").sum()
1010 B
1011 2013-01-01 09:00:00 0.0
1012 2013-01-01 09:00:02 1.0
1013 2013-01-01 09:00:03 3.0
1014 2013-01-01 09:00:05 NaN
1015 2013-01-01 09:00:06 4.0
1016
1017 Rolling sum with forward looking windows with 2 observations.
1018
1019 >>> indexer = pd.api.indexers.FixedForwardWindowIndexer(window_size=2)
1020 >>> df.rolling(window=indexer, min_periods=1).sum()
1021 B
1022 0 1.0
1023 1 3.0
1024 2 2.0
1025 3 4.0
1026 4 4.0
1027
1028 **min_periods**
1029
1030 Rolling sum with a window length of 2 observations, but only needs a minimum of 1
1031 observation to calculate a value.
1032
1033 >>> df.rolling(2, min_periods=1).sum()
1034 B
1035 0 0.0
1036 1 1.0
1037 2 3.0
1038 3 2.0
1039 4 4.0
1040
1041 **center**
1042
1043 Rolling sum with the result assigned to the center of the window index.
1044
1045 >>> df.rolling(3, min_periods=1, center=True).sum()
1046 B
1047 0 1.0
1048 1 3.0
1049 2 3.0
1050 3 6.0
1051 4 4.0
1052
1053 >>> df.rolling(3, min_periods=1, center=False).sum()
1054 B
1055 0 0.0
1056 1 1.0
1057 2 3.0
1058 3 3.0
1059 4 6.0
1060
1061 **step**
1062
1063 Rolling sum with a window length of 2 observations, minimum of 1 observation to
1064 calculate a value, and a step of 2.
1065
1066 >>> df.rolling(2, min_periods=1, step=2).sum()
1067 B
1068 0 0.0
1069 2 3.0
1070 4 4.0
1071
1072 **win_type**
1073
1074 Rolling sum with a window length of 2, using the Scipy ``'gaussian'``
1075 window type. ``std`` is required in the aggregation function.
1076
1077 >>> df.rolling(2, win_type="gaussian").sum(std=3)
1078 B
1079 0 NaN
1080 1 0.986207
1081 2 2.958621
1082 3 NaN
1083 4 NaN
1084
1085 **on**
1086
1087 Rolling sum with a window length of 2 days.
1088
1089 >>> df = pd.DataFrame(
1090 ... {
1091 ... "A": [
1092 ... pd.to_datetime("2020-01-01"),
1093 ... pd.to_datetime("2020-01-01"),
1094 ... pd.to_datetime("2020-01-02"),
1095 ... ],
1096 ... "B": [1, 2, 3],
1097 ... },
1098 ... index=pd.date_range("2020", periods=3),
1099 ... )
1100
1101 >>> df
1102 A B
1103 2020-01-01 2020-01-01 1
1104 2020-01-02 2020-01-01 2
1105 2020-01-03 2020-01-02 3
1106
1107 >>> df.rolling("2D", on="A").sum()
1108 A B
1109 2020-01-01 2020-01-01 1.0
1110 2020-01-02 2020-01-01 3.0
1111 2020-01-03 2020-01-02 6.0
1112 """
1113
1114 _attributes = [
1115 "window",
1116 "min_periods",
1117 "center",
1118 "win_type",
1119 "on",
1120 "closed",
1121 "step",
1122 "method",
1123 ]
1124
1125 def _validate(self) -> None:
1126 super()._validate()
1127
1128 if not isinstance(self.win_type, str):
1129 raise ValueError(f"Invalid win_type {self.win_type}")
1130 signal = import_optional_dependency(
1131 "scipy.signal.windows", extra="Scipy is required to generate window weight."
1132 )
1133 self._scipy_weight_generator = getattr(signal, self.win_type, None)
1134 if self._scipy_weight_generator is None:
1135 raise ValueError(f"Invalid win_type {self.win_type}")
1136
1137 if isinstance(self.window, BaseIndexer):
1138 raise NotImplementedError(
1139 "BaseIndexer subclasses not implemented with win_types."
1140 )
1141 if not is_integer(self.window) or self.window < 0:
1142 raise ValueError("window must be an integer 0 or greater")
1143
1144 if self.method != "single":
1145 raise NotImplementedError("'single' is the only supported method type.")
1146
1147 def _center_window(self, result: np.ndarray, offset: int) -> np.ndarray:
1148 """
1149 Center the result in the window for weighted rolling aggregations.
1150 """
1151 if offset > 0:
1152 lead_indexer = [slice(offset, None)]
1153 result = np.copy(result[tuple(lead_indexer)])
1154 return result
1155
1156 def _apply(
1157 self,
1158 func: Callable[[np.ndarray, int, int], np.ndarray],
1159 name: str,
1160 numeric_only: bool = False,
1161 numba_args: tuple[Any, ...] = (),
1162 **kwargs,
1163 ):
1164 """
1165 Rolling with weights statistical measure using supplied function.
1166
1167 Designed to be used with passed-in Cython array-based functions.
1168
1169 Parameters
1170 ----------
1171 func : callable function to apply
1172 name : str,
1173 numeric_only : bool, default False
1174 Whether to only operate on bool, int, and float columns
1175 numba_args : tuple
1176 unused
1177 **kwargs
1178 additional arguments for scipy windows if necessary
1179
1180 Returns
1181 -------
1182 y : type of input
1183 """
1184 # "None" not callable [misc]
1185 window = self._scipy_weight_generator( # type: ignore[misc]
1186 self.window, **kwargs
1187 )
1188 offset = (len(window) - 1) // 2 if self.center else 0
1189
1190 def homogeneous_func(values: np.ndarray):
1191 # calculation function
1192
1193 if values.size == 0:
1194 return values.copy()
1195
1196 def calc(x):
1197 additional_nans = np.full(offset, np.nan)
1198 x = np.concatenate((x, additional_nans))
1199 return func(
1200 x,
1201 window,
1202 self.min_periods if self.min_periods is not None else len(window),
1203 )
1204
1205 with np.errstate(all="ignore"):
1206 # Our weighted aggregations return memoryviews
1207 result = np.asarray(calc(values))
1208
1209 if self.center:
1210 result = self._center_window(result, offset)
1211
1212 return result
1213
1214 return self._apply_columnwise(homogeneous_func, name, numeric_only)[
1215 :: self.step
1216 ]
1217
1218 def aggregate(self, func=None, *args, **kwargs):
1219 """
1220 Aggregate using one or more operations over the specified axis.
1221
1222 Parameters
1223 ----------
1224 func : function, str, list or dict
1225 Function to use for aggregating the data. If a function, must either
1226 work when passed a Series/DataFrame or
1227 when passed to Series/DataFrame.apply.
1228
1229 Accepted combinations are:
1230
1231 - function
1232 - string function name
1233 - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
1234 - dict of axis labels -> functions, function names or list of such.
1235
1236 *args
1237 Positional arguments to pass to `func`.
1238 **kwargs
1239 Keyword arguments to pass to `func`.
1240
1241 Returns
1242 -------
1243 scalar, Series or DataFrame
1244
1245 The return can be:
1246
1247 * scalar : when Series.agg is called with single function
1248 * Series : when DataFrame.agg is called with a single function
1249 * DataFrame : when DataFrame.agg is called with several functions
1250
1251 See Also
1252 --------
1253 DataFrame.aggregate : Similar DataFrame method.
1254 Series.aggregate : Similar Series method.
1255
1256 Notes
1257 -----
1258 The aggregation operations are always performed over an axis, either the
1259 index (default) or the column axis. This behavior is different from
1260 `numpy` aggregation functions (`mean`, `median`, `prod`, `sum`, `std`,
1261 `var`), where the default is to compute the aggregation of the flattened
1262 array, e.g., ``numpy.mean(arr_2d)`` as opposed to
1263 ``numpy.mean(arr_2d, axis=0)``.
1264
1265 `agg` is an alias for `aggregate`. Use the alias.
1266
1267 Functions that mutate the passed object can produce unexpected
1268 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
1269 for more details.
1270
1271 A passed user-defined-function will be passed a Series for evaluation.
1272
1273 If ``func`` defines an index relabeling, ``axis`` must be ``0`` or ``index``.
1274
1275 Examples
1276 --------
1277 >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
1278 >>> df
1279 A B C
1280 0 1 4 7
1281 1 2 5 8
1282 2 3 6 9
1283
1284 >>> df.rolling(2, win_type="boxcar").agg("mean")
1285 A B C
1286 0 NaN NaN NaN
1287 1 1.5 4.5 7.5
1288 2 2.5 5.5 8.5
1289 """
1290 result = ResamplerWindowApply(self, func, args=args, kwargs=kwargs).agg()
1291 if result is None:
1292 # these must apply directly
1293 result = func(self)
1294
1295 return result
1296
1297 agg = aggregate
1298
1299 def sum(self, numeric_only: bool = False, **kwargs):
1300 """
1301 Calculate the rolling weighted window sum.
1302
1303 Parameters
1304 ----------
1305 numeric_only : bool, default False
1306 Include only float, int, boolean columns.
1307
1308 **kwargs
1309 Keyword arguments to configure the ``SciPy`` weighted window type.
1310
1311 Returns
1312 -------
1313 Series or DataFrame
1314 Return type is the same as the original object with ``np.float64`` dtype.
1315
1316 See Also
1317 --------
1318 Series.rolling : Calling rolling with Series data.
1319 DataFrame.rolling : Calling rolling with DataFrames.
1320 Series.sum : Aggregating sum for Series.
1321 DataFrame.sum : Aggregating sum for DataFrame.
1322
1323 Examples
1324 --------
1325 >>> ser = pd.Series([0, 1, 5, 2, 8])
1326
1327 To get an instance of :class:`~pandas.core.window.rolling.Window` we need
1328 to pass the parameter `win_type`.
1329
1330 >>> type(ser.rolling(2, win_type="gaussian"))
1331 <class 'pandas.api.typing.Window'>
1332
1333 In order to use the `SciPy` Gaussian window we need to provide the parameters
1334 `M` and `std`. The parameter `M` corresponds to 2 in our example.
1335 We pass the second parameter `std` as a parameter of the following method
1336 (`sum` in this case):
1337
1338 >>> ser.rolling(2, win_type="gaussian").sum(std=3)
1339 0 NaN
1340 1 0.986207
1341 2 5.917243
1342 3 6.903450
1343 4 9.862071
1344 dtype: float64
1345 """
1346 window_func = window_aggregations.roll_weighted_sum
1347 # error: Argument 1 to "_apply" of "Window" has incompatible type
1348 # "Callable[[ndarray, ndarray, int], ndarray]"; expected
1349 # "Callable[[ndarray, int, int], ndarray]"
1350 return self._apply(
1351 window_func, # type: ignore[arg-type]
1352 name="sum",
1353 numeric_only=numeric_only,
1354 **kwargs,
1355 )
1356
1357 def mean(self, numeric_only: bool = False, **kwargs):
1358 """
1359 Calculate the rolling weighted window mean.
1360
1361 Parameters
1362 ----------
1363 numeric_only : bool, default False
1364 Include only float, int, boolean columns.
1365
1366 **kwargs
1367 Keyword arguments to configure the ``SciPy`` weighted window type.
1368
1369 Returns
1370 -------
1371 Series or DataFrame
1372 Return type is the same as the original object with ``np.float64`` dtype.
1373
1374 See Also
1375 --------
1376 Series.rolling : Calling rolling with Series data.
1377 DataFrame.rolling : Calling rolling with DataFrames.
1378 Series.mean : Aggregating mean for Series.
1379 DataFrame.mean : Aggregating mean for DataFrame.
1380
1381 Examples
1382 --------
1383 >>> ser = pd.Series([0, 1, 5, 2, 8])
1384
1385 To get an instance of :class:`~pandas.core.window.rolling.Window` we need
1386 to pass the parameter `win_type`.
1387
1388 >>> type(ser.rolling(2, win_type="gaussian"))
1389 <class 'pandas.api.typing.Window'>
1390
1391 In order to use the `SciPy` Gaussian window we need to provide the parameters
1392 `M` and `std`. The parameter `M` corresponds to 2 in our example.
1393 We pass the second parameter `std` as a parameter of the following method:
1394
1395 >>> ser.rolling(2, win_type="gaussian").mean(std=3)
1396 0 NaN
1397 1 0.5
1398 2 3.0
1399 3 3.5
1400 4 5.0
1401 dtype: float64
1402 """
1403 window_func = window_aggregations.roll_weighted_mean
1404 # error: Argument 1 to "_apply" of "Window" has incompatible type
1405 # "Callable[[ndarray, ndarray, int], ndarray]"; expected
1406 # "Callable[[ndarray, int, int], ndarray]"
1407 return self._apply(
1408 window_func, # type: ignore[arg-type]
1409 name="mean",
1410 numeric_only=numeric_only,
1411 **kwargs,
1412 )
1413
1414 def var(self, ddof: int = 1, numeric_only: bool = False, **kwargs):
1415 """
1416 Calculate the rolling weighted window variance.
1417
1418 Parameters
1419 ----------
1420 ddof : int, default 1
1421 Delta Degrees of Freedom. The divisor used in calculations
1422 is ``N - ddof``, where ``N`` represents the number of elements.
1423 numeric_only : bool, default False
1424 Include only float, int, boolean columns.
1425
1426 **kwargs
1427 Keyword arguments to configure the ``SciPy`` weighted window type.
1428
1429 Returns
1430 -------
1431 Series or DataFrame
1432 Return type is the same as the original object with ``np.float64`` dtype.
1433
1434 See Also
1435 --------
1436 Series.rolling : Calling rolling with Series data.
1437 DataFrame.rolling : Calling rolling with DataFrames.
1438 Series.var : Aggregating var for Series.
1439 DataFrame.var : Aggregating var for DataFrame.
1440
1441 Examples
1442 --------
1443 >>> ser = pd.Series([0, 1, 5, 2, 8])
1444
1445 To get an instance of :class:`~pandas.core.window.rolling.Window` we need
1446 to pass the parameter `win_type`.
1447
1448 >>> type(ser.rolling(2, win_type="gaussian"))
1449 <class 'pandas.api.typing.Window'>
1450
1451 In order to use the `SciPy` Gaussian window we need to provide the parameters
1452 `M` and `std`. The parameter `M` corresponds to 2 in our example.
1453 We pass the second parameter `std` as a parameter of the following method:
1454
1455 >>> ser.rolling(2, win_type="gaussian").var(std=3)
1456 0 NaN
1457 1 0.5
1458 2 8.0
1459 3 4.5
1460 4 18.0
1461 dtype: float64
1462 """
1463 window_func = partial(window_aggregations.roll_weighted_var, ddof=ddof)
1464 kwargs.pop("name", None)
1465 return self._apply(window_func, name="var", numeric_only=numeric_only, **kwargs)
1466
1467 def std(self, ddof: int = 1, numeric_only: bool = False, **kwargs):
1468 """
1469 Calculate the rolling weighted window standard deviation.
1470
1471 Parameters
1472 ----------
1473 ddof : int, default 1
1474 Delta Degrees of Freedom. The divisor used in calculations
1475 is ``N - ddof``, where ``N`` represents the number of elements.
1476 numeric_only : bool, default False
1477 Include only float, int, boolean columns.
1478
1479 **kwargs
1480 Keyword arguments to configure the ``SciPy`` weighted window type.
1481
1482 Returns
1483 -------
1484 Series or DataFrame
1485 Return type is the same as the original object with ``np.float64`` dtype.
1486
1487 See Also
1488 --------
1489 Series.rolling : Calling rolling with Series data.
1490 DataFrame.rolling : Calling rolling with DataFrames.
1491 Series.std : Aggregating std for Series.
1492 DataFrame.std : Aggregating std for DataFrame.
1493
1494 Examples
1495 --------
1496 >>> ser = pd.Series([0, 1, 5, 2, 8])
1497
1498 To get an instance of :class:`~pandas.core.window.rolling.Window` we need
1499 to pass the parameter `win_type`.
1500
1501 >>> type(ser.rolling(2, win_type="gaussian"))
1502 <class 'pandas.api.typing.Window'>
1503
1504 In order to use the `SciPy` Gaussian window we need to provide the parameters
1505 `M` and `std`. The parameter `M` corresponds to 2 in our example.
1506 We pass the second parameter `std` as a parameter of the following method:
1507
1508 >>> ser.rolling(2, win_type="gaussian").std(std=3)
1509 0 NaN
1510 1 0.707107
1511 2 2.828427
1512 3 2.121320
1513 4 4.242641
1514 dtype: float64
1515 """
1516 return zsqrt(
1517 self.var(ddof=ddof, name="std", numeric_only=numeric_only, **kwargs)
1518 )
1519
1520
1521class RollingAndExpandingMixin(BaseWindow):
1522 def count(self, numeric_only: bool = False):
1523 window_func = window_aggregations.roll_sum
1524 return self._apply(window_func, name="count", numeric_only=numeric_only)
1525
1526 def apply(
1527 self,
1528 func: Callable[..., Any],
1529 raw: bool = False,
1530 engine: Literal["cython", "numba"] | None = None,
1531 engine_kwargs: dict[str, bool] | None = None,
1532 args: tuple[Any, ...] | None = None,
1533 kwargs: dict[str, Any] | None = None,
1534 ):
1535 if args is None:
1536 args = ()
1537 if kwargs is None:
1538 kwargs = {}
1539
1540 if not is_bool(raw):
1541 raise ValueError("raw parameter must be `True` or `False`")
1542
1543 numba_args: tuple[Any, ...] = ()
1544 if maybe_use_numba(engine):
1545 if raw is False:
1546 raise ValueError("raw must be `True` when using the numba engine")
1547 numba_args, kwargs = prepare_function_arguments(
1548 func, args, kwargs, num_required_args=1
1549 )
1550 if self.method == "single":
1551 apply_func = generate_numba_apply_func(
1552 func, **get_jit_arguments(engine_kwargs)
1553 )
1554 else:
1555 apply_func = generate_numba_table_func(
1556 func, **get_jit_arguments(engine_kwargs)
1557 )
1558 elif engine in ("cython", None):
1559 if engine_kwargs is not None:
1560 raise ValueError("cython engine does not accept engine_kwargs")
1561 apply_func = self._generate_cython_apply_func(args, kwargs, raw, func)
1562 else:
1563 raise ValueError("engine must be either 'numba' or 'cython'")
1564
1565 return self._apply(
1566 apply_func,
1567 name="apply",
1568 numba_args=numba_args,
1569 )
1570
1571 def _generate_cython_apply_func(
1572 self,
1573 args: tuple[Any, ...],
1574 kwargs: dict[str, Any],
1575 raw: bool | np.bool_,
1576 function: Callable[..., Any],
1577 ) -> Callable[[np.ndarray, np.ndarray, np.ndarray, int], np.ndarray]:
1578 from pandas import Series
1579
1580 window_func = partial(
1581 window_aggregations.roll_apply,
1582 args=args,
1583 kwargs=kwargs,
1584 raw=bool(raw),
1585 function=function,
1586 )
1587
1588 def apply_func(values, begin, end, min_periods, raw=raw):
1589 if not raw:
1590 # GH 45912
1591 values = Series(values, index=self._on, copy=False)
1592 return window_func(values, begin, end, min_periods)
1593
1594 return apply_func
1595
1596 @overload
1597 def pipe(
1598 self,
1599 func: Callable[Concatenate[Self, P], T],
1600 *args: P.args,
1601 **kwargs: P.kwargs,
1602 ) -> T: ...
1603
1604 @overload
1605 def pipe(
1606 self,
1607 func: tuple[Callable[..., T], str],
1608 *args: Any,
1609 **kwargs: Any,
1610 ) -> T: ...
1611
1612 def pipe(
1613 self,
1614 func: Callable[Concatenate[Self, P], T] | tuple[Callable[..., T], str],
1615 *args: Any,
1616 **kwargs: Any,
1617 ) -> T:
1618 return com.pipe(self, func, *args, **kwargs)
1619
1620 def sum(
1621 self,
1622 numeric_only: bool = False,
1623 engine: Literal["cython", "numba"] | None = None,
1624 engine_kwargs: dict[str, bool] | None = None,
1625 ):
1626 if maybe_use_numba(engine):
1627 if self.method == "table":
1628 func = generate_manual_numpy_nan_agg_with_axis(np.nansum)
1629 return self.apply(
1630 func,
1631 raw=True,
1632 engine=engine,
1633 engine_kwargs=engine_kwargs,
1634 )
1635 else:
1636 from pandas.core._numba.kernels import sliding_sum
1637
1638 return self._numba_apply(sliding_sum, engine_kwargs)
1639 window_func = window_aggregations.roll_sum
1640 return self._apply(window_func, name="sum", numeric_only=numeric_only)
1641
1642 def max(
1643 self,
1644 numeric_only: bool = False,
1645 engine: Literal["cython", "numba"] | None = None,
1646 engine_kwargs: dict[str, bool] | None = None,
1647 ):
1648 if maybe_use_numba(engine):
1649 if self.method == "table":
1650 func = generate_manual_numpy_nan_agg_with_axis(np.nanmax)
1651 return self.apply(
1652 func,
1653 raw=True,
1654 engine=engine,
1655 engine_kwargs=engine_kwargs,
1656 )
1657 else:
1658 from pandas.core._numba.kernels import sliding_min_max
1659
1660 return self._numba_apply(sliding_min_max, engine_kwargs, is_max=True)
1661 window_func = window_aggregations.roll_max
1662 return self._apply(window_func, name="max", numeric_only=numeric_only)
1663
1664 def min(
1665 self,
1666 numeric_only: bool = False,
1667 engine: Literal["cython", "numba"] | None = None,
1668 engine_kwargs: dict[str, bool] | None = None,
1669 ):
1670 if maybe_use_numba(engine):
1671 if self.method == "table":
1672 func = generate_manual_numpy_nan_agg_with_axis(np.nanmin)
1673 return self.apply(
1674 func,
1675 raw=True,
1676 engine=engine,
1677 engine_kwargs=engine_kwargs,
1678 )
1679 else:
1680 from pandas.core._numba.kernels import sliding_min_max
1681
1682 return self._numba_apply(sliding_min_max, engine_kwargs, is_max=False)
1683 window_func = window_aggregations.roll_min
1684 return self._apply(window_func, name="min", numeric_only=numeric_only)
1685
1686 def mean(
1687 self,
1688 numeric_only: bool = False,
1689 engine: Literal["cython", "numba"] | None = None,
1690 engine_kwargs: dict[str, bool] | None = None,
1691 ):
1692 if maybe_use_numba(engine):
1693 if self.method == "table":
1694 func = generate_manual_numpy_nan_agg_with_axis(np.nanmean)
1695 return self.apply(
1696 func,
1697 raw=True,
1698 engine=engine,
1699 engine_kwargs=engine_kwargs,
1700 )
1701 else:
1702 from pandas.core._numba.kernels import sliding_mean
1703
1704 return self._numba_apply(sliding_mean, engine_kwargs)
1705 window_func = window_aggregations.roll_mean
1706 return self._apply(window_func, name="mean", numeric_only=numeric_only)
1707
1708 def median(
1709 self,
1710 numeric_only: bool = False,
1711 engine: Literal["cython", "numba"] | None = None,
1712 engine_kwargs: dict[str, bool] | None = None,
1713 ):
1714 if maybe_use_numba(engine):
1715 if self.method == "table":
1716 func = generate_manual_numpy_nan_agg_with_axis(np.nanmedian)
1717 else:
1718 func = np.nanmedian
1719
1720 return self.apply(
1721 func,
1722 raw=True,
1723 engine=engine,
1724 engine_kwargs=engine_kwargs,
1725 )
1726 window_func = window_aggregations.roll_median_c
1727 return self._apply(window_func, name="median", numeric_only=numeric_only)
1728
1729 def std(
1730 self,
1731 ddof: int = 1,
1732 numeric_only: bool = False,
1733 engine: Literal["cython", "numba"] | None = None,
1734 engine_kwargs: dict[str, bool] | None = None,
1735 ):
1736 if maybe_use_numba(engine):
1737 if self.method == "table":
1738 raise NotImplementedError("std not supported with method='table'")
1739 from pandas.core._numba.kernels import sliding_var
1740
1741 return zsqrt(self._numba_apply(sliding_var, engine_kwargs, ddof=ddof))
1742 window_func = window_aggregations.roll_var
1743
1744 def zsqrt_func(values, begin, end, min_periods):
1745 return zsqrt(window_func(values, begin, end, min_periods, ddof=ddof))
1746
1747 return self._apply(
1748 zsqrt_func,
1749 name="std",
1750 numeric_only=numeric_only,
1751 )
1752
1753 def var(
1754 self,
1755 ddof: int = 1,
1756 numeric_only: bool = False,
1757 engine: Literal["cython", "numba"] | None = None,
1758 engine_kwargs: dict[str, bool] | None = None,
1759 ):
1760 if maybe_use_numba(engine):
1761 if self.method == "table":
1762 raise NotImplementedError("var not supported with method='table'")
1763 from pandas.core._numba.kernels import sliding_var
1764
1765 return self._numba_apply(sliding_var, engine_kwargs, ddof=ddof)
1766 window_func = partial(window_aggregations.roll_var, ddof=ddof)
1767 return self._apply(
1768 window_func,
1769 name="var",
1770 numeric_only=numeric_only,
1771 )
1772
1773 def skew(self, numeric_only: bool = False):
1774 window_func = window_aggregations.roll_skew
1775 return self._apply(
1776 window_func,
1777 name="skew",
1778 numeric_only=numeric_only,
1779 )
1780
1781 def sem(self, ddof: int = 1, numeric_only: bool = False):
1782 # Raise here so error message says sem instead of std
1783 self._validate_numeric_only("sem", numeric_only)
1784 return self.std(numeric_only=numeric_only, ddof=ddof) / (
1785 self.count(numeric_only=numeric_only)
1786 ).pow(0.5)
1787
1788 def kurt(self, numeric_only: bool = False):
1789 window_func = window_aggregations.roll_kurt
1790 return self._apply(
1791 window_func,
1792 name="kurt",
1793 numeric_only=numeric_only,
1794 )
1795
1796 def first(self, numeric_only: bool = False):
1797 window_func = window_aggregations.roll_first
1798 return self._apply(
1799 window_func,
1800 name="first",
1801 numeric_only=numeric_only,
1802 )
1803
1804 def last(self, numeric_only: bool = False):
1805 window_func = window_aggregations.roll_last
1806 return self._apply(
1807 window_func,
1808 name="last",
1809 numeric_only=numeric_only,
1810 )
1811
1812 def quantile(
1813 self,
1814 q: float,
1815 interpolation: QuantileInterpolation = "linear",
1816 numeric_only: bool = False,
1817 ):
1818 if q == 1.0:
1819 window_func = window_aggregations.roll_max
1820 elif q == 0.0:
1821 window_func = window_aggregations.roll_min
1822 else:
1823 window_func = partial(
1824 window_aggregations.roll_quantile,
1825 quantile=q,
1826 interpolation=interpolation,
1827 )
1828
1829 return self._apply(window_func, name="quantile", numeric_only=numeric_only)
1830
1831 def rank(
1832 self,
1833 method: WindowingRankType = "average",
1834 ascending: bool = True,
1835 pct: bool = False,
1836 numeric_only: bool = False,
1837 ):
1838 window_func = partial(
1839 window_aggregations.roll_rank,
1840 method=method,
1841 ascending=ascending,
1842 percentile=pct,
1843 )
1844
1845 return self._apply(window_func, name="rank", numeric_only=numeric_only)
1846
1847 def nunique(
1848 self,
1849 numeric_only: bool = False,
1850 ):
1851 window_func = partial(
1852 window_aggregations.roll_nunique,
1853 )
1854
1855 return self._apply(window_func, name="nunique", numeric_only=numeric_only)
1856
1857 def cov(
1858 self,
1859 other: DataFrame | Series | None = None,
1860 pairwise: bool | None = None,
1861 ddof: int = 1,
1862 numeric_only: bool = False,
1863 ):
1864 if self.step is not None:
1865 raise NotImplementedError("step not implemented for cov")
1866 self._validate_numeric_only("cov", numeric_only)
1867
1868 from pandas import Series
1869
1870 def cov_func(x, y):
1871 x_array = self._prep_values(x)
1872 y_array = self._prep_values(y)
1873 window_indexer = self._get_window_indexer()
1874 min_periods = (
1875 self.min_periods
1876 if self.min_periods is not None
1877 else window_indexer.window_size
1878 )
1879 start, end = window_indexer.get_window_bounds(
1880 num_values=len(x_array),
1881 min_periods=min_periods,
1882 center=self.center,
1883 closed=self.closed,
1884 step=self.step,
1885 )
1886 self._check_window_bounds(start, end, len(x_array))
1887
1888 with np.errstate(all="ignore"):
1889 mean_x_y = window_aggregations.roll_mean(
1890 x_array * y_array, start, end, min_periods
1891 )
1892 mean_x = window_aggregations.roll_mean(x_array, start, end, min_periods)
1893 mean_y = window_aggregations.roll_mean(y_array, start, end, min_periods)
1894 count_x_y = window_aggregations.roll_sum(
1895 notna(x_array + y_array).astype(np.float64), start, end, 0
1896 )
1897 result = (mean_x_y - mean_x * mean_y) * (count_x_y / (count_x_y - ddof))
1898 return Series(result, index=x.index, name=x.name, copy=False)
1899
1900 return self._apply_pairwise(
1901 self._selected_obj, other, pairwise, cov_func, numeric_only
1902 )
1903
1904 def corr(
1905 self,
1906 other: DataFrame | Series | None = None,
1907 pairwise: bool | None = None,
1908 ddof: int = 1,
1909 numeric_only: bool = False,
1910 ):
1911 if self.step is not None:
1912 raise NotImplementedError("step not implemented for corr")
1913 self._validate_numeric_only("corr", numeric_only)
1914
1915 from pandas import Series
1916
1917 def corr_func(x, y):
1918 x_array = self._prep_values(x)
1919 y_array = self._prep_values(y)
1920 window_indexer = self._get_window_indexer()
1921 min_periods = (
1922 self.min_periods
1923 if self.min_periods is not None
1924 else window_indexer.window_size
1925 )
1926 start, end = window_indexer.get_window_bounds(
1927 num_values=len(x_array),
1928 min_periods=min_periods,
1929 center=self.center,
1930 closed=self.closed,
1931 step=self.step,
1932 )
1933 self._check_window_bounds(start, end, len(x_array))
1934
1935 with np.errstate(all="ignore"):
1936 mean_x_y = window_aggregations.roll_mean(
1937 x_array * y_array, start, end, min_periods
1938 )
1939 mean_x = window_aggregations.roll_mean(x_array, start, end, min_periods)
1940 mean_y = window_aggregations.roll_mean(y_array, start, end, min_periods)
1941 count_x_y = window_aggregations.roll_sum(
1942 notna(x_array + y_array).astype(np.float64), start, end, 0
1943 )
1944 x_var = window_aggregations.roll_var(
1945 x_array, start, end, min_periods, ddof
1946 )
1947 y_var = window_aggregations.roll_var(
1948 y_array, start, end, min_periods, ddof
1949 )
1950 numerator = (mean_x_y - mean_x * mean_y) * (
1951 count_x_y / (count_x_y - ddof)
1952 )
1953 denominator = (x_var * y_var) ** 0.5
1954 result = numerator / denominator
1955 return Series(result, index=x.index, name=x.name, copy=False)
1956
1957 return self._apply_pairwise(
1958 self._selected_obj, other, pairwise, corr_func, numeric_only
1959 )
1960
1961
1962@set_module("pandas.api.typing")
1963class Rolling(RollingAndExpandingMixin):
1964 _attributes: list[str] = [
1965 "window",
1966 "min_periods",
1967 "center",
1968 "win_type",
1969 "on",
1970 "closed",
1971 "step",
1972 "method",
1973 ]
1974
1975 def _validate(self) -> None:
1976 super()._validate()
1977
1978 # we allow rolling on a datetimelike index
1979 if (
1980 self.obj.empty
1981 or isinstance(self._on, (DatetimeIndex, TimedeltaIndex, PeriodIndex))
1982 or (isinstance(self._on.dtype, ArrowDtype) and self._on.dtype.kind in "mM")
1983 ) and isinstance(self.window, (str, BaseOffset, timedelta)):
1984 self._validate_datetimelike_monotonic()
1985
1986 # this will raise ValueError on non-fixed freqs
1987 try:
1988 freq = to_offset(self.window)
1989 except (TypeError, ValueError) as err:
1990 raise ValueError(
1991 f"passed window {self.window} is not "
1992 "compatible with a datetimelike index"
1993 ) from err
1994 if isinstance(self._on, PeriodIndex):
1995 # error: Incompatible types in assignment (expression has type
1996 # "float", variable has type "Optional[int]")
1997 self._win_freq_i8 = freq.nanos / ( # type: ignore[assignment]
1998 self._on.freq.nanos / self._on.freq.n
1999 )
2000 else:
2001 try:
2002 unit = dtype_to_unit(self._on.dtype) # type: ignore[arg-type]
2003 except TypeError:
2004 # if not a datetime dtype, eg for empty dataframes
2005 unit = "ns"
2006 unit = cast("TimeUnit", unit)
2007 self._win_freq_i8 = Timedelta(freq.nanos).as_unit(unit)._value
2008
2009 # min_periods must be an integer
2010 if self.min_periods is None:
2011 self.min_periods = 1
2012
2013 if self.step is not None:
2014 raise NotImplementedError(
2015 "step is not supported with frequency windows"
2016 )
2017
2018 elif isinstance(self.window, BaseIndexer):
2019 # Passed BaseIndexer subclass should handle all other rolling kwargs
2020 pass
2021 elif not is_integer(self.window) or self.window < 0:
2022 raise ValueError("window must be an integer 0 or greater")
2023
2024 def _validate_datetimelike_monotonic(self) -> None:
2025 """
2026 Validate self._on is monotonic (increasing or decreasing) and has
2027 no NaT values for frequency windows.
2028 """
2029 if self._on.hasnans:
2030 self._raise_monotonic_error("values must not have NaT")
2031 if not (self._on.is_monotonic_increasing or self._on.is_monotonic_decreasing):
2032 self._raise_monotonic_error("values must be monotonic")
2033
2034 def _raise_monotonic_error(self, msg: str):
2035 on = self.on
2036 if on is None:
2037 on = "index"
2038 raise ValueError(f"{on} {msg}")
2039
2040 def aggregate(self, func=None, *args, **kwargs):
2041 """
2042 Aggregate using one or more operations over the specified axis.
2043
2044 Parameters
2045 ----------
2046 func : function, str, list or dict
2047 Function to use for aggregating the data. If a function, must either
2048 work when passed a Series/Dataframe or
2049 when passed to Series/Dataframe.apply.
2050
2051 Accepted combinations are:
2052
2053 - function
2054 - string function name
2055 - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
2056 - dict of axis labels -> functions, function names or list of such.
2057
2058 *args
2059 Positional arguments to pass to `func`.
2060 **kwargs
2061 Keyword arguments to pass to `func`.
2062
2063 Returns
2064 -------
2065 scalar, Series or DataFrame
2066
2067 The return can be:
2068
2069 * scalar : when Series.agg is called with single function
2070 * Series : when DataFrame.agg is called with a single function
2071 * DataFrame : when DataFrame.agg is called with several functions
2072
2073 See Also
2074 --------
2075 Series.rolling : Calling object with Series data.
2076 DataFrame.rolling : Calling object with DataFrame data.
2077
2078 Notes
2079 -----
2080 The aggregation operations are always performed over an axis, either the
2081 index (default) or the column axis. This behavior is different from
2082 `numpy` aggregation functions (`mean`, `median`, `prod`, `sum`, `std`,
2083 `var`), where the default is to compute the aggregation of the flattened
2084 array, e.g., ``numpy.mean(arr_2d)`` as opposed to
2085 ``numpy.mean(arr_2d, axis=0)``.
2086
2087 `agg` is an alias for `aggregate`. Use the alias.
2088
2089 Functions that mutate the passed object can produce unexpected
2090 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
2091 for more details.
2092
2093 A passed user-defined-function will be passed a Series for evaluation.
2094
2095 If ``func`` defines an index relabeling, ``axis`` must be ``0`` or ``index``.
2096
2097 Examples
2098 --------
2099 >>> df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6], "C": [7, 8, 9]})
2100 >>> df
2101 A B C
2102 0 1 4 7
2103 1 2 5 8
2104 2 3 6 9
2105
2106 >>> df.rolling(2).sum()
2107 A B C
2108 0 NaN NaN NaN
2109 1 3.0 9.0 15.0
2110 2 5.0 11.0 17.0
2111
2112 >>> df.rolling(2).agg({"A": "sum", "B": "min"})
2113 A B
2114 0 NaN NaN
2115 1 3.0 4.0
2116 2 5.0 5.0
2117 """
2118 return super().aggregate(func, *args, **kwargs)
2119
2120 agg = aggregate
2121
2122 def count(self, numeric_only: bool = False):
2123 """
2124 Calculate the rolling count of non NaN observations.
2125
2126 Parameters
2127 ----------
2128 numeric_only : bool, default False
2129 Include only float, int, boolean columns.
2130
2131 Returns
2132 -------
2133 Series or DataFrame
2134 Return type is the same as the original object with ``np.float64`` dtype.
2135
2136 See Also
2137 --------
2138 Series.rolling : Calling rolling with Series data.
2139 DataFrame.rolling : Calling rolling with DataFrames.
2140 Series.count : Aggregating count for Series.
2141 DataFrame.count : Aggregating count for DataFrame.
2142
2143 Examples
2144 --------
2145 >>> s = pd.Series([2, 3, np.nan, 10])
2146 >>> s.rolling(2).count()
2147 0 NaN
2148 1 2.0
2149 2 1.0
2150 3 1.0
2151 dtype: float64
2152 >>> s.rolling(3).count()
2153 0 NaN
2154 1 NaN
2155 2 2.0
2156 3 2.0
2157 dtype: float64
2158 >>> s.rolling(4).count()
2159 0 NaN
2160 1 NaN
2161 2 NaN
2162 3 3.0
2163 dtype: float64
2164 """
2165 return super().count(numeric_only)
2166
2167 def apply(
2168 self,
2169 func: Callable[..., Any],
2170 raw: bool = False,
2171 engine: Literal["cython", "numba"] | None = None,
2172 engine_kwargs: dict[str, bool] | None = None,
2173 args: tuple[Any, ...] | None = None,
2174 kwargs: dict[str, Any] | None = None,
2175 ):
2176 """
2177 Calculate the rolling custom aggregation function.
2178
2179 Parameters
2180 ----------
2181 func : function
2182 Must produce a single value from an ndarray input if ``raw=True``
2183 or a single value from a Series if ``raw=False``. Can also accept a
2184 Numba JIT function with ``engine='numba'`` specified.
2185
2186 raw : bool, default False
2187 * ``False`` : passes each row or column as a Series to the
2188 function.
2189 * ``True`` : the passed function will receive ndarray
2190 objects instead.
2191
2192 If you are just applying a NumPy reduction function this will
2193 achieve much better performance.
2194
2195 engine : str, default None
2196 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
2197 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
2198 Only available when ``raw`` is set to ``True``.
2199 * ``None`` : Defaults to ``'cython'`` or
2200 globally setting ``compute.use_numba``.
2201
2202 engine_kwargs : dict, default None
2203 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2204 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2205 and ``parallel`` dictionary keys. The values must either be ``True`` or
2206 ``False``.
2207
2208 The default ``engine_kwargs`` for the ``'numba'`` engine is
2209 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
2210 applied to both the ``func`` and the ``apply`` rolling aggregation.
2211
2212 args : tuple, default None
2213 Positional arguments to be passed into func.
2214
2215 kwargs : dict, default None
2216 Keyword arguments to be passed into func.
2217
2218 Returns
2219 -------
2220 Series or DataFrame
2221 Return type is the same as the original object with ``np.float64`` dtype.
2222
2223 See Also
2224 --------
2225 Series.rolling : Calling rolling with Series data.
2226 DataFrame.rolling : Calling rolling with DataFrames.
2227 Series.apply : Aggregating apply for Series.
2228 DataFrame.apply : Aggregating apply for DataFrame.
2229
2230 Examples
2231 --------
2232 >>> ser = pd.Series([1, 6, 5, 4])
2233 >>> ser.rolling(2).apply(lambda s: s.sum() - s.min())
2234 0 NaN
2235 1 6.0
2236 2 6.0
2237 3 5.0
2238 dtype: float64
2239 """
2240 return super().apply(
2241 func,
2242 raw=raw,
2243 engine=engine,
2244 engine_kwargs=engine_kwargs,
2245 args=args,
2246 kwargs=kwargs,
2247 )
2248
2249 @overload
2250 def pipe(
2251 self,
2252 func: Callable[Concatenate[Self, P], T],
2253 *args: P.args,
2254 **kwargs: P.kwargs,
2255 ) -> T: ...
2256
2257 @overload
2258 def pipe(
2259 self,
2260 func: tuple[Callable[..., T], str],
2261 *args: Any,
2262 **kwargs: Any,
2263 ) -> T: ...
2264
2265 @final
2266 def pipe(
2267 self,
2268 func: Callable[Concatenate[Self, P], T] | tuple[Callable[..., T], str],
2269 *args: Any,
2270 **kwargs: Any,
2271 ) -> T:
2272 """
2273 Apply a ``func`` with arguments to this Rolling object and return its result.
2274
2275 Use `.pipe` when you want to improve readability by chaining together
2276 functions that expect
2277 Series, DataFrames, GroupBy, Rolling, Expanding or Resampler
2278 objects.
2279 Instead of writing
2280
2281 >>> h = lambda x, arg2, arg3: x + 1 - arg2 * arg3
2282 >>> g = lambda x, arg1: x * 5 / arg1
2283 >>> f = lambda x: x**4
2284 >>> df = pd.DataFrame(
2285 ... {"A": [1, 2, 3, 4]}, index=pd.date_range("2012-08-02", periods=4)
2286 ... )
2287 >>> h(g(f(df.rolling("2D")), arg1=1), arg2=2, arg3=3) # doctest: +SKIP
2288
2289 You can write
2290
2291 >>> (
2292 ... df.rolling("2D").pipe(f).pipe(g, arg1=1).pipe(h, arg2=2, arg3=3)
2293 ... ) # doctest: +SKIP
2294
2295 which is much more readable.
2296
2297 Parameters
2298 ----------
2299 func : callable or tuple of (callable, str)
2300 Function to apply to this Rolling object or, alternatively,
2301 a `(callable, data_keyword)` tuple where `data_keyword` is a
2302 string indicating the keyword of `callable` that expects the
2303 Rolling object.
2304 *args : iterable, optional
2305 Positional arguments passed into `func`.
2306 **kwargs : dict, optional
2307 A dictionary of keyword arguments passed into `func`.
2308
2309 Returns
2310 -------
2311 Rolling
2312 The original object with the function `func` applied.
2313
2314 See Also
2315 --------
2316 Series.pipe : Apply a function with arguments to a series.
2317 DataFrame.pipe: Apply a function with arguments to a dataframe.
2318 apply : Apply function to each group instead of to the
2319 full Rolling object.
2320
2321 Notes
2322 -----
2323 See more `here
2324 <https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#piping-function-calls>`__.
2325
2326 Examples
2327 --------
2328
2329 >>> df = pd.DataFrame(
2330 ... {"A": [1, 2, 3, 4]}, index=pd.date_range("2012-08-02", periods=4)
2331 ... )
2332 >>> df
2333 A
2334 2012-08-02 1
2335 2012-08-03 2
2336 2012-08-04 3
2337 2012-08-05 4
2338
2339 To get the difference between each rolling
2340 2-day window's maximum and minimum
2341 value in one pass, you can do
2342
2343 >>> df.rolling("2D").pipe(lambda x: x.max() - x.min())
2344 A
2345 2012-08-02 0.0
2346 2012-08-03 1.0
2347 2012-08-04 1.0
2348 2012-08-05 1.0
2349 """
2350 return super().pipe(func, *args, **kwargs)
2351
2352 def sum(
2353 self,
2354 numeric_only: bool = False,
2355 engine: Literal["cython", "numba"] | None = None,
2356 engine_kwargs: dict[str, bool] | None = None,
2357 ):
2358 """
2359 Calculate the rolling sum.
2360
2361 Parameters
2362 ----------
2363 numeric_only : bool, default False
2364 Include only float, int, boolean columns.
2365
2366 engine : str, default None
2367 * ``'cython'`` : Runs the operation through C-extensions from cython.
2368 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2369 * ``None`` : Defaults to ``'cython'`` or
2370 globally setting ``compute.use_numba``
2371
2372 engine_kwargs : dict, default None
2373 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2374 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2375 and ``parallel`` dictionary keys. The values must either be ``True`` or
2376 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2377 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2378
2379 Returns
2380 -------
2381 Series or DataFrame
2382 Return type is the same as the original object with ``np.float64`` dtype.
2383
2384 See Also
2385 --------
2386 Series.rolling : Calling rolling with Series data.
2387 DataFrame.rolling : Calling rolling with DataFrames.
2388 Series.sum : Aggregating sum for Series.
2389 DataFrame.sum : Aggregating sum for DataFrame.
2390
2391 Notes
2392 -----
2393 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba`
2394 for extended documentation and performance considerations
2395 for the Numba engine.
2396
2397 Examples
2398 --------
2399 >>> s = pd.Series([1, 2, 3, 4, 5])
2400 >>> s
2401 0 1
2402 1 2
2403 2 3
2404 3 4
2405 4 5
2406 dtype: int64
2407
2408 >>> s.rolling(3).sum()
2409 0 NaN
2410 1 NaN
2411 2 6.0
2412 3 9.0
2413 4 12.0
2414 dtype: float64
2415
2416 >>> s.rolling(3, center=True).sum()
2417 0 NaN
2418 1 6.0
2419 2 9.0
2420 3 12.0
2421 4 NaN
2422 dtype: float64
2423
2424 For DataFrame, each sum is computed column-wise.
2425
2426 >>> df = pd.DataFrame({"A": s, "B": s**2})
2427 >>> df
2428 A B
2429 0 1 1
2430 1 2 4
2431 2 3 9
2432 3 4 16
2433 4 5 25
2434
2435 >>> df.rolling(3).sum()
2436 A B
2437 0 NaN NaN
2438 1 NaN NaN
2439 2 6.0 14.0
2440 3 9.0 29.0
2441 4 12.0 50.0
2442 """
2443 return super().sum(
2444 numeric_only=numeric_only,
2445 engine=engine,
2446 engine_kwargs=engine_kwargs,
2447 )
2448
2449 def max(
2450 self,
2451 numeric_only: bool = False,
2452 *args,
2453 engine: Literal["cython", "numba"] | None = None,
2454 engine_kwargs: dict[str, bool] | None = None,
2455 **kwargs,
2456 ):
2457 """
2458 Calculate the rolling maximum.
2459
2460 Parameters
2461 ----------
2462 numeric_only : bool, default False
2463 Include only float, int, boolean columns.
2464
2465 *args : iterable, optional
2466 Positional arguments passed into ``func``.
2467
2468 engine : str, default None
2469 * ``'cython'`` : Runs the operation through C-extensions from cython.
2470 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2471 * ``None`` : Defaults to ``'cython'`` or
2472 globally setting ``compute.use_numba``
2473
2474 engine_kwargs : dict, default None
2475 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2476 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2477 and ``parallel`` dictionary keys. The values must either be ``True`` or
2478 ``False``.
2479
2480 The default ``engine_kwargs`` for the ``'numba'`` engine is
2481 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2482
2483 **kwargs : mapping, optional
2484 A dictionary of keyword arguments passed into ``func``.
2485
2486 Returns
2487 -------
2488 Series or DataFrame
2489 Return type is the same as the original object with ``np.float64`` dtype.
2490
2491 See Also
2492 --------
2493 Series.rolling : Calling rolling with Series data.
2494 DataFrame.rolling : Calling rolling with DataFrames.
2495 Series.max : Aggregating max for Series.
2496 DataFrame.max : Aggregating max for DataFrame.
2497
2498 Notes
2499 -----
2500 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba`
2501 for extended documentation and performance considerations
2502 for the Numba engine.
2503
2504 Examples
2505 --------
2506 >>> ser = pd.Series([1, 2, 3, 4])
2507 >>> ser.rolling(2).max()
2508 0 NaN
2509 1 2.0
2510 2 3.0
2511 3 4.0
2512 dtype: float64
2513 """
2514 return super().max(
2515 numeric_only=numeric_only,
2516 engine=engine,
2517 engine_kwargs=engine_kwargs,
2518 )
2519
2520 def min(
2521 self,
2522 numeric_only: bool = False,
2523 engine: Literal["cython", "numba"] | None = None,
2524 engine_kwargs: dict[str, bool] | None = None,
2525 ):
2526 """
2527 Calculate the rolling minimum.
2528
2529 Parameters
2530 ----------
2531 numeric_only : bool, default False
2532 Include only float, int, boolean columns.
2533
2534 engine : str, default None
2535 * ``'cython'`` : Runs the operation through C-extensions from cython.
2536 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2537 * ``None`` : Defaults to ``'cython'`` or
2538 globally setting ``compute.use_numba``
2539
2540 engine_kwargs : dict, default None
2541 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2542 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2543 and ``parallel`` dictionary keys. The values must either be ``True`` or
2544 ``False``.
2545
2546 The default ``engine_kwargs`` for the ``'numba'`` engine is
2547 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2548
2549 Returns
2550 -------
2551 Series or DataFrame
2552 Return type is the same as the original object with ``np.float64`` dtype.
2553
2554 See Also
2555 --------
2556 Series.rolling : Calling rolling with Series data.
2557 DataFrame.rolling : Calling rolling with DataFrames.
2558 Series.min : Aggregating min for Series.
2559 DataFrame.min : Aggregating min for DataFrame.
2560
2561 Notes
2562 -----
2563 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba`
2564 for extended documentation and performance considerations
2565 for the Numba engine.
2566
2567 Examples
2568 --------
2569 Performing a rolling minimum with a window size of 3.
2570
2571 >>> s = pd.Series([4, 3, 5, 2, 6])
2572 >>> s.rolling(3).min()
2573 0 NaN
2574 1 NaN
2575 2 3.0
2576 3 2.0
2577 4 2.0
2578 dtype: float64
2579 """
2580 return super().min(
2581 numeric_only=numeric_only,
2582 engine=engine,
2583 engine_kwargs=engine_kwargs,
2584 )
2585
2586 def mean(
2587 self,
2588 numeric_only: bool = False,
2589 engine: Literal["cython", "numba"] | None = None,
2590 engine_kwargs: dict[str, bool] | None = None,
2591 ):
2592 """
2593 Calculate the rolling mean.
2594
2595 Parameters
2596 ----------
2597 numeric_only : bool, default False
2598 Include only float, int, boolean columns.
2599
2600 engine : str, default None
2601 * ``'cython'`` : Runs the operation through C-extensions from cython.
2602 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2603 * ``None`` : Defaults to ``'cython'`` or
2604 globally setting ``compute.use_numba``
2605
2606 engine_kwargs : dict, default None
2607 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2608 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2609 and ``parallel`` dictionary keys. The values must either be ``True`` or
2610 ``False``.
2611
2612 The default ``engine_kwargs`` for the ``'numba'`` engine is
2613 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2614
2615 Returns
2616 -------
2617 Series or DataFrame
2618 Return type is the same as the original object with ``np.float64`` dtype.
2619
2620 See Also
2621 --------
2622 Series.rolling : Calling rolling with Series data.
2623 DataFrame.rolling : Calling rolling with DataFrames.
2624 Series.mean : Aggregating mean for Series.
2625 DataFrame.mean : Aggregating mean for DataFrame.
2626
2627 Notes
2628 -----
2629 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba`
2630 for extended documentation and performance considerations
2631 for the Numba engine.
2632
2633 Examples
2634 --------
2635 The below examples will show rolling mean calculations with window sizes of
2636 two and three, respectively.
2637
2638 >>> s = pd.Series([1, 2, 3, 4])
2639 >>> s.rolling(2).mean()
2640 0 NaN
2641 1 1.5
2642 2 2.5
2643 3 3.5
2644 dtype: float64
2645
2646 >>> s.rolling(3).mean()
2647 0 NaN
2648 1 NaN
2649 2 2.0
2650 3 3.0
2651 dtype: float64
2652 """
2653 return super().mean(
2654 numeric_only=numeric_only,
2655 engine=engine,
2656 engine_kwargs=engine_kwargs,
2657 )
2658
2659 def median(
2660 self,
2661 numeric_only: bool = False,
2662 engine: Literal["cython", "numba"] | None = None,
2663 engine_kwargs: dict[str, bool] | None = None,
2664 ):
2665 """
2666 Calculate the rolling median.
2667
2668 Parameters
2669 ----------
2670 numeric_only : bool, default False
2671 Include only float, int, boolean columns.
2672
2673 engine : str, default None
2674 * ``'cython'`` : Runs the operation through C-extensions from cython.
2675 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2676 * ``None`` : Defaults to ``'cython'`` or
2677 globally setting ``compute.use_numba``
2678
2679 engine_kwargs : dict, default None
2680 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2681 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2682 and ``parallel`` dictionary keys. The values must either be ``True`` or
2683 ``False``.
2684
2685 The default ``engine_kwargs`` for the ``'numba'`` engine is
2686 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2687
2688 Returns
2689 -------
2690 Series or DataFrame
2691 Return type is the same as the original object with ``np.float64`` dtype.
2692
2693 See Also
2694 --------
2695 Series.rolling : Calling rolling with Series data.
2696 DataFrame.rolling : Calling rolling with DataFrames.
2697 Series.median : Aggregating median for Series.
2698 DataFrame.median : Aggregating median for DataFrame.
2699
2700 Notes
2701 -----
2702 See :ref:`window.numba_engine` and :ref:`enhancingperf.numba`
2703 for extended documentation and performance considerations
2704 for the Numba engine.
2705
2706 Examples
2707 --------
2708 Compute the rolling median of a series with a window size of 3.
2709
2710 >>> s = pd.Series([0, 1, 2, 3, 4])
2711 >>> s.rolling(3).median()
2712 0 NaN
2713 1 NaN
2714 2 1.0
2715 3 2.0
2716 4 3.0
2717 dtype: float64
2718 """
2719 return super().median(
2720 numeric_only=numeric_only,
2721 engine=engine,
2722 engine_kwargs=engine_kwargs,
2723 )
2724
2725 def std(
2726 self,
2727 ddof: int = 1,
2728 numeric_only: bool = False,
2729 engine: Literal["cython", "numba"] | None = None,
2730 engine_kwargs: dict[str, bool] | None = None,
2731 ):
2732 """
2733 Calculate the rolling standard deviation.
2734
2735 Parameters
2736 ----------
2737 ddof : int, default 1
2738 Delta Degrees of Freedom. The divisor used in calculations
2739 is ``N - ddof``, where ``N`` represents the number of elements.
2740
2741 numeric_only : bool, default False
2742 Include only float, int, boolean columns.
2743
2744 engine : str, default None
2745 * ``'cython'`` : Runs the operation through C-extensions from cython.
2746 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2747 * ``None`` : Defaults to ``'cython'`` or
2748 globally setting ``compute.use_numba``
2749
2750 engine_kwargs : dict, default None
2751 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2752 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2753 and ``parallel`` dictionary keys. The values must either be ``True`` or
2754 ``False``.
2755
2756 The default ``engine_kwargs`` for the ``'numba'`` engine is
2757 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2758
2759 Returns
2760 -------
2761 Series or DataFrame
2762 Return type is the same as the original object with ``np.float64`` dtype.
2763
2764 See Also
2765 --------
2766 numpy.std : Equivalent method for NumPy array.
2767 Series.rolling : Calling rolling with Series data.
2768 DataFrame.rolling : Calling rolling with DataFrames.
2769 Series.std : Aggregating std for Series.
2770 DataFrame.std : Aggregating std for DataFrame.
2771
2772 Notes
2773 -----
2774 The default ``ddof`` of 1 used in :meth:`Series.std` is different
2775 than the default ``ddof`` of 0 in :func:`numpy.std`.
2776
2777 A minimum of one period is required for the rolling calculation.
2778
2779 Examples
2780 --------
2781 >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
2782 >>> s.rolling(3).std()
2783 0 NaN
2784 1 NaN
2785 2 0.577350
2786 3 1.000000
2787 4 1.000000
2788 5 1.154701
2789 6 0.000000
2790 dtype: float64
2791 """
2792 return super().std(
2793 ddof=ddof,
2794 numeric_only=numeric_only,
2795 engine=engine,
2796 engine_kwargs=engine_kwargs,
2797 )
2798
2799 def var(
2800 self,
2801 ddof: int = 1,
2802 numeric_only: bool = False,
2803 engine: Literal["cython", "numba"] | None = None,
2804 engine_kwargs: dict[str, bool] | None = None,
2805 ):
2806 """
2807 Calculate the rolling variance.
2808
2809 Parameters
2810 ----------
2811 ddof : int, default 1
2812 Delta Degrees of Freedom. The divisor used in calculations
2813 is ``N - ddof``, where ``N`` represents the number of elements.
2814
2815 numeric_only : bool, default False
2816 Include only float, int, boolean columns.
2817
2818 engine : str, default None
2819 * ``'cython'`` : Runs the operation through C-extensions from cython.
2820 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2821 * ``None`` : Defaults to ``'cython'`` or
2822 globally setting ``compute.use_numba``
2823
2824 engine_kwargs : dict, default None
2825 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2826 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2827 and ``parallel`` dictionary keys. The values must either be ``True`` or
2828 ``False``.
2829
2830 The default ``engine_kwargs`` for the ``'numba'`` engine is
2831 ``{'nopython': True, 'nogil': False, 'parallel': False}``.
2832
2833 Returns
2834 -------
2835 Series or DataFrame
2836 Return type is the same as the original object with ``np.float64`` dtype.
2837
2838 See Also
2839 --------
2840 numpy.var : Equivalent method for NumPy array.
2841 Series.rolling : Calling rolling with Series data.
2842 DataFrame.rolling : Calling rolling with DataFrames.
2843 Series.var : Aggregating var for Series.
2844 DataFrame.var : Aggregating var for DataFrame.
2845
2846 Notes
2847 -----
2848 The default ``ddof`` of 1 used in :meth:`Series.var` is different
2849 than the default ``ddof`` of 0 in :func:`numpy.var`.
2850
2851 A minimum of one period is required for the rolling calculation.
2852
2853 Examples
2854 --------
2855 >>> s = pd.Series([5, 5, 6, 7, 5, 5, 5])
2856 >>> s.rolling(3).var()
2857 0 NaN
2858 1 NaN
2859 2 0.333333
2860 3 1.000000
2861 4 1.000000
2862 5 1.333333
2863 6 0.000000
2864 dtype: float64
2865 """
2866 return super().var(
2867 ddof=ddof,
2868 numeric_only=numeric_only,
2869 engine=engine,
2870 engine_kwargs=engine_kwargs,
2871 )
2872
2873 def skew(self, numeric_only: bool = False):
2874 """
2875 Calculate the rolling unbiased skewness.
2876
2877 Parameters
2878 ----------
2879 numeric_only : bool, default False
2880 Include only float, int, boolean columns.
2881
2882 Returns
2883 -------
2884 Series or DataFrame
2885 Return type is the same as the original object with ``np.float64`` dtype.
2886
2887 See Also
2888 --------
2889 scipy.stats.skew : Third moment of a probability density.
2890 Series.rolling : Calling rolling with Series data.
2891 DataFrame.rolling : Calling rolling with DataFrames.
2892 Series.skew : Aggregating skew for Series.
2893 DataFrame.skew : Aggregating skew for DataFrame.
2894
2895 Notes
2896 -----
2897
2898 A minimum of three periods is required for the rolling calculation.
2899
2900 Examples
2901 --------
2902 >>> ser = pd.Series([1, 5, 2, 7, 15, 6])
2903 >>> ser.rolling(3).skew().round(6)
2904 0 NaN
2905 1 NaN
2906 2 1.293343
2907 3 -0.585583
2908 4 0.670284
2909 5 1.652317
2910 dtype: float64
2911 """
2912 return super().skew(numeric_only=numeric_only)
2913
2914 def sem(self, ddof: int = 1, numeric_only: bool = False):
2915 """
2916 Calculate the rolling standard error of mean.
2917
2918 Parameters
2919 ----------
2920 ddof : int, default 1
2921 Delta Degrees of Freedom. The divisor used in calculations
2922 is ``N - ddof``, where ``N`` represents the number of elements.
2923
2924 numeric_only : bool, default False
2925 Include only float, int, boolean columns.
2926
2927 Returns
2928 -------
2929 Series or DataFrame
2930 Return type is the same as the original object with ``np.float64`` dtype.
2931
2932 See Also
2933 --------
2934 Series.rolling : Calling rolling with Series data.
2935 DataFrame.rolling : Calling rolling with DataFrames.
2936 Series.sem : Aggregating sem for Series.
2937 DataFrame.sem : Aggregating sem for DataFrame.
2938
2939 Notes
2940 -----
2941 A minimum of one period is required for the calculation.
2942
2943 Examples
2944 --------
2945 >>> s = pd.Series([0, 1, 2, 3])
2946 >>> s.rolling(2, min_periods=1).sem()
2947 0 NaN
2948 1 0.5
2949 2 0.5
2950 3 0.5
2951 dtype: float64
2952 """
2953 # Raise here so error message says sem instead of std
2954 self._validate_numeric_only("sem", numeric_only)
2955 return self.std(numeric_only=numeric_only, ddof=ddof) / (
2956 self.count(numeric_only)
2957 ).pow(0.5)
2958
2959 def kurt(self, numeric_only: bool = False):
2960 """
2961 Calculate the rolling Fisher's definition of kurtosis without bias.
2962
2963 Parameters
2964 ----------
2965 numeric_only : bool, default False
2966 Include only float, int, boolean columns.
2967
2968 Returns
2969 -------
2970 Series or DataFrame
2971 Return type is the same as the original object with ``np.float64`` dtype.
2972
2973 See Also
2974 --------
2975 scipy.stats.kurtosis : Reference SciPy method.
2976 Series.rolling : Calling rolling with Series data.
2977 DataFrame.rolling : Calling rolling with DataFrames.
2978 Series.kurt : Aggregating kurt for Series.
2979 DataFrame.kurt : Aggregating kurt for DataFrame.
2980
2981 Notes
2982 -----
2983 A minimum of four periods is required for the calculation.
2984
2985 Examples
2986 --------
2987 The example below will show a rolling calculation with a window size of
2988 four matching the equivalent function call using `scipy.stats`.
2989
2990 >>> arr = [1, 2, 3, 4, 999]
2991 >>> import scipy.stats
2992 >>> print(f"{scipy.stats.kurtosis(arr[:-1], bias=False):.6f}")
2993 -1.200000
2994 >>> print(f"{scipy.stats.kurtosis(arr[1:], bias=False):.6f}")
2995 3.999946
2996 >>> s = pd.Series(arr)
2997 >>> s.rolling(4).kurt()
2998 0 NaN
2999 1 NaN
3000 2 NaN
3001 3 -1.200000
3002 4 3.999946
3003 dtype: float64
3004 """
3005 return super().kurt(numeric_only=numeric_only)
3006
3007 def first(self, numeric_only: bool = False):
3008 """
3009 Calculate the rolling First (left-most) element of the window.
3010
3011 Parameters
3012 ----------
3013 numeric_only : bool, default False
3014 Include only float, int, boolean columns.
3015
3016 Returns
3017 -------
3018 Series or DataFrame
3019 Return type is the same as the original object with ``np.float64`` dtype.
3020
3021 See Also
3022 --------
3023 GroupBy.first : Similar method for GroupBy objects.
3024 Rolling.last : Method to get the last element in each window.
3025
3026 Examples
3027 --------
3028 The example below will show a rolling calculation with a window size of
3029 three.
3030
3031 >>> s = pd.Series(range(5))
3032 >>> s.rolling(3).first()
3033 0 NaN
3034 1 NaN
3035 2 0.0
3036 3 1.0
3037 4 2.0
3038 dtype: float64
3039 """
3040 return super().first(numeric_only=numeric_only)
3041
3042 def last(self, numeric_only: bool = False):
3043 """
3044 Calculate the rolling Last (right-most) element of the window.
3045
3046 Parameters
3047 ----------
3048 numeric_only : bool, default False
3049 Include only float, int, boolean columns.
3050
3051 Returns
3052 -------
3053 Series or DataFrame
3054 Return type is the same as the original object with ``np.float64`` dtype.
3055
3056 See Also
3057 --------
3058 GroupBy.last : Similar method for GroupBy objects.
3059 Rolling.first : Method to get the first element in each window.
3060
3061 Examples
3062 --------
3063 The example below will show a rolling calculation with a window size of
3064 three.
3065
3066 >>> s = pd.Series(range(5))
3067 >>> s.rolling(3).last()
3068 0 NaN
3069 1 NaN
3070 2 2.0
3071 3 3.0
3072 4 4.0
3073 dtype: float64
3074 """
3075 return super().last(numeric_only=numeric_only)
3076
3077 def quantile(
3078 self,
3079 q: float,
3080 interpolation: QuantileInterpolation = "linear",
3081 numeric_only: bool = False,
3082 ):
3083 """
3084 Calculate the rolling quantile.
3085
3086 Parameters
3087 ----------
3088 q : float
3089 Quantile to compute. 0 <= quantile <= 1.
3090
3091 interpolation : {'linear', 'lower', 'higher', 'midpoint', 'nearest'}
3092 This optional parameter specifies the interpolation method to use,
3093 when the desired quantile lies between two data points `i` and `j`:
3094
3095 * linear: `i + (j - i) * fraction`, where `fraction` is the
3096 fractional part of the index surrounded by `i` and `j`.
3097 * lower: `i`.
3098 * higher: `j`.
3099 * nearest: `i` or `j` whichever is nearest.
3100 * midpoint: (`i` + `j`) / 2.
3101
3102 numeric_only : bool, default False
3103 Include only float, int, boolean columns.
3104
3105 Returns
3106 -------
3107 Series or DataFrame
3108 Return type is the same as the original object with ``np.float64`` dtype.
3109
3110 See Also
3111 --------
3112 Series.rolling : Calling rolling with Series data.
3113 DataFrame.rolling : Calling rolling with DataFrames.
3114 Series.quantile : Aggregating quantile for Series.
3115 DataFrame.quantile : Aggregating quantile for DataFrame.
3116
3117 Examples
3118 --------
3119 >>> s = pd.Series([1, 2, 3, 4])
3120 >>> s.rolling(2).quantile(0.4, interpolation="lower")
3121 0 NaN
3122 1 1.0
3123 2 2.0
3124 3 3.0
3125 dtype: float64
3126
3127 >>> s.rolling(2).quantile(0.4, interpolation="midpoint")
3128 0 NaN
3129 1 1.5
3130 2 2.5
3131 3 3.5
3132 dtype: float64
3133 """
3134 return super().quantile(
3135 q=q,
3136 interpolation=interpolation,
3137 numeric_only=numeric_only,
3138 )
3139
3140 def rank(
3141 self,
3142 method: WindowingRankType = "average",
3143 ascending: bool = True,
3144 pct: bool = False,
3145 numeric_only: bool = False,
3146 ):
3147 """
3148 Calculate the rolling rank.
3149
3150 Parameters
3151 ----------
3152 method : {'average', 'min', 'max'}, default 'average'
3153 How to rank the group of records that have the same value (i.e. ties):
3154
3155 * average: average rank of the group
3156 * min: lowest rank in the group
3157 * max: highest rank in the group
3158
3159 ascending : bool, default True
3160 Whether or not the elements should be ranked in ascending order.
3161
3162 pct : bool, default False
3163 Whether or not to display the returned rankings in percentile
3164 form.
3165
3166 numeric_only : bool, default False
3167 Include only float, int, boolean columns.
3168
3169 Returns
3170 -------
3171 Series or DataFrame
3172 Return type is the same as the original object with ``np.float64`` dtype.
3173
3174 See Also
3175 --------
3176 Series.rolling : Calling rolling with Series data.
3177 DataFrame.rolling : Calling rolling with DataFrames.
3178 Series.rank : Aggregating rank for Series.
3179 DataFrame.rank : Aggregating rank for DataFrame.
3180
3181 Examples
3182 --------
3183 >>> s = pd.Series([1, 4, 2, 3, 5, 3])
3184 >>> s.rolling(3).rank()
3185 0 NaN
3186 1 NaN
3187 2 2.0
3188 3 2.0
3189 4 3.0
3190 5 1.5
3191 dtype: float64
3192
3193 >>> s.rolling(3).rank(method="max")
3194 0 NaN
3195 1 NaN
3196 2 2.0
3197 3 2.0
3198 4 3.0
3199 5 2.0
3200 dtype: float64
3201
3202 >>> s.rolling(3).rank(method="min")
3203 0 NaN
3204 1 NaN
3205 2 2.0
3206 3 2.0
3207 4 3.0
3208 5 1.0
3209 dtype: float64
3210 """
3211 return super().rank(
3212 method=method,
3213 ascending=ascending,
3214 pct=pct,
3215 numeric_only=numeric_only,
3216 )
3217
3218 def nunique(
3219 self,
3220 numeric_only: bool = False,
3221 ):
3222 """
3223 Calculate the rolling nunique.
3224
3225 .. versionadded:: 3.0.0
3226
3227 Parameters
3228 ----------
3229 numeric_only : bool, default False
3230 Include only float, int, boolean columns.
3231
3232 Returns
3233 -------
3234 Series or DataFrame
3235 Return type is the same as the original object with ``np.float64`` dtype.
3236
3237 See Also
3238 --------
3239 Series.rolling : Calling rolling with Series data.
3240 DataFrame.rolling : Calling rolling with DataFrames.
3241 Series.nunique : Aggregating nunique for Series.
3242 DataFrame.nunique : Aggregating nunique for DataFrame.
3243
3244 Examples
3245 --------
3246 >>> s = pd.Series([1, 4, 2, np.nan, 3, 3, 4, 5])
3247 >>> s.rolling(3).nunique()
3248 0 NaN
3249 1 NaN
3250 2 3.0
3251 3 NaN
3252 4 NaN
3253 5 NaN
3254 6 2.0
3255 7 3.0
3256 dtype: float64
3257 """
3258 return super().nunique(
3259 numeric_only=numeric_only,
3260 )
3261
3262 def cov(
3263 self,
3264 other: DataFrame | Series | None = None,
3265 pairwise: bool | None = None,
3266 ddof: int = 1,
3267 numeric_only: bool = False,
3268 ):
3269 """
3270 Calculate the rolling sample covariance.
3271
3272 Parameters
3273 ----------
3274 other : Series or DataFrame, optional
3275 If not supplied then will default to self and produce pairwise
3276 output.
3277
3278 pairwise : bool, default None
3279 If False then only matching columns between self and other will be
3280 used and the output will be a DataFrame.
3281 If True then all pairwise combinations will be calculated and the
3282 output will be a MultiIndexed DataFrame in the case of DataFrame
3283 inputs. In the case of missing elements, only complete pairwise
3284 observations will be used.
3285
3286 ddof : int, default 1
3287 Delta Degrees of Freedom. The divisor used in calculations
3288 is ``N - ddof``, where ``N`` represents the number of elements.
3289
3290 numeric_only : bool, default False
3291 Include only float, int, boolean columns.
3292
3293 Returns
3294 -------
3295 Series or DataFrame
3296 Return type is the same as the original object with ``np.float64`` dtype.
3297
3298 See Also
3299 --------
3300 Series.rolling : Calling rolling with Series data.
3301 DataFrame.rolling : Calling rolling with DataFrames.
3302 Series.cov : Aggregating cov for Series.
3303 DataFrame.cov : Aggregating cov for DataFrame.
3304
3305 Examples
3306 --------
3307 >>> ser1 = pd.Series([1, 2, 3, 4])
3308 >>> ser2 = pd.Series([1, 4, 5, 8])
3309 >>> ser1.rolling(2).cov(ser2)
3310 0 NaN
3311 1 1.5
3312 2 0.5
3313 3 1.5
3314 dtype: float64
3315 """
3316 return super().cov(
3317 other=other,
3318 pairwise=pairwise,
3319 ddof=ddof,
3320 numeric_only=numeric_only,
3321 )
3322
3323 def corr(
3324 self,
3325 other: DataFrame | Series | None = None,
3326 pairwise: bool | None = None,
3327 ddof: int = 1,
3328 numeric_only: bool = False,
3329 ):
3330 """
3331 Calculate the rolling correlation.
3332
3333 Parameters
3334 ----------
3335 other : Series or DataFrame, optional
3336 If not supplied then will default to self and produce pairwise
3337 output.
3338
3339 pairwise : bool, default None
3340 If False then only matching columns between self and other will be
3341 used and the output will be a DataFrame.
3342 If True then all pairwise combinations will be calculated and the
3343 output will be a MultiIndexed DataFrame in the case of DataFrame
3344 inputs. In the case of missing elements, only complete pairwise
3345 observations will be used.
3346
3347 ddof : int, default 1
3348 Delta Degrees of Freedom. The divisor used in calculations
3349 is ``N - ddof``, where ``N`` represents the number of elements.
3350
3351 numeric_only : bool, default False
3352 Include only float, int, boolean columns.
3353
3354 Returns
3355 -------
3356 Series or DataFrame
3357 Return type is the same as the original object with ``np.float64`` dtype.
3358
3359 See Also
3360 --------
3361 cov : Similar method to calculate covariance.
3362 numpy.corrcoef : NumPy Pearson's correlation calculation.
3363 Series.rolling : Calling rolling with Series data.
3364 DataFrame.rolling : Calling rolling with DataFrames.
3365 Series.corr : Aggregating corr for Series.
3366 DataFrame.corr : Aggregating corr for DataFrame.
3367
3368 Notes
3369 -----
3370 This function uses Pearson's definition of correlation
3371 (https://en.wikipedia.org/wiki/Pearson_correlation_coefficient).
3372
3373 When `other` is not specified, the output will be self correlation (e.g.
3374 all 1's), except for :class:`~pandas.DataFrame` inputs with `pairwise`
3375 set to `True`.
3376
3377 Function will return ``NaN`` for correlations of equal valued sequences;
3378 this is the result of a 0/0 division error.
3379
3380 When `pairwise` is set to `False`, only matching columns between `self` and
3381 `other` will be used.
3382
3383 When `pairwise` is set to `True`, the output will be a MultiIndex DataFrame
3384 with the original index on the first level, and the `other` DataFrame
3385 columns on the second level.
3386
3387 In the case of missing elements, only complete pairwise observations
3388 will be used.
3389
3390 Examples
3391 --------
3392 The below example shows a rolling calculation with a window size of
3393 four matching the equivalent function call using :meth:`numpy.corrcoef`.
3394
3395 >>> v1 = [3, 3, 3, 5, 8]
3396 >>> v2 = [3, 4, 4, 4, 8]
3397 >>> np.corrcoef(v1[:-1], v2[:-1])
3398 array([[1. , 0.33333333],
3399 [0.33333333, 1. ]])
3400 >>> np.corrcoef(v1[1:], v2[1:])
3401 array([[1. , 0.9169493],
3402 [0.9169493, 1. ]])
3403 >>> s1 = pd.Series(v1)
3404 >>> s2 = pd.Series(v2)
3405 >>> s1.rolling(4).corr(s2)
3406 0 NaN
3407 1 NaN
3408 2 NaN
3409 3 0.333333
3410 4 0.916949
3411 dtype: float64
3412
3413 The below example shows a similar rolling calculation on a
3414 DataFrame using the pairwise option.
3415
3416 >>> matrix = np.array(
3417 ... [[51.0, 35.0], [49.0, 30.0], [47.0, 32.0], [46.0, 31.0], [50.0, 36.0]]
3418 ... )
3419 >>> np.corrcoef(matrix[:-1, 0], matrix[:-1, 1])
3420 array([[1. , 0.6263001],
3421 [0.6263001, 1. ]])
3422 >>> np.corrcoef(matrix[1:, 0], matrix[1:, 1])
3423 array([[1. , 0.55536811],
3424 [0.55536811, 1. ]])
3425 >>> df = pd.DataFrame(matrix, columns=["X", "Y"])
3426 >>> df
3427 X Y
3428 0 51.0 35.0
3429 1 49.0 30.0
3430 2 47.0 32.0
3431 3 46.0 31.0
3432 4 50.0 36.0
3433 >>> df.rolling(4).corr(pairwise=True)
3434 X Y
3435 0 X NaN NaN
3436 Y NaN NaN
3437 1 X NaN NaN
3438 Y NaN NaN
3439 2 X NaN NaN
3440 Y NaN NaN
3441 3 X 1.000000 0.626300
3442 Y 0.626300 1.000000
3443 4 X 1.000000 0.555368
3444 Y 0.555368 1.000000
3445 """
3446 return super().corr(
3447 other=other,
3448 pairwise=pairwise,
3449 ddof=ddof,
3450 numeric_only=numeric_only,
3451 )
3452
3453
3454Rolling.__doc__ = Window.__doc__
3455
3456
3457@set_module("pandas.api.typing")
3458class RollingGroupby(BaseWindowGroupby, Rolling):
3459 """
3460 Provide a rolling groupby implementation.
3461 """
3462
3463 _attributes = Rolling._attributes + BaseWindowGroupby._attributes
3464
3465 def _get_window_indexer(self) -> GroupbyIndexer:
3466 """
3467 Return an indexer class that will compute the window start and end bounds
3468
3469 Returns
3470 -------
3471 GroupbyIndexer
3472 """
3473 rolling_indexer: type[BaseIndexer]
3474 indexer_kwargs: dict[str, Any] | None = None
3475 index_array = self._index_array
3476 if isinstance(self.window, BaseIndexer):
3477 rolling_indexer = type(self.window)
3478 indexer_kwargs = self.window.__dict__.copy()
3479 assert isinstance(indexer_kwargs, dict) # for mypy
3480 # We'll be using the index of each group later
3481 indexer_kwargs.pop("index_array", None)
3482 window = self.window
3483 elif self._win_freq_i8 is not None:
3484 rolling_indexer = VariableWindowIndexer
3485 # error: Incompatible types in assignment (expression has type
3486 # "int", variable has type "BaseIndexer")
3487 window = self._win_freq_i8 # type: ignore[assignment]
3488 else:
3489 rolling_indexer = FixedWindowIndexer
3490 window = self.window
3491 window_indexer = GroupbyIndexer(
3492 index_array=index_array,
3493 window_size=window,
3494 groupby_indices=self._grouper.indices,
3495 window_indexer=rolling_indexer,
3496 indexer_kwargs=indexer_kwargs,
3497 )
3498 return window_indexer
3499
3500 def _validate_datetimelike_monotonic(self) -> None:
3501 """
3502 Validate that each group in self._on is monotonic
3503 """
3504 # GH 46061
3505 if self._on.hasnans:
3506 self._raise_monotonic_error("values must not have NaT")
3507 for group_indices in self._grouper.indices.values():
3508 group_on = self._on.take(group_indices)
3509 if not (
3510 group_on.is_monotonic_increasing or group_on.is_monotonic_decreasing
3511 ):
3512 on = "index" if self.on is None else self.on
3513 raise ValueError(
3514 f"Each group within {on} must be monotonic. "
3515 f"Sort the values in {on} first."
3516 )