1"""
2Define the SeriesGroupBy and DataFrameGroupBy
3classes that hold the groupby interfaces (and some implementations).
4
5These are user facing as the result of the ``df.groupby(...)`` operations,
6which here returns a DataFrameGroupBy object.
7"""
8
9from __future__ import annotations
10
11from collections import abc
12from collections.abc import Callable
13import dataclasses
14from functools import partial
15from textwrap import dedent
16from typing import (
17 TYPE_CHECKING,
18 Any,
19 Literal,
20 TypeAlias,
21 TypeVar,
22 cast,
23)
24import warnings
25
26import numpy as np
27
28from pandas._libs import Interval
29from pandas._libs.hashtable import duplicated
30from pandas.errors import (
31 Pandas4Warning,
32 SpecificationError,
33)
34from pandas.util._decorators import set_module
35from pandas.util._exceptions import find_stack_level
36
37from pandas.core.dtypes.common import (
38 ensure_int64,
39 is_bool,
40 is_dict_like,
41 is_integer_dtype,
42 is_list_like,
43 is_numeric_dtype,
44 is_scalar,
45)
46from pandas.core.dtypes.dtypes import (
47 CategoricalDtype,
48 IntervalDtype,
49)
50from pandas.core.dtypes.inference import is_hashable
51from pandas.core.dtypes.missing import (
52 isna,
53 notna,
54)
55
56from pandas.core import algorithms
57from pandas.core.apply import (
58 GroupByApply,
59 maybe_mangle_lambdas,
60 reconstruct_func,
61 validate_func_kwargs,
62)
63import pandas.core.common as com
64from pandas.core.frame import DataFrame
65from pandas.core.groupby import base
66from pandas.core.groupby.groupby import (
67 GroupBy,
68 GroupByPlot,
69)
70from pandas.core.indexes.api import (
71 Index,
72 MultiIndex,
73 all_indexes_same,
74 default_index,
75)
76from pandas.core.series import Series
77from pandas.core.sorting import get_group_index
78from pandas.core.util.numba_ import maybe_use_numba
79
80from pandas.plotting import boxplot_frame_groupby
81
82if TYPE_CHECKING:
83 from collections.abc import (
84 Hashable,
85 Sequence,
86 )
87
88 from pandas._typing import (
89 ArrayLike,
90 BlockManager,
91 CorrelationMethod,
92 IndexLabel,
93 Manager,
94 SingleBlockManager,
95 TakeIndexer,
96 )
97
98 from pandas import Categorical
99 from pandas.core.generic import NDFrame
100
101# TODO(typing) the return value on this callable should be any *scalar*.
102AggScalar: TypeAlias = str | Callable[..., Any]
103# TODO: validate types on ScalarResult and move to _typing
104# Blocked from using by https://github.com/python/mypy/issues/1484
105# See note at _mangle_lambda_list
106ScalarResult = TypeVar("ScalarResult")
107
108
109@set_module("pandas")
110@dataclasses.dataclass
111class NamedAgg:
112 """
113 Helper for column specific aggregation with control over output column names.
114
115 Parameters
116 ----------
117 column : Hashable
118 Column label in the DataFrame to apply aggfunc.
119 aggfunc : function or str
120 Function to apply to the provided column. If string, the name of a built-in
121 pandas function.
122 *args, **kwargs : Any
123 Optional positional and keyword arguments passed to ``aggfunc``.
124
125 See Also
126 --------
127 DataFrame.groupby : Group DataFrame using a mapper or by a Series of columns.
128
129 Examples
130 --------
131 >>> df = pd.DataFrame({"key": [1, 1, 2], "a": [-1, 0, 1], 1: [10, 11, 12]})
132 >>> agg_a = pd.NamedAgg(column="a", aggfunc="min")
133 >>> agg_1 = pd.NamedAgg(column=1, aggfunc=lambda x: np.mean(x))
134 >>> df.groupby("key").agg(result_a=agg_a, result_1=agg_1)
135 result_a result_1
136 key
137 1 -1 10.5
138 2 1 12.0
139
140 >>> def n_between(ser, low, high, **kwargs):
141 ... return ser.between(low, high, **kwargs).sum()
142
143 >>> agg_between = pd.NamedAgg("a", n_between, 0, 1)
144 >>> df.groupby("key").agg(count_between=agg_between)
145 count_between
146 key
147 1 1
148 2 1
149
150 >>> agg_between_kw = pd.NamedAgg("a", n_between, 0, 1, inclusive="both")
151 >>> df.groupby("key").agg(count_between_kw=agg_between_kw)
152 count_between_kw
153 key
154 1 1
155 2 1
156 """
157
158 column: Hashable
159 aggfunc: AggScalar
160 args: tuple[Any, ...] = ()
161 kwargs: dict[str, Any] = dataclasses.field(default_factory=dict)
162
163 def __init__(
164 self,
165 column: Hashable,
166 aggfunc: Callable[..., Any] | str,
167 *args: Any,
168 **kwargs: Any,
169 ) -> None:
170 self.column = column
171 self.aggfunc = aggfunc
172 self.args = args
173 self.kwargs = kwargs
174
175 def __getitem__(self, key: int) -> Any:
176 """Provide backward-compatible tuple-style access."""
177 if key == 0:
178 return self.column
179 elif key == 1:
180 return self.aggfunc
181 elif key == 2:
182 return self.args
183 elif key == 3:
184 return self.kwargs
185 raise IndexError("index out of range")
186
187
188@set_module("pandas.api.typing")
189class SeriesGroupBy(GroupBy[Series]):
190 def _wrap_agged_manager(self, mgr: Manager) -> Series:
191 out = self.obj._constructor_from_mgr(mgr, axes=mgr.axes)
192 out._name = self.obj.name
193 return out
194
195 def _get_data_to_aggregate(
196 self, *, numeric_only: bool = False, name: str | None = None
197 ) -> SingleBlockManager:
198 ser = self._obj_with_exclusions
199 single = ser._mgr
200 if numeric_only and not is_numeric_dtype(ser.dtype):
201 # GH#41291 match Series behavior
202 kwd_name = "numeric_only"
203 raise TypeError(
204 f"Cannot use {kwd_name}=True with "
205 f"{type(self).__name__}.{name} and non-numeric dtypes."
206 )
207 return single
208
209 def apply(self, func, *args, **kwargs) -> Series:
210 """
211 Apply function ``func`` group-wise and combine the results together.
212
213 The function passed to ``apply`` must take a series as its first
214 argument and return a DataFrame, Series or scalar. ``apply`` will
215 then take care of combining the results back together into a single
216 dataframe or series. ``apply`` is therefore a highly flexible
217 grouping method.
218
219 While ``apply`` is a very flexible method, its downside is that
220 using it can be quite a bit slower than using more specific methods
221 like ``agg`` or ``transform``. Pandas offers a wide range of method that will
222 be much faster than using ``apply`` for their specific purposes, so try to
223 use them before reaching for ``apply``.
224
225 Parameters
226 ----------
227 func : callable
228 A callable that takes a series as its first argument, and
229 returns a dataframe, a series or a scalar. In addition the
230 callable may take positional and keyword arguments.
231
232 *args : tuple
233 Optional positional arguments to pass to ``func``.
234
235 **kwargs : dict
236 Optional keyword arguments to pass to ``func``.
237
238 Returns
239 -------
240 Series or DataFrame
241 A pandas object with the result of applying ``func`` to each group.
242
243 See Also
244 --------
245 pipe : Apply function to the full GroupBy object instead of to each
246 group.
247 aggregate : Apply aggregate function to the GroupBy object.
248 transform : Apply function column-by-column to the GroupBy object.
249 Series.apply : Apply a function to a Series.
250 DataFrame.apply : Apply a function to each row or column of a DataFrame.
251
252 Notes
253 -----
254 The resulting dtype will reflect the return value of the passed ``func``,
255 see the examples below.
256
257 Functions that mutate the passed object can produce unexpected
258 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
259 for more details.
260
261 Examples
262 --------
263 >>> s = pd.Series([0, 1, 2], index="a a b".split())
264 >>> g1 = s.groupby(s.index, group_keys=False)
265 >>> g2 = s.groupby(s.index, group_keys=True)
266
267 From ``s`` above we can see that ``g`` has two groups, ``a`` and ``b``.
268 Notice that ``g1`` have ``g2`` have two groups, ``a`` and ``b``, and only
269 differ in their ``group_keys`` argument. Calling `apply` in various ways,
270 we can get different grouping results:
271
272 Example 1: The function passed to `apply` takes a Series as
273 its argument and returns a Series. `apply` combines the result for
274 each group together into a new Series.
275
276 The resulting dtype will reflect the return value of the passed ``func``.
277
278 >>> g1.apply(lambda x: x * 2 if x.name == "a" else x / 2)
279 a 0.0
280 a 2.0
281 b 1.0
282 dtype: float64
283
284 In the above, the groups are not part of the index. We can have them included
285 by using ``g2`` where ``group_keys=True``:
286
287 >>> g2.apply(lambda x: x * 2 if x.name == "a" else x / 2)
288 a a 0.0
289 a 2.0
290 b b 1.0
291 dtype: float64
292
293 Example 2: The function passed to `apply` takes a Series as
294 its argument and returns a scalar. `apply` combines the result for
295 each group together into a Series, including setting the index as
296 appropriate:
297
298 >>> g1.apply(lambda x: x.max() - x.min())
299 a 1
300 b 0
301 dtype: int64
302
303 The ``group_keys`` argument has no effect here because the result is not
304 like-indexed (i.e. :ref:`a transform <groupby.transform>`) when compared
305 to the input.
306
307 >>> g2.apply(lambda x: x.max() - x.min())
308 a 1
309 b 0
310 dtype: int64
311 """
312 return super().apply(func, *args, **kwargs)
313
314 def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs):
315 """
316 Aggregate using one or more operations.
317
318 The ``aggregate`` method enables flexible and efficient aggregation of grouped
319 data using a variety of functions, including built-in, user-defined, and
320 optimized JIT-compiled functions.
321
322 Parameters
323 ----------
324 func : function, str, list or None
325 Function to use for aggregating the data. If a function, must either
326 work when passed a Series or when passed to Series.apply.
327
328 Accepted combinations are:
329
330 - function
331 - string function name
332 - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
333 - None, in which case ``**kwargs`` are used with Named Aggregation. Here
334 the output has one column for each element in ``**kwargs``. The name of
335 the column is keyword, whereas the value determines the aggregation
336 used to compute the values in the column.
337
338 Can also accept a Numba JIT function with
339 ``engine='numba'`` specified. Only passing a single function is supported
340 with this engine.
341
342 If the ``'numba'`` engine is chosen, the function must be
343 a user defined function with ``values`` and ``index`` as the
344 first and second arguments respectively in the function signature.
345 Each group's index will be passed to the user defined function
346 and optionally available for use.
347
348 *args
349 Positional arguments to pass to func.
350 engine : str, default None
351 * ``'cython'`` : Runs the function through C-extensions from cython.
352 * ``'numba'`` : Runs the function through JIT compiled code from numba.
353 * ``None`` : Defaults to ``'cython'`` or globally setting
354 ``compute.use_numba``
355
356 engine_kwargs : dict, default None
357 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
358 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
359 and ``parallel`` dictionary keys. The values must either be ``True`` or
360 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
361 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
362 applied to the function
363
364 **kwargs
365 * If ``func`` is None, ``**kwargs`` are used to define the output names and
366 aggregations via Named Aggregation. See ``func`` entry.
367 * Otherwise, keyword arguments to be passed into func.
368
369 Returns
370 -------
371 Series
372 Aggregated Series based on the grouping and the applied aggregation
373 functions.
374
375 See Also
376 --------
377 SeriesGroupBy.apply : Apply function func group-wise
378 and combine the results together.
379 SeriesGroupBy.transform : Transforms the Series on each group
380 based on the given function.
381 Series.aggregate : Aggregate using one or more operations.
382
383 Notes
384 -----
385 When using ``engine='numba'``, there will be no "fall back" behavior internally.
386 The group data and group index will be passed as numpy arrays to the JITed
387 user defined function, and no alternative execution attempts will be tried.
388
389 Functions that mutate the passed object can produce unexpected
390 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
391 for more details.
392
393 The resulting dtype will reflect the return value of the passed ``func``,
394 see the examples below.
395
396 Examples
397 --------
398 >>> s = pd.Series([1, 2, 3, 4])
399
400 >>> s
401 0 1
402 1 2
403 2 3
404 3 4
405 dtype: int64
406
407 >>> s.groupby([1, 1, 2, 2]).min()
408 1 1
409 2 3
410 dtype: int64
411
412 >>> s.groupby([1, 1, 2, 2]).agg("min")
413 1 1
414 2 3
415 dtype: int64
416
417 >>> s.groupby([1, 1, 2, 2]).agg(["min", "max"])
418 min max
419 1 1 2
420 2 3 4
421
422 The output column names can be controlled by passing
423 the desired column names and aggregations as keyword arguments.
424
425 >>> s.groupby([1, 1, 2, 2]).agg(
426 ... minimum="min",
427 ... maximum="max",
428 ... )
429 minimum maximum
430 1 1 2
431 2 3 4
432
433 The resulting dtype will reflect the return value of the aggregating
434 function.
435
436 >>> s.groupby([1, 1, 2, 2]).agg(lambda x: x.astype(float).min())
437 1 1.0
438 2 3.0
439 dtype: float64
440 """
441 relabeling = func is None
442 columns = None
443 if relabeling:
444 columns, func = validate_func_kwargs(kwargs)
445 kwargs = {}
446
447 if isinstance(func, str):
448 if maybe_use_numba(engine) and engine is not None:
449 # Not all agg functions support numba, only propagate numba kwargs
450 # if user asks for numba, and engine is not None
451 # (if engine is None, the called function will handle the case where
452 # numba is requested via the global option)
453 kwargs["engine"] = engine
454 if engine_kwargs is not None:
455 kwargs["engine_kwargs"] = engine_kwargs
456 return getattr(self, func)(*args, **kwargs)
457
458 elif isinstance(func, abc.Iterable):
459 # Catch instances of lists / tuples
460 # but not the class list / tuple itself.
461 func = maybe_mangle_lambdas(func)
462 kwargs["engine"] = engine
463 kwargs["engine_kwargs"] = engine_kwargs
464 ret = self._aggregate_multiple_funcs(func, *args, **kwargs)
465 if relabeling:
466 # columns is not narrowed by mypy from relabeling flag
467 assert columns is not None # for mypy
468 ret.columns = columns
469 if not self.as_index:
470 ret = ret.reset_index()
471 return ret
472
473 else:
474 if maybe_use_numba(engine):
475 return self._aggregate_with_numba(
476 func, *args, engine_kwargs=engine_kwargs, **kwargs
477 )
478
479 if self.ngroups == 0:
480 # e.g. test_evaluate_with_empty_groups without any groups to
481 # iterate over, we have no output on which to do dtype
482 # inference. We default to using the existing dtype.
483 # xref GH#51445
484 obj = self._obj_with_exclusions
485 return self._wrap_aggregated_output(
486 self.obj._constructor(
487 [],
488 name=self.obj.name,
489 index=self._grouper.result_index,
490 dtype=obj.dtype,
491 )
492 )
493 return self._python_agg_general(func, *args, **kwargs)
494
495 agg = aggregate
496
497 def _python_agg_general(self, func, *args, **kwargs):
498 f = lambda x: func(x, *args, **kwargs)
499
500 obj = self._obj_with_exclusions
501 result = self._grouper.agg_series(obj, f)
502 res = obj._constructor(result, name=obj.name)
503 return self._wrap_aggregated_output(res)
504
505 def _aggregate_multiple_funcs(self, arg, *args, **kwargs) -> DataFrame:
506 if isinstance(arg, dict):
507 raise SpecificationError("nested renamer is not supported")
508
509 if any(isinstance(x, (tuple, list)) for x in arg):
510 arg = ((x, x) if not isinstance(x, (tuple, list)) else x for x in arg)
511 else:
512 # list of functions / function names
513 columns = (com.get_callable_name(f) or f for f in arg)
514 arg = zip(columns, arg, strict=True)
515
516 results: dict[base.OutputKey, DataFrame | Series] = {}
517 with com.temp_setattr(self, "as_index", True):
518 # Combine results using the index, need to adjust index after
519 # if as_index=False (GH#50724)
520 for idx, (name, func) in enumerate(arg):
521 key = base.OutputKey(label=name, position=idx)
522 results[key] = self.aggregate(func, *args, **kwargs)
523
524 if any(isinstance(x, DataFrame) for x in results.values()):
525 from pandas import concat
526
527 res_df = concat(
528 results.values(), axis=1, keys=[key.label for key in results]
529 )
530 return res_df
531
532 indexed_output = {key.position: val for key, val in results.items()}
533 output = self.obj._constructor_expanddim(indexed_output, index=None)
534 output.columns = Index(key.label for key in results)
535
536 return output
537
538 def _wrap_applied_output(
539 self,
540 data: Series,
541 values: list[Any],
542 not_indexed_same: bool = False,
543 is_transform: bool = False,
544 ) -> DataFrame | Series:
545 """
546 Wrap the output of SeriesGroupBy.apply into the expected result.
547
548 Parameters
549 ----------
550 data : Series
551 Input data for groupby operation.
552 values : List[Any]
553 Applied output for each group.
554 not_indexed_same : bool, default False
555 Whether the applied outputs are not indexed the same as the group axes.
556
557 Returns
558 -------
559 DataFrame or Series
560 """
561 if len(values) == 0:
562 # GH #6265
563 if is_transform:
564 # GH#47787 see test_group_on_empty_multiindex
565 res_index = data.index
566 elif not self.group_keys:
567 res_index = None
568 else:
569 res_index = self._grouper.result_index
570
571 return self.obj._constructor(
572 [],
573 name=self.obj.name,
574 index=res_index,
575 dtype=data.dtype,
576 )
577 assert values is not None
578
579 if isinstance(values[0], dict):
580 # GH #823 #24880
581 index = self._grouper.result_index
582 res_df = self.obj._constructor_expanddim(values, index=index)
583 # if self.observed is False,
584 # keep all-NaN rows created while re-indexing
585 res_ser = res_df.stack()
586 res_ser.name = self.obj.name
587 return res_ser
588 elif isinstance(values[0], (Series, DataFrame)):
589 result = self._concat_objects(
590 values,
591 not_indexed_same=not_indexed_same,
592 is_transform=is_transform,
593 )
594 if isinstance(result, Series):
595 result.name = self.obj.name
596 if not self.as_index and not_indexed_same:
597 result = self._insert_inaxis_grouper(result)
598 result.index = default_index(len(result))
599 return result.__finalize__(self.obj, method="groupby")
600 else:
601 # GH #6265 #24880
602 result = self.obj._constructor(
603 data=values, index=self._grouper.result_index, name=self.obj.name
604 )
605 if not self.as_index:
606 result = self._insert_inaxis_grouper(result)
607 result.index = default_index(len(result))
608 return result.__finalize__(self.obj, method="groupby")
609
610 __examples_series_doc = dedent(
611 """
612 >>> ser = pd.Series([390.0, 350.0, 30.0, 20.0],
613 ... index=["Falcon", "Falcon", "Parrot", "Parrot"],
614 ... name="Max Speed")
615 >>> grouped = ser.groupby([1, 1, 2, 2])
616 >>> grouped.transform(lambda x: (x - x.mean()) / x.std())
617 Falcon 0.707107
618 Falcon -0.707107
619 Parrot 0.707107
620 Parrot -0.707107
621 Name: Max Speed, dtype: float64
622
623 Broadcast result of the transformation
624
625 >>> grouped.transform(lambda x: x.max() - x.min())
626 Falcon 40.0
627 Falcon 40.0
628 Parrot 10.0
629 Parrot 10.0
630 Name: Max Speed, dtype: float64
631
632 >>> grouped.transform("mean")
633 Falcon 370.0
634 Falcon 370.0
635 Parrot 25.0
636 Parrot 25.0
637 Name: Max Speed, dtype: float64
638
639 The resulting dtype will reflect the return value of the passed ``func``,
640 for example:
641
642 >>> grouped.transform(lambda x: x.astype(int).max())
643 Falcon 390
644 Falcon 390
645 Parrot 30
646 Parrot 30
647 Name: Max Speed, dtype: int64
648 """
649 )
650
651 def transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):
652 """
653 Call function producing a same-indexed Series on each group.
654
655 Returns a Series having the same indexes as the original object
656 filled with the transformed values.
657
658 Parameters
659 ----------
660 func : function, str
661 Function to apply to each group.
662 See the Notes section below for requirements.
663
664 Accepted inputs are:
665
666 - String
667 - Python function
668 - Numba JIT function with ``engine='numba'`` specified.
669
670 Only passing a single function is supported with this engine.
671 If the ``'numba'`` engine is chosen, the function must be
672 a user defined function with ``values`` and ``index`` as the
673 first and second arguments respectively in the function signature.
674 Each group's index will be passed to the user defined function
675 and optionally available for use.
676
677 If a string is chosen, then it needs to be the name
678 of the groupby method you want to use.
679 *args
680 Positional arguments to pass to func.
681 engine : str, default None
682 * ``'cython'`` : Runs the function through C-extensions from cython.
683 * ``'numba'`` : Runs the function through JIT compiled code from numba.
684 * ``None`` : Defaults to ``'cython'``
685 or the global setting ``compute.use_numba``
686
687 engine_kwargs : dict, default None
688 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
689 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
690 and ``parallel`` dictionary keys. The values must either be ``True`` or
691 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
692 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
693 applied to the function
694
695 **kwargs
696 Keyword arguments to be passed into func.
697
698 Returns
699 -------
700 Series
701 Series with the same indexes as the original object filled
702 with transformed values.
703
704 See Also
705 --------
706 Series.groupby.apply : Apply function ``func`` group-wise and combine
707 the results together.
708 Series.groupby.aggregate : Aggregate using one or more operations.
709 Series.transform : Call ``func`` on self producing a Series with the
710 same axis shape as self.
711
712 Notes
713 -----
714 Each group is endowed the attribute 'name' in case you need to know
715 which group you are working on.
716
717 The current implementation imposes three requirements on f:
718
719 * f must return a value that either has the same shape as the input
720 subframe or can be broadcast to the shape of the input subframe.
721 For example, if `f` returns a scalar it will be broadcast to have the
722 same shape as the input subframe.
723 * if this is a DataFrame, f must support application column-by-column
724 in the subframe. If f also supports application to the entire subframe,
725 then a fast path is used starting from the second chunk.
726 * f must not mutate groups. Mutation is not supported and may
727 produce unexpected results. See :ref:`gotchas.udf-mutation` for more details.
728
729 When using ``engine='numba'``, there will be no "fall back" behavior internally.
730 The group data and group index will be passed as numpy arrays to the JITed
731 user defined function, and no alternative execution attempts will be tried.
732
733 The resulting dtype will reflect the return value of the passed ``func``,
734 see the examples below.
735
736 .. versionchanged:: 2.0.0
737
738 When using ``.transform`` on a grouped DataFrame and
739 the transformation function returns a DataFrame,
740 pandas now aligns the result's index with the input's index.
741 You can call ``.to_numpy()`` on the result of
742 the transformation function to avoid alignment.
743
744 Examples
745 --------
746
747 >>> ser = pd.Series(
748 ... [390.0, 350.0, 30.0, 20.0],
749 ... index=["Falcon", "Falcon", "Parrot", "Parrot"],
750 ... name="Max Speed",
751 ... )
752 >>> grouped = ser.groupby([1, 1, 2, 2])
753 >>> grouped.transform(lambda x: (x - x.mean()) / x.std())
754 Falcon 0.707107
755 Falcon -0.707107
756 Parrot 0.707107
757 Parrot -0.707107
758 Name: Max Speed, dtype: float64
759
760 Broadcast result of the transformation
761
762 >>> grouped.transform(lambda x: x.max() - x.min())
763 Falcon 40.0
764 Falcon 40.0
765 Parrot 10.0
766 Parrot 10.0
767 Name: Max Speed, dtype: float64
768
769 >>> grouped.transform("mean")
770 Falcon 370.0
771 Falcon 370.0
772 Parrot 25.0
773 Parrot 25.0
774 Name: Max Speed, dtype: float64
775
776 The resulting dtype will reflect the return value of the passed ``func``,
777 for example:
778
779 >>> grouped.transform(lambda x: x.astype(int).max())
780 Falcon 390
781 Falcon 390
782 Parrot 30
783 Parrot 30
784 Name: Max Speed, dtype: int64
785 """
786 return self._transform(
787 func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
788 )
789
790 def _cython_transform(self, how: str, numeric_only: bool = False, **kwargs):
791 obj = self._obj_with_exclusions
792
793 try:
794 result = self._grouper._cython_operation(
795 "transform", obj._values, how, 0, **kwargs
796 )
797 except NotImplementedError as err:
798 # e.g. test_groupby_raises_string
799 raise TypeError(f"{how} is not supported for {obj.dtype} dtype") from err
800
801 return obj._constructor(result, index=self.obj.index, name=obj.name)
802
803 def _transform_general(
804 self, func: Callable, engine, engine_kwargs, *args, **kwargs
805 ) -> Series:
806 """
807 Transform with a callable `func`.
808 """
809 if maybe_use_numba(engine):
810 return self._transform_with_numba(
811 func, *args, engine_kwargs=engine_kwargs, **kwargs
812 )
813 assert callable(func)
814 klass = type(self.obj)
815
816 results = []
817 for name, group in self._grouper.get_iterator(
818 self._obj_with_exclusions,
819 ):
820 # this setattr is needed for test_transform_lambda_with_datetimetz
821 object.__setattr__(group, "name", name)
822 res = func(group, *args, **kwargs)
823
824 results.append(klass(res, index=group.index))
825
826 # check for empty "results" to avoid concat ValueError
827 if results:
828 from pandas.core.reshape.concat import concat
829
830 concatenated = concat(results, ignore_index=True)
831 result = self._set_result_index_ordered(concatenated)
832 else:
833 result = self.obj._constructor(dtype=np.float64)
834
835 result.name = self.obj.name
836 return result
837
838 def filter(self, func, dropna: bool = True, *args, **kwargs):
839 """
840 Filter elements from groups that don't satisfy a criterion.
841
842 Elements from groups are filtered if they do not satisfy the
843 boolean criterion specified by func.
844
845 Parameters
846 ----------
847 func : function
848 Criterion to apply to each group. Should return True or False.
849 dropna : bool, optional
850 Drop groups that do not pass the filter. True by default; if False,
851 groups that evaluate False are filled with NaNs.
852 *args : tuple
853 Optional positional arguments to pass to `func`.
854 **kwargs : dict
855 Optional keyword arguments to pass to `func`.
856
857 Returns
858 -------
859 Series
860 The filtered subset of the original Series.
861
862 See Also
863 --------
864 Series.filter: Filter elements of ungrouped Series.
865 DataFrameGroupBy.filter : Filter elements from groups base on criterion.
866
867 Notes
868 -----
869 Functions that mutate the passed object can produce unexpected
870 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
871 for more details.
872
873 Examples
874 --------
875 >>> df = pd.DataFrame(
876 ... {
877 ... "A": ["foo", "bar", "foo", "bar", "foo", "bar"],
878 ... "B": [1, 2, 3, 4, 5, 6],
879 ... "C": [2.0, 5.0, 8.0, 1.0, 2.0, 9.0],
880 ... }
881 ... )
882 >>> grouped = df.groupby("A")
883 >>> df.groupby("A").B.filter(lambda x: x.mean() > 3.0)
884 1 2
885 3 4
886 5 6
887 Name: B, dtype: int64
888 """
889 if isinstance(func, str):
890 wrapper = lambda x: getattr(x, func)(*args, **kwargs)
891 else:
892 wrapper = lambda x: func(x, *args, **kwargs)
893
894 # Interpret np.nan as False.
895 def true_and_notna(x) -> bool:
896 b = wrapper(x)
897 return notna(b) and b
898
899 try:
900 indices = [
901 self._get_index(name)
902 for name, group in self._grouper.get_iterator(self._obj_with_exclusions)
903 if true_and_notna(group)
904 ]
905 except (ValueError, TypeError) as err:
906 raise TypeError("the filter must return a boolean result") from err
907
908 filtered = self._apply_filter(indices, dropna)
909 return filtered
910
911 def nunique(self, dropna: bool = True) -> Series | DataFrame:
912 """
913 Return number of unique elements in the group.
914
915 Parameters
916 ----------
917 dropna : bool, default True
918 Don't include NaN in the counts.
919
920 Returns
921 -------
922 Series
923 Number of unique values within each group.
924
925 See Also
926 --------
927 core.resample.Resampler.nunique : Method nunique for Resampler.
928
929 Examples
930 --------
931 >>> lst = ["a", "a", "b", "b"]
932 >>> ser = pd.Series([1, 2, 3, 3], index=lst)
933 >>> ser
934 a 1
935 a 2
936 b 3
937 b 3
938 dtype: int64
939 >>> ser.groupby(level=0).nunique()
940 a 2
941 b 1
942 dtype: int64
943 """
944 ids = self._grouper.ids
945 ngroups = self._grouper.ngroups
946 val = self.obj._values
947 codes, uniques = algorithms.factorize(val, use_na_sentinel=dropna, sort=False)
948
949 if self._grouper.has_dropped_na:
950 mask = ids >= 0
951 ids = ids[mask]
952 codes = codes[mask]
953
954 group_index = get_group_index(
955 labels=[ids, codes],
956 shape=(ngroups, len(uniques)),
957 sort=False,
958 xnull=dropna,
959 )
960
961 if dropna:
962 mask = group_index >= 0
963 if (~mask).any():
964 ids = ids[mask]
965 group_index = group_index[mask]
966
967 mask = duplicated(group_index, "first")
968 res = np.bincount(ids[~mask], minlength=ngroups)
969 res = ensure_int64(res)
970
971 ri = self._grouper.result_index
972 result: Series | DataFrame = self.obj._constructor(
973 res, index=ri, name=self.obj.name
974 )
975 if not self.as_index:
976 result = self._insert_inaxis_grouper(result)
977 result.index = default_index(len(result))
978 return result
979
980 def describe(self, percentiles=None, include=None, exclude=None) -> Series:
981 """
982 Generate descriptive statistics.
983
984 Descriptive statistics include those that summarize the central
985 tendency, dispersion and shape of a
986 dataset's distribution, excluding ``NaN`` values.
987
988 Analyzes both numeric and object series, as well
989 as ``DataFrame`` column sets of mixed data types. The output
990 will vary depending on what is provided. Refer to the notes
991 below for more detail.
992
993 Parameters
994 ----------
995 percentiles : list-like of numbers, optional
996 The percentiles to include in the output. All should
997 fall between 0 and 1. The default, ``None``, will automatically
998 return the 25th, 50th, and 75th percentiles.
999 include : 'all', list-like of dtypes or None (default), optional
1000 A white list of data types to include in the result. Ignored
1001 for ``Series``. Here are the options:
1002
1003 - 'all' : All columns of the input will be included in the output.
1004 - A list-like of dtypes : Limits the results to the
1005 provided data types.
1006 To limit the result to numeric types submit
1007 ``numpy.number``. To limit it instead to object columns submit
1008 the ``numpy.object`` data type. Strings
1009 can also be used in the style of
1010 ``select_dtypes`` (e.g. ``df.describe(include=['O'])``). To
1011 select pandas categorical columns, use ``'category'``
1012 - None (default) : The result will include all numeric columns.
1013 exclude : list-like of dtypes or None (default), optional,
1014 A black list of data types to omit from the result. Ignored
1015 for ``Series``. Here are the options:
1016
1017 - A list-like of dtypes : Excludes the provided data types
1018 from the result. To exclude numeric types submit
1019 ``numpy.number``. To exclude object columns submit the data
1020 type ``numpy.object``. Strings can also be used in the style of
1021 ``select_dtypes`` (e.g. ``df.describe(exclude=['O'])``). To
1022 exclude pandas categorical columns, use ``'category'``
1023 - None (default) : The result will exclude nothing.
1024
1025 Returns
1026 -------
1027 Series or DataFrame
1028 Summary statistics of the Series or Dataframe provided.
1029
1030 See Also
1031 --------
1032 DataFrame.count: Count number of non-NA/null observations.
1033 DataFrame.max: Maximum of the values in the object.
1034 DataFrame.min: Minimum of the values in the object.
1035 DataFrame.mean: Mean of the values.
1036 DataFrame.std: Standard deviation of the observations.
1037 DataFrame.select_dtypes: Subset of a DataFrame including/excluding
1038 columns based on their dtype.
1039
1040 Notes
1041 -----
1042 For numeric data, the result's index will include ``count``,
1043 ``mean``, ``std``, ``min``, ``max`` as well as lower, ``50`` and
1044 upper percentiles. By default the lower percentile is ``25`` and the
1045 upper percentile is ``75``. The ``50`` percentile is the
1046 same as the median.
1047
1048 For object data (e.g. strings), the result's index
1049 will include ``count``, ``unique``, ``top``, and ``freq``. The ``top``
1050 is the most common value. The ``freq`` is the most common value's
1051 frequency.
1052
1053 If multiple object values have the highest count, then the
1054 ``count`` and ``top`` results will be arbitrarily chosen from
1055 among those with the highest count.
1056
1057 For mixed data types provided via a ``DataFrame``, the default is to
1058 return only an analysis of numeric columns. If the DataFrame consists
1059 only of object and categorical data without any numeric columns, the
1060 default is to return an analysis of both the object and categorical
1061 columns. If ``include='all'`` is provided as an option, the result
1062 will include a union of attributes of each type.
1063
1064 The `include` and `exclude` parameters can be used to limit
1065 which columns in a ``DataFrame`` are analyzed for the output.
1066 The parameters are ignored when analyzing a ``Series``.
1067
1068 Examples
1069 --------
1070 Describing a numeric ``Series``.
1071
1072 >>> s = pd.Series([1, 2, 3, 4])
1073
1074 >>> s
1075 0 1
1076 1 2
1077 2 3
1078 3 4
1079 dtype: int64
1080
1081 >>> s.groupby([1, 1, 2, 2]).describe()
1082 count mean std min 25% 50% 75% max
1083 1 2.0 1.5 0.707107 1.0 1.25 1.5 1.75 2.0
1084 2 2.0 3.5 0.707107 3.0 3.25 3.5 3.75 4.0
1085 """
1086 return super().describe(
1087 percentiles=percentiles, include=include, exclude=exclude
1088 )
1089
1090 def value_counts(
1091 self,
1092 normalize: bool = False,
1093 sort: bool = True,
1094 ascending: bool = False,
1095 bins=None,
1096 dropna: bool = True,
1097 ) -> Series | DataFrame:
1098 """
1099 Return a Series or DataFrame containing counts of unique rows.
1100
1101 Parameters
1102 ----------
1103 normalize : bool, default False
1104 Return proportions rather than frequencies.
1105 sort : bool, default True
1106 Sort by frequencies.
1107 ascending : bool, default False
1108 Sort in ascending order.
1109 bins : int or list of ints, optional
1110 Rather than count values, group them into half-open bins,
1111 a convenience for pd.cut, only works with numeric data.
1112 dropna : bool, default True
1113 Don't include counts of rows that contain NA values.
1114
1115 Returns
1116 -------
1117 Series or DataFrame
1118 Series if the groupby ``as_index`` is True, otherwise DataFrame.
1119
1120 See Also
1121 --------
1122 Series.value_counts: Equivalent method on Series.
1123 DataFrame.value_counts: Equivalent method on DataFrame.
1124 DataFrameGroupBy.value_counts: Equivalent method on DataFrameGroupBy.
1125
1126 Notes
1127 -----
1128 - If the groupby ``as_index`` is True then the returned Series will have a
1129 MultiIndex with one level per input column.
1130 - If the groupby ``as_index`` is False then the returned DataFrame will have an
1131 additional column with the value_counts. The column is labelled 'count' or
1132 'proportion', depending on the ``normalize`` parameter.
1133
1134 By default, rows that contain any NA values are omitted from
1135 the result.
1136
1137 By default, the result will be in descending order so that the
1138 first element of each group is the most frequently-occurring row.
1139
1140 Examples
1141 --------
1142 >>> s = pd.Series(
1143 ... [1, 1, 2, 3, 2, 3, 3, 1, 1, 3, 3, 3],
1144 ... index=["A", "A", "A", "A", "A", "A", "B", "B", "B", "B", "B", "B"],
1145 ... )
1146 >>> s
1147 A 1
1148 A 1
1149 A 2
1150 A 3
1151 A 2
1152 A 3
1153 B 3
1154 B 1
1155 B 1
1156 B 3
1157 B 3
1158 B 3
1159 dtype: int64
1160 >>> g1 = s.groupby(s.index)
1161 >>> g1.value_counts(bins=2)
1162 A (0.997, 2.0] 4
1163 (2.0, 3.0] 2
1164 B (2.0, 3.0] 4
1165 (0.997, 2.0] 2
1166 Name: count, dtype: int64
1167 >>> g1.value_counts(normalize=True)
1168 A 1 0.333333
1169 2 0.333333
1170 3 0.333333
1171 B 3 0.666667
1172 1 0.333333
1173 Name: proportion, dtype: float64
1174 """
1175 name = "proportion" if normalize else "count"
1176
1177 if bins is None:
1178 result = self._value_counts(
1179 normalize=normalize, sort=sort, ascending=ascending, dropna=dropna
1180 )
1181 result.name = name
1182 return result
1183
1184 from pandas.core.reshape.merge import get_join_indexers
1185 from pandas.core.reshape.tile import cut
1186
1187 ids = self._grouper.ids
1188 val = self.obj._values
1189
1190 index_names = [*self._grouper.names, self.obj.name]
1191
1192 if isinstance(val.dtype, CategoricalDtype) or (
1193 bins is not None and not np.iterable(bins)
1194 ):
1195 # scalar bins cannot be done at top level
1196 # in a backward compatible way
1197 # GH38672 relates to categorical dtype
1198 ser = self.apply(
1199 Series.value_counts,
1200 normalize=normalize,
1201 sort=sort,
1202 ascending=ascending,
1203 bins=bins,
1204 )
1205 ser.name = name
1206 ser.index.names = index_names
1207 return ser
1208
1209 # groupby removes null keys from groupings
1210 mask = ids != -1
1211 ids, val = ids[mask], val[mask]
1212
1213 lab: Index | np.ndarray
1214 if bins is None:
1215 lab, lev = algorithms.factorize(val, sort=True)
1216 llab = lambda lab, inc: lab[inc]
1217 else:
1218 # lab is a Categorical with categories an IntervalIndex
1219 cat_ser = cut(Series(val, copy=False), bins, include_lowest=True)
1220 cat_obj = cast("Categorical", cat_ser._values)
1221 lev = cat_obj.categories
1222 lab = lev.take(
1223 cat_obj.codes,
1224 allow_fill=True,
1225 fill_value=lev._na_value,
1226 )
1227 llab = lambda lab, inc: lab[inc]._multiindex.codes[-1]
1228
1229 if isinstance(lab.dtype, IntervalDtype):
1230 # TODO: should we do this inside II?
1231 lab_interval = cast(Interval, lab)
1232
1233 sorter = np.lexsort((lab_interval.left, lab_interval.right, ids))
1234 else:
1235 sorter = np.lexsort((lab, ids))
1236
1237 ids, lab = ids[sorter], lab[sorter]
1238
1239 # group boundaries are where group ids change
1240 idchanges = 1 + np.nonzero(ids[1:] != ids[:-1])[0]
1241 idx = np.r_[0, idchanges]
1242 if not len(ids):
1243 idx = idchanges
1244
1245 # new values are where sorted labels change
1246 lchanges = llab(lab, slice(1, None)) != llab(lab, slice(None, -1))
1247 inc = np.r_[True, lchanges]
1248 if not len(val):
1249 inc = lchanges
1250 inc[idx] = True # group boundaries are also new values
1251 out = np.diff(np.nonzero(np.r_[inc, True])[0]) # value counts
1252
1253 # num. of times each group should be repeated
1254 rep = partial(np.repeat, repeats=np.add.reduceat(inc, idx))
1255
1256 # multi-index components
1257 if isinstance(self._grouper.result_index, MultiIndex):
1258 codes = list(self._grouper.result_index.codes)
1259 else:
1260 codes = [
1261 algorithms.factorize(
1262 self._grouper.result_index,
1263 sort=self._grouper._sort,
1264 use_na_sentinel=self._grouper.dropna,
1265 )[0]
1266 ]
1267 codes = [rep(level_codes) for level_codes in codes] + [llab(lab, inc)]
1268 levels = [*self._grouper.levels, lev]
1269
1270 if dropna:
1271 mask = codes[-1] != -1
1272 if mask.all():
1273 dropna = False
1274 else:
1275 out, codes = out[mask], [level_codes[mask] for level_codes in codes]
1276
1277 if normalize:
1278 out = out.astype("float")
1279 d = np.diff(np.r_[idx, len(ids)])
1280 if dropna:
1281 m = ids[lab == -1]
1282 np.add.at(d, m, -1)
1283 acc = rep(d)[mask]
1284 else:
1285 acc = rep(d)
1286 out /= acc
1287
1288 if sort and bins is None:
1289 cat = ids[inc][mask] if dropna else ids[inc]
1290 sorter = np.lexsort((out if ascending else -out, cat))
1291 out, codes[-1] = out[sorter], codes[-1][sorter]
1292
1293 if bins is not None:
1294 # for compat. with libgroupby.value_counts need to ensure every
1295 # bin is present at every index level, null filled with zeros
1296 diff = np.zeros(len(out), dtype="bool")
1297 for level_codes in codes[:-1]:
1298 diff |= np.r_[True, level_codes[1:] != level_codes[:-1]]
1299
1300 ncat, nbin = diff.sum(), len(levels[-1])
1301
1302 left = [np.repeat(np.arange(ncat), nbin), np.tile(np.arange(nbin), ncat)]
1303
1304 right = [diff.cumsum() - 1, codes[-1]]
1305
1306 # error: Argument 1 to "get_join_indexers" has incompatible type
1307 # "List[ndarray[Any, Any]]"; expected "List[Union[Union[ExtensionArray,
1308 # ndarray[Any, Any]], Index, Series]]
1309 _, idx = get_join_indexers(
1310 left, # type: ignore[arg-type]
1311 right,
1312 sort=False,
1313 how="left",
1314 )
1315 if idx is not None:
1316 out = np.where(idx != -1, out[idx], 0)
1317
1318 if sort:
1319 sorter = np.lexsort((out if ascending else -out, left[0]))
1320 out, left[-1] = out[sorter], left[-1][sorter]
1321
1322 # build the multi-index w/ full levels
1323 def build_codes(lev_codes: np.ndarray) -> np.ndarray:
1324 return np.repeat(lev_codes[diff], nbin)
1325
1326 codes = [build_codes(lev_codes) for lev_codes in codes[:-1]]
1327 codes.append(left[-1])
1328
1329 mi = MultiIndex(
1330 levels=levels, codes=codes, names=index_names, verify_integrity=False
1331 )
1332
1333 if is_integer_dtype(out.dtype):
1334 out = ensure_int64(out)
1335 result = self.obj._constructor(out, index=mi, name=name)
1336 if not self.as_index:
1337 result = result.reset_index()
1338 return result
1339
1340 def take(
1341 self,
1342 indices: TakeIndexer,
1343 **kwargs,
1344 ) -> Series:
1345 """
1346 Return the elements in the given *positional* indices in each group.
1347
1348 This means that we are not indexing according to actual values in
1349 the index attribute of the object. We are indexing according to the
1350 actual position of the element in the object.
1351
1352 If a requested index does not exist for some group, this method will raise.
1353 To get similar behavior that ignores indices that don't exist, see
1354 :meth:`.SeriesGroupBy.nth`.
1355
1356 Parameters
1357 ----------
1358 indices : array-like
1359 An array of ints indicating which positions to take in each group.
1360
1361 **kwargs
1362 For compatibility with :meth:`numpy.take`. Has no effect on the
1363 output.
1364
1365 Returns
1366 -------
1367 Series
1368 A Series containing the elements taken from each group.
1369
1370 See Also
1371 --------
1372 Series.take : Take elements from a Series along an axis.
1373 Series.loc : Select a subset of a DataFrame by labels.
1374 Series.iloc : Select a subset of a DataFrame by positions.
1375 numpy.take : Take elements from an array along an axis.
1376 SeriesGroupBy.nth : Similar to take, won't raise if indices don't exist.
1377
1378 Examples
1379 --------
1380 >>> df = pd.DataFrame(
1381 ... [
1382 ... ("falcon", "bird", 389.0),
1383 ... ("parrot", "bird", 24.0),
1384 ... ("lion", "mammal", 80.5),
1385 ... ("monkey", "mammal", np.nan),
1386 ... ("rabbit", "mammal", 15.0),
1387 ... ],
1388 ... columns=["name", "class", "max_speed"],
1389 ... index=[4, 3, 2, 1, 0],
1390 ... )
1391 >>> df
1392 name class max_speed
1393 4 falcon bird 389.0
1394 3 parrot bird 24.0
1395 2 lion mammal 80.5
1396 1 monkey mammal NaN
1397 0 rabbit mammal 15.0
1398 >>> gb = df["name"].groupby([1, 1, 2, 2, 2])
1399
1400 Take elements at rows 0 and 1 in each group.
1401
1402 >>> gb.take([0, 1])
1403 1 4 falcon
1404 3 parrot
1405 2 2 lion
1406 1 monkey
1407 Name: name, dtype: str
1408
1409 We may take elements using negative integers for positive indices,
1410 starting from the end of the object, just like with Python lists.
1411
1412 >>> gb.take([-1, -2])
1413 1 3 parrot
1414 4 falcon
1415 2 0 rabbit
1416 1 monkey
1417 Name: name, dtype: str
1418 """
1419 result = self._op_via_apply("take", indices=indices, **kwargs)
1420 return result
1421
1422 def skew(
1423 self,
1424 skipna: bool = True,
1425 numeric_only: bool = False,
1426 **kwargs,
1427 ) -> Series:
1428 """
1429 Return unbiased skew within groups.
1430
1431 Normalized by N-1.
1432
1433 Parameters
1434 ----------
1435 skipna : bool, default True
1436 Exclude NA/null values when computing the result.
1437
1438 numeric_only : bool, default False
1439 Include only float, int, boolean columns. Not implemented for Series.
1440
1441 **kwargs
1442 Additional keyword arguments to be passed to the function.
1443
1444 Returns
1445 -------
1446 Series
1447 Unbiased skew within groups.
1448
1449 See Also
1450 --------
1451 Series.skew : Return unbiased skew over requested axis.
1452
1453 Examples
1454 --------
1455 >>> ser = pd.Series(
1456 ... [390.0, 350.0, 357.0, np.nan, 22.0, 20.0, 30.0],
1457 ... index=[
1458 ... "Falcon",
1459 ... "Falcon",
1460 ... "Falcon",
1461 ... "Falcon",
1462 ... "Parrot",
1463 ... "Parrot",
1464 ... "Parrot",
1465 ... ],
1466 ... name="Max Speed",
1467 ... )
1468 >>> ser
1469 Falcon 390.0
1470 Falcon 350.0
1471 Falcon 357.0
1472 Falcon NaN
1473 Parrot 22.0
1474 Parrot 20.0
1475 Parrot 30.0
1476 Name: Max Speed, dtype: float64
1477 >>> ser.groupby(level=0).skew()
1478 Falcon 1.525174
1479 Parrot 1.457863
1480 Name: Max Speed, dtype: float64
1481 >>> ser.groupby(level=0).skew(skipna=False)
1482 Falcon NaN
1483 Parrot 1.457863
1484 Name: Max Speed, dtype: float64
1485 """
1486
1487 return self._cython_agg_general(
1488 "skew", alt=None, skipna=skipna, numeric_only=numeric_only, **kwargs
1489 )
1490
1491 def kurt(
1492 self,
1493 skipna: bool = True,
1494 numeric_only: bool = False,
1495 **kwargs,
1496 ) -> Series:
1497 """
1498 Return unbiased kurtosis within groups.
1499
1500 Parameters
1501 ----------
1502 skipna : bool, default True
1503 Exclude NA/null values when computing the result.
1504
1505 numeric_only : bool, default False
1506 Include only float, int, boolean columns. Not implemented for Series.
1507
1508 **kwargs
1509 Additional keyword arguments to be passed to the function.
1510
1511 Returns
1512 -------
1513 Series
1514 Unbiased kurtosis within groups.
1515
1516 See Also
1517 --------
1518 Series.kurt : Return unbiased kurtosis over requested axis.
1519
1520 Examples
1521 --------
1522 >>> ser = pd.Series(
1523 ... [390.0, 350.0, 357.0, 333.0, np.nan, 22.0, 20.0, 30.0, 40.0, 41.0],
1524 ... index=[
1525 ... "Falcon",
1526 ... "Falcon",
1527 ... "Falcon",
1528 ... "Falcon",
1529 ... "Falcon",
1530 ... "Parrot",
1531 ... "Parrot",
1532 ... "Parrot",
1533 ... "Parrot",
1534 ... "Parrot",
1535 ... ],
1536 ... name="Max Speed",
1537 ... )
1538 >>> ser
1539 Falcon 390.0
1540 Falcon 350.0
1541 Falcon 357.0
1542 Falcon 333.0
1543 Falcon NaN
1544 Parrot 22.0
1545 Parrot 20.0
1546 Parrot 30.0
1547 Parrot 40.0
1548 Parrot 41.0
1549 Name: Max Speed, dtype: float64
1550 >>> ser.groupby(level=0).kurt()
1551 Falcon 1.622109
1552 Parrot -2.878714
1553 Name: Max Speed, dtype: float64
1554 >>> ser.groupby(level=0).kurt(skipna=False)
1555 Falcon NaN
1556 Parrot -2.878714
1557 Name: Max Speed, dtype: float64
1558 """
1559
1560 def alt(obj):
1561 # This should not be reached since the cython path should raise
1562 # TypeError and not NotImplementedError.
1563 raise TypeError(f"'kurt' is not supported for dtype={obj.dtype}")
1564
1565 return self._cython_agg_general(
1566 "kurt", alt=alt, skipna=skipna, numeric_only=numeric_only, **kwargs
1567 )
1568
1569 @property
1570 def plot(self) -> GroupByPlot:
1571 """
1572 Make plots of groups from a Series.
1573
1574 Uses the backend specified by the option ``plotting.backend``.
1575 By default, matplotlib is used.
1576
1577 Returns
1578 -------
1579 GroupByPlot
1580 A plotting object that can be used to create plots for each group.
1581
1582 See Also
1583 --------
1584 Series.plot : Make plots of Series.
1585
1586 Examples
1587 --------
1588 >>> ser = pd.Series([1, 2, 3, 4, 5], index=["a", "a", "b", "b", "c"])
1589 >>> g = ser.groupby(level=0)
1590 >>> g.plot() # doctest: +SKIP
1591 """
1592 result = GroupByPlot(self)
1593 return result
1594
1595 def nlargest(
1596 self, n: int = 5, keep: Literal["first", "last", "all"] = "first"
1597 ) -> Series:
1598 """
1599 Return the largest `n` elements.
1600
1601 Parameters
1602 ----------
1603 n : int, default 5
1604 Return this many descending sorted values.
1605 keep : {'first', 'last', 'all'}, default 'first'
1606 When there are duplicate values that cannot all fit in a
1607 Series of `n` elements:
1608
1609 - ``first`` : return the first `n` occurrences in order
1610 of appearance.
1611 - ``last`` : return the last `n` occurrences in reverse
1612 order of appearance.
1613 - ``all`` : keep all occurrences. This can result in a Series of
1614 size larger than `n`.
1615
1616 Returns
1617 -------
1618 Series
1619 The `n` largest values in the Series, sorted in decreasing order.
1620
1621 See Also
1622 --------
1623 Series.nsmallest: Get the `n` smallest elements.
1624 Series.sort_values: Sort Series by values.
1625 Series.head: Return the first `n` rows.
1626
1627 Notes
1628 -----
1629 Faster than ``.sort_values(ascending=False).head(n)`` for small `n`
1630 relative to the size of the ``Series`` object.
1631
1632 Examples
1633 --------
1634 >>> s = pd.Series([1, 2, 3, 4, 5, 6])
1635
1636 >>> s
1637 0 1
1638 1 2
1639 2 3
1640 3 4
1641 4 5
1642 5 6
1643 dtype: int64
1644
1645 >>> s.groupby([1, 1, 1, 2, 2, 2]).nlargest(n=2)
1646 1 2 3
1647 1 2
1648 2 5 6
1649 4 5
1650 dtype: int64
1651 """
1652 f = partial(Series.nlargest, n=n, keep=keep)
1653 data = self._obj_with_exclusions
1654 # Don't change behavior if result index happens to be the same, i.e.
1655 # already ordered and n >= all group sizes.
1656 result = self._python_apply_general(f, data, not_indexed_same=True)
1657 return result
1658
1659 def nsmallest(
1660 self, n: int = 5, keep: Literal["first", "last", "all"] = "first"
1661 ) -> Series:
1662 """
1663 Return the smallest `n` elements.
1664
1665 Parameters
1666 ----------
1667 n : int, default 5
1668 Return this many ascending sorted values.
1669 keep : {'first', 'last', 'all'}, default 'first'
1670 When there are duplicate values that cannot all fit in a
1671 Series of `n` elements:
1672
1673 - ``first`` : return the first `n` occurrences in order
1674 of appearance.
1675 - ``last`` : return the last `n` occurrences in reverse
1676 order of appearance.
1677 - ``all`` : keep all occurrences. This can result in a Series of
1678 size larger than `n`.
1679
1680 Returns
1681 -------
1682 Series
1683 The `n` smallest values in the Series, sorted in increasing order.
1684
1685 See Also
1686 --------
1687 Series.nlargest: Get the `n` largest elements.
1688 Series.sort_values: Sort Series by values.
1689 Series.head: Return the first `n` rows.
1690
1691 Notes
1692 -----
1693 Faster than ``.sort_values().head(n)`` for small `n` relative to
1694 the size of the ``Series`` object.
1695
1696 Examples
1697 --------
1698 >>> s = pd.Series([1, 2, 3, 4, 5, 6])
1699
1700 >>> s
1701 0 1
1702 1 2
1703 2 3
1704 3 4
1705 4 5
1706 5 6
1707 dtype: int64
1708
1709 >>> s.groupby([1, 1, 1, 2, 2, 2]).nsmallest(n=2)
1710 1 0 1
1711 1 2
1712 2 3 4
1713 4 5
1714 dtype: int64
1715 """
1716 f = partial(Series.nsmallest, n=n, keep=keep)
1717 data = self._obj_with_exclusions
1718 # Don't change behavior if result index happens to be the same, i.e.
1719 # already ordered and n >= all group sizes.
1720 result = self._python_apply_general(f, data, not_indexed_same=True)
1721 return result
1722
1723 def idxmin(self, skipna: bool = True) -> Series:
1724 """
1725 Return the row label of the minimum value.
1726
1727 If multiple values equal the minimum, the first row label with that
1728 value is returned.
1729
1730 Parameters
1731 ----------
1732 skipna : bool, default True
1733 Exclude NA values.
1734
1735 Returns
1736 -------
1737 Series
1738 Indexes of minima in each group.
1739
1740 Raises
1741 ------
1742 ValueError
1743 When there are no valid values for a group. Then can happen if:
1744
1745 * There is an unobserved group and ``observed=False``.
1746 * All values for a group are NA.
1747 * Some values for a group are NA and ``skipna=False``.
1748
1749 .. versionchanged:: 3.0.0
1750 Previously if all values for a group are NA or some values for a group are
1751 NA and ``skipna=False``, this method would return NA. Now it raises instead.
1752
1753 See Also
1754 --------
1755 numpy.argmin : Return indices of the minimum values
1756 along the given axis.
1757 DataFrame.idxmin : Return index of first occurrence of minimum
1758 over requested axis.
1759 Series.idxmax : Return index *label* of the first occurrence
1760 of maximum of values.
1761
1762 Examples
1763 --------
1764 >>> ser = pd.Series(
1765 ... [1, 2, 3, 4],
1766 ... index=pd.DatetimeIndex(
1767 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1768 ... ),
1769 ... )
1770 >>> ser
1771 2023-01-01 1
1772 2023-01-15 2
1773 2023-02-01 3
1774 2023-02-15 4
1775 dtype: int64
1776
1777 >>> ser.groupby(["a", "a", "b", "b"]).idxmin()
1778 a 2023-01-01
1779 b 2023-02-01
1780 dtype: datetime64[us]
1781 """
1782 return self._idxmax_idxmin("idxmin", skipna=skipna)
1783
1784 def idxmax(self, skipna: bool = True) -> Series:
1785 """
1786 Return the row label of the maximum value.
1787
1788 If multiple values equal the maximum, the first row label with that
1789 value is returned.
1790
1791 Parameters
1792 ----------
1793 skipna : bool, default True
1794 Exclude NA values.
1795
1796 Returns
1797 -------
1798 Series
1799 Indexes of maxima in each group.
1800
1801 Raises
1802 ------
1803 ValueError
1804 When there are no valid values for a group. Then can happen if:
1805
1806 * There is an unobserved group and ``observed=False``.
1807 * All values for a group are NA.
1808 * Some values for a group are NA and ``skipna=False``.
1809
1810 .. versionchanged:: 3.0.0
1811 Previously if all values for a group are NA or some values for a group are
1812 NA and ``skipna=False``, this method would return NA. Now it raises instead.
1813
1814 See Also
1815 --------
1816 numpy.argmax : Return indices of the maximum values
1817 along the given axis.
1818 DataFrame.idxmax : Return index of first occurrence of maximum
1819 over requested axis.
1820 Series.idxmin : Return index *label* of the first occurrence
1821 of minimum of values.
1822
1823 Examples
1824 --------
1825 >>> ser = pd.Series(
1826 ... [1, 2, 3, 4],
1827 ... index=pd.DatetimeIndex(
1828 ... ["2023-01-01", "2023-01-15", "2023-02-01", "2023-02-15"]
1829 ... ),
1830 ... )
1831 >>> ser
1832 2023-01-01 1
1833 2023-01-15 2
1834 2023-02-01 3
1835 2023-02-15 4
1836 dtype: int64
1837
1838 >>> ser.groupby(["a", "a", "b", "b"]).idxmax()
1839 a 2023-01-15
1840 b 2023-02-15
1841 dtype: datetime64[us]
1842 """
1843 return self._idxmax_idxmin("idxmax", skipna=skipna)
1844
1845 def corr(
1846 self,
1847 other: Series,
1848 method: CorrelationMethod = "pearson",
1849 min_periods: int | None = None,
1850 ) -> Series:
1851 """
1852 Compute correlation between each group and another Series.
1853
1854 Parameters
1855 ----------
1856 other : Series
1857 Series to compute correlation with.
1858 method : {'pearson', 'kendall', 'spearman'}, default 'pearson'
1859 Method of correlation to use.
1860 min_periods : int, optional
1861 Minimum number of observations required per pair of columns to
1862 have a valid result.
1863
1864 Returns
1865 -------
1866 Series
1867 Correlation value for each group.
1868
1869 See Also
1870 --------
1871 Series.corr : Equivalent method on ``Series``.
1872
1873 Examples
1874 --------
1875 >>> s = pd.Series([1, 2, 3, 4], index=[0, 0, 1, 1])
1876 >>> g = s.groupby([0, 0, 1, 1])
1877 >>> g.corr() # doctest: +SKIP
1878 """
1879 result = self._op_via_apply(
1880 "corr", other=other, method=method, min_periods=min_periods
1881 )
1882 return result
1883
1884 def cov(
1885 self, other: Series, min_periods: int | None = None, ddof: int | None = 1
1886 ) -> Series:
1887 """
1888 Compute covariance between each group and another Series.
1889
1890 Parameters
1891 ----------
1892 other : Series
1893 Series to compute covariance with.
1894 min_periods : int, optional
1895 Minimum number of observations required per pair of columns to
1896 have a valid result.
1897 ddof : int, optional
1898 Delta degrees of freedom for variance calculation.
1899
1900 Returns
1901 -------
1902 Series
1903 Covariance value for each group.
1904
1905 See Also
1906 --------
1907 Series.cov : Equivalent method on ``Series``.
1908
1909 Examples
1910 --------
1911 >>> s = pd.Series([1, 2, 3, 4], index=[0, 0, 1, 1])
1912 >>> g = s.groupby([0, 0, 1, 1])
1913 >>> g.cov() # doctest: +SKIP
1914 """
1915 result = self._op_via_apply(
1916 "cov", other=other, min_periods=min_periods, ddof=ddof
1917 )
1918 return result
1919
1920 @property
1921 def is_monotonic_increasing(self) -> Series:
1922 """
1923 Return whether each group's values are monotonically increasing.
1924
1925 Returns
1926 -------
1927 Series
1928
1929 See Also
1930 --------
1931 SeriesGroupBy.is_monotonic_decreasing : Return whether each group's values
1932 are monotonically decreasing.
1933
1934 Examples
1935 --------
1936 >>> s = pd.Series([2, 1, 3, 4], index=["Falcon", "Falcon", "Parrot", "Parrot"])
1937 >>> s.groupby(level=0).is_monotonic_increasing
1938 Falcon False
1939 Parrot True
1940 dtype: bool
1941 """
1942 return self.apply(lambda ser: ser.is_monotonic_increasing)
1943
1944 @property
1945 def is_monotonic_decreasing(self) -> Series:
1946 """
1947 Return whether each group's values are monotonically decreasing.
1948
1949 Returns
1950 -------
1951 Series
1952
1953 See Also
1954 --------
1955 SeriesGroupBy.is_monotonic_increasing : Return whether each group's values
1956 are monotonically increasing.
1957
1958 Examples
1959 --------
1960 >>> s = pd.Series([2, 1, 3, 4], index=["Falcon", "Falcon", "Parrot", "Parrot"])
1961 >>> s.groupby(level=0).is_monotonic_decreasing
1962 Falcon True
1963 Parrot False
1964 dtype: bool
1965 """
1966 return self.apply(lambda ser: ser.is_monotonic_decreasing)
1967
1968 def hist(
1969 self,
1970 by=None,
1971 ax=None,
1972 grid: bool = True,
1973 xlabelsize: int | None = None,
1974 xrot: float | None = None,
1975 ylabelsize: int | None = None,
1976 yrot: float | None = None,
1977 figsize: tuple[float, float] | None = None,
1978 bins: int | Sequence[int] = 10,
1979 backend: str | None = None,
1980 legend: bool = False,
1981 **kwargs,
1982 ):
1983 """
1984 Draw histogram for each group's values using :meth:`Series.hist` API.
1985
1986 Parameters
1987 ----------
1988 by : object, optional
1989 Grouping key.
1990 ax : matplotlib.axes.Axes, optional
1991 Axis to draw the histogram on.
1992 grid : bool, default True
1993 Show axis grid lines.
1994 xlabelsize : int, default None
1995 X axis label size.
1996 xrot : float, default None
1997 Rotation for x ticks.
1998 ylabelsize : int, default None
1999 Y axis label size.
2000 yrot : float, default None
2001 Rotation for y ticks.
2002 figsize : tuple, optional
2003 Figure size in inches.
2004 bins : int or sequence, default 10
2005 Number of histogram bins or bin edges.
2006 backend : str or callable or None, optional
2007 Plotting backend to use (e.g. 'matplotlib'). If None, use the default
2008 plotting backend.
2009 legend : bool, default False
2010 Whether to draw the legend.
2011 **kwargs
2012 Additional keyword arguments passed to :meth:`Series.hist`.
2013
2014 Returns
2015 -------
2016 matplotlib.axes.Axes or ndarray of Axes
2017 The returned matplotlib axes or array of axes depending on input.
2018
2019 See Also
2020 --------
2021 Series.hist : Equivalent histogram plotting method on Series.
2022
2023 Examples
2024 --------
2025 >>> df = pd.DataFrame({"val": [1, 2, 2, 3, 3, 3]}, index=[0, 0, 1, 1, 2, 2])
2026 >>> g = df["val"].groupby([0, 0, 1, 1, 2, 2])
2027 >>> g.hist() # doctest: +SKIP
2028 """
2029 result = self._op_via_apply(
2030 "hist",
2031 by=by,
2032 ax=ax,
2033 grid=grid,
2034 xlabelsize=xlabelsize,
2035 xrot=xrot,
2036 ylabelsize=ylabelsize,
2037 yrot=yrot,
2038 figsize=figsize,
2039 bins=bins,
2040 backend=backend,
2041 legend=legend,
2042 **kwargs,
2043 )
2044 return result
2045
2046 @property
2047 def dtype(self) -> Series:
2048 """
2049 Return the dtype object of the underlying data for each group.
2050
2051 Mirrors :meth:`Series.dtype` applied group-wise.
2052
2053 Returns
2054 -------
2055 Series
2056 Dtype of each group's values.
2057 """
2058 return self.apply(lambda ser: ser.dtype)
2059
2060 def unique(self) -> Series:
2061 """
2062 Return unique values for each group.
2063
2064 It returns unique values for each of the grouped values. Returned in
2065 order of appearance. Hash table-based unique, therefore does NOT sort.
2066
2067 Returns
2068 -------
2069 Series
2070 Unique values for each of the grouped values.
2071
2072 See Also
2073 --------
2074 Series.unique : Return unique values of Series object.
2075
2076 Examples
2077 --------
2078 >>> df = pd.DataFrame(
2079 ... [
2080 ... ("Chihuahua", "dog", 6.1),
2081 ... ("Beagle", "dog", 15.2),
2082 ... ("Chihuahua", "dog", 6.9),
2083 ... ("Persian", "cat", 9.2),
2084 ... ("Chihuahua", "dog", 7),
2085 ... ("Persian", "cat", 8.8),
2086 ... ],
2087 ... columns=["breed", "animal", "height_in"],
2088 ... )
2089 >>> df
2090 breed animal height_in
2091 0 Chihuahua dog 6.1
2092 1 Beagle dog 15.2
2093 2 Chihuahua dog 6.9
2094 3 Persian cat 9.2
2095 4 Chihuahua dog 7.0
2096 5 Persian cat 8.8
2097 >>> ser = df.groupby("animal")["breed"].unique()
2098 >>> ser
2099 animal
2100 cat [Persian]
2101 dog [Chihuahua, Beagle]
2102 Name: breed, dtype: object
2103 """
2104 result = self._op_via_apply("unique")
2105 return result
2106
2107
2108@set_module("pandas.api.typing")
2109class DataFrameGroupBy(GroupBy[DataFrame]):
2110 def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs):
2111 """
2112 Aggregate using one or more operations.
2113
2114 The ``aggregate`` function allows the application of one or more aggregation
2115 operations on groups of data within a DataFrameGroupBy object. It supports
2116 various aggregation methods, including user-defined functions and predefined
2117 functions such as 'sum', 'mean', etc.
2118
2119 Parameters
2120 ----------
2121 func : function, str, list, dict or None
2122 Function to use for aggregating the data. If a function, must either
2123 work when passed a DataFrame or when passed to DataFrame.apply.
2124
2125 Accepted combinations are:
2126
2127 - function
2128 - string function name
2129 - list of functions and/or function names, e.g. ``[np.sum, 'mean']``
2130 - dict of index labels -> functions, function names or list of such.
2131 - None, in which case ``**kwargs`` are used with Named Aggregation. Here the
2132 output has one column for each element in ``**kwargs``. The name of the
2133 column is keyword, whereas the value determines the aggregation used to
2134 compute the values in the column.
2135
2136 Can also accept a Numba JIT function with
2137 ``engine='numba'`` specified. Only passing a single function is supported
2138 with this engine.
2139
2140 If the ``'numba'`` engine is chosen, the function must be
2141 a user defined function with ``values`` and ``index`` as the
2142 first and second arguments respectively in the function signature.
2143 Each group's index will be passed to the user defined function
2144 and optionally available for use.
2145
2146 *args
2147 Positional arguments to pass to func.
2148 engine : str, default None
2149 * ``'cython'`` : Runs the function through C-extensions from cython.
2150 * ``'numba'`` : Runs the function through JIT compiled code from numba.
2151 * ``None`` : Defaults to ``'cython'`` or globally setting
2152 ``compute.use_numba``
2153
2154 engine_kwargs : dict, default None
2155 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2156 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2157 and ``parallel`` dictionary keys. The values must either be ``True`` or
2158 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2159 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
2160 applied to the function
2161
2162 **kwargs
2163 * If ``func`` is None, ``**kwargs`` are used to define the output names and
2164 aggregations via Named Aggregation. See ``func`` entry.
2165 * Otherwise, keyword arguments to be passed into func.
2166
2167 Returns
2168 -------
2169 DataFrame
2170 Aggregated DataFrame based on the grouping and the applied aggregation
2171 functions.
2172
2173 See Also
2174 --------
2175 DataFrame.groupby.apply : Apply function func group-wise
2176 and combine the results together.
2177 DataFrame.groupby.transform : Transforms the Series on each group
2178 based on the given function.
2179 DataFrame.aggregate : Aggregate using one or more operations.
2180
2181 Notes
2182 -----
2183 When using ``engine='numba'``, there will be no "fall back" behavior internally.
2184 The group data and group index will be passed as numpy arrays to the JITed
2185 user defined function, and no alternative execution attempts will be tried.
2186
2187 Functions that mutate the passed object can produce unexpected
2188 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
2189 for more details.
2190
2191 The resulting dtype will reflect the return value of the passed ``func``,
2192 see the examples below.
2193
2194 Examples
2195 --------
2196 >>> data = {
2197 ... "A": [1, 1, 2, 2],
2198 ... "B": [1, 2, 3, 4],
2199 ... "C": [0.362838, 0.227877, 1.267767, -0.562860],
2200 ... }
2201 >>> df = pd.DataFrame(data)
2202 >>> df
2203 A B C
2204 0 1 1 0.362838
2205 1 1 2 0.227877
2206 2 2 3 1.267767
2207 3 2 4 -0.562860
2208
2209 The aggregation is for each column.
2210
2211 >>> df.groupby("A").agg("min")
2212 B C
2213 A
2214 1 1 0.227877
2215 2 3 -0.562860
2216
2217 Multiple aggregations
2218
2219 >>> df.groupby("A").agg(["min", "max"])
2220 B C
2221 min max min max
2222 A
2223 1 1 2 0.227877 0.362838
2224 2 3 4 -0.562860 1.267767
2225
2226 Select a column for aggregation
2227
2228 >>> df.groupby("A").B.agg(["min", "max"])
2229 min max
2230 A
2231 1 1 2
2232 2 3 4
2233
2234 User-defined function for aggregation
2235
2236 >>> df.groupby("A").agg(lambda x: sum(x) + 2)
2237 B C
2238 A
2239 1 5 2.590715
2240 2 9 2.704907
2241
2242 Different aggregations per column
2243
2244 >>> df.groupby("A").agg({"B": ["min", "max"], "C": "sum"})
2245 B C
2246 min max sum
2247 A
2248 1 1 2 0.590715
2249 2 3 4 0.704907
2250
2251 To control the output names with different aggregations per column,
2252 pandas supports "named aggregation"
2253
2254 >>> df.groupby("A").agg(
2255 ... b_min=pd.NamedAgg(column="B", aggfunc="min"),
2256 ... c_sum=pd.NamedAgg(column="C", aggfunc="sum"),
2257 ... )
2258 b_min c_sum
2259 A
2260 1 1 0.590715
2261 2 3 0.704907
2262
2263 - The keywords are the *output* column names
2264 - The values are tuples whose first element is the column to select
2265 and the second element is the aggregation to apply to that column.
2266 Pandas provides the ``pandas.NamedAgg`` namedtuple with the fields
2267 ``['column', 'aggfunc']`` to make it clearer what the arguments are.
2268 As usual, the aggregation can be a callable or a string alias.
2269
2270 See :ref:`groupby.aggregate.named` for more.
2271
2272 The resulting dtype will reflect the return value of the aggregating
2273 function.
2274
2275 >>> df.groupby("A")[["B"]].agg(lambda x: x.astype(float).min())
2276 B
2277 A
2278 1 1.0
2279 2 3.0
2280 """
2281 relabeling, func, columns, order = reconstruct_func(func, **kwargs)
2282 func = maybe_mangle_lambdas(func)
2283
2284 if maybe_use_numba(engine):
2285 # Not all agg functions support numba, only propagate numba kwargs
2286 # if user asks for numba
2287 kwargs["engine"] = engine
2288 kwargs["engine_kwargs"] = engine_kwargs
2289
2290 op = GroupByApply(self, func, args=args, kwargs=kwargs)
2291 result = op.agg()
2292 if not is_dict_like(func) and result is not None:
2293 # GH #52849
2294 if not self.as_index and is_list_like(func):
2295 return result.reset_index()
2296 else:
2297 return result
2298 elif relabeling:
2299 # this should be the only (non-raising) case with relabeling
2300 # used reordered index of columns
2301 result = cast(DataFrame, result)
2302 result = result.iloc[:, order]
2303 result = cast(DataFrame, result)
2304 # error: Incompatible types in assignment (expression has type
2305 # "Optional[List[str]]", variable has type
2306 # "Union[Union[Union[ExtensionArray, ndarray[Any, Any]],
2307 # Index, Series], Sequence[Any]]")
2308 result.columns = columns # type: ignore[assignment]
2309
2310 if result is None:
2311 # Remove the kwargs we inserted
2312 # (already stored in engine, engine_kwargs arguments)
2313 if "engine" in kwargs:
2314 del kwargs["engine"]
2315 del kwargs["engine_kwargs"]
2316 # at this point func is not a str, list-like, dict-like,
2317 # or a known callable(e.g. sum)
2318 if maybe_use_numba(engine):
2319 return self._aggregate_with_numba(
2320 func, *args, engine_kwargs=engine_kwargs, **kwargs
2321 )
2322 # grouper specific aggregations
2323 if self._grouper.nkeys > 1:
2324 # test_groupby_as_index_series_scalar gets here with 'not self.as_index'
2325 return self._python_agg_general(func, *args, **kwargs)
2326 elif args or kwargs:
2327 # test_pass_args_kwargs gets here (with and without as_index)
2328 # can't return early
2329 result = self._aggregate_frame(func, *args, **kwargs)
2330
2331 else:
2332 # try to treat as if we are passing a list
2333 gba = GroupByApply(self, [func], args=(), kwargs={})
2334 try:
2335 result = gba.agg()
2336
2337 except ValueError as err:
2338 if "No objects to concatenate" not in str(err):
2339 raise
2340 # _aggregate_frame can fail with e.g. func=Series.mode,
2341 # where it expects 1D values but would be getting 2D values
2342 # In other tests, using aggregate_frame instead of GroupByApply
2343 # would give correct values but incorrect dtypes
2344 # object vs float64 in test_cython_agg_empty_buckets
2345 # float64 vs int64 in test_category_order_apply
2346 result = self._aggregate_frame(func)
2347
2348 else:
2349 # GH#32040, GH#35246
2350 # e.g. test_groupby_as_index_select_column_sum_empty_df
2351 result = cast(DataFrame, result)
2352 result.columns = self._obj_with_exclusions.columns.copy()
2353
2354 if not self.as_index:
2355 result = self._insert_inaxis_grouper(result)
2356 result.index = default_index(len(result))
2357
2358 return result
2359
2360 agg = aggregate
2361
2362 def _python_agg_general(self, func, *args, **kwargs):
2363 f = lambda x: func(x, *args, **kwargs)
2364
2365 if self.ngroups == 0:
2366 # e.g. test_evaluate_with_empty_groups different path gets different
2367 # result dtype in empty case.
2368 return self._python_apply_general(f, self._selected_obj, is_agg=True)
2369
2370 obj = self._obj_with_exclusions
2371
2372 if not len(obj.columns):
2373 # e.g. test_margins_no_values_no_cols
2374 return self._python_apply_general(f, self._selected_obj)
2375
2376 output: dict[int, ArrayLike] = {}
2377 for idx, (name, ser) in enumerate(obj.items()):
2378 result = self._grouper.agg_series(ser, f)
2379 output[idx] = result
2380
2381 res = self.obj._constructor(output)
2382 res.columns = obj.columns.copy(deep=False)
2383 return self._wrap_aggregated_output(res)
2384
2385 def _aggregate_frame(self, func, *args, **kwargs) -> DataFrame:
2386 if self._grouper.nkeys != 1:
2387 raise AssertionError("Number of keys must be 1")
2388
2389 obj = self._obj_with_exclusions
2390
2391 result: dict[Hashable, NDFrame | np.ndarray] = {}
2392 for name, grp_df in self._grouper.get_iterator(obj):
2393 fres = func(grp_df, *args, **kwargs)
2394 result[name] = fres
2395
2396 result_index = self._grouper.result_index
2397 out = self.obj._constructor(result, index=obj.columns, columns=result_index)
2398 out = out.T
2399
2400 return out
2401
2402 def _wrap_applied_output(
2403 self,
2404 data: DataFrame,
2405 values: list,
2406 not_indexed_same: bool = False,
2407 is_transform: bool = False,
2408 ):
2409 if len(values) == 0:
2410 if is_transform:
2411 # GH#47787 see test_group_on_empty_multiindex
2412 res_index = data.index
2413 elif not self.group_keys:
2414 res_index = None
2415 else:
2416 res_index = self._grouper.result_index
2417
2418 result = self.obj._constructor(index=res_index, columns=data.columns)
2419 result = result.astype(data.dtypes)
2420 return result
2421
2422 # GH12824
2423 # using values[0] here breaks test_groupby_apply_none_first
2424 first_not_none = next(com.not_none(*values), None)
2425
2426 if first_not_none is None:
2427 # GH9684 - All values are None, return an empty frame
2428 # GH57775 - Ensure that columns and dtypes from original frame are kept.
2429 result = self.obj._constructor(columns=data.columns)
2430 result = result.astype(data.dtypes)
2431 return result
2432 elif isinstance(first_not_none, DataFrame):
2433 return self._concat_objects(
2434 values,
2435 not_indexed_same=not_indexed_same,
2436 is_transform=is_transform,
2437 )
2438
2439 key_index = self._grouper.result_index if self.as_index else None
2440
2441 if isinstance(first_not_none, (np.ndarray, Index)):
2442 # GH#1738: values is list of arrays of unequal lengths
2443 # fall through to the outer else clause
2444 # TODO: sure this is right? we used to do this
2445 # after raising AttributeError above
2446 # GH 18930
2447 if not is_hashable(self._selection):
2448 # error: Need type annotation for "name"
2449 name = tuple(self._selection) # type: ignore[var-annotated, arg-type]
2450 else:
2451 # error: Incompatible types in assignment
2452 # (expression has type "Hashable", variable
2453 # has type "Tuple[Any, ...]")
2454 name = self._selection # type: ignore[assignment]
2455 return self.obj._constructor_sliced(values, index=key_index, name=name)
2456 elif not isinstance(first_not_none, Series):
2457 # values are not series or array-like but scalars
2458 # self._selection not passed through to Series as the
2459 # result should not take the name of original selection
2460 # of columns
2461 if self.as_index:
2462 return self.obj._constructor_sliced(values, index=key_index)
2463 else:
2464 result = self.obj._constructor(values, columns=[self._selection])
2465 result = self._insert_inaxis_grouper(result)
2466 return result
2467 else:
2468 # values are Series
2469 return self._wrap_applied_output_series(
2470 values,
2471 not_indexed_same,
2472 first_not_none,
2473 key_index,
2474 is_transform,
2475 )
2476
2477 def _wrap_applied_output_series(
2478 self,
2479 values: list[Series],
2480 not_indexed_same: bool,
2481 first_not_none,
2482 key_index: Index | None,
2483 is_transform: bool,
2484 ) -> DataFrame | Series:
2485 kwargs = first_not_none._construct_axes_dict()
2486 backup = Series(**kwargs)
2487 values = [x if (x is not None) else backup for x in values]
2488
2489 all_indexed_same = all_indexes_same(x.index for x in values)
2490
2491 if not all_indexed_same:
2492 # GH 8467
2493 return self._concat_objects(
2494 values,
2495 not_indexed_same=True,
2496 is_transform=is_transform,
2497 )
2498
2499 # Combine values
2500 # vstack+constructor is faster than concat and handles MI-columns
2501 stacked_values = np.vstack([np.asarray(v) for v in values])
2502
2503 index = key_index
2504 columns = first_not_none.index.copy()
2505 if columns.name is None:
2506 # GH6124 - propagate name of Series when it's consistent
2507 names = {v.name for v in values}
2508 if len(names) == 1:
2509 columns.name = next(iter(names))
2510
2511 if stacked_values.dtype == object:
2512 # We'll have the DataFrame constructor do inference
2513 stacked_values = stacked_values.tolist()
2514 result = self.obj._constructor(stacked_values, index=index, columns=columns)
2515
2516 if not self.as_index:
2517 result = self._insert_inaxis_grouper(result)
2518
2519 return result.__finalize__(self.obj, method="groupby")
2520
2521 def _cython_transform(
2522 self,
2523 how: str,
2524 numeric_only: bool = False,
2525 **kwargs,
2526 ) -> DataFrame:
2527 # We have multi-block tests
2528 # e.g. test_rank_min_int, test_cython_transform_frame
2529 # test_transform_numeric_ret
2530 mgr: BlockManager = self._get_data_to_aggregate(
2531 numeric_only=numeric_only, name=how
2532 )
2533
2534 def arr_func(bvalues: ArrayLike) -> ArrayLike:
2535 return self._grouper._cython_operation(
2536 "transform", bvalues, how, 1, **kwargs
2537 )
2538
2539 res_mgr = mgr.apply(arr_func)
2540
2541 res_df = self.obj._constructor_from_mgr(res_mgr, axes=res_mgr.axes)
2542 return res_df
2543
2544 def _transform_general(self, func, engine, engine_kwargs, *args, **kwargs):
2545 if maybe_use_numba(engine):
2546 return self._transform_with_numba(
2547 func, *args, engine_kwargs=engine_kwargs, **kwargs
2548 )
2549 from pandas.core.reshape.concat import concat
2550
2551 applied = []
2552 obj = self._obj_with_exclusions
2553 gen = self._grouper.get_iterator(obj)
2554 fast_path, slow_path = self._define_paths(func, *args, **kwargs)
2555
2556 # Determine whether to use slow or fast path by evaluating on the first group.
2557 # Need to handle the case of an empty generator and process the result so that
2558 # it does not need to be computed again.
2559 try:
2560 name, group = next(gen)
2561 except StopIteration:
2562 pass
2563 else:
2564 # 2023-02-27 No tests broken by disabling this pinning
2565 object.__setattr__(group, "name", name)
2566 try:
2567 path, res = self._choose_path(fast_path, slow_path, group)
2568 except ValueError as err:
2569 # e.g. test_transform_with_non_scalar_group
2570 msg = "transform must return a scalar value for each group"
2571 raise ValueError(msg) from err
2572 if group.size > 0:
2573 res = _wrap_transform_general_frame(self.obj, group, res)
2574 applied.append(res)
2575
2576 # Compute and process with the remaining groups
2577 for name, group in gen:
2578 if group.size == 0:
2579 continue
2580 # 2023-02-27 No tests broken by disabling this pinning
2581 object.__setattr__(group, "name", name)
2582 res = path(group)
2583
2584 res = _wrap_transform_general_frame(self.obj, group, res)
2585 applied.append(res)
2586
2587 concat_index = obj.columns
2588 concatenated = concat(
2589 applied, axis=0, verify_integrity=False, ignore_index=True
2590 )
2591 concatenated = concatenated.reindex(concat_index, axis=1)
2592 return self._set_result_index_ordered(concatenated)
2593
2594 __examples_dataframe_doc = dedent(
2595 """
2596 >>> df = pd.DataFrame({'A' : ['foo', 'bar', 'foo', 'bar',
2597 ... 'foo', 'bar'],
2598 ... 'B' : ['one', 'one', 'two', 'three',
2599 ... 'two', 'two'],
2600 ... 'C' : [1, 5, 5, 2, 5, 5],
2601 ... 'D' : [2.0, 5., 8., 1., 2., 9.]})
2602 >>> grouped = df.groupby('A')[['C', 'D']]
2603 >>> grouped.transform(lambda x: (x - x.mean()) / x.std())
2604 C D
2605 0 -1.154701 -0.577350
2606 1 0.577350 0.000000
2607 2 0.577350 1.154701
2608 3 -1.154701 -1.000000
2609 4 0.577350 -0.577350
2610 5 0.577350 1.000000
2611
2612 Broadcast result of the transformation
2613
2614 >>> grouped.transform(lambda x: x.max() - x.min())
2615 C D
2616 0 4.0 6.0
2617 1 3.0 8.0
2618 2 4.0 6.0
2619 3 3.0 8.0
2620 4 4.0 6.0
2621 5 3.0 8.0
2622
2623 >>> grouped.transform("mean")
2624 C D
2625 0 3.666667 4.0
2626 1 4.000000 5.0
2627 2 3.666667 4.0
2628 3 4.000000 5.0
2629 4 3.666667 4.0
2630 5 4.000000 5.0
2631
2632 The resulting dtype will reflect the return value of the passed ``func``,
2633 for example:
2634
2635 >>> grouped.transform(lambda x: x.astype(int).max())
2636 C D
2637 0 5 8
2638 1 5 9
2639 2 5 8
2640 3 5 9
2641 4 5 8
2642 5 5 9
2643 """
2644 )
2645
2646 def transform(self, func, *args, engine=None, engine_kwargs=None, **kwargs):
2647 """
2648 Call function producing a same-indexed DataFrame on each group.
2649
2650 Returns a DataFrame having the same indexes as the original object
2651 filled with the transformed values.
2652
2653 Parameters
2654 ----------
2655 func : function, str
2656 Function to apply to each group.
2657 See the Notes section below for requirements.
2658
2659 Accepted inputs are:
2660
2661 - String
2662 - Python function
2663 - Numba JIT function with ``engine='numba'`` specified.
2664
2665 Only passing a single function is supported with this engine.
2666 If the ``'numba'`` engine is chosen, the function must be
2667 a user defined function with ``values`` and ``index`` as the
2668 first and second arguments respectively in the function signature.
2669 Each group's index will be passed to the user defined function
2670 and optionally available for use.
2671
2672 If a string is chosen, then it needs to be the name
2673 of the groupby method you want to use.
2674 *args
2675 Positional arguments to pass to func.
2676 engine : str, default None
2677 * ``'cython'`` : Runs the function through C-extensions from cython.
2678 * ``'numba'`` : Runs the function through JIT compiled code from numba.
2679 * ``None`` : Defaults to ``'cython'``
2680 or the global setting ``compute.use_numba``
2681
2682 engine_kwargs : dict, default None
2683 * For ``'cython'`` engine, there are no accepted ``engine_kwargs``
2684 * For ``'numba'`` engine, the engine can accept ``nopython``, ``nogil``
2685 and ``parallel`` dictionary keys. The values must either be ``True`` or
2686 ``False``. The default ``engine_kwargs`` for the ``'numba'`` engine is
2687 ``{'nopython': True, 'nogil': False, 'parallel': False}`` and will be
2688 applied to the function
2689
2690 **kwargs
2691 Keyword arguments to be passed into func.
2692
2693 Returns
2694 -------
2695 DataFrame
2696 DataFrame with the same indexes as the original object filled
2697 with transformed values.
2698
2699 See Also
2700 --------
2701 DataFrame.groupby.apply : Apply function ``func`` group-wise and combine
2702 the results together.
2703 DataFrame.groupby.aggregate : Aggregate using one or more operations.
2704 DataFrame.transform : Call ``func`` on self producing a DataFrame with the
2705 same axis shape as self.
2706
2707 Notes
2708 -----
2709 Each group is endowed the attribute 'name' in case you need to know
2710 which group you are working on.
2711
2712 The current implementation imposes three requirements on f:
2713
2714 * f must return a value that either has the same shape as the input
2715 subframe or can be broadcast to the shape of the input subframe.
2716 For example, if `f` returns a scalar it will be broadcast to have the
2717 same shape as the input subframe.
2718 * if this is a DataFrame, f must support application column-by-column
2719 in the subframe. If f also supports application to the entire subframe,
2720 then a fast path is used starting from the second chunk.
2721 * f must not mutate groups. Mutation is not supported and may
2722 produce unexpected results. See :ref:`gotchas.udf-mutation` for more details.
2723
2724 When using ``engine='numba'``, there will be no "fall back" behavior internally.
2725 The group data and group index will be passed as numpy arrays to the JITed
2726 user defined function, and no alternative execution attempts will be tried.
2727
2728 The resulting dtype will reflect the return value of the passed ``func``,
2729 see the examples below.
2730
2731 .. versionchanged:: 2.0.0
2732
2733 When using ``.transform`` on a grouped DataFrame
2734 and the transformation function returns a DataFrame,
2735 pandas now aligns the result's index with the input's index.
2736 You can call ``.to_numpy()`` on the result of the
2737 transformation function to avoid alignment.
2738
2739 Examples
2740 --------
2741
2742 >>> df = pd.DataFrame(
2743 ... {
2744 ... "A": ["foo", "bar", "foo", "bar", "foo", "bar"],
2745 ... "B": ["one", "one", "two", "three", "two", "two"],
2746 ... "C": [1, 5, 5, 2, 5, 5],
2747 ... "D": [2.0, 5.0, 8.0, 1.0, 2.0, 9.0],
2748 ... }
2749 ... )
2750 >>> grouped = df.groupby("A")[["C", "D"]]
2751 >>> grouped.transform(lambda x: (x - x.mean()) / x.std())
2752 C D
2753 0 -1.154701 -0.577350
2754 1 0.577350 0.000000
2755 2 0.577350 1.154701
2756 3 -1.154701 -1.000000
2757 4 0.577350 -0.577350
2758 5 0.577350 1.000000
2759
2760 Broadcast result of the transformation
2761
2762 >>> grouped.transform(lambda x: x.max() - x.min())
2763 C D
2764 0 4.0 6.0
2765 1 3.0 8.0
2766 2 4.0 6.0
2767 3 3.0 8.0
2768 4 4.0 6.0
2769 5 3.0 8.0
2770
2771 >>> grouped.transform("mean")
2772 C D
2773 0 3.666667 4.0
2774 1 4.000000 5.0
2775 2 3.666667 4.0
2776 3 4.000000 5.0
2777 4 3.666667 4.0
2778 5 4.000000 5.0
2779
2780 The resulting dtype will reflect the return value of the passed ``func``,
2781 for example:
2782
2783 >>> grouped.transform(lambda x: x.astype(int).max())
2784 C D
2785 0 5 8
2786 1 5 9
2787 2 5 8
2788 3 5 9
2789 4 5 8
2790 5 5 9
2791 """
2792 return self._transform(
2793 func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
2794 )
2795
2796 def _define_paths(self, func, *args, **kwargs):
2797 if isinstance(func, str):
2798 fast_path = lambda group: getattr(group, func)(*args, **kwargs)
2799 slow_path = lambda group: group.apply(
2800 lambda x: getattr(x, func)(*args, **kwargs), axis=0
2801 )
2802 else:
2803 fast_path = lambda group: func(group, *args, **kwargs)
2804 slow_path = lambda group: group.apply(
2805 lambda x: func(x, *args, **kwargs), axis=0
2806 )
2807 return fast_path, slow_path
2808
2809 def _choose_path(self, fast_path: Callable, slow_path: Callable, group: DataFrame):
2810 path = slow_path
2811 res = slow_path(group)
2812
2813 if self.ngroups == 1:
2814 # no need to evaluate multiple paths when only
2815 # a single group exists
2816 return path, res
2817
2818 # if we make it here, test if we can use the fast path
2819 try:
2820 res_fast = fast_path(group)
2821 except AssertionError:
2822 raise # pragma: no cover
2823 except Exception:
2824 # GH#29631 For user-defined function, we can't predict what may be
2825 # raised; see test_transform.test_transform_fastpath_raises
2826 return path, res
2827
2828 # verify fast path returns either:
2829 # a DataFrame with columns equal to group.columns
2830 # OR a Series with index equal to group.columns
2831 if isinstance(res_fast, DataFrame):
2832 if not res_fast.columns.equals(group.columns):
2833 return path, res
2834 elif isinstance(res_fast, Series):
2835 if not res_fast.index.equals(group.columns):
2836 return path, res
2837 else:
2838 return path, res
2839
2840 if res_fast.equals(res):
2841 path = fast_path
2842
2843 return path, res
2844
2845 def filter(self, func, dropna: bool = True, *args, **kwargs) -> DataFrame:
2846 """
2847 Filter elements from groups that don't satisfy a criterion.
2848
2849 Elements from groups are filtered if they do not satisfy the
2850 boolean criterion specified by func.
2851
2852 Parameters
2853 ----------
2854 func : function
2855 Criterion to apply to each group. Should return True or False.
2856 dropna : bool
2857 Drop groups that do not pass the filter. True by default; if False,
2858 groups that evaluate False are filled with NaNs.
2859 *args : tuple
2860 Additional positional arguments to pass to `func`.
2861 **kwargs : dict
2862 Additional keyword arguments to pass to `func`.
2863
2864 Returns
2865 -------
2866 DataFrame
2867 The filtered subset of the original DataFrame.
2868
2869 See Also
2870 --------
2871 DataFrame.filter: Filter elements of ungrouped DataFrame.
2872 SeriesGroupBy.filter : Filter elements from groups base on criterion.
2873
2874 Notes
2875 -----
2876 Each subframe is endowed the attribute 'name' in case you need to know
2877 which group you are working on.
2878
2879 Functions that mutate the passed object can produce unexpected
2880 behavior or errors and are not supported. See :ref:`gotchas.udf-mutation`
2881 for more details.
2882
2883 Examples
2884 --------
2885 >>> df = pd.DataFrame(
2886 ... {
2887 ... "A": ["foo", "bar", "foo", "bar", "foo", "bar"],
2888 ... "B": [1, 2, 3, 4, 5, 6],
2889 ... "C": [2.0, 5.0, 8.0, 1.0, 2.0, 9.0],
2890 ... }
2891 ... )
2892 >>> grouped = df.groupby("A")
2893 >>> grouped.filter(lambda x: x["B"].mean() > 3.0)
2894 A B C
2895 1 bar 2 5.0
2896 3 bar 4 1.0
2897 5 bar 6 9.0
2898 """
2899 indices = []
2900
2901 obj = self._selected_obj
2902 gen = self._grouper.get_iterator(obj)
2903
2904 for name, group in gen:
2905 # 2023-02-27 no tests are broken this pinning, but it is documented in the
2906 # docstring above.
2907 object.__setattr__(group, "name", name)
2908
2909 res = func(group, *args, **kwargs)
2910
2911 try:
2912 res = res.squeeze()
2913 except AttributeError: # allow e.g., scalars and frames to pass
2914 pass
2915
2916 # interpret the result of the filter
2917 if is_bool(res) or (is_scalar(res) and isna(res)):
2918 if notna(res) and res:
2919 indices.append(self._get_index(name))
2920 else:
2921 # non scalars aren't allowed
2922 raise TypeError(
2923 f"filter function returned a {type(res).__name__}, "
2924 "but expected a scalar bool"
2925 )
2926
2927 return self._apply_filter(indices, dropna)
2928
2929 def __getitem__(self, key) -> DataFrameGroupBy | SeriesGroupBy:
2930 # per GH 23566
2931 if isinstance(key, tuple) and len(key) > 1:
2932 # if len == 1, then it becomes a SeriesGroupBy and this is actually
2933 # valid syntax, so don't raise
2934 raise ValueError(
2935 "Cannot subset columns with a tuple with more than one element. "
2936 "Use a list instead."
2937 )
2938 return super().__getitem__(key)
2939
2940 def _gotitem(self, key, ndim: int, subset=None):
2941 """
2942 sub-classes to define
2943 return a sliced object
2944
2945 Parameters
2946 ----------
2947 key : string / list of selections
2948 ndim : {1, 2}
2949 requested ndim of result
2950 subset : object, default None
2951 subset to act on
2952 """
2953 if ndim == 2:
2954 if subset is None:
2955 subset = self.obj
2956 return DataFrameGroupBy(
2957 subset,
2958 self.keys,
2959 level=self.level,
2960 grouper=self._grouper,
2961 exclusions=self.exclusions,
2962 selection=key,
2963 as_index=self.as_index,
2964 sort=self.sort,
2965 group_keys=self.group_keys,
2966 observed=self.observed,
2967 dropna=self.dropna,
2968 )
2969 elif ndim == 1:
2970 if subset is None:
2971 subset = self.obj[key]
2972 return SeriesGroupBy(
2973 subset,
2974 self.keys,
2975 level=self.level,
2976 grouper=self._grouper,
2977 exclusions=self.exclusions,
2978 selection=key,
2979 as_index=self.as_index,
2980 sort=self.sort,
2981 group_keys=self.group_keys,
2982 observed=self.observed,
2983 dropna=self.dropna,
2984 )
2985
2986 raise AssertionError("invalid ndim for _gotitem")
2987
2988 def _get_data_to_aggregate(
2989 self, *, numeric_only: bool = False, name: str | None = None
2990 ) -> BlockManager:
2991 obj = self._obj_with_exclusions
2992 mgr = obj._mgr
2993 if numeric_only:
2994 mgr = mgr.get_numeric_data()
2995 return mgr
2996
2997 def _wrap_agged_manager(self, mgr: BlockManager) -> DataFrame:
2998 return self.obj._constructor_from_mgr(mgr, axes=mgr.axes)
2999
3000 def _apply_to_column_groupbys(self, func) -> DataFrame:
3001 from pandas.core.reshape.concat import concat
3002
3003 obj = self._obj_with_exclusions
3004 columns = obj.columns
3005 sgbs = (
3006 SeriesGroupBy(
3007 obj.iloc[:, i],
3008 selection=colname,
3009 grouper=self._grouper,
3010 exclusions=self.exclusions,
3011 observed=self.observed,
3012 )
3013 for i, colname in enumerate(obj.columns)
3014 )
3015 results = [func(sgb) for sgb in sgbs]
3016
3017 if not results:
3018 # concat would raise
3019 res_df = DataFrame([], columns=columns, index=self._grouper.result_index)
3020 else:
3021 res_df = concat(results, keys=columns, axis=1)
3022
3023 if not self.as_index:
3024 res_df.index = default_index(len(res_df))
3025 res_df = self._insert_inaxis_grouper(res_df)
3026 return res_df
3027
3028 def nunique(self, dropna: bool = True) -> DataFrame:
3029 """
3030 Return DataFrame with counts of unique elements in each position.
3031
3032 Parameters
3033 ----------
3034 dropna : bool, default True
3035 Don't include NaN in the counts.
3036
3037 Returns
3038 -------
3039 nunique: DataFrame
3040 Counts of unique elements in each position.
3041
3042 See Also
3043 --------
3044 DataFrame.nunique : Count number of distinct elements in specified axis.
3045
3046 Examples
3047 --------
3048 >>> df = pd.DataFrame(
3049 ... {
3050 ... "id": ["spam", "egg", "egg", "spam", "ham", "ham"],
3051 ... "value1": [1, 5, 5, 2, 5, 5],
3052 ... "value2": list("abbaxy"),
3053 ... }
3054 ... )
3055 >>> df
3056 id value1 value2
3057 0 spam 1 a
3058 1 egg 5 b
3059 2 egg 5 b
3060 3 spam 2 a
3061 4 ham 5 x
3062 5 ham 5 y
3063
3064 >>> df.groupby("id").nunique()
3065 value1 value2
3066 id
3067 egg 1 1
3068 ham 1 2
3069 spam 2 1
3070
3071 Check for rows with the same id but conflicting values:
3072
3073 >>> df.groupby("id").filter(lambda g: (g.nunique() > 1).any())
3074 id value1 value2
3075 0 spam 1 a
3076 3 spam 2 a
3077 4 ham 5 x
3078 5 ham 5 y
3079 """
3080 return self._apply_to_column_groupbys(lambda sgb: sgb.nunique(dropna))
3081
3082 def idxmax(
3083 self,
3084 skipna: bool = True,
3085 numeric_only: bool = False,
3086 ) -> DataFrame:
3087 """
3088 Return index of first occurrence of maximum in each group.
3089
3090 Parameters
3091 ----------
3092 skipna : bool, default True
3093 Exclude NA values.
3094 numeric_only : bool, default False
3095 Include only `float`, `int` or `boolean` data.
3096
3097 Returns
3098 -------
3099 DataFrame
3100 Indexes of maxima in each column according to the group.
3101
3102 Raises
3103 ------
3104 ValueError
3105 When there are no valid values for a group. Then can happen if:
3106
3107 * There is an unobserved group and ``observed=False``.
3108 * All values for a group are NA.
3109 * Some values for a group are NA and ``skipna=False``.
3110
3111 .. versionchanged:: 3.0.0
3112 Previously if all values for a group are NA or some values for a group are
3113 NA and ``skipna=False``, this method would return NA. Now it raises instead.
3114
3115 See Also
3116 --------
3117 Series.idxmax : Return index of the maximum element.
3118 DataFrame.idxmax : Indexes of maxima along the specified axis.
3119
3120 Notes
3121 -----
3122 This method is the DataFrame version of ``ndarray.argmax``.
3123
3124 Examples
3125 --------
3126 Consider a dataset containing food consumption in Argentina.
3127
3128 >>> df = pd.DataFrame(
3129 ... {
3130 ... "consumption": [10.51, 103.11, 55.48],
3131 ... "co2_emissions": [37.2, 19.66, 1712],
3132 ... "food_type": ["meat", "plant", "meat"],
3133 ... },
3134 ... index=["Pork", "Wheat Products", "Beef"],
3135 ... )
3136
3137 >>> df
3138 consumption co2_emissions food_type
3139 Pork 10.51 37.20 meat
3140 Wheat Products 103.11 19.66 plant
3141 Beef 55.48 1712.00 meat
3142
3143 By default, it returns the index for the maximum value in each column
3144 according to the group.
3145
3146 >>> df.groupby("food_type").idxmax()
3147 consumption co2_emissions
3148 food_type
3149 meat Beef Beef
3150 plant Wheat Products Wheat Products
3151 """
3152 return self._idxmax_idxmin("idxmax", numeric_only=numeric_only, skipna=skipna)
3153
3154 def idxmin(
3155 self,
3156 skipna: bool = True,
3157 numeric_only: bool = False,
3158 ) -> DataFrame:
3159 """
3160 Return index of first occurrence of minimum in each group.
3161
3162 Parameters
3163 ----------
3164 skipna : bool, default True
3165 Exclude NA values.
3166 numeric_only : bool, default False
3167 Include only `float`, `int` or `boolean` data.
3168
3169 Returns
3170 -------
3171 DataFrame
3172 Indexes of minima in each column according to the group.
3173
3174 Raises
3175 ------
3176 ValueError
3177 When there are no valid values for a group. Then can happen if:
3178
3179 * There is an unobserved group and ``observed=False``.
3180 * All values for a group are NA.
3181 * Some values for a group are NA and ``skipna=False``.
3182
3183 .. versionchanged:: 3.0.0
3184 Previously if all values for a group are NA or some values for a group are
3185 NA and ``skipna=False``, this method would return NA. Now it raises instead.
3186
3187 See Also
3188 --------
3189 Series.idxmin : Return index of the minimum element.
3190 DataFrame.idxmin : Indexes of minima along the specified axis.
3191
3192 Notes
3193 -----
3194 This method is the DataFrame version of ``ndarray.argmin``.
3195
3196 Examples
3197 --------
3198 Consider a dataset containing food consumption in Argentina.
3199
3200 >>> df = pd.DataFrame(
3201 ... {
3202 ... "consumption": [10.51, 103.11, 55.48],
3203 ... "co2_emissions": [37.2, 19.66, 1712],
3204 ... "food_type": ["meat", "plant", "meat"],
3205 ... },
3206 ... index=["Pork", "Wheat Products", "Beef"],
3207 ... )
3208
3209 >>> df
3210 consumption co2_emissions food_type
3211 Pork 10.51 37.20 meat
3212 Wheat Products 103.11 19.66 plant
3213 Beef 55.48 1712.00 meat
3214
3215 By default, it returns the index for the minimum value in each column
3216 according to the group.
3217
3218 >>> df.groupby("food_type").idxmin()
3219 consumption co2_emissions
3220 food_type
3221 meat Pork Pork
3222 plant Wheat Products Wheat Products
3223 """
3224 return self._idxmax_idxmin("idxmin", numeric_only=numeric_only, skipna=skipna)
3225
3226 boxplot = boxplot_frame_groupby
3227
3228 def value_counts(
3229 self,
3230 subset: Sequence[Hashable] | None = None,
3231 normalize: bool = False,
3232 sort: bool = True,
3233 ascending: bool = False,
3234 dropna: bool = True,
3235 ) -> DataFrame | Series:
3236 """
3237 Return a Series or DataFrame containing counts of unique rows.
3238
3239 Parameters
3240 ----------
3241 subset : list-like, optional
3242 Columns to use when counting unique combinations.
3243 normalize : bool, default False
3244 Return proportions rather than frequencies.
3245 sort : bool, default True
3246 Stable sort by frequencies when True. When False, non-grouping
3247 columns will appear in the order they occur in within groups.
3248
3249 .. versionchanged:: 3.0.0
3250
3251 In prior versions, ``sort=False`` would sort the non-grouping columns
3252 by label.
3253 ascending : bool, default False
3254 Sort in ascending order.
3255 dropna : bool, default True
3256 Don't include counts of rows that contain NA values.
3257
3258 Returns
3259 -------
3260 Series or DataFrame
3261 Series if the groupby ``as_index`` is True, otherwise DataFrame.
3262
3263 See Also
3264 --------
3265 Series.value_counts: Equivalent method on Series.
3266 DataFrame.value_counts: Equivalent method on DataFrame.
3267 SeriesGroupBy.value_counts: Equivalent method on SeriesGroupBy.
3268
3269 Notes
3270 -----
3271 - If the groupby ``as_index`` is True then the returned Series will have a
3272 MultiIndex with one level per input column.
3273 - If the groupby ``as_index`` is False then the returned DataFrame will have an
3274 additional column with the value_counts. The column is labelled 'count' or
3275 'proportion', depending on the ``normalize`` parameter.
3276
3277 By default, rows that contain any NA values are omitted from
3278 the result.
3279
3280 By default, the result will be in descending order so that the
3281 first element of each group is the most frequently-occurring row.
3282
3283 Examples
3284 --------
3285 >>> df = pd.DataFrame(
3286 ... {
3287 ... "gender": ["male", "male", "female", "male", "female", "male"],
3288 ... "education": ["low", "medium", "high", "low", "high", "low"],
3289 ... "country": ["US", "FR", "US", "FR", "FR", "FR"],
3290 ... }
3291 ... )
3292
3293 >>> df
3294 gender education country
3295 0 male low US
3296 1 male medium FR
3297 2 female high US
3298 3 male low FR
3299 4 female high FR
3300 5 male low FR
3301
3302 >>> df.groupby("gender").value_counts()
3303 gender education country
3304 female high US 1
3305 FR 1
3306 male low FR 2
3307 US 1
3308 medium FR 1
3309 Name: count, dtype: int64
3310
3311 >>> df.groupby("gender").value_counts(ascending=True)
3312 gender education country
3313 female high US 1
3314 FR 1
3315 male low US 1
3316 medium FR 1
3317 low FR 2
3318 Name: count, dtype: int64
3319
3320 >>> df.groupby("gender").value_counts(normalize=True)
3321 gender education country
3322 female high US 0.50
3323 FR 0.50
3324 male low FR 0.50
3325 US 0.25
3326 medium FR 0.25
3327 Name: proportion, dtype: float64
3328
3329 >>> df.groupby("gender", as_index=False).value_counts()
3330 gender education country count
3331 0 female high US 1
3332 1 female high FR 1
3333 2 male low FR 2
3334 3 male low US 1
3335 4 male medium FR 1
3336
3337 >>> df.groupby("gender", as_index=False).value_counts(normalize=True)
3338 gender education country proportion
3339 0 female high US 0.50
3340 1 female high FR 0.50
3341 2 male low FR 0.50
3342 3 male low US 0.25
3343 4 male medium FR 0.25
3344 """
3345 return self._value_counts(subset, normalize, sort, ascending, dropna)
3346
3347 def take(
3348 self,
3349 indices: TakeIndexer,
3350 **kwargs,
3351 ) -> DataFrame:
3352 """
3353 Return the elements in the given *positional* indices in each group.
3354
3355 This means that we are not indexing according to actual values in
3356 the index attribute of the object. We are indexing according to the
3357 actual position of the element in the object.
3358
3359 If a requested index does not exist for some group, this method will raise.
3360 To get similar behavior that ignores indices that don't exist, see
3361 :meth:`.DataFrameGroupBy.nth`.
3362
3363 Parameters
3364 ----------
3365 indices : array-like
3366 An array of ints indicating which positions to take.
3367
3368 **kwargs
3369 For compatibility with :meth:`numpy.take`. Has no effect on the
3370 output.
3371
3372 Returns
3373 -------
3374 DataFrame
3375 A DataFrame containing the elements taken from each group.
3376
3377 See Also
3378 --------
3379 DataFrame.take : Take elements from a Series along an axis.
3380 DataFrame.loc : Select a subset of a DataFrame by labels.
3381 DataFrame.iloc : Select a subset of a DataFrame by positions.
3382 numpy.take : Take elements from an array along an axis.
3383
3384 Examples
3385 --------
3386 >>> df = pd.DataFrame(
3387 ... [
3388 ... ("falcon", "bird", 389.0),
3389 ... ("parrot", "bird", 24.0),
3390 ... ("lion", "mammal", 80.5),
3391 ... ("monkey", "mammal", np.nan),
3392 ... ("rabbit", "mammal", 15.0),
3393 ... ],
3394 ... columns=["name", "class", "max_speed"],
3395 ... index=[4, 3, 2, 1, 0],
3396 ... )
3397 >>> df
3398 name class max_speed
3399 4 falcon bird 389.0
3400 3 parrot bird 24.0
3401 2 lion mammal 80.5
3402 1 monkey mammal NaN
3403 0 rabbit mammal 15.0
3404 >>> gb = df.groupby([1, 1, 2, 2, 2])
3405
3406 Take elements at rows 0 and 1.
3407
3408 Note how the indices selected in the result do not correspond to
3409 our input indices 0 and 1. That's because we are selecting the 0th
3410 and 1st rows, not rows whose indices equal 0 and 1.
3411
3412 >>> gb.take([0, 1])
3413 name class max_speed
3414 1 4 falcon bird 389.0
3415 3 parrot bird 24.0
3416 2 2 lion mammal 80.5
3417 1 monkey mammal NaN
3418
3419 The order of the specified indices influences the order in the result.
3420 Here, the order is swapped from the previous example.
3421
3422 >>> gb.take([1, 0])
3423 name class max_speed
3424 1 3 parrot bird 24.0
3425 4 falcon bird 389.0
3426 2 1 monkey mammal NaN
3427 2 lion mammal 80.5
3428
3429 We may take elements using negative integers for positive indices,
3430 starting from the end of the object, just like with Python lists.
3431
3432 >>> gb.take([-1, -2])
3433 name class max_speed
3434 1 3 parrot bird 24.0
3435 4 falcon bird 389.0
3436 2 0 rabbit mammal 15.0
3437 1 monkey mammal NaN
3438 """
3439 result = self._op_via_apply("take", indices=indices, **kwargs)
3440 return result
3441
3442 def skew(
3443 self,
3444 skipna: bool = True,
3445 numeric_only: bool = False,
3446 **kwargs,
3447 ) -> DataFrame:
3448 """
3449 Return unbiased skew within groups.
3450
3451 Normalized by N-1.
3452
3453 Parameters
3454 ----------
3455 skipna : bool, default True
3456 Exclude NA/null values when computing the result.
3457
3458 numeric_only : bool, default False
3459 Include only float, int, boolean columns.
3460
3461 **kwargs
3462 Additional keyword arguments to be passed to the function.
3463
3464 Returns
3465 -------
3466 DataFrame
3467 Unbiased skew within groups.
3468
3469 See Also
3470 --------
3471 DataFrame.skew : Return unbiased skew over requested axis.
3472
3473 Examples
3474 --------
3475 >>> arrays = [
3476 ... ["falcon", "parrot", "cockatoo", "kiwi", "lion", "monkey", "rabbit"],
3477 ... ["bird", "bird", "bird", "bird", "mammal", "mammal", "mammal"],
3478 ... ]
3479 >>> index = pd.MultiIndex.from_arrays(arrays, names=("name", "class"))
3480 >>> df = pd.DataFrame(
3481 ... {"max_speed": [389.0, 24.0, 70.0, np.nan, 80.5, 21.5, 15.0]},
3482 ... index=index,
3483 ... )
3484 >>> df
3485 max_speed
3486 name class
3487 falcon bird 389.0
3488 parrot bird 24.0
3489 cockatoo bird 70.0
3490 kiwi bird NaN
3491 lion mammal 80.5
3492 monkey mammal 21.5
3493 rabbit mammal 15.0
3494 >>> gb = df.groupby(["class"])
3495 >>> gb.skew()
3496 max_speed
3497 class
3498 bird 1.628296
3499 mammal 1.669046
3500 >>> gb.skew(skipna=False)
3501 max_speed
3502 class
3503 bird NaN
3504 mammal 1.669046
3505 """
3506
3507 def alt(obj):
3508 # This should not be reached since the cython path should raise
3509 # TypeError and not NotImplementedError.
3510 raise TypeError(f"'skew' is not supported for dtype={obj.dtype}")
3511
3512 return self._cython_agg_general(
3513 "skew", alt=alt, skipna=skipna, numeric_only=numeric_only, **kwargs
3514 )
3515
3516 def kurt(
3517 self,
3518 skipna: bool = True,
3519 numeric_only: bool = False,
3520 **kwargs,
3521 ) -> DataFrame:
3522 """
3523 Return unbiased kurtosis within groups.
3524
3525 Parameters
3526 ----------
3527 skipna : bool, default True
3528 Exclude NA/null values when computing the result.
3529
3530 numeric_only : bool, default False
3531 Include only float, int, boolean columns.
3532
3533 **kwargs
3534 Additional keyword arguments to be passed to the function.
3535
3536 Returns
3537 -------
3538 DataFrame
3539 Unbiased kurtosis within groups.
3540
3541 See Also
3542 --------
3543 DataFrame.kurt : Return unbiased kurtosis over requested axis.
3544
3545 Examples
3546 --------
3547 >>> arrays = [
3548 ... [
3549 ... "falcon",
3550 ... "parrot",
3551 ... "cockatoo",
3552 ... "kiwi",
3553 ... "eagle",
3554 ... "lion",
3555 ... "monkey",
3556 ... "rabbit",
3557 ... "dog",
3558 ... "wolf",
3559 ... ],
3560 ... [
3561 ... "bird",
3562 ... "bird",
3563 ... "bird",
3564 ... "bird",
3565 ... "bird",
3566 ... "mammal",
3567 ... "mammal",
3568 ... "mammal",
3569 ... "mammal",
3570 ... "mammal",
3571 ... ],
3572 ... ]
3573 >>> index = pd.MultiIndex.from_arrays(arrays, names=("name", "class"))
3574 >>> df = pd.DataFrame(
3575 ... {
3576 ... "max_speed": [
3577 ... 389.0,
3578 ... 24.0,
3579 ... 70.0,
3580 ... np.nan,
3581 ... 350.0,
3582 ... 80.5,
3583 ... 21.5,
3584 ... 15.0,
3585 ... 40.0,
3586 ... 50.0,
3587 ... ]
3588 ... },
3589 ... index=index,
3590 ... )
3591 >>> df
3592 max_speed
3593 name class
3594 falcon bird 389.0
3595 parrot bird 24.0
3596 cockatoo bird 70.0
3597 kiwi bird NaN
3598 eagle bird 350.0
3599 lion mammal 80.5
3600 monkey mammal 21.5
3601 rabbit mammal 15.0
3602 dog mammal 40.0
3603 wolf mammal 50.0
3604 >>> gb = df.groupby(["class"])
3605 >>> gb.kurt()
3606 max_speed
3607 class
3608 bird -5.493277
3609 mammal 0.204125
3610 >>> gb.kurt(skipna=False)
3611 max_speed
3612 class
3613 bird NaN
3614 mammal 0.204125
3615 """
3616
3617 return self._cython_agg_general(
3618 "kurt", alt=None, skipna=skipna, numeric_only=numeric_only, **kwargs
3619 )
3620
3621 @property
3622 def plot(self) -> GroupByPlot:
3623 """
3624 Make plots of groups from a DataFrame.
3625
3626 Uses the backend specified by the option ``plotting.backend``.
3627 By default, matplotlib is used.
3628
3629 Returns
3630 -------
3631 GroupByPlot
3632 A plotting object that can be used to create plots for each group.
3633
3634 See Also
3635 --------
3636 DataFrame.plot : Make plots of DataFrame.
3637
3638 Examples
3639 --------
3640 >>> df = pd.DataFrame(
3641 ... {"A": [1, 2, 3, 4], "B": [5, 6, 7, 8]}, index=["a", "a", "b", "b"]
3642 ... )
3643 >>> g = df.groupby(level=0)
3644 >>> g.plot() # doctest: +SKIP
3645 """
3646 result = GroupByPlot(self)
3647 return result
3648
3649 def corr(
3650 self,
3651 method: str | Callable[[np.ndarray, np.ndarray], float] = "pearson",
3652 min_periods: int = 1,
3653 numeric_only: bool = False,
3654 ) -> DataFrame:
3655 """
3656 Compute pairwise correlation of columns, excluding NA/null values.
3657
3658 Parameters
3659 ----------
3660 method : {'pearson', 'kendall', 'spearman'} or callable
3661 Method of correlation:
3662
3663 * pearson : standard correlation coefficient
3664 * kendall : Kendall Tau correlation coefficient
3665 * spearman : Spearman rank correlation
3666 * callable: callable with input two 1d ndarrays
3667 and returning a float. Note that the returned matrix from corr
3668 will have 1 along the diagonals and will be symmetric
3669 regardless of the callable's behavior.
3670 min_periods : int, optional
3671 Minimum number of observations required per pair of columns
3672 to have a valid result. Currently only available for Pearson
3673 and Spearman correlation.
3674 numeric_only : bool, default False
3675 Include only `float`, `int` or `boolean` data.
3676
3677 .. versionchanged:: 2.0.0
3678 The default value of ``numeric_only`` is now ``False``.
3679
3680 Returns
3681 -------
3682 DataFrame
3683 Correlation matrix.
3684
3685 See Also
3686 --------
3687 DataFrame.corrwith : Compute pairwise correlation with another
3688 DataFrame or Series.
3689 Series.corr : Compute the correlation between two Series.
3690
3691 Notes
3692 -----
3693 Pearson, Kendall and Spearman correlation are currently computed using
3694 pairwise complete observations.
3695
3696 * `Pearson correlation coefficient <https://en.wikipedia.org/wiki/Pearson_correlation_coefficient>`_
3697 * `Kendall rank correlation coefficient <https://en.wikipedia.org/wiki/Kendall_rank_correlation_coefficient>`_
3698 * `Spearman's rank correlation coefficient <https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient>`_
3699
3700 Examples
3701 --------
3702 >>> df = pd.DataFrame(
3703 ... {
3704 ... "age": [2, 3, 4, 6, 6, 1, 2, 1],
3705 ... "weight": [2.1, 3.2, 4.1, 6.5, 3.3, 2.1, 4.1, 1.9],
3706 ... "pet": ["dog", "cat", "dog", "cat", "dog", "cat", "dog", "cat"],
3707 ... }
3708 ... )
3709 >>> df
3710 age weight pet
3711 0 2 2.1 dog
3712 1 3 3.2 cat
3713 2 4 4.1 dog
3714 3 6 6.5 cat
3715 4 6 3.3 dog
3716 5 1 2.1 cat
3717 6 2 4.1 dog
3718 7 1 1.9 cat
3719 >>> df.groupby("pet").corr()
3720 age weight
3721 pet
3722 cat age 1.000000 0.989321
3723 weight 0.989321 1.000000
3724 dog age 1.000000 0.184177
3725 weight 0.184177 1.000000
3726 """
3727 result = self._op_via_apply(
3728 "corr", method=method, min_periods=min_periods, numeric_only=numeric_only
3729 )
3730 return result
3731
3732 def cov(
3733 self,
3734 min_periods: int | None = None,
3735 ddof: int | None = 1,
3736 numeric_only: bool = False,
3737 ) -> DataFrame:
3738 """
3739 Compute pairwise covariance of columns, excluding NA/null values.
3740
3741 Compute the pairwise covariance among the series of a DataFrame.
3742 The returned data frame is the `covariance matrix
3743 <https://en.wikipedia.org/wiki/Covariance_matrix>`__ of the columns
3744 of the DataFrame.
3745
3746 Both NA and null values are automatically excluded from the
3747 calculation. (See the note below about bias from missing values.)
3748 A threshold can be set for the minimum number of
3749 observations for each value created. Comparisons with observations
3750 below this threshold will be returned as ``NaN``.
3751
3752 This method is generally used for the analysis of time series data to
3753 understand the relationship between different measures
3754 across time.
3755
3756 Parameters
3757 ----------
3758 min_periods : int, optional
3759 Minimum number of observations required per pair of columns
3760 to have a valid result.
3761
3762 ddof : int, default 1
3763 Delta degrees of freedom. The divisor used in calculations
3764 is ``N - ddof``, where ``N`` represents the number of elements.
3765 This argument is applicable only when no ``nan`` is in the dataframe.
3766
3767 numeric_only : bool, default False
3768 Include only `float`, `int` or `boolean` data.
3769
3770 .. versionchanged:: 2.0.0
3771 The default value of ``numeric_only`` is now ``False``.
3772
3773 Returns
3774 -------
3775 DataFrame
3776 The covariance matrix of the series of the DataFrame.
3777
3778 See Also
3779 --------
3780 Series.cov : Compute covariance with another Series.
3781 core.window.ewm.ExponentialMovingWindow.cov : Exponential weighted sample
3782 covariance.
3783 core.window.expanding.Expanding.cov : Expanding sample covariance.
3784 core.window.rolling.Rolling.cov : Rolling sample covariance.
3785
3786 Notes
3787 -----
3788 Returns the covariance matrix of the DataFrame's time series.
3789 The covariance is normalized by N-ddof.
3790
3791 For DataFrames that have Series that are missing data (assuming that
3792 data is `missing at random
3793 <https://en.wikipedia.org/wiki/Missing_data#Missing_at_random>`__)
3794 the returned covariance matrix will be an unbiased estimate
3795 of the variance and covariance between the member Series.
3796
3797 However, for many applications this estimate may not be acceptable
3798 because the estimate covariance matrix is not guaranteed to be positive
3799 semi-definite. This could lead to estimate correlations having
3800 absolute values which are greater than one, and/or a non-invertible
3801 covariance matrix. See `Estimation of covariance matrices
3802 <https://en.wikipedia.org/w/index.php?title=Estimation_of_covariance_
3803 matrices>`__ for more details.
3804
3805 Examples
3806 --------
3807 >>> df = pd.DataFrame(
3808 ... {
3809 ... "age": [2, 3, 4, 6, 6, 1, 2, 1],
3810 ... "weight": [2.1, 3.2, 4.1, 6.5, 3.3, 2.1, 4.1, 1.9],
3811 ... "pet": ["dog", "cat", "dog", "cat", "dog", "cat", "dog", "cat"],
3812 ... }
3813 ... )
3814 >>> df
3815 age weight pet
3816 0 2 2.1 dog
3817 1 3 3.2 cat
3818 2 4 4.1 dog
3819 3 6 6.5 cat
3820 4 6 3.3 dog
3821 5 1 2.1 cat
3822 6 2 4.1 dog
3823 7 1 1.9 cat
3824 >>> df.groupby("pet").cov()
3825 age weight
3826 pet
3827 cat age 5.583333 4.975000
3828 weight 4.975000 4.529167
3829 dog age 3.666667 0.333333
3830 weight 0.333333 0.893333
3831 """
3832 result = self._op_via_apply(
3833 "cov", min_periods=min_periods, ddof=ddof, numeric_only=numeric_only
3834 )
3835 return result
3836
3837 def hist(
3838 self,
3839 column: IndexLabel | None = None,
3840 by=None,
3841 grid: bool = True,
3842 xlabelsize: int | None = None,
3843 xrot: float | None = None,
3844 ylabelsize: int | None = None,
3845 yrot: float | None = None,
3846 ax=None,
3847 sharex: bool = False,
3848 sharey: bool = False,
3849 figsize: tuple[float, float] | None = None,
3850 layout: tuple[int, int] | None = None,
3851 bins: int | Sequence[int] = 10,
3852 backend: str | None = None,
3853 legend: bool = False,
3854 **kwargs,
3855 ):
3856 """
3857 Make a histogram of the DataFrame's columns.
3858
3859 A `histogram`_ is a representation of the distribution of data.
3860 This function calls :meth:`matplotlib.pyplot.hist`, on each series in
3861 the DataFrame, resulting in one histogram per column.
3862
3863 .. _histogram: https://en.wikipedia.org/wiki/Histogram
3864
3865 Parameters
3866 ----------
3867 column : str or sequence, optional
3868 If passed, will be used to limit data to a subset of columns.
3869 by : object, optional
3870 If passed, then used to form histograms for separate groups.
3871 grid : bool, default True
3872 Whether to show axis grid lines.
3873 xlabelsize : int, default None
3874 If specified changes the x-axis label size.
3875 xrot : float, default None
3876 Rotation of x axis labels. For example, a value of 90 displays the
3877 x labels rotated 90 degrees clockwise.
3878 ylabelsize : int, default None
3879 If specified changes the y-axis label size.
3880 yrot : float, default None
3881 Rotation of y axis labels. For example, a value of 90 displays the
3882 y labels rotated 90 degrees clockwise.
3883 ax : Matplotlib axes object, default None
3884 The axes to plot the histogram on.
3885 sharex : bool, default True if ax is None else False
3886 In case subplots=True, share x axis and set some x axis labels to
3887 invisible; defaults to True if ax is None otherwise False if an ax
3888 is passed in.
3889 Note that passing in both an ax and sharex=True will alter all x axis
3890 labels for all subplots in a figure.
3891 sharey : bool, default False
3892 In case subplots=True, share y axis and set some y axis labels to
3893 invisible.
3894 figsize : tuple, optional
3895 The size in inches of the figure to create. Uses the value in
3896 `matplotlib.rcParams` by default.
3897 layout : tuple, optional
3898 Tuple of (rows, columns) for the layout of the histograms.
3899 bins : int or sequence, default 10
3900 Number of histogram bins to be used. If an integer is given, bins + 1
3901 bin edges are calculated and returned. If bins is a sequence, gives
3902 bin edges, including left edge of first bin and right edge of last
3903 bin. In this case, bins is returned unmodified.
3904
3905 backend : str, default None
3906 Backend to use instead of the backend specified in the option
3907 ``plotting.backend``. For instance, 'matplotlib'. Alternatively, to
3908 specify the ``plotting.backend`` for the whole session, set
3909 ``pd.options.plotting.backend``.
3910
3911 legend : bool, default False
3912 Whether to show the legend.
3913
3914 **kwargs
3915 All other plotting keyword arguments to be passed to
3916 :meth:`matplotlib.pyplot.hist`.
3917
3918 Returns
3919 -------
3920 matplotlib.Axes or numpy.ndarray
3921 A ``matplotlib.Axes`` object or an array of ``Axes`` objects, depending on
3922 the layout and grouping.
3923
3924 See Also
3925 --------
3926 matplotlib.pyplot.hist : Plot a histogram using matplotlib.
3927
3928 Examples
3929 --------
3930 This example draws a histogram based on the length and width of
3931 some animals, displayed in three bins
3932
3933 .. plot::
3934 :context: close-figs
3935
3936 >>> data = {
3937 ... "length": [1.5, 0.5, 1.2, 0.9, 3],
3938 ... "width": [0.7, 0.2, 0.15, 0.2, 1.1],
3939 ... }
3940 >>> index = ["pig", "rabbit", "duck", "chicken", "horse"]
3941 >>> df = pd.DataFrame(data, index=index)
3942 >>> hist = df.groupby("length").hist(bins=3)
3943 """
3944 result = self._op_via_apply(
3945 "hist",
3946 column=column,
3947 by=by,
3948 grid=grid,
3949 xlabelsize=xlabelsize,
3950 xrot=xrot,
3951 ylabelsize=ylabelsize,
3952 yrot=yrot,
3953 ax=ax,
3954 sharex=sharex,
3955 sharey=sharey,
3956 figsize=figsize,
3957 layout=layout,
3958 bins=bins,
3959 backend=backend,
3960 legend=legend,
3961 **kwargs,
3962 )
3963 return result
3964
3965 def corrwith(
3966 self,
3967 other: DataFrame | Series,
3968 drop: bool = False,
3969 method: CorrelationMethod = "pearson",
3970 numeric_only: bool = False,
3971 ) -> DataFrame:
3972 """
3973 Compute pairwise correlation.
3974
3975 .. deprecated:: 3.0.0
3976
3977 Pairwise correlation is computed between rows or columns of
3978 DataFrame with rows or columns of Series or DataFrame. DataFrames
3979 are first aligned along both axes before computing the
3980 correlations.
3981
3982 Parameters
3983 ----------
3984 other : DataFrame, Series
3985 Object with which to compute correlations.
3986 drop : bool, default False
3987 Drop missing indices from result.
3988 method : {'pearson', 'kendall', 'spearman'} or callable
3989 Method of correlation:
3990
3991 * pearson : standard correlation coefficient
3992 * kendall : Kendall Tau correlation coefficient
3993 * spearman : Spearman rank correlation
3994 * callable: callable with input two 1d ndarrays
3995 and returning a float.
3996
3997 numeric_only : bool, default False
3998 Include only `float`, `int` or `boolean` data.
3999
4000 .. versionchanged:: 2.0.0
4001 The default value of ``numeric_only`` is now ``False``.
4002
4003 Returns
4004 -------
4005 Series
4006 Pairwise correlations.
4007
4008 See Also
4009 --------
4010 DataFrame.corr : Compute pairwise correlation of columns.
4011
4012 Examples
4013 --------
4014 >>> df1 = pd.DataFrame(
4015 ... {
4016 ... "Day": [1, 1, 1, 2, 2, 2, 3, 3, 3],
4017 ... "Data": [6, 6, 8, 5, 4, 2, 7, 3, 9],
4018 ... }
4019 ... )
4020 >>> df2 = pd.DataFrame(
4021 ... {
4022 ... "Day": [1, 1, 1, 2, 2, 2, 3, 3, 3],
4023 ... "Data": [5, 3, 8, 3, 1, 1, 2, 3, 6],
4024 ... }
4025 ... )
4026
4027 >>> df1.groupby("Day").corrwith(df2)
4028 Data Day
4029 Day
4030 1 0.917663 NaN
4031 2 0.755929 NaN
4032 3 0.576557 NaN
4033 """
4034 warnings.warn(
4035 "DataFrameGroupBy.corrwith is deprecated",
4036 Pandas4Warning,
4037 stacklevel=find_stack_level(),
4038 )
4039 result = self._op_via_apply(
4040 "corrwith",
4041 other=other,
4042 drop=drop,
4043 method=method,
4044 numeric_only=numeric_only,
4045 )
4046 return result
4047
4048
4049def _wrap_transform_general_frame(
4050 obj: DataFrame, group: DataFrame, res: DataFrame | Series
4051) -> DataFrame:
4052 from pandas import concat
4053
4054 if isinstance(res, Series):
4055 # we need to broadcast across the
4056 # other dimension; this will preserve dtypes
4057 # GH14457
4058 if res.index.is_(obj.index):
4059 res_frame = concat([res] * len(group.columns), axis=1, ignore_index=True)
4060 res_frame.columns = group.columns
4061 res_frame.index = group.index
4062 else:
4063 res_frame = obj._constructor(
4064 np.tile(res.values, (len(group.index), 1)),
4065 columns=group.columns,
4066 index=group.index,
4067 )
4068 assert isinstance(res_frame, DataFrame)
4069 return res_frame
4070 elif isinstance(res, DataFrame) and not res.index.is_(group.index):
4071 return res._align_frame(group)[0]
4072 else:
4073 return res