Repository: databricks/koalas

Issue:
Creating MultiIndex via Index class.
pandas can create MultiIndex via Index class as shown below.

```python
>>> pidx = pd.Index([(1, 'a'), (2, 'b'), (3, 'c')])
>>> pidx
MultiIndex([(1, 'a'),
            (2, 'b'),
            (3, 'c')],
           )

>>> type(pidx)
<class 'pandas.core.indexes.multi.MultiIndex'>
```

Koalas also seems can do, but the type is still `Index`, not `MultiIndex`

```python
>>> kidx = ks.Index([(1, 'a'), (2, 'b'), (3, 'c')])
>>> kidx
MultiIndex([(1, 'a'),
            (2, 'b'),
            (3, 'c')],
           )

>>> type(kidx) # this must be <class 'databricks.koalas.indexes.MultiIndex'>
<class 'databricks.koalas.indexes.Index'>
```


Discussion:
Seems pandas use `__new__` when they create various kind of Index via single class. https://github.com/pandas-dev/pandas/blob/v1.1.2/pandas/core/indexes/base.py#L289-L430

Maybe we should follow this to support various of Index type with single class ??
Cool. Do you want to work on it?
@ueshin Sure, seems interesting :)

Resolving patch:
diff --git a/databricks/koalas/base.py b/databricks/koalas/base.py
--- a/databricks/koalas/base.py
+++ b/databricks/koalas/base.py
@@ -138,23 +138,17 @@ class IndexOpsMixin(object, metaclass=ABCMeta):
     """common ops mixin to support a unified interface / docs for Series / Index
 
     Assuming there are following attributes or properties and function.
-
-    :ivar _anchor: Parent's Koalas DataFrame
-    :type _anchor: ks.DataFrame
     """
 
-    def __init__(self, anchor: DataFrame):
-        assert anchor is not None
-        self._anchor = anchor
-
     @property
     @abstractmethod
     def _internal(self) -> InternalFrame:
         pass
 
     @property
+    @abstractmethod
     def _kdf(self) -> DataFrame:
-        return self._anchor
+        pass
 
     @abstractmethod
     def _with_new_scol(self, scol: spark.Column):
diff --git a/databricks/koalas/indexes.py b/databricks/koalas/indexes.py
--- a/databricks/koalas/indexes.py
+++ b/databricks/koalas/indexes.py
@@ -108,14 +108,32 @@ class Index(IndexOpsMixin):
     Index(['a', 'b', 'c'], dtype='object')
     """
 
-    def __init__(self, data: Union[DataFrame, list], dtype=None, name=None) -> None:
+    def __new__(cls, data: Union[DataFrame, list], dtype=None, name=None, names=None):
+        assert data is not None
+        instance = None
+
         if isinstance(data, DataFrame):
             assert dtype is None
             assert name is None
         else:
-            data = DataFrame(index=pd.Index(data=data, dtype=dtype, name=name))
+            if isinstance(data, list) and all([isinstance(item, tuple) for item in data]):
+                return MultiIndex.from_tuples(data, names=names)
+            if isinstance(instance, MultiIndex):
+                index = pd.Index(data=data, dtype=dtype, name=name, names=names)
+            else:
+                index = pd.Index(data=data, dtype=dtype, name=name)
+            data = DataFrame(index=index)
+
+        if instance is None:
+            instance = object.__new__(cls)
 
-        super().__init__(data)
+        instance._anchor = data
+
+        return instance
+
+    @property
+    def _kdf(self) -> DataFrame:
+        return self._anchor
 
     @property
     def _internal(self) -> InternalFrame:
@@ -2165,10 +2183,10 @@ class MultiIndex(Index):
                )
     """
 
-    def __init__(self, kdf: DataFrame):
+    def __new__(cls, kdf: DataFrame):
         assert len(kdf._internal._index_map) > 1
 
-        super().__init__(kdf)
+        return super().__new__(cls, data=kdf)
 
     @property
     def _internal(self):
diff --git a/databricks/koalas/series.py b/databricks/koalas/series.py
--- a/databricks/koalas/series.py
+++ b/databricks/koalas/series.py
@@ -341,13 +341,15 @@ class Series(Frame, IndexOpsMixin, Generic[T]):
     """
 
     def __init__(self, data=None, index=None, dtype=None, name=None, copy=False, fastpath=False):
+        assert data is not None
+
         if isinstance(data, DataFrame):
             assert dtype is None
             assert name is None
             assert not copy
             assert not fastpath
 
-            super().__init__(data)
+            self._anchor = data
             self._column_label = index
         else:
             if isinstance(data, pd.Series):
@@ -366,10 +368,14 @@ def __init__(self, data=None, index=None, dtype=None, name=None, copy=False, fas
                 internal = internal.copy(column_labels=[None])
             anchor = DataFrame(internal)
 
-            super().__init__(anchor)
+            self._anchor = anchor
             self._column_label = anchor._internal.column_labels[0]
             anchor._kseries = {self._column_label: self}
 
+    @property
+    def _kdf(self) -> DataFrame:
+        return self._anchor
+
     @property
     def _internal(self) -> InternalFrame:
         return self._kdf._internal.select_column(self._column_label)


Test patch:
diff --git a/databricks/koalas/tests/test_indexes.py b/databricks/koalas/tests/test_indexes.py
--- a/databricks/koalas/tests/test_indexes.py
+++ b/databricks/koalas/tests/test_indexes.py
@@ -1450,6 +1450,21 @@ def test_inferred_type(self):
         kmidx = ks.from_pandas(pmidx)
         self.assert_eq(pmidx.inferred_type, kmidx.inferred_type)
 
+    def test_multi_index_from_index(self):
+        tuples = [(1, "red"), (1, "blue"), (2, "red"), (2, "blue")]
+        pmidx = pd.Index(tuples)
+        kmidx = ks.Index(tuples)
+
+        self.assertTrue(isinstance(kmidx, ks.MultiIndex))
+        self.assert_eq(pmidx, kmidx)
+
+        # Specify the `names`
+        pmidx = pd.Index(tuples, names=["Hello", "Koalas"])
+        kmidx = ks.Index(tuples, names=["Hello", "Koalas"])
+
+        self.assertTrue(isinstance(kmidx, ks.MultiIndex))
+        self.assert_eq(pmidx, kmidx)
+
     @unittest.skipIf(
         LooseVersion(pd.__version__) < LooseVersion("0.24"),
         "MultiIndex.from_frame is new in pandas 0.24",