Repository: databricks/koalas

Issue:
Multi-index conversion
Not sure if this is a bug or a feature request.

The following docstring test would fail in Koalas:

```python
        >>> index = pd.MultiIndex.from_tuples([('bird', 'falcon'),
        ...                                    ('bird', 'parrot'),
        ...                                    ('mammal', 'lion'),
        ...                                    ('mammal', 'monkey')],
        ...                                   names=['class', 'name'])
        >>> columns = pd.MultiIndex.from_tuples([('speed', 'max'),
        ...                                      ('species', 'type')])
        >>> df = pd.DataFrame([(389.0, 'fly'),
        ...                    ( 24.0, 'fly'),
        ...                    ( 80.5, 'run'),
        ...                    (np.nan, 'jump')],
        ...                   index=index,
        ...                   columns=columns)
        >>> df = ks.DataFrame(df)
        >>> df
                       speed species
                         max    type
        class  name
        bird   falcon  389.0     fly
               parrot   24.0     fly
        mammal lion     80.5     run
               monkey    NaN    jump
```

```
**********************************************************************
File "/Users/rxin/workspace/koalas/databricks/koalas/frame.py", line 1043, in reset_index
Failed example:
    df
Expected:
                   speed species
                     max    type
    class  name
    bird   falcon  389.0     fly
           parrot   24.0     fly
    mammal lion     80.5     run
           monkey    NaN    jump
Got:
                   ('speed', 'max') ('species', 'type')
    class  name                                        
    bird   falcon             389.0                 fly
           parrot              24.0                 fly
    mammal lion                80.5                 run
           monkey               NaN                jump
```

Seems like we convert the label into `('speed', 'max')`


Discussion:
When this is fixed, we should fix this TODO: https://github.com/databricks/koalas/pull/300/files#diff-5ba644c40e914732959b9b4867f30c53R1030



Resolving patch:
diff --git a/databricks/koalas/frame.py b/databricks/koalas/frame.py
--- a/databricks/koalas/frame.py
+++ b/databricks/koalas/frame.py
@@ -1670,7 +1670,7 @@ def set_index(self, keys, drop=True, append=False, inplace=False):
         else:
             return DataFrame(internal)
 
-    def reset_index(self, level=None, drop=False, inplace=False):
+    def reset_index(self, level=None, drop=False, inplace=False, col_level=0, col_fill=''):
         """Reset the index, or a level of it.
 
         For DataFrame with multi-level index, return new DataFrame with labeling information in
@@ -1688,6 +1688,13 @@ def reset_index(self, level=None, drop=False, inplace=False):
             the index to the default integer index.
         inplace : bool, default False
             Modify the DataFrame in place (do not create a new object).
+        col_level : int or str, default 0
+            If the columns have multiple levels, determines which level the
+            labels are inserted into. By default it is inserted into the first
+            level.
+        col_fill : object, default ''
+            If the columns have multiple levels, determines how the other
+            levels are named. If None then the index name is repeated.
 
         Returns
         -------
@@ -1733,8 +1740,79 @@ class  max_speed
         1    bird       24.0
         2  mammal       80.5
         3  mammal        NaN
+
+        You can also use `reset_index` with `MultiIndex`.
+
+        >>> index = pd.MultiIndex.from_tuples([('bird', 'falcon'),
+        ...                                    ('bird', 'parrot'),
+        ...                                    ('mammal', 'lion'),
+        ...                                    ('mammal', 'monkey')],
+        ...                                   names=['class', 'name'])
+        >>> columns = pd.MultiIndex.from_tuples([('speed', 'max'),
+        ...                                      ('species', 'type')])
+        >>> df = ks.DataFrame([(389.0, 'fly'),
+        ...                    ( 24.0, 'fly'),
+        ...                    ( 80.5, 'run'),
+        ...                    (np.nan, 'jump')],
+        ...                   index=index,
+        ...                   columns=columns)
+        >>> df  # doctest: +NORMALIZE_WHITESPACE
+                       speed species
+                         max    type
+        class  name
+        bird   falcon  389.0     fly
+               parrot   24.0     fly
+        mammal lion     80.5     run
+               monkey    NaN    jump
+
+        If the index has multiple levels, we can reset a subset of them:
+
+        >>> df.reset_index(level='class')  # doctest: +NORMALIZE_WHITESPACE
+                 class  speed species
+                          max    type
+        name
+        falcon    bird  389.0     fly
+        parrot    bird   24.0     fly
+        lion    mammal   80.5     run
+        monkey  mammal    NaN    jump
+
+        If we are not dropping the index, by default, it is placed in the top
+        level. We can place it in another level:
+
+        >>> df.reset_index(level='class', col_level=1)  # doctest: +NORMALIZE_WHITESPACE
+                        speed species
+                 class    max    type
+        name
+        falcon    bird  389.0     fly
+        parrot    bird   24.0     fly
+        lion    mammal   80.5     run
+        monkey  mammal    NaN    jump
+
+        When the index is inserted under another level, we can specify under
+        which one with the parameter `col_fill`:
+
+        >>> df.reset_index(level='class', col_level=1,
+        ...                col_fill='species')  # doctest: +NORMALIZE_WHITESPACE
+                      species  speed species
+                        class    max    type
+        name
+        falcon           bird  389.0     fly
+        parrot           bird   24.0     fly
+        lion           mammal   80.5     run
+        monkey         mammal    NaN    jump
+
+        If we specify a nonexistent level for `col_fill`, it is created:
+
+        >>> df.reset_index(level='class', col_level=1,
+        ...                col_fill='genus')  # doctest: +NORMALIZE_WHITESPACE
+                        genus  speed species
+                        class    max    type
+        name
+        falcon           bird  389.0     fly
+        parrot           bird   24.0     fly
+        lion           mammal   80.5     run
+        monkey         mammal    NaN    jump
         """
