Cache repeated mapping and apply
I'm not sure if this functionality exists but it is boiler plate which I need to use quite often.

Typically I need to work with very large datasets, e.g. 10mil rows or more.
On these datasets performing a calculations such as taking a date in a specific string format can be sped up incredibly by assessing what needs to be done first.

As such I find myself writing the following boiler plate code for a lot of functions.

```
def map_strptime(series, format=None):
    mapping = {x: datetime.strptime(x, format) for x in set(series)}
    return series.map(mapping)
```

Now comparing this implementation v.s a more naive implementation just using the standard apply function, e.g.

```
def nonmap_strptime(x, format=None):
    return datetime.strptime(x, format)
```

Usage:

```
df["Date"] = map_strptime(df["Trading Date"], format="%Y-%m-%d")

# vs.

df["Date"] = df["Trading Date"].apply(nonmap_strptime, format="%Y-%m-%d")
```

Assessing these two implementations for performance I get the following.
Note, my Dataset has 10 million rows across 365 days, e.g. a lot of repeated calculations.

```
# Mapped version
>>>> %timeit  df["Date"] = map_strptime(df["Trading Date"], format="%Y-%m-%d")
>>>> 900 ms

# Nonmapped version
>>>> %timeit df["Date"] = df["Trading Date"].apply(nonmap_strptime, format="%Y-%m-%d")
>>>> (Still hasn't finished in the time it has taken me to write this issue, 10min plus)
```

As you can see for some use cases, e.g. when a calculation is being repeated often a simple set - map type wrapper around the operation could improve the performance substantially.

However, if a large number of non-duplicates exists then this would obviously slow the operation down quite substantially as first the calculation would be performed, then the mapping in two steps rather than one.

To get around this the number of unique items and non-unique items could be computed. If a large difference between them exists then a shift to the mapping implementation could impart large performance gains.

I'm not sure as to the best implementation, but something along the lines of:

```
df.memoapply(*args, **kargs):

if len(set(df[cols]) >> len(df[cols]):
    # use memo version

else:
    # Use standard versions.
```

This works best for one to one type operations, I'm unsure how well it would work for one to many or for many to one type applications.

Could possible be extended by using the inbuilt merge operations of Pandas and using a multi indexed series for the result?
