Skip to content

ENH: DataFrameGroupBy.transform now accepts list, dict, and NamedAgg arguments (GH#58318) - #65164

Merged
rhshadrach merged 13 commits into
pandas-dev:mainfrom
berasaikat:issues#58318
Jun 9, 2026
Merged

ENH: DataFrameGroupBy.transform now accepts list, dict, and NamedAgg arguments (GH#58318)#65164
rhshadrach merged 13 commits into
pandas-dev:mainfrom
berasaikat:issues#58318

Conversation

@berasaikat

Copy link
Copy Markdown
Contributor

Description

Closes #58318


What this does

GroupBy.transform currently only accepts a single callable or string, while GroupBy.agg supports list-like, dict, and NamedAgg keyword arguments. This PR closes that gap by extending DataFrameGroupBy.transform to support the same call signatures as GroupBy.agg.

df = pd.DataFrame({"col": list("aab"), "val": range(3), "other": range(3)})

# Before — TypeError: 'list' object is not callable
df.groupby("col").transform(["sum", "min"])

# Now works
df.groupby("col").transform(["sum", "min"])
df.groupby("col").transform({"val": "sum", "other": "min"})
df.groupby("col").transform(
    val_sum=pd.NamedAgg(column="val", aggfunc="sum"),
    other_min=pd.NamedAgg(column="other", aggfunc="min"),
)

Changes

pandas/core/groupby/generic.py

  • func is now optional (defaults to None) to support the NamedAgg kwargs path
  • List-like dispatch: applies every function to every non-key column, returning a MultiIndex-column DataFrame (column, func)
  • Dict dispatch: applies a per-column function, returning only the specified columns. Plain {"col": "sum"} and {"name": NamedAgg(column, func)} are both supported, as are plain tuples as a NamedAgg equivalent
  • NamedAgg / plain tuple kwargs dispatch: named output columns with explicit source column selection
  • Two new private helpers: _transform_multiple_funcs (orchestrates list and dict paths) and _transform_single_column (applies one function to one column via SeriesGroupBy)
  • NotImplementedError with a clear message for SeriesGroupBy.transform(list) and dict-of-lists — both are intended for a future release
  • ValueError for duplicate column names in dict/NamedAgg paths

pandas/tests/groupby/transform/test_transform.py

  • test_transform_with_list_like
  • test_transform_with_list_like_single_column
  • test_transform_with_dict
  • test_transform_with_dict_subset_columns
  • test_transform_with_dict_of_lists_raises
  • test_transform_with_namedagg
  • test_transform_with_namedagg_same_source_column
  • test_transform_with_namedagg_plain_tuple
  • test_transform_series_groupby_list_raises
  • test_transform_dict_duplicate_column_names_raises

doc/source/whatsnew/v3.1.0.rst

- :meth:`DataFrameGroupBy.transform` now accepts list-like and dict arguments
  similar to :meth:`GroupBy.agg`, and supports :class:`NamedAgg` (:issue:`58318`)

Checklist

  • Tests added and passed
  • Type annotations added
  • Entry added to doc/source/whatsnew/v3.1.0.rst

@berasaikat
berasaikat requested a review from rhshadrach as a code owner April 11, 2026 15:35
@rhshadrach

Copy link
Copy Markdown
Member

Thanks for working on this, will take a look. If I don't update in a week, please send me a ping!

@berasaikat

Copy link
Copy Markdown
Contributor Author

@rhshadrach - Let me know if anything needs to be updated!

@rhshadrach rhshadrach left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks very good, some comments. Regarding using NamedAgg, we'll need to make a decision here as to the naming since this isn't great when used with e.g. cumsum. The implementation doesn't limit it to aggregation functions already. I'd support adding an alias NamedFunc and the (possibly) deprecating NamedAgg.

cc @jbrockmendel

Comment thread pandas/core/groupby/generic.py Outdated
raise NotImplementedError(
"Passing a list to SeriesGroupBy.transform is not yet supported "
"and is intended to be implemented in a future release. "
"See GH#58318."

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For references within the pandas code this would be fine, but for user-facing I think we should have the full URL.

Comment thread pandas/core/groupby/generic.py Outdated
1 1 0 1 0
2 2 2 2 2

.. versionchanged:: 3.0.0

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3.1.0 throughout.

Comment thread pandas/core/groupby/generic.py Outdated
if func is None:
# Named-aggregation style:
# .transform(val_sum=NamedAgg(column="val", aggfunc="sum"), ...)
transformed_func: dict = dict(kwargs.items())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary?

Comment thread pandas/core/groupby/generic.py Outdated
func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
)
else:
# Original single-function path; unchanged.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment will be not meaningful when this PR is merged; can you remove.

Comment thread pandas/core/groupby/generic.py Outdated
return self._transform_multiple_funcs(
transformed_func, *args, engine=engine, engine_kwargs=engine_kwargs
)
elif isinstance(func, dict):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is False for e.g. UserDict. Can you use is_dict_like.

Comment thread pandas/core/groupby/generic.py Outdated
return self._transform_multiple_funcs(
func, *args, engine=engine, engine_kwargs=engine_kwargs, **kwargs
)
elif isinstance(func, list):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar - use is_list_like.

Comment thread pandas/core/groupby/generic.py Outdated
"""
from pandas.core.reshape.concat import concat

if isinstance(func, dict):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Comment thread pandas/core/groupby/generic.py Outdated
# ── list path ────────────────────────────────────────────────────────
# Apply every func to every non-key column.
# _obj_with_exclusions already omits groupby keys and excluded columns.
assert isinstance(func, list)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.

Comment thread pandas/core/groupby/generic.py Outdated
Comment on lines +2919 to +2924
if (self._obj_with_exclusions.columns == column_name).sum() > 1:
raise ValueError(
f"Column label '{column_name}' is not unique in this DataFrame. "
"DataFrameGroupBy.transform with a dict or NamedAgg does not "
"support duplicate column names. See GH#58318."
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to impose this restriction?

@berasaikat

Copy link
Copy Markdown
Contributor Author

@rhshadrach - addressed all the code comments. Thank you!

Added pd.NamedFunc as an alias for pd.NamedAgg per reviewer suggestion. Deprecation of NamedAgg is left as a separate follow-up.

@jbrockmendel

Copy link
Copy Markdown
Member

I'd support adding an alias NamedFunc and the (possibly) deprecating NamedAgg.

Fine by me, no strong opinion.

@berasaikat
berasaikat requested a review from rhshadrach May 12, 2026 20:42

@rhshadrach rhshadrach left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! Just some minor style requests.

Comment thread pandas/core/groupby/generic.py Outdated
1 1 0 1 0
2 2 2 2 2

.. versionchanged:: 3.0.1

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of these versionchanged throughout the example, can you do a single one after the func argument description.

Comment thread pandas/core/groupby/generic.py Outdated
Comment on lines +2636 to +2637
``output_name=NamedFunc(column, func)`` for named transformation or
``output_name=NamedAgg(column, aggfunc)`` for named aggregation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes it seem like you have to use NamedAgg if you have an aggregation function, which is not the case. I'd suggest just leaving NamedAgg off here.

Suggested change
``output_name=NamedFunc(column, func)`` for named transformation or
``output_name=NamedAgg(column, aggfunc)`` for named aggregation.
``output_name=NamedFunc(column, func)``.

Comment thread pandas/core/groupby/generic.py Outdated
from pandas.core.reshape.concat import concat

if is_dict_like(func):
# ── dict / NamedAgg path ─────────────────────────────────────────

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# ── dict / NamedAgg path ─────────────────────────────────────────
# Also includes NamedAgg / NamedFunc

Comment thread pandas/core/groupby/generic.py Outdated
results.append(result)
return concat(results, axis=1)

# ── list path ────────────────────────────────────────────────────────

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# ── list path ────────────────────────────────────────────────────────
# list path

Comment thread pandas/core/groupby/generic.py Outdated

# ── list path ────────────────────────────────────────────────────────
# Apply every func to every non-key column.
# _obj_with_exclusions already omits groupby keys and excluded columns.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# _obj_with_exclusions already omits groupby keys and excluded columns.

I don't think this comment is adding anything.

@berasaikat
berasaikat requested a review from rhshadrach May 20, 2026 19:26
@berasaikat

Copy link
Copy Markdown
Contributor Author

Made the changes.

@rhshadrach rhshadrach left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should just refer to NamedFunc when it comes to transform; also can you add one more note in the whatsnew for NamedFunc. Something along the lines of:

- Added :class:`NamedFunc`, an alias to :class:`NamedAgg` for a more general name; either can accept arbitrary functions (:issue:`65164`)

Comment thread pandas/core/groupby/generic.py Outdated
- Numba JIT function with ``engine='numba'`` specified.
- List of strings/functions: applied to every non-key column,
returning a MultiIndex-column DataFrame ``(column, func)``.
- Dict ``{column: func}`` or ``{name: NamedAgg(column, func)}``:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedFunc here

Comment thread pandas/core/groupby/generic.py Outdated
applied per-column as specified.

.. versionchanged:: 3.1.0
Added support for list-like, dict, and :class:`NamedAgg` arguments.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedFunc here.

Comment thread doc/source/whatsnew/v3.1.0.rst Outdated
^^^^^^^^^^^^^^^^^^
- :class:`Period` now supports f-string formatting via ``__format__``, e.g. ``f"{period:%Y-%m}"`` (:issue:`48536`)
- :meth:`.DataFrameGroupBy.agg` now allows for the provided ``func`` to return a NumPy array (:issue:`63957`)
- :meth:`DataFrameGroupBy.transform` now accepts list-like and dict arguments similar to :meth:`GroupBy.agg`, and supports :class:`NamedAgg` (:issue:`58318`)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NamedFunc here

Comment thread pandas/core/api.py
"MultiIndex",
"NaT",
"NamedAgg",
"NamedFunc",

@rhshadrach rhshadrach May 28, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also be imported in pandas/__init__.py. There will likely be some API tests that break; those tests just then need updated with what to expect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Minimal updates needed in API tests.

@berasaikat
berasaikat requested a review from rhshadrach June 5, 2026 05:06

@rhshadrach rhshadrach left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@rhshadrach
rhshadrach requested a review from jbrockmendel June 8, 2026 20:21
@jbrockmendel

Copy link
Copy Markdown
Member

I don’t have bandwidth to do a proper review. No objections based on a quick glance. Happy to defer to rhshadrach

@rhshadrach
rhshadrach merged commit c79d638 into pandas-dev:main Jun 9, 2026
46 checks passed
@rhshadrach rhshadrach added Transformations e.g. cumsum, diff, rank Enhancement labels Jun 9, 2026
@rhshadrach rhshadrach added this to the 3.1 milestone Jun 9, 2026
@rhshadrach

Copy link
Copy Markdown
Member

Thanks @berasaikat

hamdanal pushed a commit to hamdanal/pandas that referenced this pull request Jun 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ENH: GroupBy.transform should accept similar arguments to GroupBy.agg

3 participants