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-31
tomando value
de D
.
Em seguida, preciso usar o value
de C
for 2015-01-31
e multiplicar por value
de A
em 2015-02-01
e adicionar B
.
Eu tentei um apply
e um shift
usando um if else
por isso dá um erro de chave.
A
eB
?A
e colunaD
?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?numba
muitas vezes é uma boa opção aqui.Respostas:
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
fonte
apply
com 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/…C
?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
fonte
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 umfor
loop regular e usar o decorador@njit
ou (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
for
loop 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
fonte
@jit(nopython=True)
no jupyter-notebook?@njit
.if
condição, então essa melhoria falhou. Recebi um erro "TypingError: Failed in nopython mode pipeline (step: nopython frontend)"if
declaração, por que ela não está sendo vetorizada por numba.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
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
apply
from pandas e a ajuda de uma variável global que mantém o controle do valor calculado anteriormente.Comparação de tempo com um
for
loop: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.
fonte
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
fonte