Mam następującą ramkę danych:
Index_Date A B C D
===============================
2015-01-31 10 10 Nan 10
2015-02-01 2 3 Nan 22
2015-02-02 10 60 Nan 280
2015-02-03 10 100 Nan 250
Wymagać:
Index_Date A B C D
===============================
2015-01-31 10 10 10 10
2015-02-01 2 3 23 22
2015-02-02 10 60 290 280
2015-02-03 10 100 3000 250
Column C
pochodzi z 2015-01-31
biorąc value
z D
.
Następnie muszę użyć value
of C
for 2015-01-31
i pomnożyć przez value
of A
on 2015-02-01
i dodać B
.
Próbowałem użyć apply
i shift
użycie if else
przez to daje kluczowy błąd.
A
iB
?A
i kolumnieD
?apply()
której funkcja użytkownika może uzyskać dostęp do jednej lub więcej wartości z poprzedniego wiersza w ramach swoich obliczeń lub przynajmniej zwrócić wartość, która jest następnie przekazywana „do siebie” w następnej iteracji. Czy nie pozwoliłoby to na pewien wzrost wydajności w porównaniu z pętlą for?numba
jest tutaj często dobrą opcją.Odpowiedzi:
Najpierw utwórz wartość pochodną:
df.loc[0, 'C'] = df.loc[0, 'D']
Następnie powtórz pozostałe wiersze i wypełnij obliczone wartości:
for i in range(1, len(df)): df.loc[i, 'C'] = df.loc[i-1, 'C'] * df.loc[i, 'A'] + df.loc[i, 'B'] Index_Date A B C D 0 2015-01-31 10 10 10 10 1 2015-02-01 2 3 23 22 2 2015-02-02 10 60 290 280
źródło
apply
funkcji, która wykonuje takie same obliczenia jak pętla, ale za kulisami byłaby to również pętla. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…C
?Biorąc pod uwagę kolumnę liczb:
lst = [] cols = ['A'] for a in range(100, 105): lst.append([a]) df = pd.DataFrame(lst, columns=cols, index=range(5)) df A 0 100 1 101 2 102 3 103 4 104
Możesz odwołać się do poprzedniego wiersza za pomocą shift:
df['Change'] = df.A - df.A.shift(1) df A Change 0 100 NaN 1 101 1.0 2 102 1.0 3 103 1.0 4 104 1.0
źródło
numba
W przypadku obliczeń rekurencyjnych, których nie można wektoryzować,
numba
korzystających z kompilacji JIT i pracujących z obiektami niższego poziomu, często uzyskuje się znaczną poprawę wydajności. Wystarczy zdefiniować zwykłąfor
pętlę i użyć dekoratora@njit
lub (dla starszych wersji)@jit(nopython=True)
:Dla ramki danych o rozsądnym rozmiarze daje to ~ 30-krotną poprawę wydajności w porównaniu ze zwykłą
for
pętlą:from numba import jit @jit(nopython=True) def calculator_nb(a, b, d): res = np.empty(d.shape) res[0] = d[0] for i in range(1, res.shape[0]): res[i] = res[i-1] * a[i] + b[i] return res df['C'] = calculator_nb(*df[list('ABD')].values.T) n = 10**5 df = pd.concat([df]*n, ignore_index=True) # benchmarking on Python 3.6.0, Pandas 0.19.2, NumPy 1.11.3, Numba 0.30.1 # calculator() is same as calculator_nb() but without @jit decorator %timeit calculator_nb(*df[list('ABD')].values.T) # 14.1 ms per loop %timeit calculator(*df[list('ABD')].values.T) # 444 ms per loop
źródło
@jit(nopython=True)
w jupyter-notebook?@njit
.if
warunek, więc ta poprawa nie powiodła się. Wystąpił błąd „TypingError: Failed in nopython mode pipeline (step: nopython frontend)”if
znajduje się stwierdzenie, dlaczego nie jest wektoryzowane przez numba.Zastosowanie funkcji rekurencyjnej do tablic numpy będzie szybsze niż bieżąca odpowiedź.
df = pd.DataFrame(np.repeat(np.arange(2, 6),3).reshape(4,3), columns=['A', 'B', 'D']) new = [df.D.values[0]] for i in range(1, len(df.index)): new.append(new[i-1]*df.A.values[i]+df.B.values[i]) df['C'] = new
Wynik
A B D C 0 1 1 1 1 1 2 2 2 4 2 3 3 3 15 3 4 4 4 64 4 5 5 5 325
źródło
Chociaż minęło trochę czasu, odkąd zadano to pytanie, opublikuję swoją odpowiedź, mając nadzieję, że komuś pomoże.
Uwaga: wiem, że to rozwiązanie nie jest standardowe , ale myślę, że działa dobrze.
import pandas as pd import numpy as np data = np.array([[10, 2, 10, 10], [10, 3, 60, 100], [np.nan] * 4, [10, 22, 280, 250]]).T idx = pd.date_range('20150131', end='20150203') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df A B C D ================================= 2015-01-31 10 10 NaN 10 2015-02-01 2 3 NaN 22 2015-02-02 10 60 NaN 280 2015-02-03 10 100 NaN 250 def calculate(mul, add): global value value = value * mul + add return value value = df.loc['2015-01-31', 'D'] df.loc['2015-01-31', 'C'] = value df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1) df A B C D ================================= 2015-01-31 10 10 10 10 2015-02-01 2 3 23 22 2015-02-02 10 60 290 280 2015-02-03 10 100 3000 250
Tak więc w zasadzie używamy a
apply
from pandas i pomocy zmiennej globalnej, która śledzi poprzednią obliczoną wartość.Porównanie czasu z
for
pętlą:data = np.random.random(size=(1000, 4)) idx = pd.date_range('20150131', end='20171026') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df.C = np.nan df.loc['2015-01-31', 'C'] = df.loc['2015-01-31', 'D'] %%timeit for i in df.loc['2015-02-01':].index.date: df.loc[i, 'C'] = df.loc[(i - pd.DateOffset(days=1)).date(), 'C'] * df.loc[i, 'A'] + df.loc[i, 'B']
3,2 s ± 114 ms na pętlę (średnia ± odchylenie standardowe z 7 przebiegów, po 1 pętli)
data = np.random.random(size=(1000, 4)) idx = pd.date_range('20150131', end='20171026') df = pd.DataFrame(data=data, columns=list('ABCD'), index=idx) df.C = np.nan def calculate(mul, add): global value value = value * mul + add return value value = df.loc['2015-01-31', 'D'] df.loc['2015-01-31', 'C'] = value %%timeit df.loc['2015-02-01':, 'C'] = df.loc['2015-02-01':].apply(lambda row: calculate(*row[['A', 'B']]), axis=1)
1,82 s ± 64,4 ms na pętlę (średnia ± odchylenie standardowe z 7 przebiegów, po 1 pętli)
Czyli średnio 0,57 razy szybciej.
źródło
Ogólnie rzecz biorąc, kluczem do uniknięcia jawnej pętli byłoby połączenie (scalenie) 2 wystąpień ramki danych na rowindex-1 == rowindex.
Wtedy miałbyś dużą ramkę danych zawierającą wiersze r i r-1, z której mógłbyś wykonać funkcję df.apply ().
Jednak obciążenie związane z tworzeniem dużego zbioru danych może zniwelować korzyści wynikające z przetwarzania równoległego ...
HTH Martin
źródło