From 0ca2233038ff539c545b3adf3bb16fef95b44464 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Fri, 12 Aug 2022 20:04:12 +0200 Subject: [PATCH 1/8] ENH: Support mask in GroupBy.cumsum --- doc/source/whatsnew/v1.5.0.rst | 2 +- pandas/_libs/groupby.pyi | 2 ++ pandas/_libs/groupby.pyx | 38 +++++++++++++++++++++++----- pandas/core/groupby/ops.py | 1 + pandas/tests/groupby/test_groupby.py | 19 ++++++++++++-- 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 48ca1fd9d16eb..83d8582373982 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -1069,7 +1069,7 @@ Groupby/resample/rolling - Bug when using ``engine="numba"`` would return the same jitted function when modifying ``engine_kwargs`` (:issue:`46086`) - Bug in :meth:`.DataFrameGroupBy.transform` fails when ``axis=1`` and ``func`` is ``"first"`` or ``"last"`` (:issue:`45986`) - Bug in :meth:`DataFrameGroupBy.cumsum` with ``skipna=False`` giving incorrect results (:issue:`46216`) -- Bug in :meth:`GroupBy.sum` with integer dtypes losing precision (:issue:`37493`) +- Bug in :meth:`GroupBy.sum` and :meth:`GroupBy.cumsum` with integer dtypes losing precision (:issue:`37493`) - Bug in :meth:`.GroupBy.cumsum` with ``timedelta64[ns]`` dtype failing to recognize ``NaT`` as a null value (:issue:`46216`) - Bug in :meth:`.GroupBy.cummin` and :meth:`.GroupBy.cummax` with nullable dtypes incorrectly altering the original data in place (:issue:`46220`) - Bug in :meth:`DataFrame.groupby` raising error when ``None`` is in first level of :class:`MultiIndex` (:issue:`47348`) diff --git a/pandas/_libs/groupby.pyi b/pandas/_libs/groupby.pyi index 3ec37718eb652..3bf80db46f59e 100644 --- a/pandas/_libs/groupby.pyi +++ b/pandas/_libs/groupby.pyi @@ -26,6 +26,8 @@ def group_cumsum( ngroups: int, is_datetimelike: bool, skipna: bool = ..., + mask: np.ndarray | None = ..., + result_mask: np.ndarray | None = ..., ) -> None: ... def group_shift_indexer( out: np.ndarray, # int64_t[::1] diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx index e4314edecfa7e..85e4f4578cf86 100644 --- a/pandas/_libs/groupby.pyx +++ b/pandas/_libs/groupby.pyx @@ -216,6 +216,8 @@ def group_cumsum( int ngroups, bint is_datetimelike, bint skipna=True, + const uint8_t[:, :] mask=None, + uint8_t[:, ::1] result_mask=None, ) -> None: """ Cumulative sum of columns of `values`, in row groups `labels`. @@ -243,10 +245,16 @@ def group_cumsum( Py_ssize_t i, j, N, K, size numeric_t val, y, t, na_val numeric_t[:, ::1] accum, compensation + uint8_t[:, ::1] accum_mask intp_t lab bint isna_entry, isna_prev = False + bint uses_mask = mask is not None N, K = (values).shape + + if uses_mask: + accum_mask = np.zeros((ngroups, K), dtype="uint8") + accum = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) compensation = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) @@ -261,18 +269,36 @@ def group_cumsum( for j in range(K): val = values[i, j] - isna_entry = _treat_as_na(val, is_datetimelike) + if uses_mask: + isna_entry = mask[i, j] + else: + isna_entry = _treat_as_na(val, is_datetimelike) if not skipna: - isna_prev = _treat_as_na(accum[lab, j], is_datetimelike) + if uses_mask: + isna_prev = accum_mask[lab, j] + else: + isna_prev = _treat_as_na(accum[lab, j], is_datetimelike) + if isna_prev: - out[i, j] = na_val + if uses_mask: + result_mask[i, j] = True + else: + out[i, j] = na_val continue if isna_entry: - out[i, j] = na_val + + if uses_mask: + result_mask[i, j] = True + else: + out[i, j] = na_val + if not skipna: - accum[lab, j] = na_val + if uses_mask: + accum_mask[lab, j] = True + else: + accum[lab, j] = na_val else: # For floats, use Kahan summation to reduce floating-point @@ -1048,7 +1074,7 @@ cdef numeric_t _get_na_val(numeric_t val, bint is_datetimelike): elif numeric_t is int64_t and is_datetimelike: na_val = NPY_NAT else: - # Will not be used, but define to avoid uninitialized warning. + # Used in case of masks na_val = 0 return na_val diff --git a/pandas/core/groupby/ops.py b/pandas/core/groupby/ops.py index 7617ca5074c9c..a6ba248dbd505 100644 --- a/pandas/core/groupby/ops.py +++ b/pandas/core/groupby/ops.py @@ -156,6 +156,7 @@ def __init__(self, kind: str, how: str, has_dropped_na: bool) -> None: "first", "rank", "sum", + "cumsum", } _cython_arity = {"ohlc": 4} # OHLC diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index a7c5b85e365ae..b088e01dde298 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -2810,12 +2810,15 @@ def test_single_element_list_grouping(): values, _ = next(iter(df.groupby(["a"]))) -def test_groupby_sum_avoid_casting_to_float(): +@pytest.mark.parametrize("func", ["sum", "cumsum"]) +def test_groupby_sum_avoid_casting_to_float(func): # GH#37493 val = 922337203685477580 df = DataFrame({"a": 1, "b": [val]}) - result = df.groupby("a").sum() - val + result = getattr(df.groupby("a"), func)() - val expected = DataFrame({"b": [0]}, index=Index([1], name="a")) + if func == "cumsum": + expected = expected.reset_index(drop=True) tm.assert_frame_equal(result, expected) @@ -2829,3 +2832,15 @@ def test_groupby_sum_support_mask(any_numeric_ea_dtype): dtype=any_numeric_ea_dtype, ) tm.assert_frame_equal(result, expected) + + +@pytest.mark.parametrize("skipna, val", [(True, 3), (False, pd.NA)]) +def test_groupby_cumsum_mask(any_numeric_ea_dtype, skipna, val): + # GH#37493 + df = DataFrame({"a": 1, "b": [1, pd.NA, 2]}, dtype=any_numeric_ea_dtype) + result = df.groupby("a").cumsum(skipna=skipna) + expected = DataFrame( + {"b": [1, pd.NA, val]}, + dtype=any_numeric_ea_dtype, + ) + tm.assert_frame_equal(result, expected) From 0fa1f3a773df80609b0ab77fa3f4ed0a31d3a929 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Sat, 13 Aug 2022 16:45:10 +0200 Subject: [PATCH 2/8] Change compiling --- doc/source/whatsnew/v1.5.0.rst | 1 + pandas/_libs/groupby.pyx | 23 +++++++++++++++++------ pandas/core/groupby/ops.py | 2 +- pandas/tests/groupby/test_groupby.py | 6 +++++- pandas/tests/groupby/test_libgroupby.py | 5 +++-- 5 files changed, 27 insertions(+), 10 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 21d25de486b82..b91353b66563a 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -1072,6 +1072,7 @@ Groupby/resample/rolling - Bug in :meth:`DataFrameGroupBy.cumsum` with ``skipna=False`` giving incorrect results (:issue:`46216`) - Bug in :meth:`GroupBy.sum` and :meth:`GroupBy.cumsum` with integer dtypes losing precision (:issue:`37493`) - Bug in :meth:`.GroupBy.cumsum` with ``timedelta64[ns]`` dtype failing to recognize ``NaT`` as a null value (:issue:`46216`) +- Bug in :meth:`GroupBy.cumsum` with integer dtypes causing overflows when sum was bigger than maximum of dtype (:issue:`37493`) - Bug in :meth:`.GroupBy.cummin` and :meth:`.GroupBy.cummax` with nullable dtypes incorrectly altering the original data in place (:issue:`46220`) - Bug in :meth:`DataFrame.groupby` raising error when ``None`` is in first level of :class:`MultiIndex` (:issue:`47348`) - Bug in :meth:`.GroupBy.cummax` with ``int64`` dtype with leading value being the smallest possible int64 (:issue:`46382`) diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx index 735ae31c32e5a..c9326d56e156c 100644 --- a/pandas/_libs/groupby.pyx +++ b/pandas/_libs/groupby.pyx @@ -207,11 +207,18 @@ def group_cumprod_float64( break +ctypedef fused cumsum_t: + int64_t + uint64_t + float32_t + float64_t + + @cython.boundscheck(False) @cython.wraparound(False) def group_cumsum( - numeric_t[:, ::1] out, - ndarray[numeric_t, ndim=2] values, + cumsum_t[:, ::1] out, + ndarray[cumsum_t, ndim=2] values, const intp_t[::1] labels, int ngroups, bint is_datetimelike, @@ -236,6 +243,10 @@ def group_cumsum( True if `values` contains datetime-like entries. skipna : bool If true, ignore nans in `values`. + mask: np.ndarray[uint8], optional + Mask of values + result_mask: np.ndarray[int8], optional + Mask of out array Notes ----- @@ -243,8 +254,8 @@ def group_cumsum( """ cdef: Py_ssize_t i, j, N, K, size - numeric_t val, y, t, na_val - numeric_t[:, ::1] accum, compensation + cumsum_t val, y, t, na_val + cumsum_t[:, ::1] accum, compensation uint8_t[:, ::1] accum_mask intp_t lab bint isna_entry, isna_prev = False @@ -258,7 +269,7 @@ def group_cumsum( accum = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) compensation = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) - na_val = _get_na_val(0, is_datetimelike) + na_val = _get_na_val(0, is_datetimelike) with nogil: for i in range(N): @@ -303,7 +314,7 @@ def group_cumsum( else: # For floats, use Kahan summation to reduce floating-point # error (https://en.wikipedia.org/wiki/Kahan_summation_algorithm) - if numeric_t == float32_t or numeric_t == float64_t: + if cumsum_t == float32_t or cumsum_t == float64_t: y = val - compensation[lab, j] t = accum[lab, j] + y compensation[lab, j] = t - accum[lab, j] - y diff --git a/pandas/core/groupby/ops.py b/pandas/core/groupby/ops.py index 2c6d100400e73..bb2c97afa529a 100644 --- a/pandas/core/groupby/ops.py +++ b/pandas/core/groupby/ops.py @@ -226,7 +226,7 @@ def _get_cython_vals(self, values: np.ndarray) -> np.ndarray: # result may still include NaN, so we have to cast values = ensure_float64(values) - elif how == "sum": + elif how in ["sum", "cumsum"]: # Avoid overflow during group op if values.dtype.kind == "i": values = ensure_int64(values) diff --git a/pandas/tests/groupby/test_groupby.py b/pandas/tests/groupby/test_groupby.py index a288108ff998c..75dc6253d703f 100644 --- a/pandas/tests/groupby/test_groupby.py +++ b/pandas/tests/groupby/test_groupby.py @@ -2835,7 +2835,7 @@ def test_groupby_sum_support_mask(any_numeric_ea_dtype): @pytest.mark.parametrize("val, dtype", [(111, "int"), (222, "uint")]) -def test_groupby_sum_overflow(val, dtype): +def test_groupby_overflow(val, dtype): # GH#37493 df = DataFrame({"a": 1, "b": [val, val]}, dtype=f"{dtype}8") result = df.groupby("a").sum() @@ -2846,6 +2846,10 @@ def test_groupby_sum_overflow(val, dtype): ) tm.assert_frame_equal(result, expected) + result = df.groupby("a").cumsum() + expected = DataFrame({"b": [val, val * 2]}, dtype=f"{dtype}64") + tm.assert_frame_equal(result, expected) + @pytest.mark.parametrize("skipna, val", [(True, 3), (False, pd.NA)]) def test_groupby_cumsum_mask(any_numeric_ea_dtype, skipna, val): diff --git a/pandas/tests/groupby/test_libgroupby.py b/pandas/tests/groupby/test_libgroupby.py index cde9b36fd0bf4..24abbd0f65795 100644 --- a/pandas/tests/groupby/test_libgroupby.py +++ b/pandas/tests/groupby/test_libgroupby.py @@ -183,9 +183,10 @@ def _check_cython_group_transform_cumulative(pd_op, np_op, dtype): tm.assert_numpy_array_equal(np_op(data), answer[:, 0], check_dtype=False) -def test_cython_group_transform_cumsum(any_real_numpy_dtype): +@pytest.mark.parametrize("np_dtype", ["int64", "uint64", "float32", "float64"]) +def test_cython_group_transform_cumsum(np_dtype): # see gh-4095 - dtype = np.dtype(any_real_numpy_dtype).type + dtype = np.dtype(np_dtype).type pd_op, np_op = group_cumsum, np.cumsum _check_cython_group_transform_cumulative(pd_op, np_op, dtype) From d9b10f164d83d0fc78fd15c2a4ead3acce16d3c4 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Sat, 13 Aug 2022 16:45:32 +0200 Subject: [PATCH 3/8] Change types --- pandas/_libs/groupby.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/groupby.pyi b/pandas/_libs/groupby.pyi index 3bf80db46f59e..b237ab3237c0d 100644 --- a/pandas/_libs/groupby.pyi +++ b/pandas/_libs/groupby.pyi @@ -20,8 +20,8 @@ def group_cumprod_float64( skipna: bool = ..., ) -> None: ... def group_cumsum( - out: np.ndarray, # numeric[:, ::1] - values: np.ndarray, # ndarray[numeric, ndim=2] + out: np.ndarray, # cumsum_t[:, ::1] + values: np.ndarray, # ndarray[cumsum_t, ndim=2] labels: np.ndarray, # const int64_t[:] ngroups: int, is_datetimelike: bool, From a7d918c0fbd6d5dac59d1a5d838ee4a5b5124aae Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Sun, 14 Aug 2022 22:12:09 +0200 Subject: [PATCH 4/8] Rename type --- pandas/_libs/groupby.pyx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx index c9326d56e156c..a7e636429de39 100644 --- a/pandas/_libs/groupby.pyx +++ b/pandas/_libs/groupby.pyx @@ -207,7 +207,7 @@ def group_cumprod_float64( break -ctypedef fused cumsum_t: +ctypedef fused int64float_t: int64_t uint64_t float32_t @@ -217,8 +217,8 @@ ctypedef fused cumsum_t: @cython.boundscheck(False) @cython.wraparound(False) def group_cumsum( - cumsum_t[:, ::1] out, - ndarray[cumsum_t, ndim=2] values, + int64float_t[:, ::1] out, + ndarray[int64float_t, ndim=2] values, const intp_t[::1] labels, int ngroups, bint is_datetimelike, @@ -254,8 +254,8 @@ def group_cumsum( """ cdef: Py_ssize_t i, j, N, K, size - cumsum_t val, y, t, na_val - cumsum_t[:, ::1] accum, compensation + int64float_t val, y, t, na_val + int64float_t[:, ::1] accum, compensation uint8_t[:, ::1] accum_mask intp_t lab bint isna_entry, isna_prev = False @@ -269,7 +269,7 @@ def group_cumsum( accum = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) compensation = np.zeros((ngroups, K), dtype=np.asarray(values).dtype) - na_val = _get_na_val(0, is_datetimelike) + na_val = _get_na_val(0, is_datetimelike) with nogil: for i in range(N): @@ -314,7 +314,7 @@ def group_cumsum( else: # For floats, use Kahan summation to reduce floating-point # error (https://en.wikipedia.org/wiki/Kahan_summation_algorithm) - if cumsum_t == float32_t or cumsum_t == float64_t: + if int64float_t == float32_t or int64float_t == float64_t: y = val - compensation[lab, j] t = accum[lab, j] + y compensation[lab, j] = t - accum[lab, j] - y From 3ee78545a177732350ca5997825a070ae8167ab7 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Mon, 15 Aug 2022 19:51:12 +0200 Subject: [PATCH 5/8] Remove duplicated type --- pandas/_libs/groupby.pyx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx index b9bf4e52e324c..1f99b0ab06413 100644 --- a/pandas/_libs/groupby.pyx +++ b/pandas/_libs/groupby.pyx @@ -871,13 +871,6 @@ def group_mean( out[i, j] = sumx[i, j] / count -ctypedef fused int64float_t: - float32_t - float64_t - int64_t - uint64_t - - @cython.wraparound(False) @cython.boundscheck(False) def group_ohlc( From ff83f13d8a076458b524be4eeba0dd5e76ddf5f3 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Mon, 15 Aug 2022 19:55:21 +0200 Subject: [PATCH 6/8] Fix annotation --- pandas/_libs/groupby.pyi | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pandas/_libs/groupby.pyi b/pandas/_libs/groupby.pyi index 39d2a0e56b441..c8e9df6cd6b38 100644 --- a/pandas/_libs/groupby.pyi +++ b/pandas/_libs/groupby.pyi @@ -20,8 +20,8 @@ def group_cumprod_float64( skipna: bool = ..., ) -> None: ... def group_cumsum( - out: np.ndarray, # cumsum_t[:, ::1] - values: np.ndarray, # ndarray[cumsum_t, ndim=2] + out: np.ndarray, # int64float_t[:, ::1] + values: np.ndarray, # ndarray[int64float_t, ndim=2] labels: np.ndarray, # const int64_t[:] ngroups: int, is_datetimelike: bool, From bcb5550e0a3c36e32ad839386321cb33248c42e8 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Wed, 17 Aug 2022 23:19:15 +0200 Subject: [PATCH 7/8] Add initialize --- pandas/_libs/groupby.pyx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pandas/_libs/groupby.pyx b/pandas/_libs/groupby.pyx index 1f99b0ab06413..563abf949dbbc 100644 --- a/pandas/_libs/groupby.pyx +++ b/pandas/_libs/groupby.pyx @@ -293,6 +293,8 @@ def group_cumsum( if isna_prev: if uses_mask: result_mask[i, j] = True + # Be deterministic, out was initialized as empty + out[i, j] = 0 else: out[i, j] = na_val continue @@ -301,6 +303,8 @@ def group_cumsum( if uses_mask: result_mask[i, j] = True + # Be deterministic, out was initialized as empty + out[i, j] = 0 else: out[i, j] = na_val From 56091503db20b650dd783b986141cdd4207ccaf7 Mon Sep 17 00:00:00 2001 From: Patrick Hoefler Date: Thu, 18 Aug 2022 09:41:35 +0200 Subject: [PATCH 8/8] Fix groupby references --- doc/source/whatsnew/v1.5.0.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/source/whatsnew/v1.5.0.rst b/doc/source/whatsnew/v1.5.0.rst index 8675e0b5adcf6..bc5f24e6fb7dd 100644 --- a/doc/source/whatsnew/v1.5.0.rst +++ b/doc/source/whatsnew/v1.5.0.rst @@ -287,7 +287,7 @@ Other enhancements - ``times`` argument in :class:`.ExponentialMovingWindow` now accepts ``np.timedelta64`` (:issue:`47003`) - :class:`.DataError`, :class:`.SpecificationError`, :class:`.SettingWithCopyError`, :class:`.SettingWithCopyWarning`, :class:`.NumExprClobberingError`, :class:`.UndefinedVariableError`, :class:`.IndexingError`, :class:`.PyperclipException`, :class:`.PyperclipWindowsException`, :class:`.CSSWarning`, :class:`.PossibleDataLossError`, :class:`.ClosedFileError`, :class:`.IncompatibilityWarning`, :class:`.AttributeConflictWarning`, :class:`.DatabaseError, :class:`.PossiblePrecisionLoss, :class:`.ValueLabelTypeMismatch, :class:`.InvalidColumnName, and :class:`.CategoricalConversionWarning` are now exposed in ``pandas.errors`` (:issue:`27656`) - Added ``check_like`` argument to :func:`testing.assert_series_equal` (:issue:`47247`) -- Add support for :meth:`GroupBy.ohlc` for extension array dtypes (:issue:`37493`) +- Add support for :meth:`.GroupBy.ohlc` for extension array dtypes (:issue:`37493`) - Allow reading compressed SAS files with :func:`read_sas` (e.g., ``.sas7bdat.gz`` files) - :func:`pandas.read_html` now supports extracting links from table cells (:issue:`13141`) - :meth:`DatetimeIndex.astype` now supports casting timezone-naive indexes to ``datetime64[s]``, ``datetime64[ms]``, and ``datetime64[us]``, and timezone-aware indexes to the corresponding ``datetime64[unit, tzname]`` dtypes (:issue:`47579`) @@ -1078,13 +1078,13 @@ Groupby/resample/rolling - Bug when using ``engine="numba"`` would return the same jitted function when modifying ``engine_kwargs`` (:issue:`46086`) - Bug in :meth:`.DataFrameGroupBy.transform` fails when ``axis=1`` and ``func`` is ``"first"`` or ``"last"`` (:issue:`45986`) - Bug in :meth:`DataFrameGroupBy.cumsum` with ``skipna=False`` giving incorrect results (:issue:`46216`) -- Bug in :meth:`GroupBy.sum` and :meth:`GroupBy.cumsum` with integer dtypes losing precision (:issue:`37493`) +- Bug in :meth:`.GroupBy.sum` and :meth:`.GroupBy.cumsum` with integer dtypes losing precision (:issue:`37493`) - Bug in :meth:`.GroupBy.cumsum` with ``timedelta64[ns]`` dtype failing to recognize ``NaT`` as a null value (:issue:`46216`) -- Bug in :meth:`GroupBy.cumsum` with integer dtypes causing overflows when sum was bigger than maximum of dtype (:issue:`37493`) +- Bug in :meth:`.GroupBy.cumsum` with integer dtypes causing overflows when sum was bigger than maximum of dtype (:issue:`37493`) - Bug in :meth:`.GroupBy.cummin` and :meth:`.GroupBy.cummax` with nullable dtypes incorrectly altering the original data in place (:issue:`46220`) - Bug in :meth:`DataFrame.groupby` raising error when ``None`` is in first level of :class:`MultiIndex` (:issue:`47348`) - Bug in :meth:`.GroupBy.cummax` with ``int64`` dtype with leading value being the smallest possible int64 (:issue:`46382`) -- Bug in :meth:`GroupBy.cumprod` ``NaN`` influences calculation in different columns with ``skipna=False`` (:issue:`48064`) +- Bug in :meth:`.GroupBy.cumprod` ``NaN`` influences calculation in different columns with ``skipna=False`` (:issue:`48064`) - Bug in :meth:`.GroupBy.max` with empty groups and ``uint64`` dtype incorrectly raising ``RuntimeError`` (:issue:`46408`) - Bug in :meth:`.GroupBy.apply` would fail when ``func`` was a string and args or kwargs were supplied (:issue:`46479`) - Bug in :meth:`SeriesGroupBy.apply` would incorrectly name its result when there was a unique group (:issue:`46369`)