1"""
2Provide the groupby split-apply-combine paradigm. Define the GroupBy
3class providing the base-class of operations.
4
5The SeriesGroupBy and DataFrameGroupBy sub-class
6(defined in pandas.core.groupby.generic)
7expose these user-facing objects to provide specific functionality.
8"""
9
10from __future__ import annotations
11
12from collections.abc import (
13 Callable,
14 Hashable,
15 Iterable,
16 Iterator,
17 Mapping,
18 Sequence,
19)
20import datetime
21from functools import (
22 partial,
23 wraps,
24)
25from typing import (
26 TYPE_CHECKING,
27 Concatenate,
28 Literal,
29 Self,
30 TypeAlias,
31 TypeVar,
32 Union,
33 cast,
34 final,
35 overload,
36)
37import warnings
38
39import numpy as np
40
41from pandas._libs import (
42 Timestamp,
43 lib,
44)
45from pandas._libs.algos import rank_1d
46import pandas._libs.groupby as libgroupby
47from pandas._libs.missing import NA
48from pandas._typing import (
49 AnyArrayLike,
50 ArrayLike,
51 DtypeObj,
52 IndexLabel,
53 IntervalClosedType,
54 NDFrameT,
55 PositionalIndexer,
56 RandomState,
57 npt,
58)
59from pandas.compat.numpy import function as nv
60from pandas.errors import (
61 AbstractMethodError,
62 DataError,
63 Pandas4Warning,
64)
65from pandas.util._decorators import cache_readonly
66from pandas.util._exceptions import find_stack_level
67
68from pandas.core.dtypes.cast import (
69 coerce_indexer_dtype,
70 ensure_dtype_can_hold_na,
71)
72from pandas.core.dtypes.common import (
73 is_bool,
74 is_bool_dtype,
75 is_float_dtype,
76 is_hashable,
77 is_integer,
78 is_integer_dtype,
79 is_list_like,
80 is_numeric_dtype,
81 is_object_dtype,
82 is_scalar,
83 is_string_dtype,
84 needs_i8_conversion,
85 pandas_dtype,
86)
87from pandas.core.dtypes.missing import (
88 isna,
89 na_value_for_dtype,
90 notna,
91)
92
93from pandas.core import (
94 algorithms,
95 sample,
96)
97from pandas.core._numba import executor
98from pandas.core.arrays import (
99 ArrowExtensionArray,
100 BaseMaskedArray,
101 ExtensionArray,
102 FloatingArray,
103 IntegerArray,
104 SparseArray,
105)
106from pandas.core.arrays.string_ import StringDtype
107from pandas.core.arrays.string_arrow import ArrowStringArray
108from pandas.core.base import (
109 PandasObject,
110 SelectionMixin,
111)
112import pandas.core.common as com
113from pandas.core.frame import DataFrame
114from pandas.core.generic import NDFrame
115from pandas.core.groupby import (
116 base,
117 numba_,
118 ops,
119)
120from pandas.core.groupby.grouper import get_grouper
121from pandas.core.groupby.indexing import (
122 GroupByIndexingMixin,
123 GroupByNthSelector,
124)
125from pandas.core.indexes.api import (
126 Index,
127 MultiIndex,
128 default_index,
129)
130from pandas.core.internals.blocks import ensure_block_shape
131from pandas.core.series import Series
132from pandas.core.sorting import get_group_index_sorter
133from pandas.core.util.numba_ import (
134 get_jit_arguments,
135 maybe_use_numba,
136 prepare_function_arguments,
137)
138
139if TYPE_CHECKING:
140 from pandas._libs.tslibs import BaseOffset
141 from pandas._libs.tslibs.timedeltas import Timedelta
142 from pandas._typing import (
143 Any,
144 P,
145 T,
146 )
147
148 from pandas.core.indexers.objects import BaseIndexer
149 from pandas.core.resample import Resampler
150 from pandas.core.window import (
151 ExpandingGroupby,
152 ExponentialMovingWindowGroupby,
153 RollingGroupby,
154 )
155
156_groupby_agg_method_engine_template = """
157Compute {fname} of group values.
158
159Parameters
160----------
161numeric_only : bool, default {no}
162 Include only float, int, boolean columns.
163
164 .. versionchanged:: 2.0.0
165
166 numeric_only no longer accepts ``None``.
167
168min_count : int, default {mc}
169 The required number of valid values to perform the operation. If fewer
170 than ``min_count`` non-NA values are present the result will be NA.
171
172engine : str, default None {e}
173 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
174 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
175 Only available when ``raw`` is set to ``True``.
176 * ``None`` : Defaults to ``'cython'`` or globally setting
177 ``compute.use_numba``
178
179engine_kwargs : dict, default None {ek}
180 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
181 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
182 and ``parallel`` dictionary keys. The values must either be ``True`` or
183 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
184 ``{{'nopython': True, 'nogil': False, 'parallel': False}}`` and will be
185 applied to both the ``func`` and the ``apply`` groupby aggregation.
186
187Returns
188-------
189Series or DataFrame
190 Computed {fname} of values within each group.
191
192See Also
193--------
194SeriesGroupBy.min : Return the min of the group values.
195DataFrameGroupBy.min : Return the min of the group values.
196SeriesGroupBy.max : Return the max of the group values.
197DataFrameGroupBy.max : Return the max of the group values.
198SeriesGroupBy.sum : Return the sum of the group values.
199DataFrameGroupBy.sum : Return the sum of the group values.
200
201Examples
202--------
203{example}
204"""
205
206_groupby_agg_method_skipna_engine_template = """
207Compute {fname} of group values.
208
209Parameters
210----------
211numeric_only : bool, default {no}
212 Include only float, int, boolean columns.
213
214 .. versionchanged:: 2.0.0
215
216 numeric_only no longer accepts ``None``.
217
218min_count : int, default {mc}
219 The required number of valid values to perform the operation. If fewer
220 than ``min_count`` non-NA values are present the result will be NA.
221
222skipna : bool, default {s}
223 Exclude NA/null values. If the entire group is NA and ``skipna`` is
224 ``True``, the result will be NA.
225
226 .. versionchanged:: 3.0.0
227
228engine : str, default None {e}
229 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
230 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
231 Only available when ``raw`` is set to ``True``.
232 * ``None`` : Defaults to ``'cython'`` or globally setting
233 ``compute.use_numba``
234
235engine_kwargs : dict, default None {ek}
236 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
237 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
238 and ``parallel`` dictionary keys. The values must either be ``True`` or
239 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
240 ``{{'nopython': True, 'nogil': False, 'parallel': False}}`` and will be
241 applied to both the ``func`` and the ``apply`` groupby aggregation.
242
243Returns
244-------
245Series or DataFrame
246 Computed {fname} of values within each group.
247
248See Also
249--------
250SeriesGroupBy.min : Return the min of the group values.
251DataFrameGroupBy.min : Return the min of the group values.
252SeriesGroupBy.max : Return the max of the group values.
253DataFrameGroupBy.max : Return the max of the group values.
254SeriesGroupBy.sum : Return the sum of the group values.
255DataFrameGroupBy.sum : Return the sum of the group values.
256
257Examples
258--------
259{example}
260"""
261
262_pipe_template = """
263Apply a ``func`` with arguments to this %(klass)s object and return its result.
264
265Use `.pipe` when you want to improve readability by chaining together
266functions that expect Series, DataFrames, GroupBy or Resampler objects.
267Instead of writing
268
269>>> h = lambda x, arg2, arg3: x + 1 - arg2 * arg3
270>>> g = lambda x, arg1: x * 5 / arg1
271>>> f = lambda x: x ** 4
272>>> df = pd.DataFrame([["a", 4], ["b", 5]], columns=["group", "value"])
273>>> h(g(f(df.groupby('group')), arg1=1), arg2=2, arg3=3) # doctest: +SKIP
274
275You can write
276
277>>> (df.groupby('group')
278... .pipe(f)
279... .pipe(g, arg1=1)
280... .pipe(h, arg2=2, arg3=3)) # doctest: +SKIP
281
282which is much more readable.
283
284Parameters
285----------
286func : callable or tuple of (callable, str)
287 Function to apply to this %(klass)s object or, alternatively,
288 a `(callable, data_keyword)` tuple where `data_keyword` is a
289 string indicating the keyword of `callable` that expects the
290 %(klass)s object.
291*args : iterable, optional
292 Positional arguments passed into `func`.
293**kwargs : dict, optional
294 A dictionary of keyword arguments passed into `func`.
295
296Returns
297-------
298%(klass)s
299 The original object with the function `func` applied.
300
301See Also
302--------
303Series.pipe : Apply a function with arguments to a series.
304DataFrame.pipe: Apply a function with arguments to a dataframe.
305apply : Apply function to each group instead of to the
306 full %(klass)s object.
307
308Notes
309-----
310See more `here
311<https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#piping-function-calls>`_
312
313Examples
314--------
315%(examples)s
316"""
317
318_transform_template = """
319Call function producing a same-indexed %(klass)s on each group.
320
321Returns a %(klass)s having the same indexes as the original object
322filled with the transformed values.
323
324Parameters
325----------
326func : function, str
327 Function to apply to each group. See the Notes section below for requirements.
328
329 Accepted inputs are:
330
331 - String
332 - Python function
333 - Numba JIT function with ``engine='numba'`` specified.
334
335 Only passing a single function is supported with this engine.
336 If the ``'numba'`` engine is chosen, the function must be
337 a user defined function with ``values`` and ``index`` as the
338 first and second arguments respectively in the function signature.
339 Each group's index will be passed to the user defined function
340 and optionally available for use.
341
342 If a string is chosen, then it needs to be the name
343 of the groupby method you want to use.
344*args
345 Positional arguments to pass to func.
346engine : str, default None
347 * ``'cython'`` : Runs the function through C-extensions from cython.
348 * ``'numba'`` : Runs the function through JIT compiled code from numba.
349 * ``None`` : Defaults to ``'cython'`` or the global setting ``compute.use_numba``
350
351engine_kwargs : dict, default None
352 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
353 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
354 and ``parallel`` dictionary keys. The values must either be ``True`` or
355 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
356 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
357 applied to the function
358
359**kwargs
360 Keyword arguments to be passed into func.
361
362Returns
363-------
364%(klass)s
365 %(klass)s with the same indexes as the original object filled
366 with transformed values.
367
368See Also
369--------
370%(klass)s.groupby.apply : Apply function ``func`` group-wise and combine
371 the results together.
372%(klass)s.groupby.aggregate : Aggregate using one or more operations.
373%(klass)s.transform : Call ``func`` on self producing a %(klass)s with the
374 same axis shape as self.
375
376Notes
377-----
378Each group is endowed the attribute 'name' in case you need to know
379which group you are working on.
380
381The current implementation imposes three requirements on f:
382
383* f must return a value that either has the same shape as the input
384 subframe or can be broadcast to the shape of the input subframe.
385 For example, if `f` returns a scalar it will be broadcast to have the
386 same shape as the input subframe.
387* if this is a DataFrame, f must support application column-by-column
388 in the subframe. If f also supports application to the entire subframe,
389 then a fast path is used starting from the second chunk.
390* f must not mutate groups. Mutation is not supported and may
391 produce unexpected results. See :ref:`gotchas.udf-mutation` for more details.
392
393When using ``engine='numba'``, there will be no "fall back" behavior internally.
394The group data and group index will be passed as numpy arrays to the JITed
395user defined function, and no alternative execution attempts will be tried.
396
397The resulting dtype will reflect the return value of the passed ``func``,
398see the examples below.
399
400.. versionchanged:: 2.0.0
401
402 When using ``.transform`` on a grouped DataFrame and the transformation function
403 returns a DataFrame, pandas now aligns the result's index
404 with the input's index. You can call ``.to_numpy()`` on the
405 result of the transformation function to avoid alignment.
406
407Examples
408--------
409%(example)s"""
410
411
412@final
413class GroupByPlot(PandasObject):
414 """
415 Class implementing the .plot attribute for groupby objects.
416 """
417
418 def __init__(self, groupby: GroupBy) -> None:
419 self._groupby = groupby
420
421 def __call__(self, *args, **kwargs):
422 def f(self):
423 return self.plot(*args, **kwargs)
424
425 f.__name__ = "plot"
426 return self._groupby._python_apply_general(f, self._groupby._selected_obj)
427
428 def __getattr__(self, name: str):
429 def attr(*args, **kwargs):
430 def f(self):
431 return getattr(self.plot, name)(*args, **kwargs)
432
433 return self._groupby._python_apply_general(f, self._groupby._selected_obj)
434
435 return attr
436
437
438_KeysArgType: TypeAlias = (
439 Hashable
440 | list[Hashable]
441 | Callable[[Hashable], Hashable]
442 | list[Callable[[Hashable], Hashable]]
443 | Mapping[Hashable, Hashable]
444)
445
446
447class BaseGroupBy(PandasObject, SelectionMixin[NDFrameT], GroupByIndexingMixin):
448 _hidden_attrs = PandasObject._hidden_attrs | {
449 "as_index",
450 "dropna",
451 "exclusions",
452 "grouper",
453 "group_keys",
454 "keys",
455 "level",
456 "obj",
457 "observed",
458 "sort",
459 }
460
461 _grouper: ops.BaseGrouper
462 keys: _KeysArgType | None = None
463 level: IndexLabel | None = None
464 group_keys: bool
465
466 @final
467 def __len__(self) -> int:
468 return self._grouper.ngroups
469
470 @final
471 def __repr__(self) -> str:
472 # TODO: Better repr for GroupBy object
473 return object.__repr__(self)
474
475 @final
476 @property
477 def groups(self) -> dict[Hashable, Index]:
478 """
479 Dict {group name -> group labels}.
480
481 This property provides a dictionary representation of the groupings formed
482 during a groupby operation, where each key represents a unique group value from
483 the specified column(s), and each value is a list of index labels
484 that belong to that group.
485
486 See Also
487 --------
488 core.groupby.DataFrameGroupBy.get_group : Retrieve group from a
489 ``DataFrameGroupBy`` object with provided name.
490 core.groupby.SeriesGroupBy.get_group : Retrieve group from a
491 ``SeriesGroupBy`` object with provided name.
492 core.resample.Resampler.get_group : Retrieve group from a
493 ``Resampler`` object with provided name.
494
495 Examples
496 --------
497
498 For SeriesGroupBy:
499
500 >>> lst = ["a", "a", "b"]
501 >>> ser = pd.Series([1, 2, 3], index=lst)
502 >>> ser
503 a 1
504 a 2
505 b 3
506 dtype: int64
507 >>> ser.groupby(level=0).groups
508 {'a': ['a', 'a'], 'b': ['b']}
509
510 For DataFrameGroupBy:
511
512 >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]]
513 >>> df = pd.DataFrame(data, columns=["a", "b", "c"])
514 >>> df
515 a b c
516 0 1 2 3
517 1 1 5 6
518 2 7 8 9
519 >>> df.groupby(by="a").groups
520 {1: [0, 1], 7: [2]}
521
522 For Resampler:
523
524 >>> ser = pd.Series(
525 ... [1, 2, 3, 4],
526 ... index=pd.DatetimeIndex(
527 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
528 ... ),
529 ... )
530 >>> ser
531 2023-01-01 1
532 2023-01-15 2
533 2023-02-01 3
534 2023-02-15 4
535 dtype: int64
536 >>> ser.resample("MS").groups
537 {Timestamp('2023-01-01 00:00:00'): np.int64(2),
538 Timestamp('2023-02-01 00:00:00'): np.int64(4)}
539 """
540 if isinstance(self.keys, list) and len(self.keys) == 1:
541 warnings.warn(
542 "In a future version, the keys of `groups` will be a "
543 f"tuple with a single element, e.g. ({self.keys[0]},) , "
544 f"instead of a scalar, e.g. {self.keys[0]}, when grouping "
545 "by a list with a single element. Use ``df.groupby(by='a').groups`` "
546 "instead of ``df.groupby(by=['a']).groups`` to avoid this warning",
547 Pandas4Warning,
548 stacklevel=find_stack_level(),
549 )
550 return self._grouper.groups
551
552 @final
553 @property
554 def ngroups(self) -> int:
555 return self._grouper.ngroups
556
557 @final
558 @property
559 def indices(self) -> dict[Hashable, npt.NDArray[np.intp]]:
560 """
561 Dict {group name -> group indices}.
562
563 The dictionary keys represent the group labels (e.g., timestamps for a
564 time-based resampling operation), and the values are arrays of integer
565 positions indicating where the elements of each group are located in the
566 original data. This property is particularly useful when working with
567 resampled data, as it provides insight into how the original time-series data
568 has been grouped.
569
570 See Also
571 --------
572 core.groupby.DataFrameGroupBy.indices : Provides a mapping of group rows to
573 positions of the elements.
574 core.groupby.SeriesGroupBy.indices : Provides a mapping of group rows to
575 positions of the elements.
576 core.resample.Resampler.indices : Provides a mapping of group rows to
577 positions of the elements.
578
579 Examples
580 --------
581
582 For SeriesGroupBy:
583
584 >>> lst = ["a", "a", "b"]
585 >>> ser = pd.Series([1, 2, 3], index=lst)
586 >>> ser
587 a 1
588 a 2
589 b 3
590 dtype: int64
591 >>> ser.groupby(level=0).indices
592 {'a': array([0, 1]), 'b': array([2])}
593
594 For DataFrameGroupBy:
595
596 >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]]
597 >>> df = pd.DataFrame(
598 ... data, columns=["a", "b", "c"], index=["owl", "toucan", "eagle"]
599 ... )
600 >>> df
601 a b c
602 owl 1 2 3
603 toucan 1 5 6
604 eagle 7 8 9
605 >>> df.groupby(by=["a"]).indices
606 {np.int64(1): array([0, 1]), np.int64(7): array([2])}
607
608 For Resampler:
609
610 >>> ser = pd.Series(
611 ... [1, 2, 3, 4],
612 ... index=pd.DatetimeIndex(
613 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
614 ... ),
615 ... )
616 >>> ser
617 2023-01-01 1
618 2023-01-15 2
619 2023-02-01 3
620 2023-02-15 4
621 dtype: int64
622 >>> ser.resample("MS").indices
623 defaultdict(<class 'list'>, {Timestamp('2023-01-01 00:00:00'): [0, 1],
624 Timestamp('2023-02-01 00:00:00'): [2, 3]})
625 """
626 return self._grouper.indices
627
628 @final
629 def _get_index(self, name):
630 """
631 Safe get multiple indices, translate keys for
632 datelike to underlying repr.
633 """
634
635 def get_converter(s):
636 # possibly convert to the actual key types
637 # in the indices, could be a Timestamp or an np.datetime64
638 if isinstance(s, datetime.datetime):
639 return lambda key: Timestamp(key)
640 elif isinstance(s, np.datetime64):
641 return lambda key: Timestamp(key).asm8
642 else:
643 return lambda key: key
644
645 if isna(name):
646 return self.indices.get(np.nan, [])
647 if isinstance(name, tuple):
648 name = tuple(np.nan if isna(comp) else comp for comp in name)
649
650 if len(self.indices) > 0:
651 index_sample = next(iter(self.indices))
652 else:
653 index_sample = None # Dummy sample
654
655 if isinstance(index_sample, tuple):
656 if not isinstance(name, tuple):
657 msg = "must supply a tuple to get_group with multiple grouping keys"
658 raise ValueError(msg)
659 if not len(name) == len(index_sample):
660 try:
661 # If the original grouper was a tuple
662 return self.indices[name]
663 except KeyError as err:
664 # turns out it wasn't a tuple
665 msg = (
666 "must supply a same-length tuple to get_group "
667 "with multiple grouping keys"
668 )
669 raise ValueError(msg) from err
670
671 converters = (get_converter(s) for s in index_sample)
672 name = tuple(f(n) for f, n in zip(converters, name, strict=True))
673 else:
674 converter = get_converter(index_sample)
675 name = converter(name)
676
677 return self.indices.get(name, [])
678
679 @final
680 @cache_readonly
681 def _selected_obj(self):
682 # Note: _selected_obj is always just `self.obj` for SeriesGroupBy
683 if isinstance(self.obj, Series):
684 return self.obj
685
686 if self._selection is not None:
687 if is_hashable(self._selection):
688 # i.e. a single key, so selecting it will return a Series.
689 # In this case, _obj_with_exclusions would wrap the key
690 # in a list and return a single-column DataFrame.
691 return self.obj[self._selection]
692
693 # Otherwise _selection is equivalent to _selection_list, so
694 # _selected_obj matches _obj_with_exclusions, so we can reuse
695 # that and avoid making a copy.
696 return self._obj_with_exclusions
697
698 return self.obj
699
700 @final
701 def _dir_additions(self) -> set[str]:
702 return self.obj._dir_additions()
703
704 @overload
705 def pipe(
706 self,
707 func: Callable[Concatenate[Self, P], T],
708 *args: P.args,
709 **kwargs: P.kwargs,
710 ) -> T: ...
711
712 @overload
713 def pipe(
714 self,
715 func: tuple[Callable[..., T], str],
716 *args: Any,
717 **kwargs: Any,
718 ) -> T: ...
719
720 def pipe(
721 self,
722 func: Callable[Concatenate[Self, P], T] | tuple[Callable[..., T], str],
723 *args: Any,
724 **kwargs: Any,
725 ) -> T:
726 """
727 Apply a ``func`` with arguments to this GroupBy object and return its result.
728
729 Use `.pipe` when you want to improve readability by chaining together
730 functions that expect Series, DataFrames, GroupBy or Resampler objects.
731 Instead of writing
732
733 >>> h = lambda x, arg2, arg3: x + 1 - arg2 * arg3
734 >>> g = lambda x, arg1: x * 5 / arg1
735 >>> f = lambda x: x**4
736 >>> df = pd.DataFrame([["a", 4], ["b", 5]], columns=["group", "value"])
737 >>> h(g(f(df.groupby("group")), arg1=1), arg2=2, arg3=3) # doctest: +SKIP
738
739 You can write
740
741 >>> (
742 ... df.groupby("group").pipe(f).pipe(g, arg1=1).pipe(h, arg2=2, arg3=3)
743 ... ) # doctest: +SKIP
744
745 which is much more readable.
746
747 Parameters
748 ----------
749 func : callable or tuple of (callable, str)
750 Function to apply to this GroupBy object or, alternatively,
751 a `(callable, data_keyword)` tuple where `data_keyword` is a
752 string indicating the keyword of `callable` that expects the
753 GroupBy object.
754 *args : iterable, optional
755 Positional arguments passed into `func`.
756 **kwargs : dict, optional
757 A dictionary of keyword arguments passed into `func`.
758
759 Returns
760 -------
761 GroupBy
762 The return type of `func`.
763
764 See Also
765 --------
766 Series.pipe : Apply a function with arguments to a series.
767 DataFrame.pipe : Apply a function with arguments to a dataframe.
768 apply : Apply function to each group instead of to the
769 full GroupBy object.
770
771 Notes
772 -----
773 See more `here
774 <https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html#piping-function-calls>`_
775
776 Examples
777 --------
778 >>> df = pd.DataFrame({"A": "a b a b".split(), "B": [1, 2, 3, 4]})
779 >>> df
780 A B
781 0 a 1
782 1 b 2
783 2 a 3
784 3 b 4
785
786 To get the difference between each groups maximum and minimum value in one
787 pass, you can do
788
789 >>> df.groupby("A").pipe(lambda x: x.max() - x.min())
790 B
791 A
792 a 2
793 b 2
794 """
795 return com.pipe(self, func, *args, **kwargs)
796
797 @final
798 def get_group(self, name) -> DataFrame | Series:
799 """
800 Construct DataFrame from group with provided name.
801
802 Parameters
803 ----------
804 name : object
805 The name of the group to get as a DataFrame.
806
807 Returns
808 -------
809 Series or DataFrame
810 Get the respective Series or DataFrame corresponding to the group provided.
811
812 See Also
813 --------
814 DataFrameGroupBy.groups: Dictionary representation of the groupings formed
815 during a groupby operation.
816 DataFrameGroupBy.indices: Provides a mapping of group rows to positions
817 of the elements.
818 SeriesGroupBy.groups: Dictionary representation of the groupings formed
819 during a groupby operation.
820 SeriesGroupBy.indices: Provides a mapping of group rows to positions
821 of the elements.
822
823 Examples
824 --------
825
826 For SeriesGroupBy:
827
828 >>> lst = ["a", "a", "b"]
829 >>> ser = pd.Series([1, 2, 3], index=lst)
830 >>> ser
831 a 1
832 a 2
833 b 3
834 dtype: int64
835 >>> ser.groupby(level=0).get_group("a")
836 a 1
837 a 2
838 dtype: int64
839
840 For DataFrameGroupBy:
841
842 >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]]
843 >>> df = pd.DataFrame(
844 ... data, columns=["a", "b", "c"], index=["owl", "toucan", "eagle"]
845 ... )
846 >>> df
847 a b c
848 owl 1 2 3
849 toucan 1 5 6
850 eagle 7 8 9
851 >>> df.groupby(by=["a"]).get_group((1,))
852 a b c
853 owl 1 2 3
854 toucan 1 5 6
855
856 For Resampler:
857
858 >>> ser = pd.Series(
859 ... [1, 2, 3, 4],
860 ... index=pd.DatetimeIndex(
861 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
862 ... ),
863 ... )
864 >>> ser
865 2023-01-01 1
866 2023-01-15 2
867 2023-02-01 3
868 2023-02-15 4
869 dtype: int64
870 >>> ser.resample("MS").get_group("2023-01-01")
871 2023-01-01 1
872 2023-01-15 2
873 dtype: int64
874 """
875 keys = self.keys
876 level = self.level
877 # mypy doesn't recognize level/keys as being sized when passed to len
878 if (is_list_like(level) and len(level) == 1) or ( # type: ignore[arg-type]
879 is_list_like(keys) and len(keys) == 1 # type: ignore[arg-type]
880 ):
881 # GH#25971
882 if isinstance(name, tuple) and len(name) == 1:
883 name = name[0]
884 else:
885 raise KeyError(name)
886
887 inds = self._get_index(name)
888 if not len(inds):
889 raise KeyError(name)
890 return self._selected_obj.iloc[inds]
891
892 @final
893 def __iter__(self) -> Iterator[tuple[Hashable, NDFrameT]]:
894 """
895 Groupby iterator.
896
897 This method provides an iterator over the groups created by the ``resample``
898 or ``groupby`` operation on the object. The method yields tuples where
899 the first element is the label (group key) corresponding to each group or
900 resampled bin, and the second element is the subset of the data that falls
901 within that group or bin.
902
903 Returns
904 -------
905 Iterator
906 Generator yielding a sequence of (name, subsetted object)
907 for each group.
908
909 See Also
910 --------
911 Series.groupby : Group data by a specific key or column.
912 DataFrame.groupby : Group DataFrame using mapper or by columns.
913 DataFrame.resample : Resample a DataFrame.
914 Series.resample : Resample a Series.
915
916 Examples
917 --------
918
919 For SeriesGroupBy:
920
921 >>> lst = ["a", "a", "b"]
922 >>> ser = pd.Series([1, 2, 3], index=lst)
923 >>> ser
924 a 1
925 a 2
926 b 3
927 dtype: int64
928 >>> for x, y in ser.groupby(level=0):
929 ... print(f"{x}\\n{y}\\n")
930 a
931 a 1
932 a 2
933 dtype: int64
934 b
935 b 3
936 dtype: int64
937
938 For DataFrameGroupBy:
939
940 >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]]
941 >>> df = pd.DataFrame(data, columns=["a", "b", "c"])
942 >>> df
943 a b c
944 0 1 2 3
945 1 1 5 6
946 2 7 8 9
947 >>> for x, y in df.groupby(by=["a"]):
948 ... print(f"{x}\\n{y}\\n")
949 (1,)
950 a b c
951 0 1 2 3
952 1 1 5 6
953 (7,)
954 a b c
955 2 7 8 9
956
957 For Resampler:
958
959 >>> ser = pd.Series(
960 ... [1, 2, 3, 4],
961 ... index=pd.DatetimeIndex(
962 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
963 ... ),
964 ... )
965 >>> ser
966 2023-01-01 1
967 2023-01-15 2
968 2023-02-01 3
969 2023-02-15 4
970 dtype: int64
971 >>> for x, y in ser.resample("MS"):
972 ... print(f"{x}\\n{y}\\n")
973 2023-01-01 00:00:00
974 2023-01-01 1
975 2023-01-15 2
976 dtype: int64
977 2023-02-01 00:00:00
978 2023-02-01 3
979 2023-02-15 4
980 dtype: int64
981 """
982 keys = self.keys
983 level = self.level
984 result = self._grouper.get_iterator(self._selected_obj)
985 # mypy: Argument 1 to "len" has incompatible type "Hashable"; expected "Sized"
986 if (is_list_like(level) and len(level) == 1) or ( # type: ignore[arg-type]
987 isinstance(keys, list) and len(keys) == 1
988 ):
989 # GH#42795 - when keys is a list, return tuples even when length is 1
990 result = (((key,), group) for key, group in result)
991 return result
992
993
994# To track operations that expand dimensions, like ohlc
995OutputFrameOrSeries = TypeVar("OutputFrameOrSeries", bound=NDFrame)
996
997
998class GroupBy(BaseGroupBy[NDFrameT]):
999 """
1000 Class for grouping and aggregating relational data.
1001
1002 See aggregate, transform, and apply functions on this object.
1003
1004 It's easiest to use obj.groupby(...) to use GroupBy, but you can also do:
1005
1006 ::
1007
1008 grouped = groupby(obj, ...)
1009
1010 Parameters
1011 ----------
1012 obj : pandas object
1013 level : int, default None
1014 Level of MultiIndex
1015 groupings : list of Grouping objects
1016 Most users should ignore this
1017 exclusions : array-like, optional
1018 List of columns to exclude
1019 name : str
1020 Most users should ignore this
1021
1022 Returns
1023 -------
1024 **Attributes**
1025 groups : dict
1026 {group name -> group labels}
1027 len(grouped) : int
1028 Number of groups
1029
1030 Notes
1031 -----
1032 After grouping, see aggregate, apply, and transform functions. Here are
1033 some other brief notes about usage. When grouping by multiple groups, the
1034 result index will be a MultiIndex (hierarchical) by default.
1035
1036 Iteration produces (key, group) tuples, i.e. chunking the data by group. So
1037 you can write code like:
1038
1039 ::
1040
1041 grouped = obj.groupby(keys)
1042 for key, group in grouped:
1043 # do something with the data
1044
1045 Function calls on GroupBy, if not specially implemented, "dispatch" to the
1046 grouped data. So if you group a DataFrame and wish to invoke the std()
1047 method on each group, you can simply do:
1048
1049 ::
1050
1051 df.groupby(mapper).std()
1052
1053 rather than
1054
1055 ::
1056
1057 df.groupby(mapper).aggregate(np.std)
1058
1059 You can pass arguments to these "wrapped" functions, too.
1060
1061 See the online documentation for full exposition on these topics and much
1062 more
1063 """
1064
1065 _grouper: ops.BaseGrouper
1066 as_index: bool
1067
1068 @final
1069 def __init__(
1070 self,
1071 obj: NDFrameT,
1072 keys: _KeysArgType | None = None,
1073 level: IndexLabel | None = None,
1074 grouper: ops.BaseGrouper | None = None,
1075 exclusions: frozenset[Hashable] | None = None,
1076 selection: IndexLabel | None = None,
1077 as_index: bool = True,
1078 sort: bool = True,
1079 group_keys: bool = True,
1080 observed: bool = False,
1081 dropna: bool = True,
1082 ) -> None:
1083 self._selection = selection
1084
1085 assert isinstance(obj, NDFrame), type(obj)
1086
1087 self.level = level
1088 self.as_index = as_index
1089 self.keys = keys
1090 self.sort = sort
1091 self.group_keys = group_keys
1092 self.dropna = dropna
1093
1094 if grouper is None:
1095 grouper, exclusions, obj = get_grouper(
1096 obj,
1097 keys,
1098 level=level,
1099 sort=sort,
1100 observed=observed,
1101 dropna=self.dropna,
1102 )
1103
1104 self.observed = observed
1105 self.obj = obj
1106 self._grouper = grouper
1107 self.exclusions = frozenset(exclusions) if exclusions else frozenset()
1108
1109 def __getattr__(self, attr: str):
1110 if attr in self._internal_names_set:
1111 return object.__getattribute__(self, attr)
1112 if attr in self.obj:
1113 return self[attr]
1114
1115 raise AttributeError(
1116 f"'{type(self).__name__}' object has no attribute '{attr}'"
1117 )
1118
1119 @final
1120 def _op_via_apply(self, name: str, *args, **kwargs):
1121 """Compute the result of an operation by using GroupBy's apply."""
1122 f = getattr(type(self._obj_with_exclusions), name)
1123
1124 def curried(x):
1125 return f(x, *args, **kwargs)
1126
1127 # preserve the name so we can detect it when calling plot methods,
1128 # to avoid duplicates
1129 curried.__name__ = name
1130
1131 # special case otherwise extra plots are created when catching the
1132 # exception below
1133 if name in base.plotting_methods:
1134 return self._python_apply_general(curried, self._selected_obj)
1135
1136 is_transform = name in base.transformation_kernels
1137 result = self._python_apply_general(
1138 curried,
1139 self._obj_with_exclusions,
1140 is_transform=is_transform,
1141 not_indexed_same=not is_transform,
1142 )
1143
1144 if self._grouper.has_dropped_na and is_transform:
1145 # result will have dropped rows due to nans, fill with null
1146 # and ensure index is ordered same as the input
1147 result = self._set_result_index_ordered(result)
1148 return result
1149
1150 # -----------------------------------------------------------------
1151 # Dispatch/Wrapping
1152
1153 @final
1154 def _concat_objects(
1155 self,
1156 values,
1157 not_indexed_same: bool = False,
1158 is_transform: bool = False,
1159 ):
1160 from pandas.core.reshape.concat import concat
1161
1162 if self.group_keys and not is_transform:
1163 if self.as_index:
1164 # possible MI return case
1165 group_keys = self._grouper.result_index
1166 group_levels = self._grouper.levels
1167 group_names = self._grouper.names
1168
1169 result = concat(
1170 values,
1171 axis=0,
1172 keys=group_keys,
1173 levels=group_levels,
1174 names=group_names,
1175 sort=False,
1176 )
1177 else:
1178 result = concat(values, axis=0)
1179
1180 elif not not_indexed_same:
1181 result = concat(values, axis=0)
1182
1183 ax = self._selected_obj.index
1184 if self.dropna:
1185 labels = self._grouper.ids
1186 mask = labels != -1
1187 ax = ax[mask]
1188
1189 # this is a very unfortunate situation
1190 # we can't use reindex to restore the original order
1191 # when the ax has duplicates
1192 # so we resort to this
1193 # GH 14776, 30667
1194 # TODO: can we reuse e.g. _reindex_non_unique?
1195 if ax.has_duplicates and not result.axes[0].equals(ax):
1196 # e.g. test_category_order_transformer
1197 target = algorithms.unique1d(ax._values)
1198 indexer, _ = result.index.get_indexer_non_unique(target)
1199 result = result.take(indexer, axis=0)
1200 else:
1201 result = result.reindex(ax, axis=0)
1202
1203 else:
1204 result = concat(values, axis=0)
1205
1206 if self.obj.ndim == 1:
1207 name = self.obj.name
1208 elif is_hashable(self._selection):
1209 name = self._selection
1210 else:
1211 name = None
1212
1213 if isinstance(result, Series) and name is not None:
1214 result.name = name
1215
1216 return result.__finalize__(self.obj, method="groupby")
1217
1218 @final
1219 def _set_result_index_ordered(
1220 self, result: OutputFrameOrSeries
1221 ) -> OutputFrameOrSeries:
1222 # set the result index on the passed values object and
1223 # return the new object, xref 8046
1224
1225 index = self.obj.index
1226
1227 if self._grouper.is_monotonic and not self._grouper.has_dropped_na:
1228 # shortcut if we have an already ordered grouper
1229 result = result.set_axis(index, axis=0)
1230 return result
1231
1232 # row order is scrambled => sort the rows by position in original index
1233 original_positions = Index(self._grouper.result_ilocs, copy=False)
1234 result = result.set_axis(original_positions, axis=0)
1235 result = result.sort_index(axis=0)
1236 if self._grouper.has_dropped_na:
1237 # Add back in any missing rows due to dropna - index here is integral
1238 # with values referring to the row of the input so can use RangeIndex
1239 result = result.reindex(default_index(len(index)), axis=0)
1240 result = result.set_axis(index, axis=0)
1241
1242 return result
1243
1244 @final
1245 def _insert_inaxis_grouper(
1246 self, result: Series | DataFrame, qs: npt.NDArray[np.float64] | None = None
1247 ) -> DataFrame:
1248 if isinstance(result, Series):
1249 result = result.to_frame()
1250
1251 n_groupings = len(self._grouper.groupings)
1252
1253 if qs is not None:
1254 result.insert(
1255 0, f"level_{n_groupings}", np.tile(qs, len(result) // len(qs))
1256 )
1257
1258 # zip in reverse so we can always insert at loc 0
1259 for level, (name, lev) in enumerate(
1260 zip(
1261 reversed(self._grouper.names),
1262 self._grouper.get_group_levels(),
1263 strict=True,
1264 )
1265 ):
1266 if name is None:
1267 # Behave the same as .reset_index() when a level is unnamed
1268 name = (
1269 "index"
1270 if n_groupings == 1 and qs is None
1271 else f"level_{n_groupings - level - 1}"
1272 )
1273
1274 # GH #28549
1275 # When using .apply(-), name will be in columns already
1276 if name not in result.columns:
1277 # if in_axis:
1278 if qs is None:
1279 result.insert(0, name, lev)
1280 else:
1281 result.insert(0, name, Index(np.repeat(lev, len(qs)), copy=False))
1282
1283 return result
1284
1285 @final
1286 def _wrap_aggregated_output(
1287 self,
1288 result: Series | DataFrame,
1289 qs: npt.NDArray[np.float64] | None = None,
1290 ):
1291 """
1292 Wraps the output of GroupBy aggregations into the expected result.
1293
1294 Parameters
1295 ----------
1296 result : Series, DataFrame
1297
1298 Returns
1299 -------
1300 Series or DataFrame
1301 """
1302 # ATM we do not get here for SeriesGroupBy; when we do, we will
1303 # need to require that result.name already match self.obj.name
1304
1305 if not self.as_index:
1306 # `not self.as_index` is only relevant for DataFrameGroupBy,
1307 # enforced in __init__
1308 result = self._insert_inaxis_grouper(result, qs=qs)
1309 result = result._consolidate()
1310 result.index = default_index(len(result))
1311
1312 else:
1313 index = self._grouper.result_index
1314 if qs is not None:
1315 # We get here with len(qs) != 1 and not self.as_index
1316 # in test_pass_args_kwargs
1317 index = _insert_quantile_level(index, qs)
1318 result.index = index
1319
1320 return result
1321
1322 def _wrap_applied_output(
1323 self,
1324 data,
1325 values: list,
1326 not_indexed_same: bool = False,
1327 is_transform: bool = False,
1328 ):
1329 raise AbstractMethodError(self)
1330
1331 # -----------------------------------------------------------------
1332 # numba
1333
1334 @final
1335 def _numba_prep(self, data: DataFrame):
1336 ngroups = self._grouper.ngroups
1337 sorted_index = self._grouper.result_ilocs
1338 sorted_ids = self._grouper._sorted_ids
1339
1340 sorted_data = data.take(sorted_index, axis=0).to_numpy()
1341 # GH 46867
1342 index_data = data.index
1343 if isinstance(index_data, MultiIndex):
1344 if len(self._grouper.groupings) > 1:
1345 raise NotImplementedError(
1346 "Grouping with more than 1 grouping labels and "
1347 "a MultiIndex is not supported with engine='numba'"
1348 )
1349 group_key = self._grouper.groupings[0].name
1350 index_data = index_data.get_level_values(group_key)
1351 sorted_index_data = index_data.take(sorted_index).to_numpy()
1352
1353 starts, ends = lib.generate_slices(sorted_ids, ngroups)
1354 return (
1355 starts,
1356 ends,
1357 sorted_index_data,
1358 sorted_data,
1359 )
1360
1361 def _numba_agg_general(
1362 self,
1363 func: Callable,
1364 dtype_mapping: dict[np.dtype, Any],
1365 engine_kwargs: dict[str, bool] | None,
1366 **aggregator_kwargs,
1367 ):
1368 """
1369 Perform groupby with a standard numerical aggregation function (e.g. mean)
1370 with Numba.
1371 """
1372 if not self.as_index:
1373 raise NotImplementedError(
1374 "as_index=False is not supported. Use .reset_index() instead."
1375 )
1376
1377 data = self._obj_with_exclusions
1378 df = data if data.ndim == 2 else data.to_frame()
1379
1380 aggregator = executor.generate_shared_aggregator(
1381 func,
1382 dtype_mapping,
1383 True, # is_grouped_kernel
1384 **get_jit_arguments(engine_kwargs),
1385 )
1386 # Pass group ids to kernel directly if it can handle it
1387 # (This is faster since it doesn't require a sort)
1388 ids = self._grouper.ids
1389 ngroups = self._grouper.ngroups
1390
1391 res_mgr = df._mgr.apply(
1392 aggregator, labels=ids, ngroups=ngroups, **aggregator_kwargs
1393 )
1394 res_mgr.axes[1] = self._grouper.result_index
1395 result = df._constructor_from_mgr(res_mgr, axes=res_mgr.axes)
1396
1397 if data.ndim == 1:
1398 result = result.squeeze("columns")
1399 result.name = data.name
1400 else:
1401 result.columns = data.columns
1402 return result
1403
1404 @final
1405 def _transform_with_numba(self, func, *args, engine_kwargs=None, **kwargs):
1406 """
1407 Perform groupby transform routine with the numba engine.
1408
1409 This routine mimics the data splitting routine of the DataSplitter class
1410 to generate the indices of each group in the sorted data and then passes the
1411 data and indices into a Numba jitted function.
1412 """
1413 data = self._obj_with_exclusions
1414 index_sorting = self._grouper.result_ilocs
1415 df = data if data.ndim == 2 else data.to_frame()
1416
1417 starts, ends, sorted_index, sorted_data = self._numba_prep(df)
1418 numba_.validate_udf(func)
1419 args, kwargs = prepare_function_arguments(
1420 func, args, kwargs, num_required_args=2
1421 )
1422 numba_transform_func = numba_.generate_numba_transform_func(
1423 func, **get_jit_arguments(engine_kwargs)
1424 )
1425 result = numba_transform_func(
1426 sorted_data,
1427 sorted_index,
1428 starts,
1429 ends,
1430 len(df.columns),
1431 *args,
1432 )
1433 # result values needs to be resorted to their original positions since we
1434 # evaluated the data sorted by group
1435 result = result.take(np.argsort(index_sorting), axis=0)
1436 index = data.index
1437 if data.ndim == 1:
1438 result_kwargs = {"name": data.name}
1439 result = result.ravel()
1440 else:
1441 result_kwargs = {"columns": data.columns}
1442 return data._constructor(result, index=index, **result_kwargs)
1443
1444 @final
1445 def _aggregate_with_numba(self, func, *args, engine_kwargs=None, **kwargs):
1446 """
1447 Perform groupby aggregation routine with the numba engine.
1448
1449 This routine mimics the data splitting routine of the DataSplitter class
1450 to generate the indices of each group in the sorted data and then passes the
1451 data and indices into a Numba jitted function.
1452 """
1453 data = self._obj_with_exclusions
1454 df = data if data.ndim == 2 else data.to_frame()
1455
1456 starts, ends, sorted_index, sorted_data = self._numba_prep(df)
1457 numba_.validate_udf(func)
1458 args, kwargs = prepare_function_arguments(
1459 func, args, kwargs, num_required_args=2
1460 )
1461 numba_agg_func = numba_.generate_numba_agg_func(
1462 func, **get_jit_arguments(engine_kwargs)
1463 )
1464 result = numba_agg_func(
1465 sorted_data,
1466 sorted_index,
1467 starts,
1468 ends,
1469 len(df.columns),
1470 *args,
1471 )
1472 index = self._grouper.result_index
1473 if data.ndim == 1:
1474 result_kwargs = {"name": data.name}
1475 result = result.ravel()
1476 else:
1477 result_kwargs = {"columns": data.columns}
1478 res = data._constructor(result, index=index, **result_kwargs)
1479 if not self.as_index:
1480 res = self._insert_inaxis_grouper(res)
1481 res.index = default_index(len(res))
1482 return res
1483
1484 # -----------------------------------------------------------------
1485 # apply/agg/transform
1486
1487 def apply(self, func, *args, include_groups: bool = False, **kwargs) -> NDFrameT:
1488 """
1489 Apply function ``func`` group-wise and combine the results together.
1490
1491 The function passed to ``apply`` must take a dataframe as its first
1492 argument and return a DataFrame, Series or scalar. ``apply`` will
1493 then take care of combining the results back together into a single
1494 dataframe or series. ``apply`` is therefore a highly flexible
1495 grouping method.
1496
1497 While ``apply`` is a very flexible method, its downside is that
1498 using it can be quite a bit slower than using more specific methods
1499 like ``agg`` or ``transform``. Pandas offers a wide range of method that will
1500 be much faster than using ``apply`` for their specific purposes, so try to
1501 use them before reaching for ``apply``.
1502
1503 Parameters
1504 ----------
1505 func : callable
1506 A callable that takes a dataframe as its first argument, and
1507 returns a dataframe, a series or a scalar. In addition the
1508 callable may take positional and keyword arguments.
1509
1510 *args : tuple
1511 Optional positional arguments to pass to ``func``.
1512
1513 include_groups : bool, default False
1514 When True, will attempt to apply ``func`` to the groupings in
1515 the case that they are columns of the DataFrame. If this raises a
1516 TypeError, the result will be computed with the groupings excluded.
1517 When False, the groupings will be excluded when applying ``func``.
1518
1519 .. versionadded:: 2.2.0
1520
1521 .. versionchanged:: 3.0.0
1522
1523 The default changed from True to False, and True is no longer allowed.
1524
1525 **kwargs : dict
1526 Optional keyword arguments to pass to ``func``.
1527
1528 Returns
1529 -------
1530 Series or DataFrame
1531 A pandas object with the result of applying ``func`` to each group.
1532
1533 See Also
1534 --------
1535 pipe : Apply function to the full GroupBy object instead of to each
1536 group.
1537 aggregate : Apply aggregate function to the GroupBy object.
1538 transform : Apply function column-by-column to the GroupBy object.
1539 Series.apply : Apply a function to a Series.
1540 DataFrame.apply : Apply a function to each row or column of a DataFrame.
1541
1542 Notes
1543 -----
1544 The resulting dtype will reflect the return value of the passed ``func``,
1545 see the examples below.
1546
1547 Functions that mutate the passed object can produce unexpected
1548 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
1549 for more details.
1550
1551 Examples
1552 --------
1553 >>> df = pd.DataFrame({"A": "a a b".split(), "B": [1, 2, 3], "C": [4, 6, 5]})
1554 >>> g1 = df.groupby("A", group_keys=False)
1555 >>> g2 = df.groupby("A", group_keys=True)
1556
1557 Notice that ``g1`` and ``g2`` have two groups, ``a`` and ``b``, and only
1558 differ in their ``group_keys`` argument. Calling `apply` in various ways,
1559 we can get different grouping results:
1560
1561 Example 1: below the function passed to `apply` takes a DataFrame as
1562 its argument and returns a DataFrame. `apply` combines the result for
1563 each group together into a new DataFrame:
1564
1565 >>> g1[["B", "C"]].apply(lambda x: x / x.sum())
1566 B C
1567 0 0.333333 0.4
1568 1 0.666667 0.6
1569 2 1.000000 1.0
1570
1571 In the above, the groups are not part of the index. We can have them included
1572 by using ``g2`` where ``group_keys=True``:
1573
1574 >>> g2[["B", "C"]].apply(lambda x: x / x.sum())
1575 B C
1576 A
1577 a 0 0.333333 0.4
1578 1 0.666667 0.6
1579 b 2 1.000000 1.0
1580
1581 Example 2: The function passed to `apply` takes a DataFrame as
1582 its argument and returns a Series. `apply` combines the result for
1583 each group together into a new DataFrame.
1584
1585 The resulting dtype will reflect the return value of the passed ``func``.
1586
1587 >>> g1[["B", "C"]].apply(lambda x: x.astype(float).max() - x.min())
1588 B C
1589 A
1590 a 1.0 2.0
1591 b 0.0 0.0
1592
1593 >>> g2[["B", "C"]].apply(lambda x: x.astype(float).max() - x.min())
1594 B C
1595 A
1596 a 1.0 2.0
1597 b 0.0 0.0
1598
1599 The ``group_keys`` argument has no effect here because the result is not
1600 like-indexed (i.e. :ref:`a transform <groupby.transform>`) when compared
1601 to the input.
1602
1603 Example 3: The function passed to `apply` takes a DataFrame as
1604 its argument and returns a scalar. `apply` combines the result for
1605 each group together into a Series, including setting the index as
1606 appropriate:
1607
1608 >>> g1.apply(lambda x: x.C.max() - x.B.min())
1609 A
1610 a 5
1611 b 2
1612 dtype: int64
1613
1614 Example 4: The function passed to ``apply`` returns ``None`` for one of the
1615 group. This group is filtered from the result:
1616
1617 >>> g1.apply(lambda x: None if x.iloc[0, 0] == 3 else x)
1618 B C
1619 0 1 4
1620 1 2 6
1621 """
1622 if include_groups:
1623 raise ValueError("include_groups=True is no longer allowed.")
1624 if isinstance(func, str):
1625 if hasattr(self, func):
1626 res = getattr(self, func)
1627 if callable(res):
1628 return res(*args, **kwargs)
1629 elif args or kwargs:
1630 raise ValueError(f"Cannot pass arguments to property {func}")
1631 return res
1632
1633 else:
1634 raise TypeError(f"apply func should be callable, not '{func}'")
1635
1636 elif args or kwargs:
1637 if callable(func):
1638
1639 @wraps(func)
1640 def f(g):
1641 return func(g, *args, **kwargs)
1642
1643 else:
1644 raise ValueError(
1645 "func must be a callable if args or kwargs are supplied"
1646 )
1647 else:
1648 f = func
1649
1650 return self._python_apply_general(f, self._obj_with_exclusions)
1651
1652 @final
1653 def _python_apply_general(
1654 self,
1655 f: Callable,
1656 data: DataFrame | Series,
1657 not_indexed_same: bool | None = None,
1658 is_transform: bool = False,
1659 is_agg: bool = False,
1660 ) -> NDFrameT:
1661 """
1662 Apply function f in python space
1663
1664 Parameters
1665 ----------
1666 f : callable
1667 Function to apply
1668 data : Series or DataFrame
1669 Data to apply f to
1670 not_indexed_same: bool, optional
1671 When specified, overrides the value of not_indexed_same. Apply behaves
1672 differently when the result index is equal to the input index, but
1673 this can be coincidental leading to value-dependent behavior.
1674 is_transform : bool, default False
1675 Indicator for whether the function is actually a transform
1676 and should not have group keys prepended.
1677 is_agg : bool, default False
1678 Indicator for whether the function is an aggregation. When the
1679 result is empty, we don't want to warn for this case.
1680 See _GroupBy._python_agg_general.
1681
1682 Returns
1683 -------
1684 Series or DataFrame
1685 data after applying f
1686 """
1687 values, mutated = self._grouper.apply_groupwise(f, data)
1688 if not_indexed_same is None:
1689 not_indexed_same = mutated
1690
1691 return self._wrap_applied_output(
1692 data,
1693 values,
1694 not_indexed_same,
1695 is_transform,
1696 )
1697
1698 @final
1699 def _agg_general(
1700 self,
1701 numeric_only: bool = False,
1702 min_count: int = -1,
1703 *,
1704 alias: str,
1705 npfunc: Callable | None = None,
1706 **kwargs,
1707 ):
1708 result = self._cython_agg_general(
1709 how=alias,
1710 alt=npfunc,
1711 numeric_only=numeric_only,
1712 min_count=min_count,
1713 **kwargs,
1714 )
1715 return result.__finalize__(self.obj, method="groupby")
1716
1717 def _agg_py_fallback(
1718 self, how: str, values: ArrayLike, ndim: int, alt: Callable
1719 ) -> ArrayLike:
1720 """
1721 Fallback to pure-python aggregation if _cython_operation raises
1722 NotImplementedError.
1723 """
1724 # We get here with a) EADtypes and b) object dtype
1725 assert alt is not None
1726
1727 if values.ndim == 1:
1728 # For DataFrameGroupBy we only get here with ExtensionArray
1729 ser = Series(values, copy=False)
1730 else:
1731 # We only get here with values.dtype == object
1732 df = DataFrame(values.T, dtype=values.dtype)
1733 # bc we split object blocks in grouped_reduce, we have only 1 col
1734 # otherwise we'd have to worry about block-splitting GH#39329
1735 assert df.shape[1] == 1
1736 # Avoid call to self.values that can occur in DataFrame
1737 # reductions; see GH#28949
1738 ser = df.iloc[:, 0]
1739
1740 # We do not get here with UDFs, so we know that our dtype
1741 # should always be preserved by the implemented aggregations
1742 # TODO: Is this exactly right; see WrappedCythonOp get_result_dtype?
1743 try:
1744 res_values = self._grouper.agg_series(ser, alt, preserve_dtype=True)
1745 except Exception as err:
1746 msg = f"agg function failed [how->{how},dtype->{ser.dtype}]"
1747 # preserve the kind of exception that raised
1748 raise type(err)(msg) from err
1749
1750 dtype = ser.dtype
1751 if dtype == object:
1752 res_values = res_values.astype(object, copy=False)
1753 elif is_string_dtype(dtype):
1754 # mypy doesn't infer dtype is an ExtensionDtype
1755 string_array_cls = dtype.construct_array_type() # type: ignore[union-attr]
1756 res_values = string_array_cls._from_sequence(res_values, dtype=dtype)
1757
1758 # If we are DataFrameGroupBy and went through a SeriesGroupByPath
1759 # then we need to reshape
1760 # GH#32223 includes case with IntegerArray values, ndarray res_values
1761 # test_groupby_duplicate_columns with object dtype values
1762 return ensure_block_shape(res_values, ndim=ndim)
1763
1764 @final
1765 def _cython_agg_general(
1766 self,
1767 how: str,
1768 alt: Callable | None = None,
1769 numeric_only: bool = False,
1770 min_count: int = -1,
1771 **kwargs,
1772 ):
1773 # Note: we never get here with how="ohlc" for DataFrameGroupBy;
1774 # that goes through SeriesGroupBy
1775
1776 if not is_bool(numeric_only):
1777 raise ValueError("numeric_only accepts only Boolean values")
1778
1779 data = self._get_data_to_aggregate(numeric_only=numeric_only, name=how)
1780
1781 def array_func(values: ArrayLike) -> ArrayLike:
1782 try:
1783 result = self._grouper._cython_operation(
1784 "aggregate",
1785 values,
1786 how,
1787 axis=data.ndim - 1,
1788 min_count=min_count,
1789 **kwargs,
1790 )
1791 except NotImplementedError:
1792 # generally if we have numeric_only=False
1793 # and non-applicable functions
1794 # try to python agg
1795 # TODO: shouldn't min_count matter?
1796 # TODO: avoid special casing SparseArray here
1797 if how in ["any", "all"] and isinstance(values, SparseArray):
1798 pass
1799 elif alt is None or how in ["any", "all", "std", "sem"]:
1800 raise # TODO: re-raise as TypeError? should not be reached
1801 else:
1802 return result
1803
1804 assert alt is not None
1805 result = self._agg_py_fallback(how, values, ndim=data.ndim, alt=alt)
1806 return result
1807
1808 new_mgr = data.grouped_reduce(array_func)
1809 res = self._wrap_agged_manager(new_mgr)
1810 if how in ["idxmin", "idxmax"]:
1811 # mypy expects how to be Literal["idxmin", "idxmax"].
1812 res = self._wrap_idxmax_idxmin(res, how=how, skipna=kwargs["skipna"]) # type: ignore[arg-type]
1813 out = self._wrap_aggregated_output(res)
1814 return out
1815
1816 def _cython_transform(self, how: str, numeric_only: bool = False, **kwargs):
1817 raise AbstractMethodError(self)
1818
1819 @final
1820 def _transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):
1821 # optimized transforms
1822 if not isinstance(func, str):
1823 return self._transform_general(func, engine, engine_kwargs, *args, **kwargs)
1824
1825 elif func not in base.transform_kernel_allowlist:
1826 msg = f"'{func}' is not a valid function name for transform(name)"
1827 raise ValueError(msg)
1828 elif func in base.cythonized_kernels or func in base.transformation_kernels:
1829 # cythonized transform or canned "agg+broadcast"
1830 if engine is not None:
1831 kwargs["engine"] = engine
1832 kwargs["engine_kwargs"] = engine_kwargs
1833 return getattr(self, func)(*args, **kwargs)
1834
1835 else:
1836 # i.e. func in base.reduction_kernels
1837 if self.observed:
1838 return self._reduction_kernel_transform(
1839 func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
1840 )
1841
1842 with (
1843 com.temp_setattr(self, "observed", True),
1844 com.temp_setattr(self, "_grouper", self._grouper.observed_grouper),
1845 ):
1846 return self._reduction_kernel_transform(
1847 func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
1848 )
1849
1850 @final
1851 def _reduction_kernel_transform(
1852 self, func, *args, engine=None, engine_kwargs=None, **kwargs
1853 ):
1854 # GH#30918 Use _transform_fast only when we know func is an aggregation
1855 # If func is a reduction, we need to broadcast the
1856 # result to the whole group. Compute func result
1857 # and deal with possible broadcasting below.
1858 with com.temp_setattr(self, "as_index", True):
1859 # GH#49834 - result needs groups in the index for
1860 # _wrap_transform_fast_result
1861 if func in ["idxmin", "idxmax"]:
1862 func = cast(Literal["idxmin", "idxmax"], func)
1863 result = self._idxmax_idxmin(func, True, *args, **kwargs)
1864 else:
1865 if engine is not None:
1866 kwargs["engine"] = engine
1867 kwargs["engine_kwargs"] = engine_kwargs
1868 result = getattr(self, func)(*args, **kwargs)
1869
1870 return self._wrap_transform_fast_result(result)
1871
1872 @final
1873 def _wrap_transform_fast_result(self, result: NDFrameT) -> NDFrameT:
1874 """
1875 Fast transform path for aggregations.
1876 """
1877 obj = self._obj_with_exclusions
1878
1879 # for each col, reshape to size of original frame by take operation
1880 ids = self._grouper.ids
1881 result = result.reindex(self._grouper.result_index, axis=0)
1882
1883 if self.obj.ndim == 1:
1884 # i.e. SeriesGroupBy
1885 out = algorithms.take_nd(result._values, ids)
1886 output = obj._constructor(out, index=obj.index, name=obj.name)
1887 else:
1888 # `.size()` gives Series output on DataFrame input, need axis 0
1889 # GH#46209
1890 # Don't convert indices: negative indices need to give rise
1891 # to null values in the result
1892 new_ax = result.index.take(ids)
1893 output = result._reindex_with_indexers({0: (new_ax, ids)}, allow_dups=True)
1894 output = output.set_axis(obj.index, axis=0)
1895 return output
1896
1897 # -----------------------------------------------------------------
1898 # Utilities
1899
1900 @final
1901 def _apply_filter(self, indices, dropna):
1902 if len(indices) == 0:
1903 indices = np.array([], dtype="int64")
1904 else:
1905 indices = np.sort(np.concatenate(indices))
1906 if dropna:
1907 filtered = self._selected_obj.take(indices, axis=0)
1908 else:
1909 mask = np.empty(len(self._selected_obj.index), dtype=bool)
1910 mask.fill(False)
1911 mask[indices.astype(int)] = True
1912 # mask fails to broadcast when passed to where; broadcast manually.
1913 mask = np.tile(mask, [*self._selected_obj.shape[1:], 1]).T
1914 filtered = self._selected_obj.where(mask) # Fill with NaNs.
1915 return filtered
1916
1917 @final
1918 def _cumcount_array(self, ascending: bool = True) -> np.ndarray:
1919 """
1920 Parameters
1921 ----------
1922 ascending : bool, default True
1923 If False, number in reverse, from length of group - 1 to 0.
1924
1925 Notes
1926 -----
1927 this is currently implementing sort=False
1928 (though the default is sort=True) for groupby in general
1929 """
1930 ids = self._grouper.ids
1931 ngroups = self._grouper.ngroups
1932 sorter = get_group_index_sorter(ids, ngroups)
1933 ids, count = ids[sorter], len(ids)
1934
1935 if count == 0:
1936 return np.empty(0, dtype=np.int64)
1937
1938 run = np.r_[True, ids[:-1] != ids[1:]]
1939 rep = np.diff(np.r_[np.nonzero(run)[0], count])
1940 out = (~run).cumsum()
1941
1942 if ascending:
1943 out -= np.repeat(out[run], rep)
1944 else:
1945 out = np.repeat(out[np.r_[run[1:], True]], rep) - out
1946
1947 if self._grouper.has_dropped_na:
1948 out = np.where(ids == -1, np.nan, out.astype(np.float64, copy=False))
1949 else:
1950 out = out.astype(np.int64, copy=False)
1951
1952 rev = np.empty(count, dtype=np.intp)
1953 rev[sorter] = np.arange(count, dtype=np.intp)
1954 return out[rev]
1955
1956 # -----------------------------------------------------------------
1957
1958 @final
1959 @property
1960 def _obj_1d_constructor(self) -> Callable:
1961 # GH28330 preserve subclassed Series/DataFrames
1962 if isinstance(self.obj, DataFrame):
1963 return self.obj._constructor_sliced
1964 assert isinstance(self.obj, Series)
1965 return self.obj._constructor
1966
1967 @final
1968 def any(self, skipna: bool = True) -> NDFrameT:
1969 """
1970 Return True if any value in the group is truthful, else False.
1971
1972 Parameters
1973 ----------
1974 skipna : bool, default True
1975 Flag to ignore nan values during truth testing.
1976
1977 Returns
1978 -------
1979 Series or DataFrame
1980 DataFrame or Series of boolean values, where a value is True if any element
1981 is True within its respective group, False otherwise.
1982
1983 See Also
1984 --------
1985 Series.any : Apply function any to a Series.
1986 DataFrame.any : Apply function any to each row or column of a DataFrame.
1987
1988 Examples
1989 --------
1990 For SeriesGroupBy:
1991
1992 >>> lst = ["a", "a", "b"]
1993 >>> ser = pd.Series([1, 2, 0], index=lst)
1994 >>> ser
1995 a 1
1996 a 2
1997 b 0
1998 dtype: int64
1999 >>> ser.groupby(level=0).any()
2000 a True
2001 b False
2002 dtype: bool
2003
2004 For DataFrameGroupBy:
2005
2006 >>> data = [[1, 0, 3], [1, 0, 6], [7, 1, 9]]
2007 >>> df = pd.DataFrame(
2008 ... data, columns=["a", "b", "c"], index=["ostrich", "penguin", "parrot"]
2009 ... )
2010 >>> df
2011 a b c
2012 ostrich 1 0 3
2013 penguin 1 0 6
2014 parrot 7 1 9
2015 >>> df.groupby(by=["a"]).any()
2016 b c
2017 a
2018 1 False True
2019 7 True True
2020 """
2021 return self._cython_agg_general(
2022 "any",
2023 alt=lambda x: Series(x, copy=False).any(skipna=skipna),
2024 skipna=skipna,
2025 )
2026
2027 @final
2028 def all(self, skipna: bool = True) -> NDFrameT:
2029 """
2030 Return True if all values in the group are truthful, else False.
2031
2032 Parameters
2033 ----------
2034 skipna : bool, default True
2035 Flag to ignore nan values during truth testing.
2036
2037 Returns
2038 -------
2039 Series or DataFrame
2040 DataFrame or Series of boolean values, where a value is True if all elements
2041 are True within its respective group, False otherwise.
2042
2043 See Also
2044 --------
2045 Series.all : Apply function all to a Series.
2046 DataFrame.all : Apply function all to each row or column of a DataFrame.
2047
2048 Examples
2049 --------
2050
2051 For SeriesGroupBy:
2052
2053 >>> lst = ["a", "a", "b"]
2054 >>> ser = pd.Series([1, 2, 0], index=lst)
2055 >>> ser
2056 a 1
2057 a 2
2058 b 0
2059 dtype: int64
2060 >>> ser.groupby(level=0).all()
2061 a True
2062 b False
2063 dtype: bool
2064
2065 For DataFrameGroupBy:
2066
2067 >>> data = [[1, 0, 3], [1, 5, 6], [7, 8, 9]]
2068 >>> df = pd.DataFrame(
2069 ... data, columns=["a", "b", "c"], index=["ostrich", "penguin", "parrot"]
2070 ... )
2071 >>> df
2072 a b c
2073 ostrich 1 0 3
2074 penguin 1 5 6
2075 parrot 7 8 9
2076 >>> df.groupby(by=["a"]).all()
2077 b c
2078 a
2079 1 False True
2080 7 True True
2081 """
2082 return self._cython_agg_general(
2083 "all",
2084 alt=lambda x: Series(x, copy=False).all(skipna=skipna),
2085 skipna=skipna,
2086 )
2087
2088 @final
2089 def count(self) -> NDFrameT:
2090 """
2091 Compute count of group, excluding missing values.
2092
2093 Returns
2094 -------
2095 Series or DataFrame
2096 Count of values within each group.
2097
2098 See Also
2099 --------
2100 Series.count : Apply function count to a Series.
2101 DataFrame.count : Apply function count to each row or column of a DataFrame.
2102
2103 Examples
2104 --------
2105 For SeriesGroupBy:
2106
2107 >>> lst = ["a", "a", "b"]
2108 >>> ser = pd.Series([1, 2, np.nan], index=lst)
2109 >>> ser
2110 a 1.0
2111 a 2.0
2112 b NaN
2113 dtype: float64
2114 >>> ser.groupby(level=0).count()
2115 a 2
2116 b 0
2117 dtype: int64
2118
2119 For DataFrameGroupBy:
2120
2121 >>> data = [[1, np.nan, 3], [1, np.nan, 6], [7, 8, 9]]
2122 >>> df = pd.DataFrame(
2123 ... data, columns=["a", "b", "c"], index=["cow", "horse", "bull"]
2124 ... )
2125 >>> df
2126 a b c
2127 cow 1 NaN 3
2128 horse 1 NaN 6
2129 bull 7 8.0 9
2130 >>> df.groupby("a").count()
2131 b c
2132 a
2133 1 0 2
2134 7 1 1
2135
2136 For Resampler:
2137
2138 >>> ser = pd.Series(
2139 ... [1, 2, 3, 4],
2140 ... index=pd.DatetimeIndex(
2141 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
2142 ... ),
2143 ... )
2144 >>> ser
2145 2023-01-01 1
2146 2023-01-15 2
2147 2023-02-01 3
2148 2023-02-15 4
2149 dtype: int64
2150 >>> ser.resample("MS").count()
2151 2023-01-01 2
2152 2023-02-01 2
2153 Freq: MS, dtype: int64
2154 """
2155 data = self._get_data_to_aggregate()
2156 ids = self._grouper.ids
2157 ngroups = self._grouper.ngroups
2158 mask = ids != -1
2159
2160 is_series = data.ndim == 1
2161
2162 def hfunc(bvalues: ArrayLike) -> ArrayLike:
2163 # TODO(EA2D): reshape would not be necessary with 2D EAs
2164 if bvalues.ndim == 1:
2165 # EA
2166 masked = mask & ~isna(bvalues).reshape(1, -1)
2167 else:
2168 masked = mask & ~isna(bvalues)
2169
2170 counted = lib.count_level_2d(masked, labels=ids, max_bin=ngroups)
2171 if isinstance(bvalues, BaseMaskedArray):
2172 return IntegerArray(
2173 counted[0], mask=np.zeros(counted.shape[1], dtype=np.bool_)
2174 )
2175 elif isinstance(bvalues, ArrowExtensionArray) and not isinstance(
2176 bvalues.dtype, StringDtype
2177 ):
2178 dtype = pandas_dtype("int64[pyarrow]")
2179 return type(bvalues)._from_sequence(counted[0], dtype=dtype)
2180 if is_series:
2181 assert counted.ndim == 2
2182 assert counted.shape[0] == 1
2183 return counted[0]
2184 return counted
2185
2186 new_mgr = data.grouped_reduce(hfunc)
2187 new_obj = self._wrap_agged_manager(new_mgr)
2188 result = self._wrap_aggregated_output(new_obj)
2189
2190 return result
2191
2192 @final
2193 def mean(
2194 self,
2195 numeric_only: bool = False,
2196 skipna: bool = True,
2197 engine: Literal["cython", "numba"] | None = None,
2198 engine_kwargs: dict[str, bool] | None = None,
2199 ):
2200 """
2201 Compute mean of groups, excluding missing values.
2202
2203 Parameters
2204 ----------
2205 numeric_only : bool, default False
2206 Include only float, int, boolean columns.
2207
2208 .. versionchanged:: 2.0.0
2209
2210 numeric_only no longer accepts ``None`` and defaults to ``False``.
2211
2212 skipna : bool, default True
2213 Exclude NA/null values. If an entire group is NA, the result will be NA.
2214
2215 engine : str, default None
2216 * ``'cython'`` : Runs the operation through C-extensions from cython.
2217 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2218 * ``None`` : Defaults to ``'cython'`` or globally setting
2219 ``compute.use_numba``
2220
2221 engine_kwargs : dict, default None
2222 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2223 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2224 and ``parallel`` dictionary keys. The values must either be ``True`` or
2225 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2226 ``{'nopython': True, 'nogil': False, 'parallel': False}``
2227
2228 Returns
2229 -------
2230 pandas.Series or pandas.DataFrame
2231 Mean of values within each group. Same object type as the caller.
2232
2233 See Also
2234 --------
2235 Series.mean : Apply function mean to a Series.
2236 DataFrame.mean : Apply function mean to each row or column of a DataFrame.
2237
2238 Examples
2239 --------
2240 >>> df = pd.DataFrame(
2241 ... {"A": [1, 1, 2, 1, 2], "B": [np.nan, 2, 3, 4, 5], "C": [1, 2, 1, 1, 2]},
2242 ... columns=["A", "B", "C"],
2243 ... )
2244
2245 Groupby one column and return the mean of the remaining columns in
2246 each group.
2247
2248 >>> df.groupby("A").mean()
2249 B C
2250 A
2251 1 3.0 1.333333
2252 2 4.0 1.500000
2253
2254 Groupby two columns and return the mean of the remaining column.
2255
2256 >>> df.groupby(["A", "B"]).mean()
2257 C
2258 A B
2259 1 2.0 2.0
2260 4.0 1.0
2261 2 3.0 1.0
2262 5.0 2.0
2263
2264 Groupby one column and return the mean of only particular column in
2265 the group.
2266
2267 >>> df.groupby("A")["B"].mean()
2268 A
2269 1 3.0
2270 2 4.0
2271 Name: B, dtype: float64
2272 """
2273
2274 if maybe_use_numba(engine):
2275 from pandas.core._numba.kernels import grouped_mean
2276
2277 return self._numba_agg_general(
2278 grouped_mean,
2279 executor.float_dtype_mapping,
2280 engine_kwargs,
2281 min_periods=0,
2282 skipna=skipna,
2283 )
2284 else:
2285 result = self._cython_agg_general(
2286 "mean",
2287 alt=lambda x: Series(x, copy=False).mean(
2288 numeric_only=numeric_only, skipna=skipna
2289 ),
2290 numeric_only=numeric_only,
2291 skipna=skipna,
2292 )
2293 return result.__finalize__(self.obj, method="groupby")
2294
2295 @final
2296 def median(self, numeric_only: bool = False, skipna: bool = True) -> NDFrameT:
2297 """
2298 Compute median of groups, excluding missing values.
2299
2300 For multiple groupings, the result index will be a MultiIndex
2301
2302 Parameters
2303 ----------
2304 numeric_only : bool, default False
2305 Include only float, int, boolean columns.
2306
2307 .. versionchanged:: 2.0.0
2308
2309 numeric_only no longer accepts ``None`` and defaults to False.
2310
2311 skipna : bool, default True
2312 Exclude NA/null values. If an entire group is NA, the result will be NA.
2313
2314 .. versionadded:: 3.0.0
2315
2316 Returns
2317 -------
2318 Series or DataFrame
2319 Median of values within each group.
2320
2321 See Also
2322 --------
2323 Series.median : Apply function median to a Series.
2324 DataFrame.median : Apply function median to each row or column of a DataFrame.
2325
2326 Examples
2327 --------
2328 For SeriesGroupBy:
2329
2330 >>> lst = ["a", "a", "a", "b", "b", "b"]
2331 >>> ser = pd.Series([7, 2, 8, 4, 3, 3], index=lst)
2332 >>> ser
2333 a 7
2334 a 2
2335 a 8
2336 b 4
2337 b 3
2338 b 3
2339 dtype: int64
2340 >>> ser.groupby(level=0).median()
2341 a 7.0
2342 b 3.0
2343 dtype: float64
2344
2345 For DataFrameGroupBy:
2346
2347 >>> data = {"a": [1, 3, 5, 7, 7, 8, 3], "b": [1, 4, 8, 4, 4, 2, 1]}
2348 >>> df = pd.DataFrame(
2349 ... data, index=["dog", "dog", "dog", "mouse", "mouse", "mouse", "mouse"]
2350 ... )
2351 >>> df
2352 a b
2353 dog 1 1
2354 dog 3 4
2355 dog 5 8
2356 mouse 7 4
2357 mouse 7 4
2358 mouse 8 2
2359 mouse 3 1
2360 >>> df.groupby(level=0).median()
2361 a b
2362 dog 3.0 4.0
2363 mouse 7.0 3.0
2364
2365 For Resampler:
2366
2367 >>> ser = pd.Series(
2368 ... [1, 2, 3, 3, 4, 5],
2369 ... index=pd.DatetimeIndex(
2370 ... [
2371 ... "2023-01-01",
2372 ... "2023-01-10",
2373 ... "2023-01-15",
2374 ... "2023-02-01",
2375 ... "2023-02-10",
2376 ... "2023-02-15",
2377 ... ]
2378 ... ),
2379 ... )
2380 >>> ser.resample("MS").median()
2381 2023-01-01 2.0
2382 2023-02-01 4.0
2383 Freq: MS, dtype: float64
2384 """
2385 result = self._cython_agg_general(
2386 "median",
2387 alt=lambda x: Series(x, copy=False).median(
2388 numeric_only=numeric_only, skipna=skipna
2389 ),
2390 numeric_only=numeric_only,
2391 skipna=skipna,
2392 )
2393 return result.__finalize__(self.obj, method="groupby")
2394
2395 @final
2396 def std(
2397 self,
2398 ddof: int = 1,
2399 engine: Literal["cython", "numba"] | None = None,
2400 engine_kwargs: dict[str, bool] | None = None,
2401 numeric_only: bool = False,
2402 skipna: bool = True,
2403 ):
2404 """
2405 Compute standard deviation of groups, excluding missing values.
2406
2407 For multiple groupings, the result index will be a MultiIndex.
2408
2409 Parameters
2410 ----------
2411 ddof : int, default 1
2412 Delta Degrees of Freedom. The divisor used in calculations is ``N - ddof``,
2413 where ``N`` represents the number of elements.
2414
2415 engine : str, default None
2416 * ``'cython'`` : Runs the operation through C-extensions from cython.
2417 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2418 * ``None`` : Defaults to ``'cython'`` or globally setting
2419 ``compute.use_numba``
2420
2421 engine_kwargs : dict, default None
2422 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2423 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2424 and ``parallel`` dictionary keys. The values must either be ``True`` or
2425 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2426 ``{'nopython': True, 'nogil': False, 'parallel': False}``
2427
2428 numeric_only : bool, default False
2429 Include only `float`, `int` or `boolean` data.
2430
2431 .. versionchanged:: 2.0.0
2432
2433 numeric_only now defaults to ``False``.
2434
2435 skipna : bool, default True
2436 Exclude NA/null values. If an entire group is NA, the result will be NA.
2437
2438 .. versionadded:: 3.0.0
2439
2440 Returns
2441 -------
2442 Series or DataFrame
2443 Standard deviation of values within each group.
2444
2445 See Also
2446 --------
2447 Series.std : Apply function std to a Series.
2448 DataFrame.std : Apply function std to each row or column of a DataFrame.
2449
2450 Examples
2451 --------
2452 For SeriesGroupBy:
2453
2454 >>> lst = ["a", "a", "a", "b", "b", "b"]
2455 >>> ser = pd.Series([7, 2, 8, 4, 3, 3], index=lst)
2456 >>> ser
2457 a 7
2458 a 2
2459 a 8
2460 b 4
2461 b 3
2462 b 3
2463 dtype: int64
2464 >>> ser.groupby(level=0).std()
2465 a 3.21455
2466 b 0.57735
2467 dtype: float64
2468
2469 For DataFrameGroupBy:
2470
2471 >>> data = {"a": [1, 3, 5, 7, 7, 8, 3], "b": [1, 4, 8, 4, 4, 2, 1]}
2472 >>> df = pd.DataFrame(
2473 ... data, index=["dog", "dog", "dog", "mouse", "mouse", "mouse", "mouse"]
2474 ... )
2475 >>> df
2476 a b
2477 dog 1 1
2478 dog 3 4
2479 dog 5 8
2480 mouse 7 4
2481 mouse 7 4
2482 mouse 8 2
2483 mouse 3 1
2484 >>> df.groupby(level=0).std()
2485 a b
2486 dog 2.000000 3.511885
2487 mouse 2.217356 1.500000
2488 """
2489 if maybe_use_numba(engine):
2490 from pandas.core._numba.kernels import grouped_var
2491
2492 return np.sqrt(
2493 self._numba_agg_general(
2494 grouped_var,
2495 executor.float_dtype_mapping,
2496 engine_kwargs,
2497 min_periods=0,
2498 ddof=ddof,
2499 skipna=skipna,
2500 )
2501 )
2502 else:
2503 return self._cython_agg_general(
2504 "std",
2505 alt=lambda x: Series(x, copy=False).std(ddof=ddof, skipna=skipna),
2506 numeric_only=numeric_only,
2507 ddof=ddof,
2508 skipna=skipna,
2509 )
2510
2511 @final
2512 def var(
2513 self,
2514 ddof: int = 1,
2515 engine: Literal["cython", "numba"] | None = None,
2516 engine_kwargs: dict[str, bool] | None = None,
2517 numeric_only: bool = False,
2518 skipna: bool = True,
2519 ):
2520 """
2521 Compute variance of groups, excluding missing values.
2522
2523 For multiple groupings, the result index will be a MultiIndex.
2524
2525 Parameters
2526 ----------
2527 ddof : int, default 1
2528 Degrees of freedom.
2529
2530 engine : str, default None
2531 * ``'cython'`` : Runs the operation through C-extensions from cython.
2532 * ``'numba'`` : Runs the operation through JIT compiled code from numba.
2533 * ``None`` : Defaults to ``'cython'`` or globally setting
2534 ``compute.use_numba``
2535
2536 engine_kwargs : dict, default None
2537 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2538 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2539 and ``parallel`` dictionary keys. The values must either be ``True`` or
2540 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2541 ``{'nopython': True, 'nogil': False, 'parallel': False}``
2542
2543 numeric_only : bool, default False
2544 Include only `float`, `int` or `boolean` data.
2545
2546 .. versionchanged:: 2.0.0
2547
2548 numeric_only now defaults to ``False``.
2549
2550 skipna : bool, default True
2551 Exclude NA/null values. If an entire group is NA, the result will be NA.
2552
2553 .. versionadded:: 3.0.0
2554
2555 Returns
2556 -------
2557 Series or DataFrame
2558 Variance of values within each group.
2559
2560 See Also
2561 --------
2562 Series.var : Apply function var to a Series.
2563 DataFrame.var : Apply function var to each row or column of a DataFrame.
2564
2565 Examples
2566 --------
2567 For SeriesGroupBy:
2568
2569 >>> lst = ["a", "a", "a", "b", "b", "b"]
2570 >>> ser = pd.Series([7, 2, 8, 4, 3, 3], index=lst)
2571 >>> ser
2572 a 7
2573 a 2
2574 a 8
2575 b 4
2576 b 3
2577 b 3
2578 dtype: int64
2579 >>> ser.groupby(level=0).var()
2580 a 10.333333
2581 b 0.333333
2582 dtype: float64
2583
2584 For DataFrameGroupBy:
2585
2586 >>> data = {"a": [1, 3, 5, 7, 7, 8, 3], "b": [1, 4, 8, 4, 4, 2, 1]}
2587 >>> df = pd.DataFrame(
2588 ... data, index=["dog", "dog", "dog", "mouse", "mouse", "mouse", "mouse"]
2589 ... )
2590 >>> df
2591 a b
2592 dog 1 1
2593 dog 3 4
2594 dog 5 8
2595 mouse 7 4
2596 mouse 7 4
2597 mouse 8 2
2598 mouse 3 1
2599 >>> df.groupby(level=0).var()
2600 a b
2601 dog 4.000000 12.333333
2602 mouse 4.916667 2.250000
2603 """
2604 if maybe_use_numba(engine):
2605 from pandas.core._numba.kernels import grouped_var
2606
2607 return self._numba_agg_general(
2608 grouped_var,
2609 executor.float_dtype_mapping,
2610 engine_kwargs,
2611 min_periods=0,
2612 ddof=ddof,
2613 skipna=skipna,
2614 )
2615 else:
2616 return self._cython_agg_general(
2617 "var",
2618 alt=lambda x: Series(x, copy=False).var(ddof=ddof, skipna=skipna),
2619 numeric_only=numeric_only,
2620 ddof=ddof,
2621 skipna=skipna,
2622 )
2623
2624 @final
2625 def _value_counts(
2626 self,
2627 subset: Sequence[Hashable] | None = None,
2628 normalize: bool = False,
2629 sort: bool = True,
2630 ascending: bool = False,
2631 dropna: bool = True,
2632 ) -> DataFrame | Series:
2633 """
2634 Shared implementation of value_counts for SeriesGroupBy and DataFrameGroupBy.
2635
2636 SeriesGroupBy additionally supports a bins argument. See the docstring of
2637 DataFrameGroupBy.value_counts for a description of arguments.
2638 """
2639 name = "proportion" if normalize else "count"
2640
2641 df = self.obj
2642 obj = self._obj_with_exclusions
2643
2644 in_axis_names = {
2645 grouping.name for grouping in self._grouper.groupings if grouping.in_axis
2646 }
2647 if isinstance(obj, Series):
2648 _name = obj.name
2649 keys: Iterable[Series] = [] if _name in in_axis_names else [obj]
2650 else:
2651 unique_cols = set(obj.columns)
2652 if subset is not None:
2653 subsetted = set(subset)
2654 clashing = subsetted & set(in_axis_names)
2655 if clashing:
2656 raise ValueError(
2657 f"Keys {clashing} in subset cannot be in "
2658 "the groupby column keys."
2659 )
2660 doesnt_exist = subsetted - unique_cols
2661 if doesnt_exist:
2662 raise ValueError(
2663 f"Keys {doesnt_exist} in subset do not exist in the DataFrame."
2664 )
2665 else:
2666 subsetted = unique_cols
2667
2668 keys = (
2669 # Can't use .values because the column label needs to be preserved
2670 obj.iloc[:, idx]
2671 for idx, _name in enumerate(obj.columns)
2672 if _name not in in_axis_names and _name in subsetted
2673 )
2674
2675 groupings = list(self._grouper.groupings)
2676 for key in keys:
2677 grouper, _, _ = get_grouper(
2678 df,
2679 key=key,
2680 sort=False,
2681 observed=False,
2682 dropna=dropna,
2683 )
2684 groupings += list(grouper.groupings)
2685
2686 # Take the size of the overall columns
2687 gb = df.groupby(
2688 groupings,
2689 sort=False,
2690 observed=self.observed,
2691 dropna=self.dropna,
2692 )
2693 result_series = cast(Series, gb.size())
2694 result_series.name = name
2695
2696 if sort:
2697 # Sort by the values
2698 result_series = result_series.sort_values(
2699 ascending=ascending, kind="stable"
2700 )
2701 if self.sort:
2702 # Sort by the groupings
2703 names = result_series.index.names
2704 # GH#55951 - Temporarily replace names in case they are integers
2705 result_series.index.names = range(len(names))
2706 index_level = range(len(self._grouper.groupings))
2707 result_series = result_series.sort_index(
2708 level=index_level, sort_remaining=False
2709 )
2710 result_series.index.names = names
2711
2712 if normalize:
2713 # Normalize the results by dividing by the original group sizes.
2714 # We are guaranteed to have the first N levels be the
2715 # user-requested grouping.
2716 levels = list(
2717 range(len(self._grouper.groupings), result_series.index.nlevels)
2718 )
2719 indexed_group_size = result_series.groupby(
2720 result_series.index.droplevel(levels),
2721 sort=self.sort,
2722 dropna=self.dropna,
2723 # GH#43999 - deprecation of observed=False
2724 observed=False,
2725 ).transform("sum")
2726 result_series /= indexed_group_size
2727
2728 # Handle groups of non-observed categories
2729 result_series = result_series.fillna(0.0)
2730
2731 result: Series | DataFrame
2732 if self.as_index:
2733 result = result_series
2734 else:
2735 # Convert to frame
2736 index = result_series.index
2737 columns = com.fill_missing_names(index.names)
2738 if name in columns:
2739 raise ValueError(f"Column label '{name}' is duplicate of result column")
2740 result_series.name = name
2741 result_series.index = index.set_names(range(len(columns)))
2742 result_frame = result_series.reset_index()
2743 orig_dtype = self._grouper.groupings[0].obj.columns.dtype # type: ignore[union-attr]
2744 cols = Index(columns, dtype=orig_dtype).insert(len(columns), name)
2745 result_frame.columns = cols
2746 result = result_frame
2747 return result.__finalize__(self.obj, method="value_counts")
2748
2749 @final
2750 def sem(
2751 self, ddof: int = 1, numeric_only: bool = False, skipna: bool = True
2752 ) -> NDFrameT:
2753 """
2754 Compute standard error of the mean of groups, excluding missing values.
2755
2756 For multiple groupings, the result index will be a MultiIndex.
2757
2758 Parameters
2759 ----------
2760 ddof : int, default 1
2761 Degrees of freedom.
2762
2763 numeric_only : bool, default False
2764 Include only `float`, `int` or `boolean` data.
2765
2766 .. versionchanged:: 2.0.0
2767
2768 numeric_only now defaults to ``False``.
2769
2770 skipna : bool, default True
2771 Exclude NA/null values. If an entire group is NA, the result will be NA.
2772
2773 .. versionadded:: 3.0.0
2774
2775 Returns
2776 -------
2777 Series or DataFrame
2778 Standard error of the mean of values within each group.
2779
2780 See Also
2781 --------
2782 DataFrame.sem : Return unbiased standard error of the mean over requested axis.
2783 Series.sem : Return unbiased standard error of the mean over requested axis.
2784
2785 Examples
2786 --------
2787 For SeriesGroupBy:
2788
2789 >>> lst = ["a", "a", "b", "b"]
2790 >>> ser = pd.Series([5, 10, 8, 14], index=lst)
2791 >>> ser
2792 a 5
2793 a 10
2794 b 8
2795 b 14
2796 dtype: int64
2797 >>> ser.groupby(level=0).sem()
2798 a 2.5
2799 b 3.0
2800 dtype: float64
2801
2802 For DataFrameGroupBy:
2803
2804 >>> data = [[1, 12, 11], [1, 15, 2], [2, 5, 8], [2, 6, 12]]
2805 >>> df = pd.DataFrame(
2806 ... data,
2807 ... columns=["a", "b", "c"],
2808 ... index=["tuna", "salmon", "catfish", "goldfish"],
2809 ... )
2810 >>> df
2811 a b c
2812 tuna 1 12 11
2813 salmon 1 15 2
2814 catfish 2 5 8
2815 goldfish 2 6 12
2816 >>> df.groupby("a").sem()
2817 b c
2818 a
2819 1 1.5 4.5
2820 2 0.5 2.0
2821
2822 For Resampler:
2823
2824 >>> ser = pd.Series(
2825 ... [1, 3, 2, 4, 3, 8],
2826 ... index=pd.DatetimeIndex(
2827 ... [
2828 ... "2023-01-01",
2829 ... "2023-01-10",
2830 ... "2023-01-15",
2831 ... "2023-02-01",
2832 ... "2023-02-10",
2833 ... "2023-02-15",
2834 ... ]
2835 ... ),
2836 ... )
2837 >>> ser.resample("MS").sem()
2838 2023-01-01 0.577350
2839 2023-02-01 1.527525
2840 Freq: MS, dtype: float64
2841 """
2842 if numeric_only and self.obj.ndim == 1 and not is_numeric_dtype(self.obj.dtype):
2843 raise TypeError(
2844 f"{type(self).__name__}.sem called with "
2845 f"numeric_only={numeric_only} and dtype {self.obj.dtype}"
2846 )
2847 return self._cython_agg_general(
2848 "sem",
2849 alt=lambda x: Series(x, copy=False).sem(ddof=ddof, skipna=skipna),
2850 numeric_only=numeric_only,
2851 ddof=ddof,
2852 skipna=skipna,
2853 )
2854
2855 @final
2856 def size(self) -> DataFrame | Series:
2857 """
2858 Compute group sizes.
2859
2860 Returns
2861 -------
2862 DataFrame or Series
2863 Number of rows in each group as a Series if as_index is True
2864 or a DataFrame if as_index is False.
2865
2866 See Also
2867 --------
2868 Series.size : Apply function size to a Series.
2869 DataFrame.size : Apply function size to each row or column of a DataFrame.
2870
2871 Examples
2872 --------
2873
2874 For SeriesGroupBy:
2875
2876 >>> lst = ["a", "a", "b"]
2877 >>> ser = pd.Series([1, 2, 3], index=lst)
2878 >>> ser
2879 a 1
2880 a 2
2881 b 3
2882 dtype: int64
2883 >>> ser.groupby(level=0).size()
2884 a 2
2885 b 1
2886 dtype: int64
2887
2888 >>> data = [[1, 2, 3], [1, 5, 6], [7, 8, 9]]
2889 >>> df = pd.DataFrame(
2890 ... data, columns=["a", "b", "c"], index=["owl", "toucan", "eagle"]
2891 ... )
2892 >>> df
2893 a b c
2894 owl 1 2 3
2895 toucan 1 5 6
2896 eagle 7 8 9
2897 >>> df.groupby("a").size()
2898 a
2899 1 2
2900 7 1
2901 dtype: int64
2902
2903 For Resampler:
2904
2905 >>> ser = pd.Series(
2906 ... [1, 2, 3],
2907 ... index=pd.DatetimeIndex(["2023-01-01", "2023-01-15", "2023-02-01"]),
2908 ... )
2909 >>> ser
2910 2023-01-01 1
2911 2023-01-15 2
2912 2023-02-01 3
2913 dtype: int64
2914 >>> ser.resample("MS").size()
2915 2023-01-01 2
2916 2023-02-01 1
2917 Freq: MS, dtype: int64
2918 """
2919 result = self._grouper.size()
2920 dtype_backend: None | Literal["pyarrow", "numpy_nullable"] = None
2921 if isinstance(self.obj, Series):
2922 if isinstance(self.obj.array, ArrowExtensionArray):
2923 if isinstance(self.obj.array, ArrowStringArray):
2924 if self.obj.array.dtype.na_value is np.nan:
2925 dtype_backend = None
2926 else:
2927 dtype_backend = "numpy_nullable"
2928 else:
2929 dtype_backend = "pyarrow"
2930 elif isinstance(self.obj.array, BaseMaskedArray):
2931 dtype_backend = "numpy_nullable"
2932 # TODO: For DataFrames what if columns are mixed arrow/numpy/masked?
2933
2934 # GH28330 preserve subclassed Series/DataFrames through calls
2935 if isinstance(self.obj, Series):
2936 result = self._obj_1d_constructor(result, name=self.obj.name)
2937 else:
2938 result = self._obj_1d_constructor(result)
2939
2940 if dtype_backend is not None:
2941 result = result.convert_dtypes(
2942 infer_objects=False,
2943 convert_string=False,
2944 convert_boolean=False,
2945 convert_floating=False,
2946 dtype_backend=dtype_backend,
2947 )
2948
2949 if not self.as_index:
2950 result = result.rename("size").reset_index()
2951 return result
2952
2953 @final
2954 def sum(
2955 self,
2956 numeric_only: bool = False,
2957 min_count: int = 0,
2958 skipna: bool = True,
2959 engine: Literal["cython", "numba"] | None = None,
2960 engine_kwargs: dict[str, bool] | None = None,
2961 ):
2962 """
2963 Compute sum of group values.
2964
2965 Parameters
2966 ----------
2967 numeric_only : bool, default False
2968 Include only float, int, boolean columns.
2969
2970 .. versionchanged:: 2.0.0
2971
2972 numeric_only no longer accepts ``None``.
2973
2974 min_count : int, default 0
2975 The required number of valid values to perform the operation. If fewer
2976 than ``min_count`` non-NA values are present the result will be NA.
2977
2978 skipna : bool, default True
2979 Exclude NA/null values. If the entire group is NA and ``skipna`` is
2980 ``True``, the result will be NA.
2981
2982 .. versionchanged:: 3.0.0
2983
2984 engine : str, default None None
2985 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
2986 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
2987 Only available when ``raw`` is set to ``True``.
2988 * ``None`` : Defaults to ``'cython'`` or globally setting
2989 ``compute.use_numba``
2990
2991 engine_kwargs : dict, default None None
2992 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2993 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2994 and ``parallel`` dictionary keys. The values must either be ``True`` or
2995 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2996 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
2997 applied to both the ``func`` and the ``apply`` groupby aggregation.
2998
2999 Returns
3000 -------
3001 Series or DataFrame
3002 Computed sum of values within each group.
3003
3004 See Also
3005 --------
3006 SeriesGroupBy.min : Return the min of the group values.
3007 DataFrameGroupBy.min : Return the min of the group values.
3008 SeriesGroupBy.max : Return the max of the group values.
3009 DataFrameGroupBy.max : Return the max of the group values.
3010 SeriesGroupBy.sum : Return the sum of the group values.
3011 DataFrameGroupBy.sum : Return the sum of the group values.
3012
3013 Examples
3014 --------
3015 For SeriesGroupBy:
3016
3017 >>> lst = ["a", "a", "b", "b"]
3018 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
3019 >>> ser
3020 a 1
3021 a 2
3022 b 3
3023 b 4
3024 dtype: int64
3025 >>> ser.groupby(level=0).sum()
3026 a 3
3027 b 7
3028 dtype: int64
3029
3030 For DataFrameGroupBy:
3031
3032 >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]]
3033 >>> df = pd.DataFrame(
3034 ... data,
3035 ... columns=["a", "b", "c"],
3036 ... index=["tiger", "leopard", "cheetah", "lion"],
3037 ... )
3038 >>> df
3039 a b c
3040 tiger 1 8 2
3041 leopard 1 2 5
3042 cheetah 2 5 8
3043 lion 2 6 9
3044 >>> df.groupby("a").sum()
3045 b c
3046 a
3047 1 10 7
3048 2 11 17
3049 """
3050 if maybe_use_numba(engine):
3051 from pandas.core._numba.kernels import grouped_sum
3052
3053 return self._numba_agg_general(
3054 grouped_sum,
3055 executor.default_dtype_mapping,
3056 engine_kwargs,
3057 min_periods=min_count,
3058 skipna=skipna,
3059 )
3060 else:
3061 # If we are grouping on categoricals we want unobserved categories to
3062 # return zero, rather than the default of NaN which the reindexing in
3063 # _agg_general() returns. GH #31422
3064 with com.temp_setattr(self, "observed", True):
3065 result = self._agg_general(
3066 numeric_only=numeric_only,
3067 min_count=min_count,
3068 alias="sum",
3069 npfunc=np.sum,
3070 skipna=skipna,
3071 )
3072
3073 return result
3074
3075 @final
3076 def prod(
3077 self, numeric_only: bool = False, min_count: int = 0, skipna: bool = True
3078 ) -> NDFrameT:
3079 """
3080 Compute prod of group values.
3081
3082 Parameters
3083 ----------
3084 numeric_only : bool, default False
3085 Include only float, int, boolean columns.
3086
3087 .. versionchanged:: 2.0.0
3088
3089 numeric_only no longer accepts ``None``.
3090
3091 min_count : int, default 0
3092 The required number of valid values to perform the operation. If fewer
3093 than ``min_count`` non-NA values are present the result will be NA.
3094
3095 skipna : bool, default True
3096 Exclude NA/null values. If an entire group is NA, the result will be NA.
3097
3098 .. versionadded:: 3.0.0
3099
3100 Returns
3101 -------
3102 Series or DataFrame
3103 Computed prod of values within each group.
3104
3105 See Also
3106 --------
3107 Series.prod : Return the product of the values over the requested axis.
3108 DataFrame.prod : Return the product of the values over the requested axis.
3109
3110 Examples
3111 --------
3112 For SeriesGroupBy:
3113
3114 >>> lst = ["a", "a", "b", "b"]
3115 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
3116 >>> ser
3117 a 1
3118 a 2
3119 b 3
3120 b 4
3121 dtype: int64
3122 >>> ser.groupby(level=0).prod()
3123 a 2
3124 b 12
3125 dtype: int64
3126
3127 For DataFrameGroupBy:
3128
3129 >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]]
3130 >>> df = pd.DataFrame(
3131 ... data,
3132 ... columns=["a", "b", "c"],
3133 ... index=["tiger", "leopard", "cheetah", "lion"],
3134 ... )
3135 >>> df
3136 a b c
3137 tiger 1 8 2
3138 leopard 1 2 5
3139 cheetah 2 5 8
3140 lion 2 6 9
3141 >>> df.groupby("a").prod()
3142 b c
3143 a
3144 1 16 10
3145 2 30 72
3146 """
3147 return self._agg_general(
3148 numeric_only=numeric_only,
3149 min_count=min_count,
3150 skipna=skipna,
3151 alias="prod",
3152 npfunc=np.prod,
3153 )
3154
3155 @final
3156 def min(
3157 self,
3158 numeric_only: bool = False,
3159 min_count: int = -1,
3160 skipna: bool = True,
3161 engine: Literal["cython", "numba"] | None = None,
3162 engine_kwargs: dict[str, bool] | None = None,
3163 ):
3164 """
3165 Compute min of group values.
3166
3167 Parameters
3168 ----------
3169 numeric_only : bool, default False
3170 Include only float, int, boolean columns.
3171
3172 .. versionchanged:: 2.0.0
3173
3174 numeric_only no longer accepts ``None``.
3175
3176 min_count : int, default -1
3177 The required number of valid values to perform the operation. If fewer
3178 than ``min_count`` non-NA values are present the result will be NA.
3179
3180 skipna : bool, default True
3181 Exclude NA/null values. If the entire group is NA and ``skipna`` is
3182 ``True``, the result will be NA.
3183
3184 .. versionchanged:: 3.0.0
3185
3186 engine : str, default None None
3187 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
3188 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
3189 Only available when ``raw`` is set to ``True``.
3190 * ``None`` : Defaults to ``'cython'`` or globally setting
3191 ``compute.use_numba``
3192
3193 engine_kwargs : dict, default None None
3194 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
3195 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
3196 and ``parallel`` dictionary keys. The values must either be ``True`` or
3197 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
3198 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
3199 applied to both the ``func`` and the ``apply`` groupby aggregation.
3200
3201 Returns
3202 -------
3203 Series or DataFrame
3204 Computed min of values within each group.
3205
3206 See Also
3207 --------
3208 SeriesGroupBy.min : Return the min of the group values.
3209 DataFrameGroupBy.min : Return the min of the group values.
3210 SeriesGroupBy.max : Return the max of the group values.
3211 DataFrameGroupBy.max : Return the max of the group values.
3212 SeriesGroupBy.sum : Return the sum of the group values.
3213 DataFrameGroupBy.sum : Return the sum of the group values.
3214
3215 Examples
3216 --------
3217 For SeriesGroupBy:
3218
3219 >>> lst = ["a", "a", "b", "b"]
3220 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
3221 >>> ser
3222 a 1
3223 a 2
3224 b 3
3225 b 4
3226 dtype: int64
3227 >>> ser.groupby(level=0).min()
3228 a 1
3229 b 3
3230 dtype: int64
3231
3232 For DataFrameGroupBy:
3233
3234 >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]]
3235 >>> df = pd.DataFrame(
3236 ... data,
3237 ... columns=["a", "b", "c"],
3238 ... index=["tiger", "leopard", "cheetah", "lion"],
3239 ... )
3240 >>> df
3241 a b c
3242 tiger 1 8 2
3243 leopard 1 2 5
3244 cheetah 2 5 8
3245 lion 2 6 9
3246 >>> df.groupby("a").min()
3247 b c
3248 a
3249 1 2 2
3250 2 5 8
3251 """
3252 if maybe_use_numba(engine):
3253 from pandas.core._numba.kernels import grouped_min_max
3254
3255 return self._numba_agg_general(
3256 grouped_min_max,
3257 executor.identity_dtype_mapping,
3258 engine_kwargs,
3259 min_periods=min_count,
3260 is_max=False,
3261 skipna=skipna,
3262 )
3263 else:
3264 return self._agg_general(
3265 numeric_only=numeric_only,
3266 min_count=min_count,
3267 skipna=skipna,
3268 alias="min",
3269 npfunc=np.min,
3270 )
3271
3272 @final
3273 def max(
3274 self,
3275 numeric_only: bool = False,
3276 min_count: int = -1,
3277 skipna: bool = True,
3278 engine: Literal["cython", "numba"] | None = None,
3279 engine_kwargs: dict[str, bool] | None = None,
3280 ):
3281 """
3282 Compute max of group values.
3283
3284 Parameters
3285 ----------
3286 numeric_only : bool, default False
3287 Include only float, int, boolean columns.
3288
3289 .. versionchanged:: 2.0.0
3290
3291 numeric_only no longer accepts ``None``.
3292
3293 min_count : int, default -1
3294 The required number of valid values to perform the operation. If fewer
3295 than ``min_count`` non-NA values are present the result will be NA.
3296
3297 skipna : bool, default True
3298 Exclude NA/null values. If the entire group is NA and ``skipna`` is
3299 ``True``, the result will be NA.
3300
3301 .. versionchanged:: 3.0.0
3302
3303 engine : str, default None None
3304 * ``'cython'`` : Runs rolling apply through C-extensions from cython.
3305 * ``'numba'`` : Runs rolling apply through JIT compiled code from numba.
3306 Only available when ``raw`` is set to ``True``.
3307 * ``None`` : Defaults to ``'cython'`` or globally setting
3308 ``compute.use_numba``
3309
3310 engine_kwargs : dict, default None None
3311 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
3312 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
3313 and ``parallel`` dictionary keys. The values must either be ``True`` or
3314 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
3315 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
3316 applied to both the ``func`` and the ``apply`` groupby aggregation.
3317
3318 Returns
3319 -------
3320 Series or DataFrame
3321 Computed max of values within each group.
3322
3323 See Also
3324 --------
3325 SeriesGroupBy.min : Return the min of the group values.
3326 DataFrameGroupBy.min : Return the min of the group values.
3327 SeriesGroupBy.max : Return the max of the group values.
3328 DataFrameGroupBy.max : Return the max of the group values.
3329 SeriesGroupBy.sum : Return the sum of the group values.
3330 DataFrameGroupBy.sum : Return the sum of the group values.
3331
3332 Examples
3333 --------
3334 For SeriesGroupBy:
3335
3336 >>> lst = ["a", "a", "b", "b"]
3337 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
3338 >>> ser
3339 a 1
3340 a 2
3341 b 3
3342 b 4
3343 dtype: int64
3344 >>> ser.groupby(level=0).max()
3345 a 2
3346 b 4
3347 dtype: int64
3348
3349 For DataFrameGroupBy:
3350
3351 >>> data = [[1, 8, 2], [1, 2, 5], [2, 5, 8], [2, 6, 9]]
3352 >>> df = pd.DataFrame(
3353 ... data,
3354 ... columns=["a", "b", "c"],
3355 ... index=["tiger", "leopard", "cheetah", "lion"],
3356 ... )
3357 >>> df
3358 a b c
3359 tiger 1 8 2
3360 leopard 1 2 5
3361 cheetah 2 5 8
3362 lion 2 6 9
3363 >>> df.groupby("a").max()
3364 b c
3365 a
3366 1 8 5
3367 2 6 9
3368 """
3369 if maybe_use_numba(engine):
3370 from pandas.core._numba.kernels import grouped_min_max
3371
3372 return self._numba_agg_general(
3373 grouped_min_max,
3374 executor.identity_dtype_mapping,
3375 engine_kwargs,
3376 min_periods=min_count,
3377 is_max=True,
3378 skipna=skipna,
3379 )
3380 else:
3381 return self._agg_general(
3382 numeric_only=numeric_only,
3383 min_count=min_count,
3384 skipna=skipna,
3385 alias="max",
3386 npfunc=np.max,
3387 )
3388
3389 @final
3390 def first(
3391 self, numeric_only: bool = False, min_count: int = -1, skipna: bool = True
3392 ) -> NDFrameT:
3393 """
3394 Compute the first entry of each column within each group.
3395
3396 Defaults to skipping NA elements.
3397
3398 Parameters
3399 ----------
3400 numeric_only : bool, default False
3401 Include only float, int, boolean columns.
3402 min_count : int, default -1
3403 The required number of valid values to perform the operation. If fewer
3404 than ``min_count`` valid values are present the result will be NA.
3405 skipna : bool, default True
3406 Exclude NA/null values. If an entire group is NA, the result will be NA.
3407
3408 .. versionadded:: 2.2.1
3409
3410 Returns
3411 -------
3412 Series or DataFrame
3413 First values within each group.
3414
3415 See Also
3416 --------
3417 DataFrame.groupby : Apply a function groupby to each row or column of a
3418 DataFrame.
3419 core.groupby.DataFrameGroupBy.last : Compute the last non-null entry
3420 of each column.
3421 core.groupby.DataFrameGroupBy.nth : Take the nth row from each group.
3422
3423 Examples
3424 --------
3425 >>> df = pd.DataFrame(
3426 ... dict(
3427 ... A=[1, 1, 3],
3428 ... B=[None, 5, 6],
3429 ... C=[1, 2, 3],
3430 ... D=["3/11/2000", "3/12/2000", "3/13/2000"],
3431 ... )
3432 ... )
3433 >>> df["D"] = pd.to_datetime(df["D"])
3434 >>> df.groupby("A").first()
3435 B C D
3436 A
3437 1 5.0 1 2000-03-11
3438 3 6.0 3 2000-03-13
3439 >>> df.groupby("A").first(min_count=2)
3440 B C D
3441 A
3442 1 NaN 1.0 2000-03-11
3443 3 NaN NaN NaT
3444 >>> df.groupby("A").first(numeric_only=True)
3445 B C
3446 A
3447 1 5.0 1
3448 3 6.0 3
3449 """
3450
3451 def first_compat(obj: NDFrameT):
3452 def first(x: Series):
3453 """Helper function for first item that isn't NA."""
3454 arr = x.array[notna(x.array)]
3455 if not len(arr):
3456 return x.array.dtype.na_value
3457 return arr[0]
3458
3459 if isinstance(obj, DataFrame):
3460 return obj.apply(first)
3461 elif isinstance(obj, Series):
3462 return first(obj)
3463 else: # pragma: no cover
3464 raise TypeError(type(obj))
3465
3466 return self._agg_general(
3467 numeric_only=numeric_only,
3468 min_count=min_count,
3469 alias="first",
3470 npfunc=first_compat,
3471 skipna=skipna,
3472 )
3473
3474 @final
3475 def last(
3476 self, numeric_only: bool = False, min_count: int = -1, skipna: bool = True
3477 ) -> NDFrameT:
3478 """
3479 Compute the last entry of each column within each group.
3480
3481 Defaults to skipping NA elements.
3482
3483 Parameters
3484 ----------
3485 numeric_only : bool, default False
3486 Include only float, int, boolean columns. If None, will attempt to use
3487 everything, then use only numeric data.
3488 min_count : int, default -1
3489 The required number of valid values to perform the operation. If fewer
3490 than ``min_count`` valid values are present the result will be NA.
3491 skipna : bool, default True
3492 Exclude NA/null values. If an entire group is NA, the result will be NA.
3493
3494 .. versionadded:: 2.2.1
3495
3496 Returns
3497 -------
3498 Series or DataFrame
3499 Last of values within each group.
3500
3501 See Also
3502 --------
3503 DataFrame.groupby : Apply a function groupby to each row or column of a
3504 DataFrame.
3505 core.groupby.DataFrameGroupBy.first : Compute the first non-null entry
3506 of each column.
3507 core.groupby.DataFrameGroupBy.nth : Take the nth row from each group.
3508
3509 Examples
3510 --------
3511 >>> df = pd.DataFrame(dict(A=[1, 1, 3], B=[5, None, 6], C=[1, 2, 3]))
3512 >>> df.groupby("A").last()
3513 B C
3514 A
3515 1 5.0 2
3516 3 6.0 3
3517 """
3518
3519 def last_compat(obj: NDFrameT):
3520 def last(x: Series):
3521 """Helper function for last item that isn't NA."""
3522 arr = x.array[notna(x.array)]
3523 if not len(arr):
3524 return x.array.dtype.na_value
3525 return arr[-1]
3526
3527 if isinstance(obj, DataFrame):
3528 return obj.apply(last)
3529 elif isinstance(obj, Series):
3530 return last(obj)
3531 else: # pragma: no cover
3532 raise TypeError(type(obj))
3533
3534 return self._agg_general(
3535 numeric_only=numeric_only,
3536 min_count=min_count,
3537 alias="last",
3538 npfunc=last_compat,
3539 skipna=skipna,
3540 )
3541
3542 @final
3543 def ohlc(self) -> DataFrame:
3544 """
3545 Compute open, high, low and close values of a group, excluding missing values.
3546
3547 For multiple groupings, the result index will be a MultiIndex
3548
3549 Returns
3550 -------
3551 DataFrame
3552 Open, high, low and close values within each group.
3553
3554 See Also
3555 --------
3556 DataFrame.agg : Aggregate using one or more operations over the specified axis.
3557 DataFrame.resample : Resample time-series data.
3558 DataFrame.groupby : Group DataFrame using a mapper or by a Series of columns.
3559
3560 Examples
3561 --------
3562
3563 For SeriesGroupBy:
3564
3565 >>> lst = [
3566 ... "SPX",
3567 ... "CAC",
3568 ... "SPX",
3569 ... "CAC",
3570 ... "SPX",
3571 ... "CAC",
3572 ... "SPX",
3573 ... "CAC",
3574 ... ]
3575 >>> ser = pd.Series([3.4, 9.0, 7.2, 5.2, 8.8, 9.4, 0.1, 0.5], index=lst)
3576 >>> ser
3577 SPX 3.4
3578 CAC 9.0
3579 SPX 7.2
3580 CAC 5.2
3581 SPX 8.8
3582 CAC 9.4
3583 SPX 0.1
3584 CAC 0.5
3585 dtype: float64
3586 >>> ser.groupby(level=0).ohlc()
3587 open high low close
3588 CAC 9.0 9.4 0.5 0.5
3589 SPX 3.4 8.8 0.1 0.1
3590
3591 For DataFrameGroupBy:
3592
3593 >>> data = {
3594 ... 2022: [1.2, 2.3, 8.9, 4.5, 4.4, 3, 2, 1],
3595 ... 2023: [3.4, 9.0, 7.2, 5.2, 8.8, 9.4, 8.2, 1.0],
3596 ... }
3597 >>> df = pd.DataFrame(
3598 ... data, index=["SPX", "CAC", "SPX", "CAC", "SPX", "CAC", "SPX", "CAC"]
3599 ... )
3600 >>> df
3601 2022 2023
3602 SPX 1.2 3.4
3603 CAC 2.3 9.0
3604 SPX 8.9 7.2
3605 CAC 4.5 5.2
3606 SPX 4.4 8.8
3607 CAC 3.0 9.4
3608 SPX 2.0 8.2
3609 CAC 1.0 1.0
3610 >>> df.groupby(level=0).ohlc()
3611 2022 2023
3612 open high low close open high low close
3613 CAC 2.3 4.5 1.0 1.0 9.0 9.4 1.0 1.0
3614 SPX 1.2 8.9 1.2 2.0 3.4 8.8 3.4 8.2
3615
3616 For Resampler:
3617
3618 >>> ser = pd.Series(
3619 ... [1, 3, 2, 4, 3, 5],
3620 ... index=pd.DatetimeIndex(
3621 ... [
3622 ... "2023-01-01",
3623 ... "2023-01-10",
3624 ... "2023-01-15",
3625 ... "2023-02-01",
3626 ... "2023-02-10",
3627 ... "2023-02-15",
3628 ... ]
3629 ... ),
3630 ... )
3631 >>> ser.resample("MS").ohlc()
3632 open high low close
3633 2023-01-01 1 3 1 2
3634 2023-02-01 4 5 3 5
3635 """
3636 if self.obj.ndim == 1:
3637 obj = self._selected_obj
3638
3639 is_numeric = is_numeric_dtype(obj.dtype)
3640 if not is_numeric:
3641 raise DataError("No numeric types to aggregate")
3642
3643 res_values = self._grouper._cython_operation(
3644 "aggregate", obj._values, "ohlc", axis=0, min_count=-1
3645 )
3646
3647 agg_names = ["open", "high", "low", "close"]
3648 result = self.obj._constructor_expanddim(
3649 res_values, index=self._grouper.result_index, columns=agg_names
3650 )
3651 return result
3652
3653 result = self._apply_to_column_groupbys(lambda sgb: sgb.ohlc())
3654 return result
3655
3656 def describe(
3657 self,
3658 percentiles=None,
3659 include=None,
3660 exclude=None,
3661 ) -> NDFrameT:
3662 """
3663 Generate descriptive statistics.
3664
3665 Descriptive statistics include those that summarize the central
3666 tendency, dispersion and shape of a
3667 dataset's distribution, excluding ``NaN`` values.
3668
3669 Analyzes both numeric and object series, as well
3670 as ``DataFrame`` column sets of mixed data types. The output
3671 will vary depending on what is provided. Refer to the notes
3672 below for more detail.
3673
3674 Parameters
3675 ----------
3676 percentiles : list-like of numbers, optional
3677 The percentiles to include in the output. All should
3678 fall between 0 and 1. The default, ``None``, will automatically
3679 return the 25th, 50th, and 75th percentiles.
3680 include : 'all', list-like of dtypes or None (default), optional
3681 A white list of data types to include in the result. Ignored
3682 for ``Series``. Here are the options:
3683
3684 - 'all' : All columns of the input will be included in the output.
3685 - A list-like of dtypes : Limits the results to the
3686 provided data types.
3687 To limit the result to numeric types submit
3688 ``numpy.number``. To limit it instead to object columns submit
3689 the ``numpy.object`` data type. Strings
3690 can also be used in the style of
3691 ``select_dtypes`` (e.g. ``df.describe(include=['O'])``). To
3692 select pandas categorical columns, use ``'category'``
3693 - None (default) : The result will include all numeric columns.
3694 exclude : list-like of dtypes or None (default), optional,
3695 A black list of data types to omit from the result. Ignored
3696 for ``Series``. Here are the options:
3697
3698 - A list-like of dtypes : Excludes the provided data types
3699 from the result. To exclude numeric types submit
3700 ``numpy.number``. To exclude object columns submit the data
3701 type ``numpy.object``. Strings can also be used in the style of
3702 ``select_dtypes`` (e.g. ``df.describe(exclude=['O'])``). To
3703 exclude pandas categorical columns, use ``'category'``
3704 - None (default) : The result will exclude nothing.
3705
3706 Returns
3707 -------
3708 Series or DataFrame
3709 Summary statistics of the Series or Dataframe provided.
3710
3711 See Also
3712 --------
3713 DataFrame.count: Count number of non-NA/null observations.
3714 DataFrame.max: Maximum of the values in the object.
3715 DataFrame.min: Minimum of the values in the object.
3716 DataFrame.mean: Mean of the values.
3717 DataFrame.std: Standard deviation of the observations.
3718 DataFrame.select_dtypes: Subset of a DataFrame including/excluding
3719 columns based on their dtype.
3720
3721 Notes
3722 -----
3723 For numeric data, the result's index will include ``count``,
3724 ``mean``, ``std``, ``min``, ``max`` as well as lower, ``50`` and
3725 upper percentiles. By default the lower percentile is ``25`` and the
3726 upper percentile is ``75``. The ``50`` percentile is the
3727 same as the median.
3728
3729 For object data (e.g. strings), the result's index
3730 will include ``count``, ``unique``, ``top``, and ``freq``. The ``top``
3731 is the most common value. The ``freq`` is the most common value's
3732 frequency.
3733
3734 If multiple object values have the highest count, then the
3735 ``count`` and ``top`` results will be arbitrarily chosen from
3736 among those with the highest count.
3737
3738 For mixed data types provided via a ``DataFrame``, the default is to
3739 return only an analysis of numeric columns. If the DataFrame consists
3740 only of object and categorical data without any numeric columns, the
3741 default is to return an analysis of both the object and categorical
3742 columns. If ``include='all'`` is provided as an option, the result
3743 will include a union of attributes of each type.
3744
3745 The `include` and `exclude` parameters can be used to limit
3746 which columns in a ``DataFrame`` are analyzed for the output.
3747 The parameters are ignored when analyzing a ``Series``.
3748
3749 Examples
3750 --------
3751 Describing a numeric ``Series``.
3752
3753 >>> s = pd.Series([1, 2, 3])
3754 >>> s.describe()
3755 count 3.0
3756 mean 2.0
3757 std 1.0
3758 min 1.0
3759 25% 1.5
3760 50% 2.0
3761 75% 2.5
3762 max 3.0
3763 dtype: float64
3764
3765 Describing a categorical ``Series``.
3766
3767 >>> s = pd.Series(["a", "a", "b", "c"])
3768 >>> s.describe()
3769 count 4
3770 unique 3
3771 top a
3772 freq 2
3773 dtype: object
3774
3775 Describing a timestamp ``Series``.
3776
3777 >>> s = pd.Series(
3778 ... [
3779 ... np.datetime64("2000-01-01"),
3780 ... np.datetime64("2010-01-01"),
3781 ... np.datetime64("2010-01-01"),
3782 ... ]
3783 ... )
3784 >>> s.describe()
3785 count 3
3786 mean 2006-09-01 08:00:00
3787 min 2000-01-01 00:00:00
3788 25% 2004-12-31 12:00:00
3789 50% 2010-01-01 00:00:00
3790 75% 2010-01-01 00:00:00
3791 max 2010-01-01 00:00:00
3792 dtype: object
3793
3794 Describing a ``DataFrame``. By default only numeric fields
3795 are returned.
3796
3797 >>> df = pd.DataFrame(
3798 ... {
3799 ... "categorical": pd.Categorical(["d", "e", "f"]),
3800 ... "numeric": [1, 2, 3],
3801 ... "object": ["a", "b", "c"],
3802 ... }
3803 ... )
3804 >>> df.describe()
3805 numeric
3806 count 3.0
3807 mean 2.0
3808 std 1.0
3809 min 1.0
3810 25% 1.5
3811 50% 2.0
3812 75% 2.5
3813 max 3.0
3814
3815 Describing all columns of a ``DataFrame`` regardless of data type.
3816
3817 >>> df.describe(include="all") # doctest: +SKIP
3818 categorical numeric object
3819 count 3 3.0 3
3820 unique 3 NaN 3
3821 top f NaN a
3822 freq 1 NaN 1
3823 mean NaN 2.0 NaN
3824 std NaN 1.0 NaN
3825 min NaN 1.0 NaN
3826 25% NaN 1.5 NaN
3827 50% NaN 2.0 NaN
3828 75% NaN 2.5 NaN
3829 max NaN 3.0 NaN
3830
3831 Describing a column from a ``DataFrame`` by accessing it as
3832 an attribute.
3833
3834 >>> df.numeric.describe()
3835 count 3.0
3836 mean 2.0
3837 std 1.0
3838 min 1.0
3839 25% 1.5
3840 50% 2.0
3841 75% 2.5
3842 max 3.0
3843 Name: numeric, dtype: float64
3844
3845 Including only numeric columns in a ``DataFrame`` description.
3846
3847 >>> df.describe(include=[np.number])
3848 numeric
3849 count 3.0
3850 mean 2.0
3851 std 1.0
3852 min 1.0
3853 25% 1.5
3854 50% 2.0
3855 75% 2.5
3856 max 3.0
3857
3858 Including only string columns in a ``DataFrame`` description.
3859
3860 >>> df.describe(include=[object]) # doctest: +SKIP
3861 object
3862 count 3
3863 unique 3
3864 top a
3865 freq 1
3866
3867 Including only categorical columns from a ``DataFrame`` description.
3868
3869 >>> df.describe(include=["category"])
3870 categorical
3871 count 3
3872 unique 3
3873 top d
3874 freq 1
3875
3876 Excluding numeric columns from a ``DataFrame`` description.
3877
3878 >>> df.describe(exclude=[np.number]) # doctest: +SKIP
3879 categorical object
3880 count 3 3
3881 unique 3 3
3882 top f a
3883 freq 1 1
3884
3885 Excluding object columns from a ``DataFrame`` description.
3886
3887 >>> df.describe(exclude=[object]) # doctest: +SKIP
3888 categorical numeric
3889 count 3 3.0
3890 unique 3 NaN
3891 top f NaN
3892 freq 1 NaN
3893 mean NaN 2.0
3894 std NaN 1.0
3895 min NaN 1.0
3896 25% NaN 1.5
3897 50% NaN 2.0
3898 75% NaN 2.5
3899 max NaN 3.0
3900 """
3901 obj = self._obj_with_exclusions
3902
3903 if len(obj) == 0:
3904 described = obj.describe(
3905 percentiles=percentiles, include=include, exclude=exclude
3906 )
3907 if obj.ndim == 1:
3908 result = described
3909 else:
3910 result = described.unstack()
3911 return result.to_frame().T.iloc[:0]
3912
3913 with com.temp_setattr(self, "as_index", True):
3914 result = self._python_apply_general(
3915 lambda x: x.describe(
3916 percentiles=percentiles, include=include, exclude=exclude
3917 ),
3918 obj,
3919 not_indexed_same=True,
3920 )
3921
3922 # GH#49256 - properly handle the grouping column(s)
3923 result = result.unstack()
3924 if not self.as_index:
3925 result = self._insert_inaxis_grouper(result)
3926 result.index = default_index(len(result))
3927
3928 return result
3929
3930 @final
3931 def resample(
3932 self, rule, *args, include_groups: bool = False, **kwargs
3933 ) -> Resampler:
3934 """
3935 Provide resampling when using a TimeGrouper.
3936
3937 Given a grouper, the function resamples it according to a string
3938 "string" -> "frequency".
3939
3940 See the :ref:`frequency aliases <timeseries.offset_aliases>`
3941 documentation for more details.
3942
3943 Parameters
3944 ----------
3945 rule : str or DateOffset
3946 The offset string or object representing target grouper conversion.
3947 *args
3948 Possible arguments are `how`, `fill_method`, `limit`, `kind` and
3949 `on`, and other arguments of `TimeGrouper`.
3950 include_groups : bool, default True
3951 When True, will attempt to include the groupings in the operation in
3952 the case that they are columns of the DataFrame. If this raises a
3953 TypeError, the result will be computed with the groupings excluded.
3954 When False, the groupings will be excluded when applying ``func``.
3955
3956 .. versionadded:: 2.2.0
3957
3958 .. versionchanged:: 3.0
3959
3960 The default was changed to False, and True is no longer allowed.
3961
3962 **kwargs
3963 Possible arguments are `how`, `fill_method`, `limit`, `kind` and
3964 `on`, and other arguments of `TimeGrouper`.
3965
3966 Returns
3967 -------
3968 DatetimeIndexResampler, PeriodIndexResampler or TimdeltaResampler
3969 Resampler object for the type of the index.
3970
3971 See Also
3972 --------
3973 Grouper : Specify a frequency to resample with when
3974 grouping by a key.
3975 DatetimeIndex.resample : Frequency conversion and resampling of
3976 time series.
3977
3978 Examples
3979 --------
3980 >>> idx = pd.date_range("1/1/2000", periods=4, freq="min")
3981 >>> df = pd.DataFrame(data=4 * [range(2)], index=idx, columns=["a", "b"])
3982 >>> df.iloc[2, 0] = 5
3983 >>> df
3984 a b
3985 2000-01-01 00:00:00 0 1
3986 2000-01-01 00:01:00 0 1
3987 2000-01-01 00:02:00 5 1
3988 2000-01-01 00:03:00 0 1
3989
3990 Downsample the DataFrame into 3 minute bins and sum the values of
3991 the timestamps falling into a bin.
3992
3993 >>> df.groupby("a").resample("3min").sum()
3994 b
3995 a
3996 0 2000-01-01 00:00:00 2
3997 2000-01-01 00:03:00 1
3998 5 2000-01-01 00:00:00 1
3999
4000 Upsample the series into 30 second bins.
4001
4002 >>> df.groupby("a").resample("30s").sum()
4003 b
4004 a
4005 0 2000-01-01 00:00:00 1
4006 2000-01-01 00:00:30 0
4007 2000-01-01 00:01:00 1
4008 2000-01-01 00:01:30 0
4009 2000-01-01 00:02:00 0
4010 2000-01-01 00:02:30 0
4011 2000-01-01 00:03:00 1
4012 5 2000-01-01 00:02:00 1
4013
4014 Resample by month. Values are assigned to the month of the period.
4015
4016 >>> df.groupby("a").resample("ME").sum()
4017 b
4018 a
4019 0 2000-01-31 3
4020 5 2000-01-31 1
4021
4022 Downsample the series into 3 minute bins as above, but close the right
4023 side of the bin interval.
4024
4025 >>> (df.groupby("a").resample("3min", closed="right").sum())
4026 b
4027 a
4028 0 1999-12-31 23:57:00 1
4029 2000-01-01 00:00:00 2
4030 5 2000-01-01 00:00:00 1
4031
4032 Downsample the series into 3 minute bins and close the right side of
4033 the bin interval, but label each bin using the right edge instead of
4034 the left.
4035
4036 >>> (df.groupby("a").resample("3min", closed="right", label="right").sum())
4037 b
4038 a
4039 0 2000-01-01 00:00:00 1
4040 2000-01-01 00:03:00 2
4041 5 2000-01-01 00:03:00 1
4042 """
4043 from pandas.core.resample import get_resampler_for_grouping
4044
4045 if include_groups:
4046 raise ValueError("include_groups=True is no longer allowed.")
4047
4048 return get_resampler_for_grouping(self, rule, *args, **kwargs)
4049
4050 @final
4051 def rolling(
4052 self,
4053 window: int | datetime.timedelta | str | BaseOffset | BaseIndexer,
4054 min_periods: int | None = None,
4055 center: bool = False,
4056 win_type: str | None = None,
4057 on: str | None = None,
4058 closed: IntervalClosedType | None = None,
4059 method: str = "single",
4060 ) -> RollingGroupby:
4061 """
4062 Return a rolling grouper, providing rolling functionality per group.
4063
4064 Parameters
4065 ----------
4066 window : int, timedelta, str, offset, or BaseIndexer subclass
4067 Interval of the moving window.
4068
4069 If an integer, the delta between the start and end of each window.
4070 The number of points in the window depends on the ``closed`` argument.
4071
4072 If a timedelta, str, or offset, the time period of each window. Each
4073 window will be a variable sized based on the observations included in
4074 the time-period. This is only valid for datetimelike indexes.
4075 To learn more about the offsets & frequency strings, please see
4076 :ref:`this link<timeseries.offset_aliases>`.
4077
4078 If a BaseIndexer subclass, the window boundaries
4079 based on the defined ``get_window_bounds`` method. Additional rolling
4080 keyword arguments, namely ``min_periods``, ``center``, ``closed`` and
4081 ``step`` will be passed to ``get_window_bounds``.
4082
4083 min_periods : int, default None
4084 Minimum number of observations in window required to have a value;
4085 otherwise, result is ``np.nan``.
4086
4087 For a window that is specified by an offset,
4088 ``min_periods`` will default to 1.
4089
4090 For a window that is specified by an integer, ``min_periods`` will default
4091 to the size of the window.
4092
4093 center : bool, default False
4094 If False, set the window labels as the right edge of the window index.
4095
4096 If True, set the window labels as the center of the window index.
4097
4098 win_type : str, default None
4099 If ``None``, all points are evenly weighted.
4100
4101 If a string, it must be a valid `scipy.signal window function
4102 <https://docs.scipy.org/doc/scipy/reference/signal.windows.html#module-scipy.signal.windows>`__.
4103
4104 Certain Scipy window types require additional parameters to be passed
4105 in the aggregation function. The additional parameters must match
4106 the keywords specified in the Scipy window type method signature.
4107
4108 on : str, optional
4109 For a DataFrame, a column label or Index level on which
4110 to calculate the rolling window, rather than the DataFrame's index.
4111
4112 Provided integer column is ignored and excluded from result since
4113 an integer index is not used to calculate the rolling window.
4114
4115 closed : str, default None
4116 Determines the inclusivity of points in the window
4117
4118 If ``'right'``, uses the window (first, last] meaning the last point
4119 is included in the calculations.
4120
4121 If ``'left'``, uses the window [first, last) meaning the first point
4122 is included in the calculations.
4123
4124 If ``'both'``, uses the window [first, last] meaning all points in
4125 the window are included in the calculations.
4126
4127 If ``'neither'``, uses the window (first, last) meaning the first
4128 and last points in the window are excluded from calculations.
4129
4130 () and [] are referencing open and closed set
4131 notation respetively.
4132
4133 Default ``None`` (``'right'``).
4134
4135 method : str {'single', 'table'}, default 'single'
4136 Execute the rolling operation per single column or row (``'single'``)
4137 or over the entire object (``'table'``).
4138
4139 This argument is only implemented when specifying ``engine='numba'``
4140 in the method call.
4141
4142 Returns
4143 -------
4144 pandas.api.typing.RollingGroupby
4145 Return a new grouper with our rolling appended.
4146
4147 See Also
4148 --------
4149 Series.rolling : Calling object with Series data.
4150 DataFrame.rolling : Calling object with DataFrames.
4151 Series.groupby : Apply a function groupby to a Series.
4152 DataFrame.groupby : Apply a function groupby.
4153
4154 Examples
4155 --------
4156 >>> df = pd.DataFrame(
4157 ... {
4158 ... "A": [1, 1, 2, 2],
4159 ... "B": [1, 2, 3, 4],
4160 ... "C": [0.362, 0.227, 1.267, -0.562],
4161 ... }
4162 ... )
4163 >>> df
4164 A B C
4165 0 1 1 0.362
4166 1 1 2 0.227
4167 2 2 3 1.267
4168 3 2 4 -0.562
4169
4170 >>> df.groupby("A").rolling(2).sum()
4171 B C
4172 A
4173 1 0 NaN NaN
4174 1 3.0 0.589
4175 2 2 NaN NaN
4176 3 7.0 0.705
4177
4178 >>> df.groupby("A").rolling(2, min_periods=1).sum()
4179 B C
4180 A
4181 1 0 1.0 0.362
4182 1 3.0 0.589
4183 2 2 3.0 1.267
4184 3 7.0 0.705
4185
4186 >>> df.groupby("A").rolling(2, on="B").sum()
4187 B C
4188 A
4189 1 0 1 NaN
4190 1 2 0.589
4191 2 2 3 NaN
4192 3 4 0.705
4193 """
4194 from pandas.core.window import RollingGroupby
4195
4196 return RollingGroupby(
4197 self._selected_obj,
4198 window=window,
4199 min_periods=min_periods,
4200 center=center,
4201 win_type=win_type,
4202 on=on,
4203 closed=closed,
4204 method=method,
4205 _grouper=self._grouper,
4206 _as_index=self.as_index,
4207 )
4208
4209 @final
4210 def expanding(
4211 self,
4212 min_periods: int = 1,
4213 method: str = "single",
4214 ) -> ExpandingGroupby:
4215 """
4216 Return an expanding grouper, providing expanding functionality per group.
4217
4218 Parameters
4219 ----------
4220 min_periods : int, default 1
4221 Minimum number of observations in window required to have a value;
4222 otherwise, result is ``np.nan``.
4223
4224 method : str {'single', 'table'}, default 'single'
4225 Execute the expanding operation per single column or row (``'single'``)
4226 or over the entire object (``'table'``).
4227
4228 This argument is only implemented when specifying ``engine='numba'``
4229 in the method call.
4230
4231 Returns
4232 -------
4233 pandas.api.typing.ExpandingGroupby
4234 An object that supports expanding transformations over each group.
4235
4236 See Also
4237 --------
4238 Series.expanding : Expanding transformations for Series.
4239 DataFrame.expanding : Expanding transformations for DataFrames.
4240 Series.groupby : Apply a function groupby to a Series.
4241 DataFrame.groupby : Apply a function groupby.
4242
4243 Examples
4244 --------
4245 >>> df = pd.DataFrame(
4246 ... {
4247 ... "Class": ["A", "A", "A", "B", "B", "B"],
4248 ... "Value": [10, 20, 30, 40, 50, 60],
4249 ... }
4250 ... )
4251 >>> df
4252 Class Value
4253 0 A 10
4254 1 A 20
4255 2 A 30
4256 3 B 40
4257 4 B 50
4258 5 B 60
4259
4260 >>> df.groupby("Class").expanding().mean()
4261 Value
4262 Class
4263 A 0 10.0
4264 1 15.0
4265 2 20.0
4266 B 3 40.0
4267 4 45.0
4268 5 50.0
4269 """
4270 from pandas.core.window import ExpandingGroupby
4271
4272 return ExpandingGroupby(
4273 self._selected_obj,
4274 min_periods=min_periods,
4275 method=method,
4276 _grouper=self._grouper,
4277 )
4278
4279 @final
4280 def ewm(
4281 self,
4282 com: float | None = None,
4283 span: float | None = None,
4284 halflife: float | str | Timedelta | None = None,
4285 alpha: float | None = None,
4286 min_periods: int | None = 0,
4287 adjust: bool = True,
4288 ignore_na: bool = False,
4289 times: np.ndarray | Series | None = None,
4290 method: str = "single",
4291 ) -> ExponentialMovingWindowGroupby:
4292 """
4293 Return an ewm grouper, providing ewm functionality per group.
4294
4295 Parameters
4296 ----------
4297 com : float, optional
4298 Specify decay in terms of center of mass.
4299 Alternative to ``span``, ``halflife``, and ``alpha``.
4300
4301 span : float, optional
4302 Specify decay in terms of span.
4303
4304 halflife : float, str, or Timedelta, optional
4305 Specify decay in terms of half-life.
4306
4307 alpha : float, optional
4308 Specify smoothing factor directly.
4309
4310 min_periods : int, default 0
4311 Minimum number of observations in the window required to have a value;
4312 otherwise, result is ``np.nan``.
4313
4314 adjust : bool, default True
4315 Divide by decaying adjustment factor to account for imbalance in
4316 relative weights.
4317
4318 ignore_na : bool, default False
4319 Ignore missing values when calculating weights.
4320
4321 times : str or array-like of datetime64, optional
4322 Times corresponding to the observations.
4323
4324 method : {'single', 'table'}, default 'single'
4325 Execute the operation per group independently (``'single'``) or over the
4326 entire object before regrouping (``'table'``). Only applicable to
4327 ``mean()``, and only when using ``engine='numba'``.
4328
4329 Returns
4330 -------
4331 pandas.api.typing.ExponentialMovingWindowGroupby
4332 An object that supports exponentially weighted moving transformations over
4333 each group.
4334
4335 See Also
4336 --------
4337 Series.ewm : EWM transformations for Series.
4338 DataFrame.ewm : EWM transformations for DataFrames.
4339 Series.groupby : Apply a function groupby to a Series.
4340 DataFrame.groupby : Apply a function groupby.
4341
4342 Examples
4343 --------
4344 >>> df = pd.DataFrame(
4345 ... {
4346 ... "Class": ["A", "A", "A", "B", "B", "B"],
4347 ... "Value": [10, 20, 30, 40, 50, 60],
4348 ... }
4349 ... )
4350 >>> df
4351 Class Value
4352 0 A 10
4353 1 A 20
4354 2 A 30
4355 3 B 40
4356 4 B 50
4357 5 B 60
4358
4359 >>> df.groupby("Class").ewm(com=0.5).mean()
4360 Value
4361 Class
4362 A 0 10.000000
4363 1 17.500000
4364 2 26.153846
4365 B 3 40.000000
4366 4 47.500000
4367 5 56.153846
4368 """
4369 from pandas.core.window import ExponentialMovingWindowGroupby
4370
4371 return ExponentialMovingWindowGroupby(
4372 self._selected_obj,
4373 com=com,
4374 span=span,
4375 halflife=halflife,
4376 alpha=alpha,
4377 min_periods=min_periods,
4378 adjust=adjust,
4379 ignore_na=ignore_na,
4380 times=times,
4381 method=method,
4382 _grouper=self._grouper,
4383 )
4384
4385 @final
4386 def _fill(self, direction: Literal["ffill", "bfill"], limit: int | None = None):
4387 """
4388 Shared function for `pad` and `backfill` to call Cython method.
4389
4390 Parameters
4391 ----------
4392 direction : {'ffill', 'bfill'}
4393 Direction passed to underlying Cython function. `bfill` will cause
4394 values to be filled backwards. `ffill` and any other values will
4395 default to a forward fill
4396 limit : int, default None
4397 Maximum number of consecutive values to fill. If `None`, this
4398 method will convert to -1 prior to passing to Cython
4399
4400 Returns
4401 -------
4402 `Series` or `DataFrame` with filled values
4403
4404 See Also
4405 --------
4406 pad : Returns Series with minimum number of char in object.
4407 backfill : Backward fill the missing values in the dataset.
4408 """
4409 # Need int value for Cython
4410 if limit is None:
4411 limit = -1
4412
4413 ids = self._grouper.ids
4414 ngroups = self._grouper.ngroups
4415
4416 col_func = partial(
4417 libgroupby.group_fillna_indexer,
4418 labels=ids,
4419 limit=limit,
4420 compute_ffill=(direction == "ffill"),
4421 ngroups=ngroups,
4422 )
4423
4424 def blk_func(values: ArrayLike) -> ArrayLike:
4425 mask = isna(values)
4426 if values.ndim == 1:
4427 indexer = np.empty(values.shape, dtype=np.intp)
4428 col_func(out=indexer, mask=mask) # type: ignore[arg-type]
4429 return algorithms.take_nd(values, indexer)
4430
4431 else:
4432 # We broadcast algorithms.take_nd analogous to
4433 # np.take_along_axis
4434 if isinstance(values, np.ndarray):
4435 dtype = values.dtype
4436 if self._grouper.has_dropped_na:
4437 # dropped null groups give rise to nan in the result
4438 dtype = ensure_dtype_can_hold_na(values.dtype)
4439 out = np.empty(values.shape, dtype=dtype)
4440 else:
4441 # Note: we only get here with backfill/pad,
4442 # so if we have a dtype that cannot hold NAs,
4443 # then there will be no -1s in indexer, so we can use
4444 # the original dtype (no need to ensure_dtype_can_hold_na)
4445 out = type(values)._empty(values.shape, dtype=values.dtype)
4446
4447 for i, value_element in enumerate(values):
4448 # call group_fillna_indexer column-wise
4449 indexer = np.empty(values.shape[1], dtype=np.intp)
4450 col_func(out=indexer, mask=mask[i])
4451 out[i, :] = algorithms.take_nd(value_element, indexer)
4452 return out
4453
4454 mgr = self._get_data_to_aggregate()
4455 res_mgr = mgr.apply(blk_func)
4456
4457 new_obj = self._wrap_agged_manager(res_mgr)
4458 new_obj.index = self.obj.index
4459 return new_obj
4460
4461 @final
4462 def ffill(self, limit: int | None = None):
4463 """
4464 Forward fill the values.
4465
4466 Parameters
4467 ----------
4468 limit : int, optional
4469 Limit of how many values to fill.
4470
4471 Returns
4472 -------
4473 Series or DataFrame
4474 Object with missing values filled.
4475
4476 See Also
4477 --------
4478 Series.ffill: Returns Series with minimum number of char in object.
4479 DataFrame.ffill: Object with missing values filled or None if inplace=True.
4480 Series.fillna: Fill NaN values of a Series.
4481 DataFrame.fillna: Fill NaN values of a DataFrame.
4482
4483 Examples
4484 --------
4485
4486 For SeriesGroupBy:
4487
4488 >>> key = [0, 0, 1, 1]
4489 >>> ser = pd.Series([np.nan, 2, 3, np.nan], index=key)
4490 >>> ser
4491 0 NaN
4492 0 2.0
4493 1 3.0
4494 1 NaN
4495 dtype: float64
4496 >>> ser.groupby(level=0).ffill()
4497 0 NaN
4498 0 2.0
4499 1 3.0
4500 1 3.0
4501 dtype: float64
4502
4503 For DataFrameGroupBy:
4504
4505 >>> df = pd.DataFrame(
4506 ... {
4507 ... "key": [0, 0, 1, 1, 1],
4508 ... "A": [np.nan, 2, np.nan, 3, np.nan],
4509 ... "B": [2, 3, np.nan, np.nan, np.nan],
4510 ... "C": [np.nan, np.nan, 2, np.nan, np.nan],
4511 ... }
4512 ... )
4513 >>> df
4514 key A B C
4515 0 0 NaN 2.0 NaN
4516 1 0 2.0 3.0 NaN
4517 2 1 NaN NaN 2.0
4518 3 1 3.0 NaN NaN
4519 4 1 NaN NaN NaN
4520
4521 Propagate non-null values forward or backward within each group along columns.
4522
4523 >>> df.groupby("key").ffill()
4524 A B C
4525 0 NaN 2.0 NaN
4526 1 2.0 3.0 NaN
4527 2 NaN NaN 2.0
4528 3 3.0 NaN 2.0
4529 4 3.0 NaN 2.0
4530
4531 Propagate non-null values forward or backward within each group along rows.
4532
4533 >>> df.T.groupby(np.array([0, 0, 1, 1])).ffill().T
4534 key A B C
4535 0 0.0 0.0 2.0 2.0
4536 1 0.0 2.0 3.0 3.0
4537 2 1.0 1.0 NaN 2.0
4538 3 1.0 3.0 NaN NaN
4539 4 1.0 1.0 NaN NaN
4540
4541 Only replace the first NaN element within a group along columns.
4542
4543 >>> df.groupby("key").ffill(limit=1)
4544 A B C
4545 0 NaN 2.0 NaN
4546 1 2.0 3.0 NaN
4547 2 NaN NaN 2.0
4548 3 3.0 NaN 2.0
4549 4 3.0 NaN NaN
4550 """
4551 return self._fill("ffill", limit=limit)
4552
4553 @final
4554 def bfill(self, limit: int | None = None):
4555 """
4556 Backward fill the values.
4557
4558 Parameters
4559 ----------
4560 limit : int, optional
4561 Limit of how many values to fill.
4562
4563 Returns
4564 -------
4565 Series or DataFrame
4566 Object with missing values filled.
4567
4568 See Also
4569 --------
4570 Series.bfill : Backward fill the missing values in the dataset.
4571 DataFrame.bfill: Backward fill the missing values in the dataset.
4572 Series.fillna: Fill NaN values of a Series.
4573 DataFrame.fillna: Fill NaN values of a DataFrame.
4574
4575 Examples
4576 --------
4577
4578 With Series:
4579
4580 >>> index = ["Falcon", "Falcon", "Parrot", "Parrot", "Parrot"]
4581 >>> s = pd.Series([None, 1, None, None, 3], index=index)
4582 >>> s
4583 Falcon NaN
4584 Falcon 1.0
4585 Parrot NaN
4586 Parrot NaN
4587 Parrot 3.0
4588 dtype: float64
4589 >>> s.groupby(level=0).bfill()
4590 Falcon 1.0
4591 Falcon 1.0
4592 Parrot 3.0
4593 Parrot 3.0
4594 Parrot 3.0
4595 dtype: float64
4596 >>> s.groupby(level=0).bfill(limit=1)
4597 Falcon 1.0
4598 Falcon 1.0
4599 Parrot NaN
4600 Parrot 3.0
4601 Parrot 3.0
4602 dtype: float64
4603
4604 With DataFrame:
4605
4606 >>> df = pd.DataFrame(
4607 ... {"A": [1, None, None, None, 4], "B": [None, None, 5, None, 7]},
4608 ... index=index,
4609 ... )
4610 >>> df
4611 A B
4612 Falcon 1.0 NaN
4613 Falcon NaN NaN
4614 Parrot NaN 5.0
4615 Parrot NaN NaN
4616 Parrot 4.0 7.0
4617 >>> df.groupby(level=0).bfill()
4618 A B
4619 Falcon 1.0 NaN
4620 Falcon NaN NaN
4621 Parrot 4.0 5.0
4622 Parrot 4.0 7.0
4623 Parrot 4.0 7.0
4624 >>> df.groupby(level=0).bfill(limit=1)
4625 A B
4626 Falcon 1.0 NaN
4627 Falcon NaN NaN
4628 Parrot NaN 5.0
4629 Parrot 4.0 7.0
4630 Parrot 4.0 7.0
4631 """
4632 return self._fill("bfill", limit=limit)
4633
4634 @final
4635 @property
4636 def nth(self) -> GroupByNthSelector:
4637 """
4638 Take the nth row from each group if n is an int, otherwise a subset of rows.
4639
4640 Can be either a call or an index. dropna is not available with index notation.
4641 Index notation accepts a comma separated list of integers and slices.
4642
4643 If dropna, will take the nth non-null row, dropna is either
4644 'all' or 'any'; this is equivalent to calling dropna(how=dropna)
4645 before the groupby.
4646
4647 Returns
4648 -------
4649 Series or DataFrame
4650 N-th value within each group.
4651
4652 See Also
4653 --------
4654 Series.nth : Apply function nth to a Series.
4655 DataFrame.nth : Apply function nth to each row or column of a DataFrame.
4656
4657 Examples
4658 --------
4659
4660 >>> df = pd.DataFrame(
4661 ... {"A": [1, 1, 2, 1, 2], "B": [np.nan, 2, 3, 4, 5]}, columns=["A", "B"]
4662 ... )
4663 >>> g = df.groupby("A")
4664 >>> g.nth(0)
4665 A B
4666 0 1 NaN
4667 2 2 3.0
4668 >>> g.nth(1)
4669 A B
4670 1 1 2.0
4671 4 2 5.0
4672 >>> g.nth(-1)
4673 A B
4674 3 1 4.0
4675 4 2 5.0
4676 >>> g.nth([0, 1])
4677 A B
4678 0 1 NaN
4679 1 1 2.0
4680 2 2 3.0
4681 4 2 5.0
4682 >>> g.nth(slice(None, -1))
4683 A B
4684 0 1 NaN
4685 1 1 2.0
4686 2 2 3.0
4687
4688 Index notation may also be used
4689
4690 >>> g.nth[0, 1]
4691 A B
4692 0 1 NaN
4693 1 1 2.0
4694 2 2 3.0
4695 4 2 5.0
4696 >>> g.nth[:-1]
4697 A B
4698 0 1 NaN
4699 1 1 2.0
4700 2 2 3.0
4701
4702 Specifying `dropna` allows ignoring ``NaN`` values
4703
4704 >>> g.nth(0, dropna="any")
4705 A B
4706 1 1 2.0
4707 2 2 3.0
4708
4709 When the specified ``n`` is larger than any of the groups, an
4710 empty DataFrame is returned
4711
4712 >>> g.nth(3, dropna="any")
4713 Empty DataFrame
4714 Columns: [A, B]
4715 Index: []
4716 """
4717 return GroupByNthSelector(self)
4718
4719 def _nth(
4720 self,
4721 n: PositionalIndexer | tuple,
4722 dropna: Literal["any", "all"] | None = None,
4723 ) -> NDFrameT:
4724 if not dropna:
4725 mask = self._make_mask_from_positional_indexer(n)
4726
4727 ids = self._grouper.ids
4728
4729 # Drop NA values in grouping
4730 mask = mask & (ids != -1)
4731
4732 out = self._mask_selected_obj(mask)
4733 return out
4734
4735 # dropna is truthy
4736 if not is_integer(n):
4737 raise ValueError("dropna option only supported for an integer argument")
4738
4739 if dropna not in ["any", "all"]:
4740 # Note: when agg-ing picker doesn't raise this, just returns NaN
4741 raise ValueError(
4742 "For a DataFrame or Series groupby.nth, dropna must be "
4743 "either None, 'any' or 'all', "
4744 f"(was passed {dropna})."
4745 )
4746
4747 # old behaviour, but with all and any support for DataFrames.
4748 # modified in GH 7559 to have better perf
4749 n = cast(int, n)
4750 dropped = self._selected_obj.dropna(how=dropna, axis=0)
4751
4752 # get a new grouper for our dropped obj
4753 grouper: np.ndarray | Index | ops.BaseGrouper
4754 if len(dropped) == len(self._selected_obj):
4755 # Nothing was dropped, can use the same grouper
4756 grouper = self._grouper
4757 else:
4758 # we don't have the grouper info available
4759 # (e.g. we have selected out
4760 # a column that is not in the current object)
4761 axis = self._grouper.axis
4762 grouper = self._grouper.codes_info[axis.isin(dropped.index)]
4763 if self._grouper.has_dropped_na:
4764 # Null groups need to still be encoded as -1 when passed to groupby
4765 nulls = grouper == -1
4766 # error: No overload variant of "where" matches argument types
4767 # "Any", "NAType", "Any"
4768 values = np.where(nulls, NA, grouper) # type: ignore[call-overload]
4769 grouper = Index(values, dtype="Int64", copy=False)
4770
4771 grb = dropped.groupby(grouper, as_index=self.as_index, sort=self.sort)
4772 return grb.nth(n)
4773
4774 @final
4775 def quantile(
4776 self,
4777 q: float | AnyArrayLike = 0.5,
4778 interpolation: Literal[
4779 "linear", "lower", "higher", "nearest", "midpoint"
4780 ] = "linear",
4781 numeric_only: bool = False,
4782 ):
4783 """
4784 Return group values at the given quantile, a la numpy.percentile.
4785
4786 Parameters
4787 ----------
4788 q : float or array-like, default 0.5 (50% quantile)
4789 Value(s) between 0 and 1 providing the quantile(s) to compute.
4790 interpolation : {'linear', 'lower', 'higher', 'midpoint', 'nearest'}
4791 Method to use when the desired quantile falls between two points.
4792 numeric_only : bool, default False
4793 Include only `float`, `int` or `boolean` data.
4794
4795 .. versionchanged:: 2.0.0
4796
4797 numeric_only now defaults to ``False``.
4798
4799 Returns
4800 -------
4801 Series or DataFrame
4802 Return type determined by caller of GroupBy object.
4803
4804 See Also
4805 --------
4806 Series.quantile : Similar method for Series.
4807 DataFrame.quantile : Similar method for DataFrame.
4808 numpy.percentile : NumPy method to compute qth percentile.
4809
4810 Examples
4811 --------
4812 >>> df = pd.DataFrame(
4813 ... [["a", 1], ["a", 2], ["a", 3], ["b", 1], ["b", 3], ["b", 5]],
4814 ... columns=["key", "val"],
4815 ... )
4816 >>> df.groupby("key").quantile()
4817 val
4818 key
4819 a 2.0
4820 b 3.0
4821 """
4822 mgr = self._get_data_to_aggregate(numeric_only=numeric_only, name="quantile")
4823 obj = self._wrap_agged_manager(mgr)
4824 splitter = self._grouper._get_splitter(obj)
4825 sdata = splitter._sorted_data
4826
4827 starts, ends = lib.generate_slices(splitter._slabels, splitter.ngroups)
4828
4829 def pre_processor(vals: ArrayLike) -> tuple[np.ndarray, DtypeObj | None]:
4830 if isinstance(vals.dtype, StringDtype) or is_object_dtype(vals.dtype):
4831 raise TypeError(
4832 f"dtype '{vals.dtype}' does not support operation 'quantile'"
4833 )
4834
4835 inference: DtypeObj | None = None
4836 if isinstance(vals, BaseMaskedArray) and is_numeric_dtype(vals.dtype):
4837 out = vals.to_numpy(dtype=float, na_value=np.nan)
4838 inference = vals.dtype
4839 elif is_integer_dtype(vals.dtype):
4840 if isinstance(vals, ExtensionArray):
4841 out = vals.to_numpy(dtype=float, na_value=np.nan)
4842 else:
4843 out = vals
4844 inference = np.dtype(np.int64)
4845 elif is_bool_dtype(vals.dtype) and isinstance(vals, ExtensionArray):
4846 out = vals.to_numpy(dtype=float, na_value=np.nan)
4847 elif is_bool_dtype(vals.dtype):
4848 # GH#51424 remove to match Series/DataFrame behavior
4849 raise TypeError("Cannot use quantile with bool dtype")
4850 elif needs_i8_conversion(vals.dtype):
4851 inference = vals.dtype
4852 # In this case we need to delay the casting until after the
4853 # np.lexsort below.
4854 # error: Incompatible return value type (got
4855 # "Tuple[Union[ExtensionArray, ndarray[Any, Any]], Union[Any,
4856 # ExtensionDtype]]", expected "Tuple[ndarray[Any, Any],
4857 # Optional[Union[dtype[Any], ExtensionDtype]]]")
4858 return vals, inference # type: ignore[return-value]
4859 elif isinstance(vals, ExtensionArray) and is_float_dtype(vals.dtype):
4860 inference = np.dtype(np.float64)
4861 out = vals.to_numpy(dtype=float, na_value=np.nan)
4862 else:
4863 out = np.asarray(vals)
4864
4865 return out, inference
4866
4867 def post_processor(
4868 vals: np.ndarray,
4869 inference: DtypeObj | None,
4870 result_mask: np.ndarray | None,
4871 orig_vals: ArrayLike,
4872 ) -> ArrayLike:
4873 if inference:
4874 # Check for edge case
4875 if isinstance(orig_vals, BaseMaskedArray):
4876 assert result_mask is not None # for mypy
4877
4878 if interpolation in {"linear", "midpoint"} and not is_float_dtype(
4879 orig_vals
4880 ):
4881 return FloatingArray(vals, result_mask)
4882 else:
4883 # Item "ExtensionDtype" of "Union[ExtensionDtype, str,
4884 # dtype[Any], Type[object]]" has no attribute "numpy_dtype"
4885 # [union-attr]
4886 with warnings.catch_warnings():
4887 # vals.astype with nan can warn with numpy >1.24
4888 warnings.filterwarnings("ignore", category=RuntimeWarning)
4889 return type(orig_vals)(
4890 vals.astype(
4891 inference.numpy_dtype # type: ignore[union-attr]
4892 ),
4893 result_mask,
4894 )
4895
4896 elif not (
4897 is_integer_dtype(inference)
4898 and interpolation in {"linear", "midpoint"}
4899 ):
4900 if needs_i8_conversion(inference):
4901 # error: Item "ExtensionArray" of "Union[ExtensionArray,
4902 # ndarray[Any, Any]]" has no attribute "_ndarray"
4903 vals = vals.astype("i8").view(
4904 orig_vals._ndarray.dtype # type: ignore[union-attr]
4905 )
4906 # error: Item "ExtensionArray" of "Union[ExtensionArray,
4907 # ndarray[Any, Any]]" has no attribute "_from_backing_data"
4908 return orig_vals._from_backing_data( # type: ignore[union-attr]
4909 vals
4910 )
4911
4912 assert isinstance(inference, np.dtype) # for mypy
4913 return vals.astype(inference)
4914
4915 return vals
4916
4917 if is_scalar(q):
4918 qs = np.array([q], dtype=np.float64)
4919 pass_qs: None | np.ndarray = None
4920 else:
4921 qs = np.asarray(q, dtype=np.float64)
4922 pass_qs = qs
4923
4924 ids = self._grouper.ids
4925 ngroups = self._grouper.ngroups
4926 if self.dropna:
4927 # splitter drops NA groups, we need to do the same
4928 ids = ids[ids >= 0]
4929 nqs = len(qs)
4930
4931 func = partial(
4932 libgroupby.group_quantile,
4933 labels=ids,
4934 qs=qs,
4935 interpolation=interpolation,
4936 starts=starts,
4937 ends=ends,
4938 )
4939
4940 def blk_func(values: ArrayLike) -> ArrayLike:
4941 orig_vals = values
4942 if isinstance(values, BaseMaskedArray):
4943 mask = values._mask
4944 result_mask = np.zeros((ngroups, nqs), dtype=np.bool_)
4945 else:
4946 mask = isna(values)
4947 result_mask = None
4948
4949 is_datetimelike = needs_i8_conversion(values.dtype)
4950
4951 vals, inference = pre_processor(values)
4952
4953 ncols = 1
4954 if vals.ndim == 2:
4955 ncols = vals.shape[0]
4956
4957 out = np.empty((ncols, ngroups, nqs), dtype=np.float64)
4958
4959 if is_datetimelike:
4960 vals = vals.view("i8")
4961
4962 if vals.ndim == 1:
4963 # EA is always 1d
4964 func(
4965 out[0],
4966 values=vals,
4967 mask=mask, # type: ignore[arg-type]
4968 result_mask=result_mask,
4969 is_datetimelike=is_datetimelike,
4970 )
4971 else:
4972 for i in range(ncols):
4973 func(
4974 out[i],
4975 values=vals[i],
4976 mask=mask[i],
4977 result_mask=None,
4978 is_datetimelike=is_datetimelike,
4979 )
4980
4981 if vals.ndim == 1:
4982 out = out.ravel("K") # type: ignore[assignment]
4983 if result_mask is not None:
4984 result_mask = result_mask.ravel("K") # type: ignore[assignment]
4985 else:
4986 out = out.reshape(ncols, ngroups * nqs) # type: ignore[assignment]
4987
4988 return post_processor(out, inference, result_mask, orig_vals)
4989
4990 res_mgr = sdata._mgr.grouped_reduce(blk_func)
4991
4992 res = self._wrap_agged_manager(res_mgr)
4993 return self._wrap_aggregated_output(res, qs=pass_qs)
4994
4995 @final
4996 def ngroup(self, ascending: bool = True):
4997 """
4998 Number each group from 0 to the number of groups - 1.
4999
5000 This is the enumerative complement of cumcount. Note that the
5001 numbers given to the groups match the order in which the groups
5002 would be seen when iterating over the groupby object, not the
5003 order they are first observed.
5004
5005 Groups with missing keys (where `pd.isna()` is True) will be labeled with `NaN`
5006 and will be skipped from the count.
5007
5008 Parameters
5009 ----------
5010 ascending : bool, default True
5011 If False, number in reverse, from number of group - 1 to 0.
5012
5013 Returns
5014 -------
5015 Series
5016 Unique numbers for each group.
5017
5018 See Also
5019 --------
5020 .cumcount : Number the rows in each group.
5021
5022 Examples
5023 --------
5024 >>> df = pd.DataFrame({"color": ["red", None, "red", "blue", "blue", "red"]})
5025 >>> df
5026 color
5027 0 red
5028 1 NaN
5029 2 red
5030 3 blue
5031 4 blue
5032 5 red
5033 >>> df.groupby("color").ngroup()
5034 0 1.0
5035 1 NaN
5036 2 1.0
5037 3 0.0
5038 4 0.0
5039 5 1.0
5040 dtype: float64
5041 >>> df.groupby("color", dropna=False).ngroup()
5042 0 1
5043 1 2
5044 2 1
5045 3 0
5046 4 0
5047 5 1
5048 dtype: int64
5049 >>> df.groupby("color", dropna=False).ngroup(ascending=False)
5050 0 1
5051 1 0
5052 2 1
5053 3 2
5054 4 2
5055 5 1
5056 dtype: int64
5057 """
5058 obj = self._obj_with_exclusions
5059 index = obj.index
5060 comp_ids = self._grouper.ids
5061
5062 dtype: type
5063 if self._grouper.has_dropped_na:
5064 comp_ids = np.where(comp_ids == -1, np.nan, comp_ids)
5065 dtype = np.float64
5066 else:
5067 dtype = np.int64
5068
5069 if any(ping._passed_categorical for ping in self._grouper.groupings):
5070 # comp_ids reflect non-observed groups, we need only observed
5071 comp_ids = rank_1d(comp_ids, ties_method="dense") - 1
5072
5073 result = self._obj_1d_constructor(comp_ids, index, dtype=dtype)
5074 if not ascending:
5075 result = self.ngroups - 1 - result
5076 return result
5077
5078 @final
5079 def cumcount(self, ascending: bool = True):
5080 """
5081 Number each item in each group from 0 to the length of that group - 1.
5082
5083 Essentially this is equivalent to
5084
5085 .. code-block:: python
5086
5087 self.apply(lambda x: pd.Series(np.arange(len(x)), x.index))
5088
5089 Parameters
5090 ----------
5091 ascending : bool, default True
5092 If False, number in reverse, from length of group - 1 to 0.
5093
5094 Returns
5095 -------
5096 Series
5097 Sequence number of each element within each group.
5098
5099 See Also
5100 --------
5101 .ngroup : Number the groups themselves.
5102
5103 Examples
5104 --------
5105 >>> df = pd.DataFrame([["a"], ["a"], ["a"], ["b"], ["b"], ["a"]], columns=["A"])
5106 >>> df
5107 A
5108 0 a
5109 1 a
5110 2 a
5111 3 b
5112 4 b
5113 5 a
5114 >>> df.groupby("A").cumcount()
5115 0 0
5116 1 1
5117 2 2
5118 3 0
5119 4 1
5120 5 3
5121 dtype: int64
5122 >>> df.groupby("A").cumcount(ascending=False)
5123 0 3
5124 1 2
5125 2 1
5126 3 1
5127 4 0
5128 5 0
5129 dtype: int64
5130 """
5131 index = self._obj_with_exclusions.index
5132 cumcounts = self._cumcount_array(ascending=ascending)
5133 return self._obj_1d_constructor(cumcounts, index)
5134
5135 @final
5136 def rank(
5137 self,
5138 method: str = "average",
5139 ascending: bool = True,
5140 na_option: str = "keep",
5141 pct: bool = False,
5142 ) -> NDFrameT:
5143 """
5144 Provide the rank of values within each group.
5145
5146 Parameters
5147 ----------
5148 method : {'average', 'min', 'max', 'first', 'dense'}, default 'average'
5149 * average: average rank of group.
5150 * min: lowest rank in group.
5151 * max: highest rank in group.
5152 * first: ranks assigned in order they appear in the array.
5153 * dense: like 'min', but rank always increases by 1 between groups.
5154 ascending : bool, default True
5155 False for ranks by high (1) to low (N).
5156 na_option : {'keep', 'top', 'bottom'}, default 'keep'
5157 * keep: leave NA values where they are.
5158 * top: smallest rank if ascending.
5159 * bottom: smallest rank if descending.
5160 pct : bool, default False
5161 Compute percentage rank of data within each group.
5162
5163 Returns
5164 -------
5165 DataFrame
5166 The ranking of values within each group.
5167
5168 See Also
5169 --------
5170 Series.rank : Apply function rank to a Series.
5171 DataFrame.rank : Apply function rank to each row or column of a DataFrame.
5172
5173 Examples
5174 --------
5175 >>> df = pd.DataFrame(
5176 ... {
5177 ... "group": ["a", "a", "a", "a", "a", "b", "b", "b", "b", "b"],
5178 ... "value": [2, 4, 2, 3, 5, 1, 2, 4, 1, 5],
5179 ... }
5180 ... )
5181 >>> df
5182 group value
5183 0 a 2
5184 1 a 4
5185 2 a 2
5186 3 a 3
5187 4 a 5
5188 5 b 1
5189 6 b 2
5190 7 b 4
5191 8 b 1
5192 9 b 5
5193 >>> for method in ["average", "min", "max", "dense", "first"]:
5194 ... df[f"{method}_rank"] = df.groupby("group")["value"].rank(method)
5195 >>> df
5196 group value average_rank min_rank max_rank dense_rank first_rank
5197 0 a 2 1.5 1.0 2.0 1.0 1.0
5198 1 a 4 4.0 4.0 4.0 3.0 4.0
5199 2 a 2 1.5 1.0 2.0 1.0 2.0
5200 3 a 3 3.0 3.0 3.0 2.0 3.0
5201 4 a 5 5.0 5.0 5.0 4.0 5.0
5202 5 b 1 1.5 1.0 2.0 1.0 1.0
5203 6 b 2 3.0 3.0 3.0 2.0 3.0
5204 7 b 4 4.0 4.0 4.0 3.0 4.0
5205 8 b 1 1.5 1.0 2.0 1.0 2.0
5206 9 b 5 5.0 5.0 5.0 4.0 5.0
5207 """
5208 if na_option not in {"keep", "top", "bottom"}:
5209 msg = "na_option must be one of 'keep', 'top', or 'bottom'"
5210 raise ValueError(msg)
5211
5212 kwargs = {
5213 "ties_method": method,
5214 "ascending": ascending,
5215 "na_option": na_option,
5216 "pct": pct,
5217 }
5218
5219 return self._cython_transform(
5220 "rank",
5221 numeric_only=False,
5222 **kwargs,
5223 )
5224
5225 @final
5226 def cumprod(self, numeric_only: bool = False, *args, **kwargs) -> NDFrameT:
5227 """
5228 Cumulative product for each group.
5229
5230 Parameters
5231 ----------
5232 numeric_only : bool, default False
5233 Include only float, int, boolean columns.
5234 *args : tuple
5235 Positional arguments to be passed to `func`.
5236 **kwargs : dict
5237 Additional/specific keyword arguments to be passed to the function,
5238 such as `numeric_only` and `skipna`.
5239
5240 Returns
5241 -------
5242 Series or DataFrame
5243 Cumulative product for each group. Same object type as the caller.
5244
5245 See Also
5246 --------
5247 Series.cumprod : Apply function cumprod to a Series.
5248 DataFrame.cumprod : Apply function cumprod to each row or column of a DataFrame.
5249
5250 Examples
5251 --------
5252 For SeriesGroupBy:
5253
5254 >>> lst = ["a", "a", "b"]
5255 >>> ser = pd.Series([6, 2, 0], index=lst)
5256 >>> ser
5257 a 6
5258 a 2
5259 b 0
5260 dtype: int64
5261 >>> ser.groupby(level=0).cumprod()
5262 a 6
5263 a 12
5264 b 0
5265 dtype: int64
5266
5267 For DataFrameGroupBy:
5268
5269 >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]]
5270 >>> df = pd.DataFrame(
5271 ... data, columns=["a", "b", "c"], index=["cow", "horse", "bull"]
5272 ... )
5273 >>> df
5274 a b c
5275 cow 1 8 2
5276 horse 1 2 5
5277 bull 2 6 9
5278 >>> df.groupby("a").groups
5279 {1: ['cow', 'horse'], 2: ['bull']}
5280 >>> df.groupby("a").cumprod()
5281 b c
5282 cow 8 2
5283 horse 16 10
5284 bull 6 9
5285 """
5286 nv.validate_groupby_func("cumprod", args, kwargs, ["skipna"])
5287 return self._cython_transform("cumprod", numeric_only, **kwargs)
5288
5289 @final
5290 def cumsum(self, numeric_only: bool = False, *args, **kwargs) -> NDFrameT:
5291 """
5292 Cumulative sum for each group.
5293
5294 Parameters
5295 ----------
5296 numeric_only : bool, default False
5297 Include only float, int, boolean columns.
5298 *args : tuple
5299 Positional arguments to be passed to `func`.
5300 **kwargs : dict
5301 Additional/specific keyword arguments to be passed to the function,
5302 such as `numeric_only` and `skipna`.
5303
5304 Returns
5305 -------
5306 Series or DataFrame
5307 Cumulative sum for each group. Same object type as the caller.
5308
5309 See Also
5310 --------
5311 Series.cumsum : Apply function cumsum to a Series.
5312 DataFrame.cumsum : Apply function cumsum to each row or column of a DataFrame.
5313
5314 Examples
5315 --------
5316 For SeriesGroupBy:
5317
5318 >>> lst = ["a", "a", "b"]
5319 >>> ser = pd.Series([6, 2, 0], index=lst)
5320 >>> ser
5321 a 6
5322 a 2
5323 b 0
5324 dtype: int64
5325 >>> ser.groupby(level=0).cumsum()
5326 a 6
5327 a 8
5328 b 0
5329 dtype: int64
5330
5331 For DataFrameGroupBy:
5332
5333 >>> data = [[1, 8, 2], [1, 2, 5], [2, 6, 9]]
5334 >>> df = pd.DataFrame(
5335 ... data, columns=["a", "b", "c"], index=["fox", "gorilla", "lion"]
5336 ... )
5337 >>> df
5338 a b c
5339 fox 1 8 2
5340 gorilla 1 2 5
5341 lion 2 6 9
5342 >>> df.groupby("a").groups
5343 {1: ['fox', 'gorilla'], 2: ['lion']}
5344 >>> df.groupby("a").cumsum()
5345 b c
5346 fox 8 2
5347 gorilla 10 7
5348 lion 6 9
5349 """
5350 nv.validate_groupby_func("cumsum", args, kwargs, ["skipna"])
5351 return self._cython_transform("cumsum", numeric_only, **kwargs)
5352
5353 @final
5354 def cummin(
5355 self,
5356 numeric_only: bool = False,
5357 **kwargs,
5358 ) -> NDFrameT:
5359 """
5360 Cumulative min for each group.
5361
5362 Parameters
5363 ----------
5364 numeric_only : bool, default False
5365 Include only `float`, `int` or `boolean` data.
5366 **kwargs : dict, optional
5367 Additional keyword arguments to be passed to the function, such as `skipna`,
5368 to control whether NA/null values are ignored.
5369
5370 Returns
5371 -------
5372 Series or DataFrame
5373 Cumulative min for each group. Same object type as the caller.
5374
5375 See Also
5376 --------
5377 Series.cummin : Apply function cummin to a Series.
5378 DataFrame.cummin : Apply function cummin to each row or column of a DataFrame.
5379
5380 Examples
5381 --------
5382 For SeriesGroupBy:
5383
5384 >>> lst = ["a", "a", "a", "b", "b", "b"]
5385 >>> ser = pd.Series([1, 6, 2, 3, 0, 4], index=lst)
5386 >>> ser
5387 a 1
5388 a 6
5389 a 2
5390 b 3
5391 b 0
5392 b 4
5393 dtype: int64
5394 >>> ser.groupby(level=0).cummin()
5395 a 1
5396 a 1
5397 a 1
5398 b 3
5399 b 0
5400 b 0
5401 dtype: int64
5402
5403 For DataFrameGroupBy:
5404
5405 >>> data = [[1, 0, 2], [1, 1, 5], [6, 6, 9]]
5406 >>> df = pd.DataFrame(
5407 ... data, columns=["a", "b", "c"], index=["snake", "rabbit", "turtle"]
5408 ... )
5409 >>> df
5410 a b c
5411 snake 1 0 2
5412 rabbit 1 1 5
5413 turtle 6 6 9
5414 >>> df.groupby("a").groups
5415 {1: ['snake', 'rabbit'], 6: ['turtle']}
5416 >>> df.groupby("a").cummin()
5417 b c
5418 snake 0 2
5419 rabbit 0 2
5420 turtle 6 9
5421 """
5422 skipna = kwargs.get("skipna", True)
5423 return self._cython_transform(
5424 "cummin", numeric_only=numeric_only, skipna=skipna
5425 )
5426
5427 @final
5428 def cummax(
5429 self,
5430 numeric_only: bool = False,
5431 **kwargs,
5432 ) -> NDFrameT:
5433 """
5434 Cumulative max for each group.
5435
5436 Returns the cumulative maximum of values within each group. The result
5437 has the same size as the input, with each element representing the
5438 maximum of all preceding elements (including itself) within its group.
5439
5440 Parameters
5441 ----------
5442 numeric_only : bool, default False
5443 Include only `float`, `int` or `boolean` data.
5444 **kwargs : dict, optional
5445 Additional keyword arguments to be passed to the function, such as `skipna`,
5446 to control whether NA/null values are ignored.
5447
5448 Returns
5449 -------
5450 Series or DataFrame
5451 Cumulative max for each group. Same object type as the caller.
5452
5453 See Also
5454 --------
5455 Series.cummax : Apply function cummax to a Series.
5456 DataFrame.cummax : Apply function cummax to each row or column of a DataFrame.
5457
5458 Examples
5459 --------
5460 For SeriesGroupBy:
5461
5462 >>> lst = ["a", "a", "a", "b", "b", "b"]
5463 >>> ser = pd.Series([1, 6, 2, 3, 1, 4], index=lst)
5464 >>> ser
5465 a 1
5466 a 6
5467 a 2
5468 b 3
5469 b 1
5470 b 4
5471 dtype: int64
5472 >>> ser.groupby(level=0).cummax()
5473 a 1
5474 a 6
5475 a 6
5476 b 3
5477 b 3
5478 b 4
5479 dtype: int64
5480
5481 For DataFrameGroupBy:
5482
5483 >>> data = [[1, 8, 2], [1, 1, 0], [2, 6, 9]]
5484 >>> df = pd.DataFrame(
5485 ... data, columns=["a", "b", "c"], index=["cow", "horse", "bull"]
5486 ... )
5487 >>> df
5488 a b c
5489 cow 1 8 2
5490 horse 1 1 0
5491 bull 2 6 9
5492 >>> df.groupby("a").groups
5493 {1: ['cow', 'horse'], 2: ['bull']}
5494 >>> df.groupby("a").cummax()
5495 b c
5496 cow 8 2
5497 horse 8 2
5498 bull 6 9
5499 """
5500 skipna = kwargs.get("skipna", True)
5501 return self._cython_transform(
5502 "cummax", numeric_only=numeric_only, skipna=skipna
5503 )
5504
5505 @final
5506 def shift(
5507 self,
5508 periods: int | Sequence[int] = 1,
5509 freq=None,
5510 fill_value=lib.no_default,
5511 suffix: str | None = None,
5512 ):
5513 """
5514 Shift each group by periods observations.
5515
5516 If freq is passed, the index will be increased using the periods and the freq.
5517
5518 Parameters
5519 ----------
5520 periods : int | Sequence[int], default 1
5521 Number of periods to shift. If a list of values, shift each group by
5522 each period.
5523 freq : str, optional
5524 Frequency string.
5525 fill_value : optional
5526 The scalar value to use for newly introduced missing values.
5527
5528 .. versionchanged:: 2.1.0
5529 Will raise a ``ValueError`` if ``freq`` is provided too.
5530
5531 suffix : str, optional
5532 A string to add to each shifted column if there are multiple periods.
5533 Ignored otherwise.
5534
5535 Returns
5536 -------
5537 Series or DataFrame
5538 Object shifted within each group.
5539
5540 See Also
5541 --------
5542 Index.shift : Shift values of Index.
5543
5544 Examples
5545 --------
5546
5547 For SeriesGroupBy:
5548
5549 >>> lst = ["a", "a", "b", "b"]
5550 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
5551 >>> ser
5552 a 1
5553 a 2
5554 b 3
5555 b 4
5556 dtype: int64
5557 >>> ser.groupby(level=0).shift(1)
5558 a NaN
5559 a 1.0
5560 b NaN
5561 b 3.0
5562 dtype: float64
5563
5564 For DataFrameGroupBy:
5565
5566 >>> data = [[1, 2, 3], [1, 5, 6], [2, 5, 8], [2, 6, 9]]
5567 >>> df = pd.DataFrame(
5568 ... data,
5569 ... columns=["a", "b", "c"],
5570 ... index=["tuna", "salmon", "catfish", "goldfish"],
5571 ... )
5572 >>> df
5573 a b c
5574 tuna 1 2 3
5575 salmon 1 5 6
5576 catfish 2 5 8
5577 goldfish 2 6 9
5578 >>> df.groupby("a").shift(1)
5579 b c
5580 tuna NaN NaN
5581 salmon 2.0 3.0
5582 catfish NaN NaN
5583 goldfish 5.0 8.0
5584 """
5585 if is_list_like(periods):
5586 periods = cast(Sequence, periods)
5587 if len(periods) == 0:
5588 raise ValueError("If `periods` is an iterable, it cannot be empty.")
5589 from pandas.core.reshape.concat import concat
5590
5591 add_suffix = True
5592 else:
5593 if not is_integer(periods):
5594 raise TypeError(
5595 f"Periods must be integer, but {periods} is {type(periods)}."
5596 )
5597 if suffix:
5598 raise ValueError("Cannot specify `suffix` if `periods` is an int.")
5599 periods = [cast(int, periods)]
5600 add_suffix = False
5601
5602 shifted_dataframes = []
5603 for period in periods:
5604 if not is_integer(period):
5605 raise TypeError(
5606 f"Periods must be integer, but {period} is {type(period)}."
5607 )
5608 period = cast(int, period)
5609 if freq is not None:
5610 f = lambda x: x.shift(
5611 period,
5612 freq,
5613 0, # axis
5614 fill_value,
5615 )
5616 shifted = self._python_apply_general(
5617 f, self._selected_obj, is_transform=True
5618 )
5619 else:
5620 if fill_value is lib.no_default:
5621 fill_value = None
5622 ids = self._grouper.ids
5623 ngroups = self._grouper.ngroups
5624 res_indexer = np.zeros(len(ids), dtype=np.int64)
5625
5626 libgroupby.group_shift_indexer(res_indexer, ids, ngroups, period)
5627
5628 obj = self._obj_with_exclusions
5629
5630 shifted = obj._reindex_with_indexers(
5631 {0: (obj.index, res_indexer)},
5632 fill_value=fill_value,
5633 allow_dups=True,
5634 )
5635
5636 if add_suffix:
5637 if isinstance(shifted, Series):
5638 shifted = cast(NDFrameT, shifted.to_frame())
5639 shifted = shifted.add_suffix(
5640 f"{suffix}_{period}" if suffix else f"_{period}"
5641 )
5642 shifted_dataframes.append(cast(Union[Series, DataFrame], shifted))
5643
5644 return (
5645 shifted_dataframes[0]
5646 if len(shifted_dataframes) == 1
5647 else concat(shifted_dataframes, axis=1, sort=False)
5648 )
5649
5650 @final
5651 def diff(
5652 self,
5653 periods: int = 1,
5654 ) -> NDFrameT:
5655 """
5656 First discrete difference of element.
5657
5658 Calculates the difference of each element compared with another
5659 element in the group (default is element in previous row).
5660
5661 Parameters
5662 ----------
5663 periods : int, default 1
5664 Periods to shift for calculating difference, accepts negative values.
5665
5666 Returns
5667 -------
5668 Series or DataFrame
5669 First differences.
5670
5671 See Also
5672 --------
5673 Series.diff : Apply function diff to a Series.
5674 DataFrame.diff : Apply function diff to each row or column of a DataFrame.
5675
5676 Examples
5677 --------
5678 For SeriesGroupBy:
5679
5680 >>> lst = ["a", "a", "a", "b", "b", "b"]
5681 >>> ser = pd.Series([7, 2, 8, 4, 3, 3], index=lst)
5682 >>> ser
5683 a 7
5684 a 2
5685 a 8
5686 b 4
5687 b 3
5688 b 3
5689 dtype: int64
5690 >>> ser.groupby(level=0).diff()
5691 a NaN
5692 a -5.0
5693 a 6.0
5694 b NaN
5695 b -1.0
5696 b 0.0
5697 dtype: float64
5698
5699 For DataFrameGroupBy:
5700
5701 >>> data = {"a": [1, 3, 5, 7, 7, 8, 3], "b": [1, 4, 8, 4, 4, 2, 1]}
5702 >>> df = pd.DataFrame(
5703 ... data, index=["dog", "dog", "dog", "mouse", "mouse", "mouse", "mouse"]
5704 ... )
5705 >>> df
5706 a b
5707 dog 1 1
5708 dog 3 4
5709 dog 5 8
5710 mouse 7 4
5711 mouse 7 4
5712 mouse 8 2
5713 mouse 3 1
5714 >>> df.groupby(level=0).diff()
5715 a b
5716 dog NaN NaN
5717 dog 2.0 3.0
5718 dog 2.0 4.0
5719 mouse NaN NaN
5720 mouse 0.0 0.0
5721 mouse 1.0 -2.0
5722 mouse -5.0 -1.0
5723 """
5724 obj = self._obj_with_exclusions
5725 shifted = self.shift(periods=periods)
5726
5727 # GH45562 - to retain existing behavior and match behavior of Series.diff(),
5728 # int8 and int16 are coerced to float32 rather than float64.
5729 dtypes_to_f32 = ["int8", "int16"]
5730 if obj.ndim == 1:
5731 if obj.dtype in dtypes_to_f32:
5732 shifted = shifted.astype("float32")
5733 else:
5734 to_coerce = [c for c, dtype in obj.dtypes.items() if dtype in dtypes_to_f32]
5735 if to_coerce:
5736 shifted = shifted.astype(dict.fromkeys(to_coerce, "float32"))
5737
5738 return obj - shifted
5739
5740 @final
5741 def pct_change(
5742 self,
5743 periods: int = 1,
5744 fill_method: None = None,
5745 freq=None,
5746 ):
5747 """
5748 Calculate pct_change of each value to previous entry in group.
5749
5750 Parameters
5751 ----------
5752 periods : int, default 1
5753 Periods to shift for calculating percentage change. Comparing with
5754 a period of 1 means adjacent elements are compared, whereas a period
5755 of 2 compares every other element.
5756
5757 fill_method : None
5758 Must be None. This argument will be removed in a future version of pandas.
5759
5760 freq : str, pandas offset object, or None, default None
5761 The frequency increment for time series data (e.g., 'M' for month-end).
5762 If None, the frequency is inferred from the index. Relevant for time
5763 series data only.
5764
5765 Returns
5766 -------
5767 Series or DataFrame
5768 Percentage changes within each group.
5769
5770 See Also
5771 --------
5772 Series.pct_change : Apply function pct_change to a Series.
5773 DataFrame.pct_change : Apply function pct_change to each row or column of
5774 a DataFrame.
5775
5776 Examples
5777 --------
5778
5779 For SeriesGroupBy:
5780
5781 >>> lst = ["a", "a", "b", "b"]
5782 >>> ser = pd.Series([1, 2, 3, 4], index=lst)
5783 >>> ser
5784 a 1
5785 a 2
5786 b 3
5787 b 4
5788 dtype: int64
5789 >>> ser.groupby(level=0).pct_change()
5790 a NaN
5791 a 1.000000
5792 b NaN
5793 b 0.333333
5794 dtype: float64
5795
5796 For DataFrameGroupBy:
5797
5798 >>> data = [[1, 2, 3], [1, 5, 6], [2, 5, 8], [2, 6, 9]]
5799 >>> df = pd.DataFrame(
5800 ... data,
5801 ... columns=["a", "b", "c"],
5802 ... index=["tuna", "salmon", "catfish", "goldfish"],
5803 ... )
5804 >>> df
5805 a b c
5806 tuna 1 2 3
5807 salmon 1 5 6
5808 catfish 2 5 8
5809 goldfish 2 6 9
5810 >>> df.groupby("a").pct_change()
5811 b c
5812 tuna NaN NaN
5813 salmon 1.5 1.000
5814 catfish NaN NaN
5815 goldfish 0.2 0.125
5816 """
5817 # GH#53491
5818 if fill_method is not None:
5819 raise ValueError(f"fill_method must be None; got {fill_method=}.")
5820
5821 # TODO(GH#23918): Remove this conditional for SeriesGroupBy when
5822 # GH#23918 is fixed
5823 if freq is not None:
5824 f = lambda x: x.pct_change(
5825 periods=periods,
5826 freq=freq,
5827 axis=0,
5828 )
5829 return self._python_apply_general(f, self._selected_obj, is_transform=True)
5830
5831 if fill_method is None: # GH30463
5832 op = "ffill"
5833 else:
5834 op = fill_method
5835 filled = getattr(self, op)(limit=0)
5836 fill_grp = filled.groupby(self._grouper.codes, group_keys=self.group_keys)
5837 shifted = fill_grp.shift(periods=periods, freq=freq)
5838 return (filled / shifted) - 1
5839
5840 @final
5841 def head(self, n: int = 5) -> NDFrameT:
5842 """
5843 Return first n rows of each group.
5844
5845 Similar to ``.apply(lambda x: x.head(n))``, but it returns a subset of rows
5846 from the original DataFrame with original index and order preserved
5847 (``as_index`` flag is ignored).
5848
5849 Parameters
5850 ----------
5851 n : int
5852 If positive: number of entries to include from start of each group.
5853 If negative: number of entries to exclude from end of each group.
5854
5855 Returns
5856 -------
5857 Series or DataFrame
5858 Subset of original Series or DataFrame as determined by n.
5859
5860 See Also
5861 --------
5862 Series.head : Apply function head to a Series.
5863 DataFrame.head : Apply function head to each row or column of a DataFrame.
5864
5865 Examples
5866 --------
5867
5868 >>> df = pd.DataFrame([[1, 2], [1, 4], [5, 6]], columns=["A", "B"])
5869 >>> df.groupby("A").head(1)
5870 A B
5871 0 1 2
5872 2 5 6
5873 >>> df.groupby("A").head(-1)
5874 A B
5875 0 1 2
5876 """
5877 mask = self._make_mask_from_positional_indexer(slice(None, n))
5878 return self._mask_selected_obj(mask)
5879
5880 @final
5881 def tail(self, n: int = 5) -> NDFrameT:
5882 """
5883 Return last n rows of each group.
5884
5885 Similar to ``.apply(lambda x: x.tail(n))``, but it returns a subset of rows
5886 from the original DataFrame with original index and order preserved
5887 (``as_index`` flag is ignored).
5888
5889 Parameters
5890 ----------
5891 n : int
5892 If positive: number of entries to include from end of each group.
5893 If negative: number of entries to exclude from start of each group.
5894
5895 Returns
5896 -------
5897 Series or DataFrame
5898 Subset of original Series or DataFrame as determined by n.
5899
5900 See Also
5901 --------
5902 Series.tail : Apply function tail to a Series.
5903 DataFrame.tail : Apply function tail to each row or column of a DataFrame.
5904
5905 Examples
5906 --------
5907
5908 >>> df = pd.DataFrame(
5909 ... [["a", 1], ["a", 2], ["b", 1], ["b", 2]], columns=["A", "B"]
5910 ... )
5911 >>> df.groupby("A").tail(1)
5912 A B
5913 1 a 2
5914 3 b 2
5915 >>> df.groupby("A").tail(-1)
5916 A B
5917 1 a 2
5918 3 b 2
5919 """
5920 if n:
5921 mask = self._make_mask_from_positional_indexer(slice(-n, None))
5922 else:
5923 mask = self._make_mask_from_positional_indexer([])
5924
5925 return self._mask_selected_obj(mask)
5926
5927 @final
5928 def _mask_selected_obj(self, mask: npt.NDArray[np.bool_]) -> NDFrameT:
5929 """
5930 Return _selected_obj with mask applied.
5931
5932 Parameters
5933 ----------
5934 mask : np.ndarray[bool]
5935 Boolean mask to apply.
5936
5937 Returns
5938 -------
5939 Series or DataFrame
5940 Filtered _selected_obj.
5941 """
5942 ids = self._grouper.ids
5943 mask = mask & (ids != -1)
5944 return self._selected_obj[mask]
5945
5946 @final
5947 def sample(
5948 self,
5949 n: int | None = None,
5950 frac: float | None = None,
5951 replace: bool = False,
5952 weights: Sequence | Series | None = None,
5953 random_state: RandomState | None = None,
5954 ):
5955 """
5956 Return a random sample of items from each group.
5957
5958 You can use `random_state` for reproducibility.
5959
5960 Parameters
5961 ----------
5962 n : int, optional
5963 Number of items to return for each group. Cannot be used with
5964 `frac` and must be no larger than the smallest group unless
5965 `replace` is True. Default is one if `frac` is None.
5966 frac : float, optional
5967 Fraction of items to return. Cannot be used with `n`.
5968 replace : bool, default False
5969 Allow or disallow sampling of the same row more than once.
5970 weights : list-like, optional
5971 Default None results in equal probability weighting.
5972 If passed a list-like then values must have the same length as
5973 the underlying DataFrame or Series object and will be used as
5974 sampling probabilities after normalization within each group.
5975 Values must be non-negative with at least one positive element
5976 within each group.
5977 random_state : int, array-like, BitGenerator, np.random.RandomState, np.random.Generator, optional
5978 If int, array-like, or BitGenerator, seed for random number generator.
5979 If np.random.RandomState or np.random.Generator, use as given.
5980 Default ``None`` results in sampling with the current state of np.random.
5981
5982 Returns
5983 -------
5984 Series or DataFrame
5985 A new object of same type as caller containing items randomly
5986 sampled within each group from the caller object.
5987
5988 See Also
5989 --------
5990 DataFrame.sample: Generate random samples from a DataFrame object.
5991 Series.sample: Generate random samples from a Series object.
5992 numpy.random.choice: Generate a random sample from a given 1-D numpy
5993 array.
5994
5995 Examples
5996 --------
5997 >>> df = pd.DataFrame(
5998 ... {"a": ["red"] * 2 + ["blue"] * 2 + ["black"] * 2, "b": range(6)}
5999 ... )
6000 >>> df
6001 a b
6002 0 red 0
6003 1 red 1
6004 2 blue 2
6005 3 blue 3
6006 4 black 4
6007 5 black 5
6008
6009 Select one row at random for each distinct value in column a. The
6010 `random_state` argument can be used to guarantee reproducibility:
6011
6012 >>> df.groupby("a").sample(n=1, random_state=1)
6013 a b
6014 4 black 4
6015 2 blue 2
6016 1 red 1
6017
6018 Set `frac` to sample fixed proportions rather than counts:
6019
6020 >>> df.groupby("a")["b"].sample(frac=0.5, random_state=2)
6021 5 5
6022 2 2
6023 0 0
6024 Name: b, dtype: int64
6025
6026 Control sample probabilities within groups by setting weights:
6027
6028 >>> df.groupby("a").sample(
6029 ... n=1,
6030 ... weights=[1, 1, 1, 0, 0, 1],
6031 ... random_state=1,
6032 ... )
6033 a b
6034 5 black 5
6035 2 blue 2
6036 0 red 0
6037 """ # noqa: E501
6038 if self._selected_obj.empty:
6039 # GH48459 prevent ValueError when object is empty
6040 return self._selected_obj
6041 size = sample.process_sampling_size(n, frac, replace)
6042 if weights is not None:
6043 weights_arr = sample.preprocess_weights(self._selected_obj, weights, axis=0)
6044
6045 random_state = com.random_state(random_state)
6046
6047 group_iterator = self._grouper.get_iterator(self._selected_obj)
6048
6049 sampled_indices = []
6050 for labels, obj in group_iterator:
6051 grp_indices = self.indices[labels]
6052 group_size = len(grp_indices)
6053 if size is not None:
6054 sample_size = size
6055 else:
6056 assert frac is not None
6057 sample_size = round(frac * group_size)
6058
6059 grp_sample = sample.sample(
6060 group_size,
6061 size=sample_size,
6062 replace=replace,
6063 weights=None if weights is None else weights_arr[grp_indices],
6064 random_state=random_state,
6065 )
6066 sampled_indices.append(grp_indices[grp_sample])
6067
6068 concatenated_sampled_indices = np.concatenate(sampled_indices)
6069 return self._selected_obj.take(concatenated_sampled_indices, axis=0)
6070
6071 def _idxmax_idxmin(
6072 self,
6073 how: Literal["idxmax", "idxmin"],
6074 ignore_unobserved: bool = False,
6075 skipna: bool = True,
6076 numeric_only: bool = False,
6077 ) -> NDFrameT:
6078 """Compute idxmax/idxmin.
6079
6080 Parameters
6081 ----------
6082 how : {'idxmin', 'idxmax'}
6083 Whether to compute idxmin or idxmax.
6084 numeric_only : bool, default False
6085 Include only float, int, boolean columns.
6086 skipna : bool, default True
6087 Exclude NA/null values. If an entire group is NA, the result will be NA.
6088 ignore_unobserved : bool, default False
6089 When True and an unobserved group is encountered, do not raise. This used
6090 for transform where unobserved groups do not play an impact on the result.
6091
6092 Returns
6093 -------
6094 Series or DataFrame
6095 idxmax or idxmin for the groupby operation.
6096 """
6097 if not self.observed and any(
6098 ping._passed_categorical for ping in self._grouper.groupings
6099 ):
6100 expected_len = len(self._grouper.result_index)
6101 # TODO: Better way to find # of observed groups?
6102 group_sizes = self._grouper.size()
6103 result_len = group_sizes[group_sizes > 0].shape[0]
6104 assert result_len <= expected_len
6105 has_unobserved = result_len < expected_len
6106
6107 raise_err: bool | np.bool_ = not ignore_unobserved and has_unobserved
6108 # Only raise an error if there are columns to compute; otherwise we return
6109 # an empty DataFrame with an index (possibly including unobserved) but no
6110 # columns
6111 data = self._obj_with_exclusions
6112 if raise_err and isinstance(data, DataFrame):
6113 if numeric_only:
6114 data = data._get_numeric_data()
6115 raise_err = len(data.columns) > 0
6116
6117 if raise_err:
6118 raise ValueError(
6119 f"Can't get {how} of an empty group due to unobserved categories. "
6120 "Specify observed=True in groupby instead."
6121 )
6122 elif not skipna and self._obj_with_exclusions.isna().any(axis=None):
6123 raise ValueError(f"{how} with skipna=False encountered an NA value.")
6124
6125 result = self._agg_general(
6126 numeric_only=numeric_only,
6127 min_count=1,
6128 alias=how,
6129 skipna=skipna,
6130 )
6131 return result
6132
6133 def _wrap_idxmax_idxmin(
6134 self, res: NDFrameT, how: Literal["idxmax", "idxmin"], skipna: bool
6135 ) -> NDFrameT:
6136 index = self.obj.index
6137 if res.size == 0:
6138 result = res.astype(index.dtype)
6139 elif skipna and res.lt(0).any(axis=None):
6140 raise ValueError(
6141 f"{how} with skipna=True encountered all NA values in a group."
6142 )
6143 else:
6144 if isinstance(index, MultiIndex):
6145 index = index.to_flat_index()
6146 values = res._values
6147 assert isinstance(values, np.ndarray)
6148 na_value = na_value_for_dtype(index.dtype, compat=False)
6149 if isinstance(res, Series):
6150 # mypy: expression has type "Series", variable has type "NDFrameT"
6151 result = res._constructor( # type: ignore[assignment]
6152 index.array.take(values, allow_fill=True, fill_value=na_value),
6153 index=res.index,
6154 name=res.name,
6155 )
6156 else:
6157 data = {}
6158 for k, column_values in enumerate(values.T):
6159 data[k] = index.array.take(
6160 column_values, allow_fill=True, fill_value=na_value
6161 )
6162 result = self.obj._constructor(data, index=res.index)
6163 result.columns = res.columns
6164 return result
6165
6166
6167def get_groupby(
6168 obj: NDFrame,
6169 by: _KeysArgType | None = None,
6170 grouper: ops.BaseGrouper | None = None,
6171 group_keys: bool = True,
6172) -> GroupBy:
6173 """
6174 Class for grouping and aggregating relational data.
6175
6176 See aggregate, transform, and apply functions on this object.
6177
6178 It's easiest to use obj.groupby(...) to use GroupBy, but you can also do:
6179
6180 ::
6181
6182 grouped = groupby(obj, ...)
6183
6184 Parameters
6185 ----------
6186 obj : pandas object
6187 level : int, default None
6188 Level of MultiIndex
6189 groupings : list of Grouping objects
6190 Most users should ignore this
6191 exclusions : array-like, optional
6192 List of columns to exclude
6193 name : str
6194 Most users should ignore this
6195
6196 Returns
6197 -------
6198 **Attributes**
6199 groups : dict
6200 {group name -> group labels}
6201 len(grouped) : int
6202 Number of groups
6203
6204 Notes
6205 -----
6206 After grouping, see aggregate, apply, and transform functions. Here are
6207 some other brief notes about usage. When grouping by multiple groups, the
6208 result index will be a MultiIndex (hierarchical) by default.
6209
6210 Iteration produces (key, group) tuples, i.e. chunking the data by group. So
6211 you can write code like:
6212
6213 ::
6214
6215 grouped = obj.groupby(keys)
6216 for key, group in grouped:
6217 # do something with the data
6218
6219 Function calls on GroupBy, if not specially implemented, "dispatch" to the
6220 grouped data. So if you group a DataFrame and wish to invoke the std()
6221 method on each group, you can simply do:
6222
6223 ::
6224
6225 df.groupby(mapper).std()
6226
6227 rather than
6228
6229 ::
6230
6231 df.groupby(mapper).aggregate(np.std)
6232
6233 You can pass arguments to these "wrapped" functions, too.
6234
6235 See the online documentation for full exposition on these topics and much
6236 more
6237 """
6238 if isinstance(obj, Series):
6239 from pandas.core.groupby.generic import SeriesGroupBy
6240
6241 return SeriesGroupBy(
6242 obj=obj,
6243 keys=by,
6244 grouper=grouper,
6245 group_keys=group_keys,
6246 )
6247 elif isinstance(obj, DataFrame):
6248 from pandas.core.groupby.generic import DataFrameGroupBy
6249
6250 return DataFrameGroupBy(
6251 obj=obj,
6252 keys=by,
6253 grouper=grouper,
6254 group_keys=group_keys,
6255 )
6256 else: # pragma: no cover
6257 raise TypeError(f"invalid type: {obj}")
6258
6259
6260def _insert_quantile_level(idx: Index, qs: npt.NDArray[np.float64]) -> MultiIndex:
6261 """
6262 Insert the sequence 'qs' of quantiles as the inner-most level of a MultiIndex.
6263
6264 The quantile level in the MultiIndex is a repeated copy of 'qs'.
6265
6266 Parameters
6267 ----------
6268 idx : Index
6269 qs : np.ndarray[float64]
6270
6271 Returns
6272 -------
6273 MultiIndex
6274 """
6275 nqs = len(qs)
6276 lev_codes, lev = Index(qs, copy=False).factorize()
6277 lev_codes = coerce_indexer_dtype(lev_codes, lev)
6278
6279 if idx._is_multi:
6280 idx = cast(MultiIndex, idx)
6281 levels = [*idx.levels, lev]
6282 codes = [np.repeat(x, nqs) for x in idx.codes] + [np.tile(lev_codes, len(idx))]
6283 mi = MultiIndex(levels=levels, codes=codes, names=[*idx.names, None])
6284 else:
6285 nidx = len(idx)
6286 idx_codes = coerce_indexer_dtype(np.arange(nidx), idx)
6287 levels = [idx, lev]
6288 codes = [np.repeat(idx_codes, nqs), np.tile(lev_codes, nidx)]
6289 mi = MultiIndex(levels=levels, codes=codes, names=[idx.name, None])
6290
6291 return mi