¿Hay alguna forma en Pandas de usar el valor de fila anterior en dataframe.apply cuando el valor anterior también se calcula en la aplicación?

94

Tengo el siguiente marco de datos:

 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

Exigir:

 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 Cse deriva 2015-01-31tomando valuede D.

Entonces necesito usar el valuede Cpara 2015-01-31y se multiplican por el valuede Ael 2015-02-01y añadir B.

He intentado un applyy un shiftuso if elsede esto da un error clave.

ctrl-alt-delete
fuente
¿Por qué sus últimas filas en los marcos de datos son diferentes para las columnas Ay B?
Anton Protopopov
@Anton se disculpa, es correcto ahora.
ctrl-alt-delete
¿Cuál es el valor de la siguiente fila en columna Ay columna D?
jezrael
7
Esta es una buena pregunta. Tengo una necesidad similar de una solución vectorizada. Sería bueno si pandas proporcionara una versión de apply()dónde la función del usuario puede acceder a uno o más valores de la fila anterior como parte de su cálculo o al menos devolver un valor que luego se pasa 'a sí mismo' en la siguiente iteración. ¿No permitiría esto algunas ganancias de eficiencia en comparación con un bucle for?
Proyecto de ley
@Bill, es posible que le interese esta respuesta que acabo de agregar, numbasuele ser una buena opción aquí.
jpp

Respuestas:

64

Primero, cree el valor derivado:

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

Luego, recorra las filas restantes y complete los 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
fuente
41
¿Hay alguna función en pandas para hacer esto sin el bucle?
ctrl-alt-delete
1
La naturaleza iterativa del cálculo donde las entradas dependen de los resultados de los pasos anteriores complica la vectorización. Quizás podría usar applycon una función que hace el mismo cálculo que el bucle, pero detrás de escena esto también sería un bucle. pandas.pydata.org/pandas-docs/version/0.17.1/generated/…
Stefan
Si utilizo este bucle y calculo en un marco de datos combinado y encuentra un Nan, funciona pero solo en la fila con Nan. No se arrojan errores, si intento un fillNa obtengo AttributeError: el objeto 'numpy.float64' no tiene atributo 'fillna' ¿Hay alguna forma de omitir la fila con Nan o establecer los valores en cero?
ctrl-alt-delete
¿Quiere decir valores perdidos en columnas distintas a C?
Stefan
Sí, tu solución está bien. Solo me aseguro de completar los Nans en el marco de datos antes del ciclo.
ctrl-alt-delete
41

Dada una columna 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

Puede hacer referencia a la fila anterior con 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
fuente
9
Esto no ayudará en esta situación porque el valor de la fila anterior no se conoce al principio. Debe calcularse en cada iteración y luego usarse en la siguiente iteración.
Proyecto de ley
6
Todavía estoy agradecido por esta respuesta porque me encontré con esto, buscando un caso en el que conozca el valor de la fila anterior. Así que gracias @kztd
Kevin Pauli
28

numba

Para cálculos recursivos que no son vectorizables, numbaque utiliza compilación JIT y trabaja con objetos de nivel inferior, a menudo produce grandes mejoras de rendimiento. Solo necesita definir un forbucle regular y usar el decorador @njito (para versiones anteriores) @jit(nopython=True):

Para un marco de datos de tamaño razonable, esto proporciona una mejora de rendimiento de ~ 30 veces en comparación con un forbucle normal :

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
fuente
1
¡Es maravilloso! Aceleré mi función, que cuenta valores de valores anteriores. ¡Gracias!
Artem Malikov
20

Aplicar la función recursiva en matrices numpy será más rápido que la respuesta actual.

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

Salida

      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

fuente
3
Esta respuesta me funciona perfectamente con un cálculo similar. Intenté usar una combinación de cumsum y shift, pero esta solución funciona mucho mejor. Gracias.
Simon
Esto también funciona perfecto para mí, gracias. Estaba luchando con muchas formas de iterrows, itertuples, apply, etc., y esto parece fácil de entender y realizar.
jaim
9

Aunque ha pasado un tiempo desde que se hizo esta pregunta, publicaré mi respuesta con la esperanza de que ayude a alguien.

Descargo de responsabilidad: sé que esta solución no es estándar , pero creo que funciona bien.

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

Básicamente, usamos un applyfrom pandas y la ayuda de una variable global que realiza un seguimiento del valor calculado anterior.


Comparación de tiempo con un forbucle:

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 bucle (media ± desviación estándar de 7 corridas, 1 bucle cada una)

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 bucle (media ± desarrollo estándar de 7 corridas, 1 bucle cada una)

Entonces, 0.57 veces más rápido en promedio.

iipr
fuente
0

En general, la clave para evitar un bucle explícito sería unir (fusionar) 2 instancias del marco de datos en rowindex-1 == rowindex.

Entonces tendría un gran marco de datos que contiene filas de r y r-1, desde donde podría hacer una función df.apply ().

Sin embargo, la sobrecarga de crear un gran conjunto de datos puede contrarrestar los beneficios del procesamiento paralelo ...

HTH Martin

Martin Alley
fuente