diff --git a/doc/source/whatsnew/v3.0.0.rst b/doc/source/whatsnew/v3.0.0.rst index 76f6eab97c4eb..9e374db2fc856 100644 --- a/doc/source/whatsnew/v3.0.0.rst +++ b/doc/source/whatsnew/v3.0.0.rst @@ -823,6 +823,7 @@ Groupby/resample/rolling - Bug in :meth:`DataFrame.resample` and :meth:`Series.resample` were not keeping the index name when the index had :class:`ArrowDtype` timestamp dtype (:issue:`61222`) - Bug in :meth:`DataFrame.resample` changing index type to :class:`MultiIndex` when the dataframe is empty and using an upsample method (:issue:`55572`) - Bug in :meth:`DataFrameGroupBy.agg` that raises ``AttributeError`` when there is dictionary input and duplicated columns, instead of returning a DataFrame with the aggregation of all duplicate columns. (:issue:`55041`) +- Bug in :meth:`DataFrameGroupBy.agg` where applying a user-defined function to an empty DataFrame returned a Series instead of an empty DataFrame. (:issue:`61503`) - Bug in :meth:`DataFrameGroupBy.apply` and :meth:`SeriesGroupBy.apply` for empty data frame with ``group_keys=False`` still creating output index using group keys. (:issue:`60471`) - Bug in :meth:`DataFrameGroupBy.apply` that was returning a completely empty DataFrame when all return values of ``func`` were ``None`` instead of returning an empty DataFrame with the original columns and dtypes. (:issue:`57775`) - Bug in :meth:`DataFrameGroupBy.apply` with ``as_index=False`` that was returning :class:`MultiIndex` instead of returning :class:`Index`. (:issue:`58291`) diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py index b520ad69aae96..49b80337c700e 100644 --- a/pandas/core/groupby/generic.py +++ b/pandas/core/groupby/generic.py @@ -504,11 +504,13 @@ def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs) # inference. We default to using the existing dtype. # xref GH#51445 obj = self._obj_with_exclusions - return self.obj._constructor( - [], - name=self.obj.name, - index=self._grouper.result_index, - dtype=obj.dtype, + return self._wrap_aggregated_output( + self.obj._constructor( + [], + name=self.obj.name, + index=self._grouper.result_index, + dtype=obj.dtype, + ) ) return self._python_agg_general(func, *args, **kwargs) diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py index b7e6e55739c17..4f6c27bd327cb 100644 --- a/pandas/tests/groupby/aggregate/test_aggregate.py +++ b/pandas/tests/groupby/aggregate/test_aggregate.py @@ -1807,3 +1807,20 @@ def test_groupby_aggregation_func_list_multi_index_duplicate_columns(): index=Index(["level1.1", "level1.2"]), ) tm.assert_frame_equal(result, expected) + + +def test_groupby_aggregate_empty_builtin_sum(): + df = DataFrame(columns=["Group", "Data"]) + result = df.groupby(["Group"], as_index=False)["Data"].agg("sum") + expected = DataFrame(columns=["Group", "Data"]) + tm.assert_frame_equal(result, expected) + + +def test_groupby_aggregate_empty_udf(): + def func(x): + return sum(x) + + df = DataFrame(columns=["Group", "Data"]) + result = df.groupby(["Group"], as_index=False)["Data"].agg(func) + expected = DataFrame(columns=["Group", "Data"]) + tm.assert_frame_equal(result, expected)