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.


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

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

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

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

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.


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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.