Repository: pandas-dev/pandas

Issue:
Resampling using `nunique` causes multi-level columns to lose their level names
#### Problem description

Resampling using `nunique` causes multi-level columns to lose their level names.
https://nbviewer.jupyter.org/gist/taljaards/20e945b7572aea1f4eb4aa4c6e823037

I only ran into this issue with `nunique`; I do not know if this is also the case for some other functions.

#### Expected Output
To not drop the level names, as in the first resample example.

#### Output of ``pd.show_versions()``

<details>

```
import pandas as pd
pd.show_versions()
Backend TkAgg is interactive backend. Turning interactive mode on.
Matplotlib support failed
Traceback (most recent call last):
  File "C:\Users\Admin\AppData\Local\JetBrains\Toolbox\apps\PyCharm-P\ch-0\183.3647.8\helpers\pydev\_pydev_bundle\pydev_import_hook.py", line 23, in do_import
    succeeded = activate_func()
  File "C:\Users\Admin\AppData\Local\JetBrains\Toolbox\apps\PyCharm-P\ch-0\183.3647.8\helpers\pydev\pydev_ipython\matplotlibtools.py", line 141, in activate_pylab
    pylab = sys.modules['pylab']
KeyError: 'pylab'
INSTALLED VERSIONS
------------------
commit: None
python: 3.6.6.final.0
python-bits: 64
OS: Windows
OS-release: 10
machine: AMD64
processor: Intel64 Family 6 Model 142 Stepping 9, GenuineIntel
byteorder: little
LC_ALL: None
LANG: None
LOCALE: None.None
pandas: 0.23.4
pytest: 3.8.2
pip: 18.1
setuptools: 40.4.3
Cython: 0.28.5
numpy: 1.15.1
scipy: 1.1.0
pyarrow: None
xarray: None
IPython: 7.0.1
sphinx: 1.8.1
patsy: 0.5.0
dateutil: 2.7.3
pytz: 2018.5
blosc: None
bottleneck: 1.2.1
tables: 3.4.4
numexpr: 2.6.8
feather: None
matplotlib: 3.0.0
openpyxl: 2.5.8
xlrd: 1.1.0
xlwt: 1.3.0
xlsxwriter: 1.1.1
lxml: 4.2.5
bs4: 4.6.3
html5lib: 1.0.1
sqlalchemy: 1.2.12
pymysql: None
psycopg2: None
jinja2: 2.10
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None
```
</details>



Discussion:
I'll have a look at this today.

Resolving patch:
diff --git a/pandas/core/groupby/generic.py b/pandas/core/groupby/generic.py
--- a/pandas/core/groupby/generic.py
+++ b/pandas/core/groupby/generic.py
@@ -1579,6 +1579,7 @@ def groupby_series(obj, col=None):
             from pandas.core.reshape.concat import concat
             results = [groupby_series(obj[col], col) for col in obj.columns]
             results = concat(results, axis=1)
+            results.columns.names = obj.columns.names
 
         if not self.as_index:
             results.index = ibase.default_index(len(results))


Test patch:
diff --git a/pandas/tests/groupby/test_function.py b/pandas/tests/groupby/test_function.py
--- a/pandas/tests/groupby/test_function.py
+++ b/pandas/tests/groupby/test_function.py
@@ -897,6 +897,15 @@ def test_nunique_with_timegrouper():
     tm.assert_series_equal(result, expected)
 
 
+def test_nunique_preserves_column_level_names():
+    # GH 23222
+    test = pd.DataFrame([1, 2, 2],
+                        columns=pd.Index(['A'], name="level_0"))
+    result = test.groupby([0, 0, 0]).nunique()
+    expected = pd.DataFrame([2], columns=test.columns)
+    tm.assert_frame_equal(result, expected)
+
+
 # count
 # --------------------------------
 
diff --git a/pandas/tests/resample/test_datetime_index.py b/pandas/tests/resample/test_datetime_index.py
--- a/pandas/tests/resample/test_datetime_index.py
+++ b/pandas/tests/resample/test_datetime_index.py
@@ -1135,6 +1135,15 @@ def test_resample_nunique():
     assert_series_equal(result, expected)
 
 
+def test_resample_nunique_preserves_column_level_names():
+    # see gh-23222
+    df = tm.makeTimeDataFrame(freq="1D").abs()
+    df.columns = pd.MultiIndex.from_arrays([df.columns.tolist()] * 2,
+                                           names=["lev0", "lev1"])
+    result = df.resample("1h").nunique()
+    tm.assert_index_equal(df.columns, result.columns)
+
+
 def test_resample_nunique_with_date_gap():
     # GH 13453
     index = pd.date_range('1-1-2000', '2-15-2000', freq='h')