The top answer is flawed in my opinion. Hopefully, no one is mass importing all of pandas into their namespace with `from pandas import *`

. Also, the `map`

method should be reserved for those times when passing it a dictionary or Series. It can take a function but this is what `apply`

is used for.

So, if you must use the above approach, I would write it like this

```
df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
```

There’s actually no reason to use zip here. You can simply do this:

```
df["A1"], df["A2"] = calculate(df['a'])
```

This second method is also much faster on larger DataFrames

```
df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})
```

DataFrame created with 300,000 rows

```
%timeit df["A1"], df["A2"] = calculate(df['a'])
2.65 ms ± 92.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%timeit df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
159 ms ± 5.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

60x faster than zip

## In general, avoid using apply

Apply is generally not much faster than iterating over a Python list. Let’s test the performance of a for-loop to do the same thing as above

```
%%timeit
A1, A2 = [], []
for val in df['a']:
A1.append(val**2)
A2.append(val**3)
df['A1'] = A1
df['A2'] = A2
298 ms ± 7.14 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

So this is twice as slow which isn’t a terrible performance regression, but if we cythonize the above, we get much better performance. Assuming, you are using ipython:

```
%load_ext cython
%%cython
cpdef power(vals):
A1, A2 = [], []
cdef double val
for val in vals:
A1.append(val**2)
A2.append(val**3)
return A1, A2
%timeit df['A1'], df['A2'] = power(df['a'])
72.7 ms ± 2.16 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```

### Directly assigning without apply

You can get even greater speed improvements if you use the direct vectorized operations.

```
%timeit df['A1'], df['A2'] = df['a'] ** 2, df['a'] ** 3
5.13 ms ± 320 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
```

This takes advantage of NumPy’s extremely fast vectorized operations instead of our loops. We now have a 30x speedup over the original.

## The simplest speed test with `apply`

The above example should clearly show how slow `apply`

can be, but just so its extra clear let’s look at the most basic example. Let’s square a Series of 10 million numbers with and without apply

```
s = pd.Series(np.random.rand(10000000))
%timeit s.apply(calc)
3.3 s ± 57.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
```

Without apply is 50x faster

```
%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
```