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.


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

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

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

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

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.


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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.