-        # TODO: add example of MultiIndex back. See https://github.com/databricks/koalas/issues/301
         if len(self._internal.index_map) == 0:
             raise NotImplementedError('Can\'t reset index because there is no index.')
 
@@ -1795,8 +1873,21 @@ def rename(index):
 
         internal = self._internal.copy(
             data_columns=[column for column, _ in new_index_map] + self._internal.data_columns,
-            index_map=index_map)
-        columns = [name for _, name in new_index_map] + self._internal.data_columns
+            index_map=index_map,
+            column_index=None)
+
+        if self._internal.column_index is not None:
+            column_depth = len(self._internal.column_index[0])
+            if col_level >= column_depth:
+                raise IndexError('Too many levels: Index has only {} levels, not {}'
+                                 .format(column_depth, col_level + 1))
+            columns = pd.MultiIndex.from_tuples(
+                [tuple(name if i == col_level else col_fill
+                       for i in range(column_depth))
+                 for _, name in new_index_map] + self._internal.column_index)
+        else:
+            columns = [name for _, name in new_index_map] + self._internal.data_columns
+
         if inplace:
             self._internal = internal
             self.columns = columns


Test patch:
diff --git a/databricks/koalas/tests/test_dataframe.py b/databricks/koalas/tests/test_dataframe.py
--- a/databricks/koalas/tests/test_dataframe.py
+++ b/databricks/koalas/tests/test_dataframe.py
@@ -96,6 +96,35 @@ def test_dataframe_multilevel_columns(self):
         self.assertRaises(KeyError, lambda: kdf['z'])
         self.assertRaises(AttributeError, lambda: kdf.z)
 
+    def test_reset_index_with_multilevel_columns(self):
+        index = pd.MultiIndex.from_tuples([('bird', 'falcon'),
+                                           ('bird', 'parrot'),
+                                           ('mammal', 'lion'),
+                                           ('mammal', 'monkey')],
+                                          names=['class', 'name'])
+        columns = pd.MultiIndex.from_tuples([('speed', 'max'),
+                                             ('species', 'type')])
+        pdf = pd.DataFrame([(389.0, 'fly'),
+                            (24.0, 'fly'),
+                            (80.5, 'run'),
+                            (np.nan, 'jump')],
+                           index=index,
+                           columns=columns)
+        kdf = ks.from_pandas(pdf)
+
+        self.assert_eq(kdf, pdf)
+        self.assert_eq(kdf.reset_index(), pdf.reset_index())
+        self.assert_eq(kdf.reset_index(level='class'), pdf.reset_index(level='class'))
+        self.assert_eq(kdf.reset_index(level='class', col_level=1),
+                       pdf.reset_index(level='class', col_level=1))
+        self.assert_eq(kdf.reset_index(level='class', col_level=1, col_fill='species'),
+                       pdf.reset_index(level='class', col_level=1, col_fill='species'))
+        self.assert_eq(kdf.reset_index(level='class', col_level=1, col_fill='genus'),
+                       pdf.reset_index(level='class', col_level=1, col_fill='genus'))
+
+        with self.assertRaisesRegex(IndexError, 'Index has only 2 levels, not 3'):
+            kdf.reset_index(col_level=2)
+
     def test_repr_cache_invalidation(self):
         # If there is any cache, inplace operations should invalidate it.
         df = ks.range(10)