Allow indices to be mapped through through dictionaries or series
State: closed

 - [X] closes #12756
 - [X] tests added / passed
 - [X] passes ``git diff upstream/master | flake8 --diff``
 - [X] whatsnew entry


Labels:
name: Datetime
description: Datetime data dtype
name: Indexing
description: Related to indexing on series/frames, not to indexes themselves
name: Reshaping
description: Concat, Merge/Join, Stack/Unstack, Explode
name: API Design
name: Clean

Comments:
can you rebase / update
# [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=h1) Report
> Merging [#15081](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=desc) into [master](https://codecov.io/gh/pandas-dev/pandas/commit/412988ed972a929203b2e867c8fdc7fcb0e7d312?src=pr&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `91.78%`.

[![Impacted file tree graph](https://codecov.io/gh/pandas-dev/pandas/pull/15081/graphs/tree.svg?width=650&src=pr&token=eZ4WkYLtcO&height=150)](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master   #15081      +/-   ##
==========================================
- Coverage   91.34%   91.31%   -0.03%     
==========================================
  Files         163      163              
  Lines       49717    49742      +25     
==========================================
+ Hits        45413    45424      +11     
- Misses       4304     4318      +14
```

| Flag | Coverage Δ | |
|---|---|---|
| #multiple | `89.11% <91.78%> (-0.01%)` | :arrow_down: |
| #single | `40.5% <42.46%> (-0.07%)` | :arrow_down: |

| [Impacted Files](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pandas/core/series.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvc2VyaWVzLnB5) | `94.76% <100%> (-0.08%)` | :arrow_down: |
| [pandas/core/indexes/datetimes.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9kYXRldGltZXMucHk=) | `95.51% <100%> (+0.02%)` | :arrow_up: |
| [pandas/core/dtypes/missing.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL21pc3NpbmcucHk=) | `90.24% <75%> (-0.5%)` | :arrow_down: |
| [pandas/core/indexes/datetimelike.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9kYXRldGltZWxpa2UucHk=) | `96.91% <83.33%> (-0.2%)` | :arrow_down: |
| [pandas/core/dtypes/cast.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZHR5cGVzL2Nhc3QucHk=) | `88.52% <85.71%> (-0.08%)` | :arrow_down: |
| [pandas/core/indexes/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvaW5kZXhlcy9iYXNlLnB5) | `96.39% <92.3%> (-0.04%)` | :arrow_down: |
| [pandas/core/base.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvYmFzZS5weQ==) | `96.54% <96.29%> (-0.03%)` | :arrow_down: |
| [pandas/io/gbq.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2lvL2dicS5weQ==) | `25% <0%> (-58.34%)` | :arrow_down: |
| [pandas/core/frame.py](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=tree#diff-cGFuZGFzL2NvcmUvZnJhbWUucHk=) | `97.8% <0%> (-0.1%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=footer). Last update [412988e...dd0b7e9](https://codecov.io/gh/pandas-dev/pandas/pull/15081?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

Hi @jreback sorry for the delay.  Work got a little crazy.  Let me know what other changes you'd like to see made.
@nateyoder this was quite close. can you rebase / update and we can get it in?
Hi Jeff,  My apologies as work has swamped me again.  I'll try to get everything up to date and working again this weekend and you can let me know if there are other changes you'd like.  Thanks for your patience.
@nateyoder great thanks. yeah when you rebase will take another look. feel free to ping.
can you rebase / update
Hi @jreback.  Apologies again on the delay.  I updated the tests to include some of the missing cases but was a little unsure how to re-use the mapping code from series like you had suggested.
I took a shot at combining the code but in at least my current implementation I still had to do some object specific code.  If you've got a better idea let me know and I'll be happy to take a look.  

I also took this opportunity to not create a series from a dict but instead use an index directly which as you can see from the below ASV results provided a slight but pretty consistent performance improvement for dicts and didn't significantly slow down series maps:

```
asv continuous -f 1.1 upstream/master HEAD -b ^series_methods.series_map -E conda:2.7
· Creating environments
· Discovering benchmarks
·· Uninstalling from conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt
·· Installing into conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt..
· Running 4 total benchmarks (2 commits * 1 environments * 2 benchmarks)
[  0.00%] · For pandas commit hash 99e11b4b:
[  0.00%] ·· Building for conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt.......................................
[  0.00%] ·· Benchmarking conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt
[ 25.00%] ··· Running series_methods.series_map_dict.time_series_map_dict              1.61±0.03ms
[ 50.00%] ··· Running series_methods.series_map_series.time_series_map_series             995±20μs
[ 50.00%] · For pandas commit hash 19fc8dac:
[ 50.00%] ·· Building for conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt...
[ 50.00%] ·· Benchmarking conda-py2.7-Cython-matplotlib-numexpr-numpy-openpyxl-pytables-pytest-scipy-sqlalchemy-xlrd-xlsxwriter-xlwt
[ 75.00%] ··· Running series_methods.series_map_dict.time_series_map_dict              2.10±0.09ms
[100.00%] ··· Running series_methods.series_map_series.time_series_map_series             970±60μs       before           after         ratio
     [19fc8dac]       [99e11b4b]
-     2.10±0.09ms      1.61±0.03ms     0.77  series_methods.series_map_dict.time_series_map_dict

SOME BENCHMARKS HAVE CHANGED SIGNIFICANTLY.
```

EDIT: Note that give that the above method did **NOT** work in both 2.7 and 3 this performance improvement was reverted and the included changes did not significantly change performance.

Sorry didn't test with 2.7.  I'll go back to just using series even though its a little slower.
Sorry for the trashing around.  I wasn't familiar with what should be an index vs a multi-index in mapping with a dictionary made of tuples.  I ended up just going back to using a series because I couldn't keep the performance going straight through the _ensure_index function.
pls rebase / update when you can
can you rebase / update
@nateyoder can you rebase / update. sorry didn't get to review this soon. 
@nateyoder going to finaly get this in! can you rebase and move note to 0.22.0
Hi @jreback.  Sorry the problem has totally been on my side.  Yes I'd love to get it in as well though I unfortunately won't be able to work on it this weekend.  If I try to get you updates early next week would that work?  Sorry for the delay!
@nateyoder sure! that is fine. just ping when ready.
Hi @jreback I tried to update this but ran into some issues locally with the tests you added from mapping type (#17491).  I unfortunately don't have a ton of time to spend debugging right now so any insight you might be able to provide would be awesome.
also refactored to remove turning things into object dtypes unecessarily

PR 
```
In [1]:         np.random.seed(1234)
   ...:         map_size = 10000
   ...:         s = Series(np.random.randint(0, map_size, 100000))
   ...:         map_series = Series(map_size - np.arange(map_size))
   ...: 

In [2]: %timeit s.map(map_series)
773 µs ± 36.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
```

0.21.0
```
In [2]: %timeit s.map(map_series)
9.3 ms ± 391 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```
@TomAugspurger and @jorisvandenbossche if you have any comments.
thanks @nateyoder glad we got this in!
@jreback thanks for doing all the work to finish it up!! Really appreciate you picking up the slack!