API: change unique to return Index
State: closed

- [x] closes #13395, closes #13565
- [x] tests added / passed
- [x] passes `git diff upstream/master | flake8 --diff`
- [x] whatsnew entry


Labels:
name: Bug
name: Indexing
description: Related to indexing on series/frames, not to indexes themselves
name: API Design
name: Compat
description: pandas objects compatability with Numpy or Python functions

Comments:
what about for. multi index?

@jreback changed to return MI. Adding explicit tests.

```
mi = pd.MultiIndex.from_arrays([[1, 2, 1, 2], [1, 1, 1, 2]])
mi 
# MultiIndex(levels=[[1, 2], [1, 2]],
#            labels=[[0, 1, 0, 1], [0, 0, 0, 1]])

# on current master
mi.unique()
# array([(1, 1), (2, 1), (2, 2)], dtype=object)

# after this PR
pd.MultiIndex.from_arrays([[1, 2, 1, 2], [1, 1, 1, 2]]).unique()
# MultiIndex(levels=[[1, 2], [1, 2]],
#            labels=[[0, 1, 1], [0, 0, 1]])
```

## [Current coverage](https://codecov.io/gh/pydata/pandas/pull/13979?src=pr) is 85.27% (diff: 100%)

> Merging [#13979](https://codecov.io/gh/pydata/pandas/pull/13979?src=pr) into [master](https://codecov.io/gh/pydata/pandas/branch/master?src=pr) will increase coverage by **<.01%**

``` diff
@@             master     #13979   diff @@
==========================================
  Files           139        139          
  Lines         50502      50511     +9   
  Methods           0          0          
  Messages          0          0          
  Branches          0          0          
==========================================
+ Hits          43063      43071     +8   
- Misses         7439       7440     +1   
  Partials          0          0          
```

> Powered by [Codecov](https://codecov.io?src=pr). Last update [be61825...28a2781](https://codecov.io/gh/pydata/pandas/compare/be61825986ba565bc038beb2f5df2750fc1aca30...28a27815690571a978fac9037ebded0214ac2be5?src=pr)

@sinhrks ok as per discussion

`Index` -> should return `Index`
`Series` -> match return types now (no API change):

```
In [1]: Series([1,2,3]).unique()
Out[1]: array([1, 2, 3])

In [2]: Series([1,2,3.5]).unique()
Out[2]: array([ 1. ,  2. ,  3.5])

In [3]: Series(['foo','bar','baz']).unique()
Out[3]: array(['foo', 'bar', 'baz'], dtype=object)

In [4]: Series(['foo','bar','baz'],dtype='category').unique()
Out[4]: 
[foo, bar, baz]
Categories (3, object): [foo, bar, baz]

In [6]: Series(pd.date_range('20130101',periods=3)).unique()
Out[6]: 
array(['2013-01-01T00:00:00.000000000', '2013-01-02T00:00:00.000000000',
       '2013-01-03T00:00:00.000000000'], dtype='datetime64[ns]')

#### this discussed in other issue should be object array of Timestamps (and is API change)
In [7]: Series(pd.date_range('20130101',periods=3,tz='US/Eastern')).unique()
Out[7]: 
array(['2013-01-01T05:00:00.000000000', '2013-01-02T05:00:00.000000000',
       '2013-01-03T05:00:00.000000000'], dtype='datetime64[ns]')

# to be honest would change this too to an object array of ``Timedelta``s, but leave for now
In [8]: Series(pd.timedelta_range('1 day',periods=3)).unique()
Out[8]: array([ 86400000000000, 172800000000000, 259200000000000], dtype='timedelta64[ns]')

# good
In [9]: Series(pd.period_range('2013-1',periods=3,freq='M')).unique()
Out[9]: 
array([Period('2013-01', 'M'), Period('2013-02', 'M'),
       Period('2013-03', 'M')], dtype=object)
```

yep, will update.

Looks good to me! 
 Small comment on the whatsnew note, but that can also be done after merge.

Moved `Series.unique` description to API change.

Just in case, categorical `Series.unique` returns `Categorical` rather than `ndarray` now (not changed by this PR)

```
pd.Series([1, 2], dtype='category').unique()
# [1, 2]
# Categories (2, int64): [1, 2]
```

@sinhrks There is a small linting error:

```
pandas/indexes/base.py:18:1: F401 'pandas.types.generic.ABCCategoricalIndex' imported but unused
```

Can be merged apart from that.

@sinhrks Thanks a lot!