diff --git a/pandas/core/apply.py b/pandas/core/apply.py index bb39e18caeaa2..968244b581826 100644 --- a/pandas/core/apply.py +++ b/pandas/core/apply.py @@ -538,8 +538,21 @@ def normalize_dictlike_arg( raise SpecificationError("nested renamer is not supported") if obj.ndim != 1: + + # Check if it is an aggregate over multiple columns, + # this means a tuple of columns has been passed, + # so we need to unnest to check fo non existing columns. + func_keys = set() + for func_key in func.keys(): + if is_list_like(func_key): + # a tuple of columns has been passed, we unnest. + for key in func_key: + func_keys.add(key) + else: + func_keys.add(func_key) + # Check for missing columns on a frame - cols = set(func.keys()) - set(obj.columns) + cols = func_keys - set(obj.columns) if len(cols) > 0: cols_sorted = list(safe_sort(list(cols))) raise KeyError(f"Column(s) {cols_sorted} do not exist") diff --git a/pandas/core/frame.py b/pandas/core/frame.py index d9264e465dbef..783983f2243b9 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -3500,7 +3500,9 @@ def __getitem__(self, key): # We are left with two options: a single key, and a collection of keys, # We interpret tuples as collections only for non-MultiIndex - is_single_key = isinstance(key, tuple) or not is_list_like(key) + is_single_key = ( + isinstance(key, tuple) or not is_list_like(key) + ) and self.columns.nlevels > 1 if is_single_key: if self.columns.nlevels > 1: diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py index 0a693967fbb19..53d3492ea0039 100644 --- a/pandas/tests/groupby/aggregate/test_aggregate.py +++ b/pandas/tests/groupby/aggregate/test_aggregate.py @@ -1274,3 +1274,15 @@ def func(ser): expected = DataFrame([[1.0]], index=[1]) tm.assert_frame_equal(res, expected) + + +def test_named_agg_multiple_columns(): + # GH29268 + df = DataFrame({"a": [5, 7, 9, 11], "b": [8, 23, 5, 9]}) + df["group"] = [0, 0, 1, 1] + + result = df.groupby("group").agg( + diff_a_b=(("a", "b"), lambda x: x["a"].max() - x["b"].max()) + ) + expected = DataFrame({"diff_a_b": [16, 2]}, index=pd.Index([0, 1], name="group")) + tm.assert_frame_equal(result, expected)