Czy w Pandas jest sposób na użycie wartości poprzedniego wiersza w dataframe.apply, gdy poprzednia wartość jest również obliczana w Apply?

97

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 Cpochodzi z 2015-01-31biorąc valuez D.

Następnie muszę użyć valueof Cfor 2015-01-31i pomnożyć przez valueof Aon 2015-02-01i dodać B.

Próbowałem użyć applyi shiftużycie if elseprzez to daje kluczowy błąd.

ctrl-alt-delete
źródło
Dlaczego ostatnie wiersze w ramkach danych są różne dla kolumn Ai B?
Anton Protopopov
@Anton przeprasza, że ​​to prawda.
ctrl-alt-delete
Jaka jest wartość następnego wiersza w kolumnie Ai kolumnie D?
jezrael
7
To jest dobre pytanie. Mam podobne zapotrzebowanie na rozwiązanie zwektoryzowane. Byłoby miło, gdyby pandy dostarczyły wersję, w 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?
Bill
@Bill, być może zainteresuje Cię ta odpowiedź , którą właśnie dodałem, numbajest tutaj często dobrą opcją.
jpp

Odpowiedzi:

68

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
Stefan
źródło
42
czy w pandach jest funkcja, która robi to bez pętli?
ctrl-alt-delete
1
Iteracyjny charakter obliczeń, w których dane wejściowe zależą od wyników poprzednich kroków, komplikuje wektoryzację. Możesz użyć applyfunkcji, 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/…
Stefan
Jeśli użyję tej pętli i obliczę na scalonej ramce danych i znajdzie Nan, działa, ale tylko w wierszu z Nan. Nie są generowane żadne błędy, jeśli spróbuję fillNa otrzymuję AttributeError: obiekt „numpy.float64” nie ma atrybutu „fillna”. Czy istnieje sposób, aby pominąć wiersz za pomocą Nan lub ustawić wartości na zero?
ctrl-alt-delete
Czy masz na myśli brakujące wartości w kolumnach innych niż C?
Stefan
Tak, twoje rozwiązanie jest w porządku. Po prostu upewniam się, że wypełniam Nans w ramce danych przed pętlą.
ctrl-alt-delete
43

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
kztd
źródło
10
To nie pomoże w tej sytuacji, ponieważ wartość z poprzedniego wiersza nie jest znana na początku. Musi być obliczany w każdej iteracji, a następnie używany w następnej iteracji.
Bill
6
Nadal jestem wdzięczny za tę odpowiedź, ponieważ natknąłem się na to, szukając przypadku, w którym znam wartość z poprzedniego wiersza. Więc dzięki @kztd
Kevin Pauli,
28

numba

W przypadku obliczeń rekurencyjnych, których nie można wektoryzować, numbakorzystających z kompilacji JIT i pracujących z obiektami niższego poziomu, często uzyskuje się znaczną poprawę wydajności. Wystarczy zdefiniować zwykłą forpętlę i użyć dekoratora @njitlub (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łą forpę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
jpp
źródło
1
To przepiękne! Przyspieszyłem moją funkcję, która liczy wartości z poprzednich wartości. Dzięki!
Artem Malikov
Jak mogę używać @jit(nopython=True)w jupyter-notebook?
sergzemsk
1
@sergzemsk, Tak jak to napisałeś (iw mojej odpowiedzi) nazywa się dekoratorem . Zauważ, że nowsze wersje numba obsługują ten skrót @njit.
jpp
@jpp mam ifwarunek, więc ta poprawa nie powiodła się. Wystąpił błąd „TypingError: Failed in nopython mode pipeline (step: nopython frontend)”
sergzemsk
@sergzemsk, proponuję zadać nowe pytanie, nie jest dla mnie jasne, gdzie ifznajduje się stwierdzenie, dlaczego nie jest wektoryzowane przez numba.
jpp
23

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
3
Ta odpowiedź działa idealnie dla mnie z podobnymi obliczeniami. Próbowałem użyć kombinacji sumy i przesunięcia, ale to rozwiązanie działa znacznie lepiej. Dzięki.
Simon
To działa również idealnie dla mnie, dzięki. Zmagałem się z wieloma formami iteracji, iteracjami, aplikacjami i tak dalej, i wydaje się to łatwe do zrozumienia i wykonania.
chaim
10

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 applyfrom pandas i pomocy zmiennej globalnej, która śledzi poprzednią obliczoną wartość.


Porównanie czasu z forpę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.

iipr
źródło
0

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

Martin Alley
źródło