Moim zdaniem najlepsza odpowiedź jest błędna. Miejmy nadzieję, że nikt nie importuje masowo wszystkich pand do swojej przestrzeni nazw z from pandas import *. Ponadto mapmetoda powinna być zarezerwowana dla tych czasów, gdy przekazujesz jej słownik lub serię. Może przyjąć funkcję, ale do tego applysłuży.
Jeśli więc musisz zastosować powyższe podejście, napisałbym to w ten sposób
df["A1"], df["A2"] = zip(*df["a"].apply(calculate))
Właściwie nie ma powodu, aby używać tutaj zip. Możesz to po prostu zrobić:
df["A1"], df["A2"] = calculate(df['a'])
Ta druga metoda jest również znacznie szybsza w przypadku większych ramek DataFrame
df = pd.DataFrame({'a': [1,2,3] * 100000, 'b': [2,3,4] * 100000})
DataFrame utworzona z 300 000 wierszy
%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 szybciej niż zip
Ogólnie rzecz biorąc, unikaj stosowania aplikacji
Stosowanie jest ogólnie niewiele szybsze niż iterowanie po liście w Pythonie. Przetestujmy wydajność pętli for, aby zrobić to samo, co powyżej
%%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)
Jest to więc dwukrotnie wolniejsze, co nie jest straszną regresją wydajności, ale jeśli zcytonujemy powyższe, otrzymamy znacznie lepszą wydajność. Zakładając, że używasz 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)
Bezpośrednie przypisywanie bez zastosowania
Możesz uzyskać jeszcze większą poprawę szybkości, jeśli używasz bezpośrednich operacji wektoryzowanych.
%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)
Wykorzystuje to niezwykle szybkie operacje wektoryzacji NumPy zamiast naszych pętli. Mamy teraz 30-krotne przyspieszenie w stosunku do oryginału.
Najprostszy test szybkości z apply
Powyższy przykład powinien jasno pokazać, jak wolno applymoże być, ale właśnie dlatego jego wyjątkowo jasny, spójrzmy na najbardziej podstawowy przykład. Wyrównajmy do kwadratu serię 10 milionów liczb z zastosowaniem i bez zastosowania
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)
Bez aplikacji jest 50x szybsze
%timeit s ** 2
66 ms ± 2 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)