Iterar a través de las columnas para sumar los 2 números anteriores de cada fila
En R, tengo un marco de datos, con las columnas 'A', 'B', 'C', 'D'. Las columnas tienen 100 filas.
Necesito iterar a través de las columnas para realizar un cálculo para todas las filas en el marco de datos que suma las 2 filas anteriores de esa columna, y luego establecer en nuevas columnas ('AA', 'AB', etc.) cuál es esa suma:
A B C D
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
5 6 7 8
6 7 8 9
a
A B C D AA AB AC AD
1 2 3 4 NA NA NA NA
2 3 4 5 3 5 7 9
3 4 5 6 5 7 9 11
4 5 6 7 7 9 11 13
5 6 7 8 9 11 13 15
6 7 8 9 11 13 15 17
¿Alguien puede explicar cómo crear una función/bucle que me permita configurar las columnas sobre las que quiero iterar (columnas seleccionadas, no todas las columnas) y las columnas que quiero configurar?
Respuestas
Una base
sola línea:
cbind(df, setNames(df + df[c(NA, 1:(nrow(df)-1)), ], paste0("A", names(df))))
Si sus datos son grandes, este podría ser el más rápido porque manipula todo el archivo data.frame
.
Una dplyr
solución usando mutate()
con across()
.
library(dplyr)
df %>%
mutate(across(A:D,
~ .x + lag(.x),
.names = "A{col}"))
# A B C D AA AB AC AD
# 1 1 2 3 4 NA NA NA NA
# 2 2 3 4 5 3 5 7 9
# 3 3 4 5 6 5 7 9 11
# 4 4 5 6 7 7 9 11 13
# 5 5 6 7 8 9 11 13 15
# 6 6 7 8 9 11 13 15 17
Si desea sumar las 3 filas anteriores, el segundo argumento de across()
, es decir .fns
, debe ser
~ .x + lag(.x) + lag(.x, 2)
que es equivalente al uso de rollsum()
in zoo
:
~ zoo::rollsum(.x, k = 3, fill = NA, align = 'right')
Punto de referencia
Una prueba de referencia con microbenchmark
paquete en una nueva data.frame
con 10000 filas y 100 columnas y evalúa cada expresión 10 veces.
# Unit: milliseconds
# expr min lq mean median uq max neval
# darren_base 18.58418 20.88498 35.51341 33.64953 39.31909 80.24725 10
# darren_dplyr_lag 39.49278 40.27038 47.26449 42.89170 43.20267 76.72435 10
# arg0naut91_dplyr_rollsum 436.22503 482.03199 524.54800 516.81706 534.94317 677.64242 10
# Grothendieck_rollsumr 3423.92097 3611.01573 3650.16656 3622.50895 3689.26404 4060.98054 10
Puede usar dplyr
's across
(y establecer nombres opcionales) con suma móvil (como se implementó, por ejemplo, en zoo
):
library(dplyr)
library(zoo)
df %>%
mutate(
across(
A:D,
~ rollsum(., k = 2, fill = NA, align = 'right'),
.names = 'A{col}'
)
)
Producción:
A B C D AA AB AC AD
1 1 2 3 4 NA NA NA NA
2 2 3 4 5 3 5 7 9
3 3 4 5 6 5 7 9 11
4 4 5 6 7 7 9 11 13
5 5 6 7 8 9 11 13 15
6 6 7 8 9 11 13 15 17
Con A:D
hemos especificado el rango de nombres de columnas a los que queremos aplicar la función. La suposición anterior en .names
el argumento es que desea pegar juntos A
como prefijo y el nombre de la columna ( {col}
).
Aquí hay una solución de tabla de datos. Como lo solicita, le permite seleccionar a qué columnas desea aplicarlo en lugar de solo para todas las columnas.
library(data.table)
x <- data.table(A=1:6, B=2:7, C=3:8, D=4:9)
selected_cols <- c('A','B','D')
new_cols <- paste0("A",selected_cols)
x[, (new_cols) := lapply(.SD, function(col) col+shift(col, 1)), .SDcols = selected_cols]
x[]
NB Esto es 2 o 3 veces más rápido que la otra respuesta más rápida.
Ese es un enfoque ingenuo con for
bucles anidados. Tenga cuidado, es muy lento si va a iterar sobre cientos de miles de filas.
i <- 1
n <- 5
df <- data.frame(A=i:(i+n), B=(i+1):(i+n+1), C=(i+2):(i+n+2), D=(i+3):(i+n+3))
for (col in colnames(df)) {
for (ind in 1:nrow(df)) {
if (ind-1==0) {next}
s <- sum(df[c(ind-1, ind), col])
df[ind, paste0('S', col)] <- s
}
}
Ese es un cumsum
método:
na.df <- data.frame(matrix(NA, 2, ncol(df)))
colnames(na.df) <- colnames(df)
cs1 <- cumsum(df)
cs2 <- rbind(cs1[-1:-2,], na.df)
sum.diff <- cs2-cs1
cbind(df, rbind(na.df[1,], cs1[2,], sum.diff[1:(nrow(sum.diff)-2),]))
Punto de referencia:
# Unit: milliseconds
# expr min lq mean median uq max neval
# darrentsai.rbind 11.5623 12.28025 23.38038 16.78240 20.83420 91.9135 100
# darrentsai.rbind.rev1 8.8267 9.10945 15.63652 9.54215 14.25090 62.6949 100
# pseudopsin.dt 7.2696 7.52080 20.26473 12.61465 17.61465 69.0110 100
# ivan866.cumsum 25.3706 30.98860 43.11623 33.78775 37.36950 91.6032 100
Creo que la mayoría de las veces el método cumsum se desperdicia en las asignaciones de df. Si se adapta correctamente al backend data.table, podría ser el más rápido.
Especificar las columnas que queremos. Mostramos varias maneras diferentes de hacerlo. Luego use rollsumr
para obtener las columnas deseadas, establezca los nombres de las columnas y vincúlelas DF
.
library(zoo)
# jx <- names(DF) # if all columns wanted
# jx <- sapply(DF, is.numeric) # if all numeric columns
# jx <- c("A", "B", "C", "D") # specify columns by name
jx <- 1:4 # specify columns by position
r <- rollsumr(DF[jx], 2, fill = NA)
colnames(r) <- paste0("A", colnames(r))
cbind(DF, r)
donación:
A B C D AA AB AC AD
1 1 2 3 4 NA NA NA NA
2 2 3 4 5 3 5 7 9
3 3 4 5 6 5 7 9 11
4 4 5 6 7 7 9 11 13
5 5 6 7 8 9 11 13 15
6 6 7 8 9 11 13 15 17
Nota
La entrada en forma reproducible:
DF <- structure(list(A = 1:6, B = 2:7, C = 3:8, D = 4:9),
class = "data.frame", row.names = c(NA, -6L))