Utilice un valor de la fila anterior en un cálculo de tabla de datos R.

81

Quiero crear una nueva columna en un data.table calculado a partir del valor actual de una columna y el anterior de otra. ¿Es posible acceder a filas anteriores?

P.ej:

> DT <- data.table(A=1:5, B=1:5*10, C=1:5*100)
> DT
   A  B   C
1: 1 10 100
2: 2 20 200
3: 3 30 300
4: 4 40 400
5: 5 50 500
> DT[, D := C + BPreviousRow] # What is the correct code here?

La respuesta correcta debe ser

> DT
   A  B   C   D
1: 1 10 100  NA
2: 2 20 200 210
3: 3 30 300 320
4: 4 40 400 430
5: 5 50 500 540
Korone
fuente
Por lo general, establezco una clave para mis tablas de datos:DT <- data.table(A=..., key = "A")
PatrickT

Respuestas:

103

Con shift()implementado en v1.9.6 , esto es bastante sencillo.

DT[ , D := C + shift(B, 1L, type="lag")]
# or equivalently, in this case,
DT[ , D := C + shift(B)]

De NEWS :

  1. La nueva función shift()implementa rápido lead/lagde vector , lista , data.frames o data.tables . Toma un typeargumento que puede ser "lag" (predeterminado) o "lead" . Permite un uso muy conveniente junto con :=o set(). Por ejemplo: DT[, (cols) := shift(.SD, 1L), by=id]. Échale un vistazo ?shiftpara obtener más información.

Consulte el historial para obtener respuestas anteriores.

Arun
fuente
¿Tiene .Nel número de fila actual o algo así? Siento preguntar aquí, pero parece que no puedo encontrarlo en los archivos de ayuda ...
SlowLearner
7
@SlowLearner: también puede resultarle .Iútil, que contiene los índices de fila para las filas en el grupo actual.
Steve Lianoglou
7
Utilice seq_len (.N - 1) en lugar de 1 :(. N-1). Esto evita problemas asociados con 1: 0.
mnel
1
+1 para el .SDejemplo: estaba tratando de usar a lapplyy obteniendo resultados extravagantes. esto es mucho más sencillo.
MichaelChirico
¿Dónde puedo encontrar un pdf actualizado con toda esta nueva información? Las viñetas oficiales 1.9.4 y los webminars no lo incluyen. Y las viñetas Rmd 1.9.5 no son cómodas y tampoco las incluyen.
skan
43

Usando dplyrusted podría hacer:

mutate(DT, D = lag(B) + C)

Lo que da:

#   A  B   C   D
#1: 1 10 100  NA
#2: 2 20 200 210
#3: 3 30 300 320
#4: 4 40 400 430
#5: 5 50 500 540
Steven Beaupré
fuente
22

Varias personas han respondido a la pregunta específica. Consulte el código a continuación para ver una función de propósito general que uso en situaciones como esta y que puede ser útil. En lugar de simplemente obtener la fila anterior, puede ir tantas filas en el "pasado" o en el "futuro" como desee.

rowShift <- function(x, shiftLen = 1L) {
  r <- (1L + shiftLen):(length(x) + shiftLen)
  r[r<1] <- NA
  return(x[r])
}

# Create column D by adding column C and the value from the previous row of column B:
DT[, D := C + rowShift(B,-1)]

# Get the Old Faithul eruption length from two events ago, and three events in the future:
as.data.table(faithful)[1:5,list(eruptLengthCurrent=eruptions,
                                 eruptLengthTwoPrior=rowShift(eruptions,-2), 
                                 eruptLengthThreeFuture=rowShift(eruptions,3))]
##   eruptLengthCurrent eruptLengthTwoPrior eruptLengthThreeFuture
##1:              3.600                  NA                  2.283
##2:              1.800                  NA                  4.533
##3:              3.333               3.600                     NA
##4:              2.283               1.800                     NA
##5:              4.533               3.333                     NA
dnlbrky
fuente
Esta es una respuesta brillante, me molesta que ya haya votado a favor de las otras respuestas porque esta es una respuesta mucho más general. De hecho, lo usaré en mi paquete geneorama (si no le importa).
geneorama
Claro, adelante. Tenía la esperanza de conseguir un poco de tiempo libre y presentarlo como una solicitud de extracción al data.tablepaquete, pero por desgracia ...
dnlbrky
Se shiftha agregado una función similar llamada a data.tablepartir de la versión 1.9.5. Vea la respuesta actualizada de @Arun.
dnlbrky
12

Basado en el comentario de @Steve Lianoglou anterior, ¿por qué no solo:

DT[, D:= C + c(NA, B[.I - 1]) ]
#    A  B   C   D
# 1: 1 10 100  NA
# 2: 2 20 200 210
# 3: 3 30 300 320
# 4: 4 40 400 430
# 5: 5 50 500 540

Y evitar el uso seq_leno heado cualquier otra función.

Gary Weissman
fuente
2
Agradable, sin embargo, esto no funcionaría si quisiera encontrar el anterior dentro de un grupo.
Mateo
1
@Matthew, tienes razón. Si estuviera subconjunto por grupo, reemplazaría .Iconseq_len(.N)
Gary Weissman
9

Siguiendo la solución de Arun, se pueden obtener resultados similares sin hacer referencia a .N

> DT[, D := C + c(NA, head(B, -1))][]
   A  B   C   D
1: 1 10 100  NA
2: 2 20 200 210
3: 3 30 300 320
4: 4 40 400 430
5: 5 50 500 540
Ryogi
fuente
¿Hay alguna razón para preferir un método a otro? ¿O es simplemente una diferencia estética?
Korone
Creo que en este escenario (es decir, donde .Nestá disponible) es principalmente una elección estética. No soy consciente de ninguna diferencia importante.
Ryogi
1

Aquí está mi solución intuitiva:

#create data frame
df <- data.frame(A=1:5, B=seq(10,50,10), C=seq(100,500, 100))`
#subtract the shift from num rows
shift  <- 1 #in this case the shift is 1
invshift <- nrow(df) - shift
#Now create the new column
df$D <- c(NA, head(df$B, invshift)+tail(df$C, invshift))`

Aquí invshift, el número de filas menos 1 es 4. le nrow(df)proporciona el número de filas en un marco de datos o en un vector. Del mismo modo, si desea tomar valores aún anteriores, reste de nrow 2, 3, ... etc, y también coloque NA en consecuencia al principio.

Abdullah Al Mahmud
fuente
-2

se puede hacer en bucle.

# Create the column D
DT$D <- 0
# for every row in DT
for (i in 1:length(DT$A)) {
  if(i==1) {
    #using NA at first line
    DT[i,4] <- NA
  } else {
    #D = C + BPreviousRow
    DT[i,4] <- DT[i,3] + DT[(i-1), 2]   
  }
}

Usando un for, incluso puede usar el valor anterior de la fila de esta nueva columna DT[(i-1), 4]

Rafael Braga
fuente