Repository: dask/dask

Issue:
Missing dask.Series.rename()
Found this today. Given

```python
import dask.dataframe as dd
import pandas as pd

my_df = dd.from_pandas(
    pd.DataFrame(
        data=list(range(0, 10)),
        columns=["a_column"]
    ),
    npartitions=1
)
```

This works:

```python
# pandas.Series.rename()
In []: my_df.a_column.compute().rename("other_name")
Out[]: 
0      0
1      1
2      2
3      3
4      4
5      5
6      6
7      7
8      8
9      9
Name: other_name, dtype: int64
```


This doesn't:

```python
# should be dd.Series.rename()
In []: my_df.a_column.rename("other_name").compute()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-26-e3083b2de283> in <module>()
----> 1 my_df.a_column.rename("other_name").compute()

AttributeError: 'Series' object has no attribute 'rename'
```

I've found [this answer in SO](https://stackoverflow.com/a/44357537/597349) from @martindurant (thanks, bro!).
Based on it (I don't know about dask's core, so please mind it), the following seems to do what I would expect from `dd.Series.rename()`:

```python
def f(df, name):
    df.name = name
    return df

In []: my_df.a_column.map_partitions(f, 'other_name')
Out[]: 
Dask Series Structure:
npartitions=1
0     int64
9      ...
Name: other_name, dtype: int64
Dask Name: f, 3 tasks
```

```
Pandas version: 0.21.0
Dask version:   0.16.0
```

Please, let me know if you need anything.

Thanks, guys!


Discussion:

Resolving patch:
diff --git a/dask/dataframe/core.py b/dask/dataframe/core.py
--- a/dask/dataframe/core.py
+++ b/dask/dataframe/core.py
@@ -1741,6 +1741,67 @@ def __repr__(self):
                                           name=key_split(self._name),
                                           task=len(self.dask))
 
+    def rename(self, index=None, inplace=False, sorted_index=False):
+        """Alter Series index labels or name
+
+        Function / dict values must be unique (1-to-1). Labels not contained in
+        a dict / Series will be left as-is. Extra labels listed don't throw an
+        error.
+
+        Alternatively, change ``Series.name`` with a scalar value.
+
+        Parameters
+        ----------
+        index : scalar, hashable sequence, dict-like or callable, optional
+            If dict-like or callable, the transformation is applied to the
+            index. Scalar or hashable sequence-like will alter the
+            ``Series.name`` attribute.
+        inplace : boolean, default False
+            Whether to return a new Series or modify this one inplace.
+        sorted_index : bool, default False
+            If true, the output ``Series`` will have known divisions inferred
+            from the input series and the transformation. Ignored for
+            non-callable/dict-like ``index`` or when the input series has
+            unknown divisions. Note that this may only be set to ``True`` if
+            you know that the transformed index is monotonicly increasing. Dask
+            will check that transformed divisions are monotonic, but cannot
+            check all the values between divisions, so incorrectly setting this
+            can result in bugs.
+
+        Returns
+        -------
+        renamed : Series
+
+        See Also
+        --------
+        pandas.Series.rename
+        """
+        from pandas.api.types import is_scalar, is_list_like, is_dict_like
+        if is_scalar(index) or (is_list_like(index) and not is_dict_like(index)):
+            res = self if inplace else self.copy()
+            res.name = index
+        else:
+            res = self.map_partitions(M.rename, index)
+            if self.known_divisions:
+                if sorted_index and (callable(index) or is_dict_like(index)):
+                    old = pd.Series(range(self.npartitions + 1),
+                                    index=self.divisions)
+                    new = old.rename(index).index
+                    if not new.is_monotonic_increasing:
+                        msg = ("sorted_index=True, but the transformed index "
+                               "isn't monotonic_increasing")
+                        raise ValueError(msg)
+                    res.divisions = tuple(new.tolist())
+                else:
+                    res = res.clear_divisions()
+            if inplace:
+                self.dask = res.dask
+                self._name = res._name
+                self.divisions = res.divisions
+                self._meta = res._meta
+                res = self
+        return res
+
     @derived_from(pd.Series)
     def round(self, decimals=0):
         return elemwise(M.round, self, decimals)


Test patch:
diff --git a/dask/dataframe/tests/test_dataframe.py b/dask/dataframe/tests/test_dataframe.py
--- a/dask/dataframe/tests/test_dataframe.py
+++ b/dask/dataframe/tests/test_dataframe.py
@@ -231,6 +231,52 @@ def test_rename_series():
     assert_eq(dind, ind)
 
 
+def test_rename_series_method():
+    # Series name
+    s = pd.Series([1, 2, 3, 4, 5, 6, 7], name='x')
+    ds = dd.from_pandas(s, 2)
+
+    assert_eq(ds.rename('y'), s.rename('y'))
+    assert ds.name == 'x'  # no mutation
+    assert_eq(ds.rename(), s.rename())
+
+    ds.rename('z', inplace=True)
+    s.rename('z', inplace=True)
+    assert ds.name == 'z'
+    assert_eq(ds, s)
+
+    # Series index
+    s = pd.Series(['a', 'b', 'c', 'd', 'e', 'f', 'g'], name='x')
+    ds = dd.from_pandas(s, 2)
+
+    for is_sorted in [True, False]:
+        res = ds.rename(lambda x: x ** 2, sorted_index=is_sorted)
+        assert_eq(res, s.rename(lambda x: x ** 2))
+        assert res.known_divisions == is_sorted
+
+        res = ds.rename(s, sorted_index=is_sorted)
+        assert_eq(res, s.rename(s))
+        assert res.known_divisions == is_sorted
+
+    with pytest.raises(ValueError):
+        ds.rename(lambda x: -x, sorted_index=True)
+    assert_eq(ds.rename(lambda x: -x), s.rename(lambda x: -x))
+
+    res = ds.rename(ds)
+    assert_eq(res, s.rename(s))
+    assert not res.known_divisions
+
+    ds2 = ds.clear_divisions()
+    res = ds2.rename(lambda x: x**2, sorted_index=True)
+    assert_eq(res, s.rename(lambda x: x**2))
+    assert not res.known_divisions
+
+    res = ds.rename(lambda x: x**2, inplace=True, sorted_index=True)
+    assert res is ds
+    s.rename(lambda x: x**2, inplace=True)
+    assert_eq(ds, s)
+
+
 def test_describe():
     # prepare test case which approx quantiles will be the same as actuals
     s = pd.Series(list(range(20)) * 4)