Repository: pandas-dev/pandas

Issue:
BUG: DataFrameGroupBy.agg with lists doesn't respect as_index=False
```
df = DataFrame({"a1": [0, 0, 1], "a2": [2, 3, 3], "b": [4, 5, 6]})
# df = df.astype({"a1": "category", "a2": "category"})
gb = df.groupby(by=["a1", "a2"], as_index=False)
result = gb.agg(["sum"])
print(result)
#         b
#       sum
# a1 a2
# 0  2    4
#    3    5
# 1  3    6
```

The expected output is:

```
  a1 a2   b
        sum
0  0  2   4
1  0  3   5
2  1  3   6
```

In particular, the index is a RangeIndex and `a1` as well as `a2` are columns.


Discussion:
This looks a bit hard, but I'll give it a try. If I won't be able to solve this within a week, I'll re-assign
take
There is also missing data in a1
The single missing number aside (from a1 column) - as this seems like a separate issue.

What should be the expected output for gb.agg(["sum", "min"]) ? 
It outputs something this, which looks reasonable - since how otherwise can we output multiple agg columns ?
```
#         b    
#       sum min
# a1 a2        
# 0  2    4   4
#    3    5   5
# 1  3    6   6
```

Therefore, if I understand correctly - if we're passing only one argument to list, we want to overwrite the list behaviour & output sth similar to gb.agg("sum"), right ? My concern is that some could argue that current output for gb.agg(["sum"]) is correct - that if you're passing in a list, you do want the output df to include "sum" annotation.
> The single missing number aside (from a1 column) - as this seems like a separate issue.

Are you meaning something like the following?

```
#         b    
#       sum min
# a1 a2        
# 0  2    4   4
# 0  3    5   5
# 1  3    6   6
```

pandas prints a sparse representation for the index. A missing entry implicitly means it is repeated from above. 

> What should be the expected output for gb.agg(["sum", "min"]) ?

I originally had the incorrect columns in the OP; it should be a MultiIndex. The OP has been updated.
Hey, I'm sorry but I'll need to drop this issue. Pandas is a bit too much for me right now, I'll come back to this in the future
I believe this is because returning too early. As shown below, it didn't go through the `as_index` check. I will postpone its return until after checking `as_index`.

https://github.com/pandas-dev/pandas/blob/935244a9b9c0fe796de315cd381d6364719bfdc1/pandas/core/groupby/generic.py#L1331-L1333
take
But Categorical data seems to be tricky. The current 
```python
result = self._insert_inaxis_grouper(result)
result.index = default_index(len(result))
```
seems unable to deal with that.

Patch:
diff --git a/doc/source/whatsnew/v2.1.0.rst b/doc/source/whatsnew/v2.1.0.rst
index da95bca7fb2a6..cbb9ef20db647 100644
--- a/doc/source/whatsnew/v2.1.0.rst
+++ b/doc/source/whatsnew/v2.1.0.rst
@@ -419,6 +419,7 @@ Groupby/resample/rolling
   grouped :class:`Series` or :class:`DataFrame` was a :class:`DatetimeIndex`, :class:`TimedeltaIndex`
   or :class:`PeriodIndex`, and the ``groupby`` method was given a function as its first argument,
   the function operated on the whole index rather than each element of the index. (:issue:`51979`)
+- Bug in :meth:`DataFrameGroupBy.agg` with lists not respecting ``as_index=False`` (:issue:`52849`)
 - Bug in :meth:`DataFrameGroupBy.apply` causing an error to be raised when the input :class:`DataFrame` was subset as a :class:`DataFrame` after groupby (``[['a']]`` and not ``['a']``) and the given callable returned :class:`Series` that were not all indexed the same. (:issue:`52444`)
 - Bug in :meth:`DataFrameGroupBy.apply` raising a ``TypeError`` when selecting multiple columns and providing a function that returns ``np.ndarray`` results (:issue:`18930`)
 - Bug in :meth:`GroupBy.groups` with a datetime key in conjunction with another key produced incorrect number of group keys (:issue:`51158`)
diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py
index 838e4d39dee10..37ef04f17a2e5 100644
--- a/pandas/core/groupby/generic.py
+++ b/pandas/core/groupby/generic.py
@@ -44,6 +44,7 @@
     is_bool,
     is_dict_like,
     is_integer_dtype,
+    is_list_like,
     is_numeric_dtype,
     is_scalar,
 )
@@ -1397,7 +1398,11 @@ def aggregate(self, func=None, *args, engine=None, engine_kwargs=None, **kwargs)
         op = GroupByApply(self, func, args=args, kwargs=kwargs)
         result = op.agg()
         if not is_dict_like(func) and result is not None:
-            return result
+            # GH #52849
+            if not self.as_index and is_list_like(func):
+                return result.reset_index()
+            else:
+                return result
         elif relabeling:
             # this should be the only (non-raising) case with relabeling
             # used reordered index of columns


Test patch:
diff --git a/pandas/tests/groupby/aggregate/test_aggregate.py b/pandas/tests/groupby/aggregate/test_aggregate.py
index 24b42421b3208..185b50e9d7833 100644
--- a/pandas/tests/groupby/aggregate/test_aggregate.py
+++ b/pandas/tests/groupby/aggregate/test_aggregate.py
@@ -1586,3 +1586,16 @@ def test_agg_multiple_with_as_index_false_subset_to_a_single_column():
     result = gb.agg(["sum", "mean"])
     expected = DataFrame({"a": [1, 2], "sum": [7, 5], "mean": [3.5, 5.0]})
     tm.assert_frame_equal(result, expected)
+
+
+def test_agg_with_as_index_false_with_list():
+    # GH#52849
+    df = DataFrame({"a1": [0, 0, 1], "a2": [2, 3, 3], "b": [4, 5, 6]})
+    gb = df.groupby(by=["a1", "a2"], as_index=False)
+    result = gb.agg(["sum"])
+
+    expected = DataFrame(
+        data=[[0, 2, 4], [0, 3, 5], [1, 3, 6]],
+        columns=MultiIndex.from_tuples([("a1", ""), ("a2", ""), ("b", "sum")]),
+    )
+    tm.assert_frame_equal(result, expected)
diff --git a/pandas/tests/groupby/test_categorical.py b/pandas/tests/groupby/test_categorical.py
index 6ce17e406db65..c0704d9684574 100644
--- a/pandas/tests/groupby/test_categorical.py
+++ b/pandas/tests/groupby/test_categorical.py
@@ -2067,11 +2067,8 @@ def test_agg_list(request, as_index, observed, reduction_func, test_series, keys
     if as_index and (test_series or reduction_func == "size"):
         expected = expected.to_frame(reduction_func)
     if not test_series:
-        if not as_index:
-            # TODO: GH#52849 - as_index=False is not respected
-            expected = expected.set_index(keys)
-        expected.columns = MultiIndex(
-            levels=[["b"], [reduction_func]], codes=[[0], [0]]
+        expected.columns = MultiIndex.from_tuples(
+            [(ind, "") for ind in expected.columns[:-1]] + [("b", reduction_func)]
         )
     elif not as_index:
         expected.columns = keys + [reduction_func]