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