Repository: pandas-dev/pandas

Issue:
DataFrame groupby with categoricals and aggreggation with pd.DataFrame.sum with skipna leads to wrong column name
#### Problem description
Consider the following data frame:

```
df = pd.DataFrame(data=(('Bob', 2),  ('Greg', None), ('Greg', 6)), columns=['Name', 'Items'])
```

```
   Name  Items
0   Bob    2.0
1  Greg    NaN
2  Greg    6.0
```

Now I want to group by `Name` and sum the `Items`, but I want the sum to be NaN if there are NaN elements. Due to a bug in pandas (https://github.com/pandas-dev/pandas/issues/20824) I cannot simply do

```
df.groupby('Name', observed=True).sum(skipna=False).reset_index()
```

because that results in:

```
   Name  Items
0   Bob    2.0
1  Greg    6.0
```

which is wrong because it's skipping the `NaN` for Greg even though it shouldn't (hence the bug). Thus I'm using the following workaround to get the correct result:

```
df.groupby('Name', observed=True).agg(pd.DataFrame.sum, skipna=False).reset_index()
```

which results in the expected:

```
   Name  Items
0   Bob    2.0
1  Greg    NaN
```

However, if we change the `Name` column to categorical then the resulting column names are wrong:

```
df_cat = df.copy()
df_cat['Name'] = df_cat['Name'].astype('category')
df_cat.groupby('Name', observed=True).agg(pd.DataFrame.sum, skipna=False).reset_index()
```

which prints:

```
  index  Items
0   Bob    2.0
1  Greg    NaN
```

As you can see, **the column that should be labelled `Name` is now called `index`.**

#### Expected Output
The same as the non-categorical version, i.e.:
```
   Name  Items
0   Bob    2.0
1  Greg    NaN
```

#### Output of ``pd.show_versions()``

INSTALLED VERSIONS
------------------
commit           : None
python           : 3.7.3.final.0
python-bits      : 64
OS               : Windows
OS-release       : 7
machine          : AMD64
processor        : Intel64 Family 6 Model 60 Stepping 3, GenuineIntel
byteorder        : little
LC_ALL           : None
LANG             : None
LOCALE           : None.None

pandas           : 0.25.1
numpy            : 1.16.3
pytz             : 2019.1
dateutil         : 2.8.0
pip              : 19.1
setuptools       : 41.0.1
Cython           : None
pytest           : None
hypothesis       : None
sphinx           : None
blosc            : None
feather          : None
xlsxwriter       : None
lxml.etree       : 4.2.5
html5lib         : None
pymysql          : None
psycopg2         : 2.8.2 (dt dec pq3 ext lo64)
jinja2           : 2.10.1
IPython          : 7.5.0
pandas_datareader: None
bs4              : 4.7.1
bottleneck       : None
fastparquet      : None
gcsfs            : None
lxml.etree       : 4.2.5
matplotlib       : 3.0.3
numexpr          : None
odfpy            : None
openpyxl         : None
pandas_gbq       : None
pyarrow          : 0.13.0
pytables         : None
s3fs             : None
scipy            : 1.2.1
sqlalchemy       : 1.3.3
tables           : None
xarray           : None
xlrd             : 1.2.0
xlwt             : None
xlsxwriter       : None



Discussion:

Resolving patch:
diff --git a/pandas/core/groupby/grouper.py b/pandas/core/groupby/grouper.py
--- a/pandas/core/groupby/grouper.py
+++ b/pandas/core/groupby/grouper.py
@@ -330,7 +330,8 @@ def __init__(
                 self._group_index = CategoricalIndex(
                     Categorical.from_codes(
                         codes=codes, categories=categories, ordered=self.grouper.ordered
-                    )
+                    ),
+                    name=self.name,
                 )
 
             # we are done


Test patch:
diff --git a/pandas/tests/groupby/test_categorical.py b/pandas/tests/groupby/test_categorical.py
--- a/pandas/tests/groupby/test_categorical.py
+++ b/pandas/tests/groupby/test_categorical.py
@@ -674,7 +674,7 @@ def test_preserve_categories():
 
     # ordered=True
     df = DataFrame({"A": Categorical(list("ba"), categories=categories, ordered=True)})
-    index = CategoricalIndex(categories, categories, ordered=True)
+    index = CategoricalIndex(categories, categories, ordered=True, name="A")
     tm.assert_index_equal(
         df.groupby("A", sort=True, observed=False).first().index, index
     )
@@ -684,8 +684,8 @@ def test_preserve_categories():
 
     # ordered=False
     df = DataFrame({"A": Categorical(list("ba"), categories=categories, ordered=False)})
-    sort_index = CategoricalIndex(categories, categories, ordered=False)
-    nosort_index = CategoricalIndex(list("bac"), list("bac"), ordered=False)
+    sort_index = CategoricalIndex(categories, categories, ordered=False, name="A")
+    nosort_index = CategoricalIndex(list("bac"), list("bac"), ordered=False, name="A")
     tm.assert_index_equal(
         df.groupby("A", sort=True, observed=False).first().index, sort_index
     )
@@ -1193,3 +1193,17 @@ def test_groupby_categorical_axis_1(code):
     result = df.groupby(cat, axis=1).mean()
     expected = df.T.groupby(cat, axis=0).mean().T
     assert_frame_equal(result, expected)
+
+
+def test_groupby_cat_preserves_structure(observed):
+    # GH 28787
+    df = DataFrame([("Bob", 1), ("Greg", 2)], columns=["Name", "Item"])
+    expected = df.copy()
+
+    result = (
+        df.groupby("Name", observed=observed)
+        .agg(pd.DataFrame.sum, skipna=True)
+        .reset_index()
+    )
+
+    assert_frame_equal(result, expected)