Repository: databricks/koalas

Issue:
Update index_names of _InternalFrame when Index name changed
When doing something with `_InternalFrame`,

sometimes i feel a little confusing when such like below case.

```python
>>> idx = ks.Index([1, 2, 3])
>>> idx
Int64Index([1, 2, 3], dtype='int64')
>>> idx.name = 'koalas'
>>> idx
Int64Index([1, 2, 3], dtype='int64', name='koalas')
>>> idx.name
'koalas'
```
it is okay when i just use `idx` itself with new name 'koalas' like above,

but the problem is occurred when i want to work with `_InternalFrame`,

since the internal `index_names` has not changed like the below.
(to be honest, the `index_map` and other internal factors also have same behavior, but let's try to talk about `index_names` for now)

```python
>>> idx
Int64Index([1, 2, 3], dtype='int64', name='koalas')
>>> idx._internal.index_names
[None]  # i expect this should be [('koalas',)]
```

and it's also the reason for several bugs like #1206 ,

so i think it might be better we check about this behavior that we update internal information also (or keep the existing behavior) when related factors are changed.
Fix Index.to_frame() to work properly when it has renamed
Resolves #1206 


- before fix
```python
>>> idx = ks.Index(['Ant', 'Bear', 'Cow'])
>>> idx.name = 'animal'
>>> idx.to_frame()  # 'animal' is not shown in created frame.
         0
Ant    Ant
Bear  Bear
Cow    Cow

>>> idx.to_pandas().to_frame()  # we have to fix like this
       animal
animal
Ant       Ant
Bear     Bear
Cow       Cow
```

- after fix
```python
>>> idx = ks.Index(['Ant', 'Bear', 'Cow'])
>>> idx.name = 'animal'
>>> idx.to_frame()  # 'animal' is shown in created frame like pandas
       animal
animal
Ant       Ant
Bear     Bear
Cow       Cow
```


Discussion:

Seems good if the build passes

Resolving patch:
diff --git a/databricks/koalas/indexes.py b/databricks/koalas/indexes.py
--- a/databricks/koalas/indexes.py
+++ b/databricks/koalas/indexes.py
@@ -309,19 +309,13 @@ def name(self, name: Union[str, Tuple[str, ...]]) -> None:
     def names(self) -> List[Union[str, Tuple[str, ...]]]:
         """Return names of the Index."""
         return [name if name is None or len(name) > 1 else name[0]
-                for name in self._kdf._internal.index_names]
+                for name in self._internal.index_names]
 
     @names.setter
     def names(self, names: List[Union[str, Tuple[str, ...]]]) -> None:
         if not is_list_like(names):
             raise ValueError('Names must be a list-like')
-        internal = self._kdf._internal
-        if len(internal.index_map) != len(names):
-            raise ValueError('Length of new names must be {}, got {}'
-                             .format(len(internal.index_map), len(names)))
-
-        names = [name if isinstance(name, (tuple, type(None))) else (name,) for name in names]
-        self._kdf._internal = internal.copy(index_map=list(zip(internal.index_columns, names)))
+        self.rename(names, inplace=True)
 
     @property
     def nlevels(self) -> int:
@@ -340,7 +334,8 @@ def nlevels(self) -> int:
         """
         return len(self._kdf._internal.index_columns)
 
-    def rename(self, name: Union[str, Tuple[str, ...]], inplace: bool = False):
+    def rename(self, name: Union[str, Tuple[str, ...], List[Union[str, Tuple[str, ...]]]],
+               inplace: bool = False):
         """
         Alter Index or MultiIndex name.
         Able to set new names without level. Defaults to returning new index.
@@ -393,15 +388,34 @@ def rename(self, name: Union[str, Tuple[str, ...]], inplace: bool = False):
                     ('b', 'y')],
                    names=['aloha', 'databricks'])
         """
-        if not inplace:
-            self = self.copy()
-        if isinstance(self, MultiIndex):
-            self.names = name  # type: ignore
+        names = self._verify_for_rename(name)
+
+        if inplace:
+            kdf = self._kdf
+        else:
+            kdf = self._kdf.copy()
+
+        kdf._internal = kdf._internal.copy(index_map=list(zip(kdf._internal.index_columns, names)))
+
+        idx = kdf.index
+        idx._internal._scol = self._scol
+        if inplace:
+            self._internal = idx._internal
+        else:
+            return idx
+
+    def _verify_for_rename(self, name):
+        if name is None or isinstance(name, tuple):
+            return [name]
+        elif isinstance(name, str):
+            return [(name,)]
+        elif is_list_like(name):
+            if len(self._internal.index_map) != len(name):
+                raise ValueError('Length of new names must be {}, got {}'
+                                 .format(len(self._internal.index_map), len(name)))
+            return [n if n is None or isinstance(n, tuple) else (n,) for n in name]
         else:
-            self.name = name
-        # return only when `inplace` is `False`
-        if not inplace:
-            return self
+            raise TypeError('name must be a hashable type')
 
     # TODO: add downcast parameter for fillna function
     def fillna(self, value):
@@ -946,11 +960,8 @@ def sort_values(self, ascending=True):
 
         internal = _InternalFrame(
             sdf=sdf.select(self._internal.index_scols),
-            index_map=self._kdf._internal.index_map)
-
-        result = DataFrame(internal).index
-
-        return result
+            index_map=self._internal.index_map)
+        return DataFrame(internal).index
 
     def sort(self, *args, **kwargs):
         """
