Repository: databricks/koalas

Issue:
DataFrame.droplevel
Implement `DataFrame.droplevel`.

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.droplevel.html


Discussion:
I would like to work on this.
@jijosg Let's go! :D
Since i am a newbie to this, please let me know if this question is not relevant.
The original implementation of droplevel refers to _get_axis(axis) method to get the level names, are we supposed to implement that method as well? I couldnt find the existing implementation of such a function. @itholic  if you could guide me forward that would be helpful. 
Thanks
Hi, @jijosg .
I don't think we have to necessarily implement `_get_axis` for now.
Just feel free to implement method whatever you want, and I'll review it for you after then :D

Resolving patch:
diff --git a/databricks/koalas/frame.py b/databricks/koalas/frame.py
--- a/databricks/koalas/frame.py
+++ b/databricks/koalas/frame.py
@@ -6181,6 +6181,115 @@ def count(self, axis=None):
             Frame._count_expr, name="count", axis=axis, numeric_only=False
         )
 
+    def droplevel(self, level, axis=0) -> "DataFrame":
+        """
+        Return DataFrame with requested index / column level(s) removed.
+
+        Parameters
+        ----------
+        level: int, str, or list-like
+            If a string is given, must be the name of a level If list-like, elements must
+            be names or positional indexes of levels.
+
+        axis: {0 or ‘index’, 1 or ‘columns’}, default 0
+
+        Returns
+        -------
+        DataFrame with requested index / column level(s) removed.
+
+        Examples
+        --------
+        >>> df = ks.DataFrame(
+        ...     [[3, 4], [7, 8], [11, 12]],
+        ...     index=pd.MultiIndex.from_tuples([(1, 2), (5, 6), (9, 10)], names=["a", "b"]),
+        ... )
+
+        >>> df.columns = pd.MultiIndex.from_tuples([
+        ...   ('c', 'e'), ('d', 'f')
+        ... ], names=['level_1', 'level_2'])
+
+        >>> df  # doctest: +NORMALIZE_WHITESPACE
+        level_1   c   d
+        level_2   e   f
+        a b
+        1 2      3   4
+        5 6      7   8
+        9 10    11  12
+        >>> df.droplevel('a')  # doctest: +NORMALIZE_WHITESPACE
+        level_1   c   d
+        level_2   e   f
+        b
+        2        3   4
+        6        7   8
+        10      11  12
+
+        >>> df.droplevel('level_2', axis=1)  # doctest: +NORMALIZE_WHITESPACE
+        level_1   c   d
+        a b
+        1 2      3   4
+        5 6      7   8
+        9 10    11  12
+        """
+        axis = validate_axis(axis)
+        kdf = self.copy()
+        if axis == 0:
+            names = self.index.names
+            nlevels = self.index.nlevels
+            if not isinstance(level, (tuple, list)):
+                level = [level]
+
+            for n in level:
+                if isinstance(n, int) and (n > nlevels - 1):
+                    raise IndexError(
+                        "Too many levels: Index has only {} levels, not {}".format(nlevels, n + 1)
+                    )
+                if isinstance(n, (str, tuple)) and (n not in names):
+                    raise KeyError("Level {} not found".format(n))
+
+            if len(level) >= nlevels:
+                raise ValueError(
+                    "Cannot remove {} levels from an index with {} "
+                    "levels: at least one level must be "
+                    "left.".format(len(level), nlevels)
+                )
+            drop_spark_index_columns = list()
+            index_spark_column_names = kdf._internal.index_spark_column_names
+            for n in level:
+                if isinstance(n, int):
+                    index_order = n
+                elif isinstance(n, (str, tuple)):
+                    index_order = kdf.index.names.index(n)
+                drop_spark_index_columns.append(index_spark_column_names[index_order])
+            sdf = kdf._internal.spark_frame
+            sdf = sdf.drop(*drop_spark_index_columns)
+            index_map = kdf._internal.index_map.copy()
+            for drop_spark_index_column in drop_spark_index_columns:
+                index_map.pop(drop_spark_index_column)
+            internal_frame = kdf._internal.copy(spark_frame=sdf, index_map=index_map)
+            kdf = DataFrame(internal_frame)
+        elif axis == 1:
+            names = self.columns.names
+            nlevels = self.columns.nlevels
+            if not isinstance(level, (tuple, list)):
+                level = [level]
+
+            for n in level:
+                if isinstance(n, int) and (n > nlevels - 1):
+                    raise IndexError(
+                        "Too many levels: Column has only {} levels, not {}".format(nlevels, n + 1)
+                    )
+                if isinstance(n, (str, tuple)) and (n not in names):
+                    raise KeyError("Level {} not found".format(n))
+
+            if len(level) >= nlevels:
+                raise ValueError(
+                    "Cannot remove {} levels from an index with {} "
+                    "levels: at least one level must be "
+                    "left.".format(len(level), nlevels)
+                )
+            kdf.columns = kdf.columns.droplevel(level)
+        return kdf
+
     def drop(
         self,
         labels=None,
diff --git a/databricks/koalas/missing/frame.py b/databricks/koalas/missing/frame.py
--- a/databricks/koalas/missing/frame.py
+++ b/databricks/koalas/missing/frame.py
@@ -46,7 +46,6 @@ class _MissingPandasLikeDataFrame(object):
     corrwith = _unsupported_function("corrwith")
     cov = _unsupported_function("cov")
     dot = _unsupported_function("dot")
-    droplevel = _unsupported_function("droplevel")
     ewm = _unsupported_function("ewm")
     first = _unsupported_function("first")
     infer_objects = _unsupported_function("infer_objects")


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
@@ -553,6 +553,24 @@ def test_dot_in_column_name(self):
             ks.Series([1]),
         )
 
+    def test_droplevel(self):
+        # droplevel is new in pandas 0.24.0
+        if LooseVersion(pd.__version__) >= LooseVersion("0.24.0"):
+            pdf = (
+                pd.DataFrame([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
+                .set_index([0, 1])
+                .rename_axis(["a", "b"])
+            )
+
+            pdf.columns = pd.MultiIndex.from_tuples(
+                [("c", "e"), ("d", "f")], names=["level_1", "level_2"]
+            )
+            kdf = ks.from_pandas(pdf)
+            self.assert_eq(pdf.droplevel("a"), kdf.droplevel("a"))
+            self.assert_eq(pdf.droplevel("level_1", axis=1), kdf.droplevel("level_1", axis=1))
+            self.assertRaises(ValueError, lambda: kdf.droplevel(["a", "b"]))
+            self.assertRaises(ValueError, lambda: kdf.droplevel(["level_1", "level_2"], axis=1))
+
     def test_drop(self):
         pdf = pd.DataFrame({"x": [1, 2], "y": [3, 4], "z": [5, 6]}, index=np.random.rand(2))
         kdf = ks.from_pandas(pdf)