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 map
metoda powinna być zarezerwowana dla tych czasów, gdy przekazujesz jej słownik lub serię. Może przyjąć funkcję, ale do tego apply
sł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 apply
moż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)