Existe uma maneira no Pandas de usar o valor da linha anterior em dataframe.apply quando o valor anterior também é calculado no apply?

97

Eu tenho o seguinte dataframe:

 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

Requer:

 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é derivado para 2015-01-31tomando valuede D.

Em seguida, preciso usar o valuede Cfor 2015-01-31e multiplicar por valuede Aem 2015-02-01e adicionar B.

Eu tentei um applye um shiftusando um if elsepor isso dá um erro de chave.

ctrl-alt-delete
fonte
Por que suas últimas linhas nos dataframes são diferentes para colunas Ae B?
Anton Protopopov
@Anton pede desculpas, está correto agora.
ctrl-alt-delete
Qual é o valor da próxima linha na coluna Ae coluna D?
jezrael de
7
Essa é uma boa pergunta. Tenho uma necessidade semelhante de uma solução vetorizada. Seria bom se o pandas fornecesse uma versão de apply()onde a função do usuário é capaz de acessar um ou mais valores da linha anterior como parte de seu cálculo ou pelo menos retornar um valor que é então passado 'para si mesmo' na próxima iteração. Isso não permitiria alguns ganhos de eficiência em comparação com um loop for?
Bill
@Bill, você pode estar interessado nesta resposta que acabei de adicionar, numbamuitas vezes é uma boa opção aqui.
jpp de

Respostas:

68

Primeiro, crie o valor derivado:

df.loc[0, 'C'] = df.loc[0, 'D']

Em seguida, itere pelas linhas restantes e preencha os valores calculados:

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
fonte
42
existe uma função no pandas para fazer isso sem o loop?
ctrl-alt-delete
1
A natureza iterativa do cálculo em que as entradas dependem dos resultados das etapas anteriores complica a vetorização. Talvez você possa usar applycom uma função que faça o mesmo cálculo que o loop, mas nos bastidores isso também seria um loop. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Stefan
Se eu usar esse loop e calcular em um dataframe mesclado e ele encontrar Nan, ele funcionará, mas apenas para a linha com Nan. Nenhum erro é gerado, se eu tentar um fillNa eu obtenho AttributeError: 'numpy.float64' objeto não tem atributo 'fillna' Existe alguma maneira de pular a linha com Nan ou definir valores para zero?
ctrl-alt-delete
Você quer dizer valores ausentes em colunas diferentes de C?
Stefan
Sim, sua solução está bem. Eu apenas garanto que preencho os Nans no dataframe antes do loop.
ctrl-alt-delete
43

Dada uma coluna de números:

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

Você pode fazer referência à linha anterior com 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
fonte
10
Isso não ajudará nessa situação porque o valor da linha anterior não é conhecido no início. Ele deve ser calculado a cada iteração e então usado na próxima iteração.
Bill
6
Ainda sou grato por essa resposta porque me deparei com isso, procurando um caso em que sei o valor da linha anterior. Então, obrigado @kztd
Kevin Pauli,
28

numba

Para cálculos recursivos que não são vetorizáveis, o numba, que usa compilação JIT e trabalha com objetos de nível inferior, geralmente produz grandes melhorias de desempenho. Você só precisa definir um forloop regular e usar o decorador @njitou (para versões mais antigas) @jit(nopython=True):

Para um dataframe de tamanho razoável, isso dá uma melhoria de desempenho de aproximadamente 30x em relação a um forloop regular :

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
fonte
1
É maravilhoso! Eu acelerei minha função, que conta valores de valores anteriores. Obrigado!
Artem Malikov
Como posso usar @jit(nopython=True)no jupyter-notebook?
sergzemsk
1
@sergzemsk, Assim como você escreveu (e na minha resposta), ele se chama decorador . Observe que as versões posteriores do numba suportam o atalho @njit.
jpp
@jpp eu tenho ifcondição, então essa melhoria falhou. Recebi um erro "TypingError: Failed in nopython mode pipeline (step: nopython frontend)"
sergzemsk
@sergzemsk, sugiro que você faça uma nova pergunta, não está claro para mim onde está a ifdeclaração, por que ela não está sendo vetorizada por numba.
jpp
23

Aplicar a função recursiva em matrizes numpy será mais rápido do que a resposta atual.

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

Resultado

      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

fonte
3
Essa resposta funciona perfeitamente para mim com um cálculo semelhante. Tentei usar uma combinação de cumsum e shift, mas essa solução funciona muito melhor. Obrigado.
Simon,
Isso também funciona perfeito para mim, obrigado. Eu estava lutando com muitas formas de iterrows, itertuples, apply e assim por diante e isso parece fácil de entender e executar.
chaim
10

Embora já faça um tempo desde que esta pergunta foi feita, postarei minha resposta esperando que ajude alguém.

Isenção de responsabilidade: sei que essa solução não é padrão , mas acho que funciona bem.

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

Basicamente, usamos um applyfrom pandas e a ajuda de uma variável global que mantém o controle do valor calculado anteriormente.


Comparação de tempo com um forloop:

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 por loop (média ± desvio padrão de 7 execuções, 1 loop cada)

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 por loop (média ± desvio padrão de 7 execuções, 1 loop cada)

Então, 0,57 vezes mais rápido em média.

iipr
fonte
0

Em geral, a chave para evitar um loop explícito seria juntar (mesclar) 2 instâncias do dataframe em rowindex-1 == rowindex.

Então você teria um grande dataframe contendo linhas de r e r-1, de onde você poderia fazer uma função df.apply ().

No entanto, a sobrecarga de criar o grande conjunto de dados pode compensar os benefícios do processamento paralelo ...

HTH Martin

Martin Alley
fonte