@@ -1457,6 +1468,15 @@ def name(self) -> str:
     def name(self, name: str) -> None:
         raise PandasNotImplementedError(class_name='pd.MultiIndex', property_name='name')
 
+    def _verify_for_rename(self, name):
+        if is_list_like(name):
+            if len(self._internal.index_map) != len(name):
+                raise ValueError('Length of new names must be {}, got {}'
+                                 .format(len(self._internal.index_map), len(name)))
+            return [n if n is None or isinstance(n, tuple) else (n,) for n in name]
+        else:
+            raise TypeError('Must pass list-like as `names`.')
+
     def swaplevel(self, i=-2, j=-1):
         """
         Swap level i with level j.


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
@@ -93,6 +93,12 @@ def test_to_frame(self):
         self.assert_eq(repr(kidx.to_frame()), repr(pidx.to_frame()))
         self.assert_eq(repr(kidx.to_frame(index=False)), repr(pidx.to_frame(index=False)))
 
+        pidx.name = 'a'
+        kidx.name = 'a'
+
+        self.assert_eq(repr(kidx.to_frame()), repr(pidx.to_frame()))
+        self.assert_eq(repr(kidx.to_frame(index=False)), repr(pidx.to_frame(index=False)))
+
         if LooseVersion(pd.__version__) >= LooseVersion('0.24'):
             # The `name` argument is added in pandas 0.24.
             self.assert_eq(repr(kidx.to_frame(name='x')), repr(pidx.to_frame(name='x')))
@@ -174,6 +180,50 @@ def test_multi_index_names(self):
         with self.assertRaises(PandasNotImplementedError):
             kidx.name = 'renamed'
 
+    def test_index_rename(self):
+        pdf = pd.DataFrame(np.random.randn(10, 5),
+                           index=pd.Index([0, 1, 2, 3, 4, 5, 6, 7, 8, 9], name='x'))
+        kdf = ks.from_pandas(pdf)
+
+        pidx = pdf.index
+        kidx = kdf.index
+
+        self.assert_eq(kidx.rename('y'), pidx.rename('y'))
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+        kidx.rename('z', inplace=True)
+        pidx.rename('z', inplace=True)
+
+        self.assert_eq(kidx, pidx)
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+        self.assert_eq(kidx.rename(None), pidx.rename(None))
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+    def test_multi_index_rename(self):
+        arrays = [[1, 1, 2, 2], ['red', 'blue', 'red', 'blue']]
+        idx = pd.MultiIndex.from_arrays(arrays, names=('number', 'color'))
+        pdf = pd.DataFrame(np.random.randn(4, 5), idx)
+        kdf = ks.from_pandas(pdf)
+
+        pmidx = pdf.index
+        kmidx = kdf.index
+
+        self.assert_eq(kmidx.rename(['n', 'c']), pmidx.rename(['n', 'c']))
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+        kmidx.rename(['num', 'col'], inplace=True)
+        pmidx.rename(['num', 'col'], inplace=True)
+
+        self.assert_eq(kmidx, pmidx)
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+        self.assert_eq(kmidx.rename([None, None]), pmidx.rename([None, None]))
+        self.assert_eq(kdf.index.names, pdf.index.names)
+
+        self.assertRaises(TypeError, lambda: kmidx.rename('number'))
+        self.assertRaises(ValueError, lambda: kmidx.rename(['number']))
+
     def test_multi_index_levshape(self):
         pidx = pd.MultiIndex.from_tuples([('a', 'x', 1), ('b', 'y', 2)])
         kidx = ks.MultiIndex.from_tuples([('a', 'x', 1), ('b', 'y', 2)])