2.4 Eficiencia en R: preasignación, vectorización y desfases
R permite escribir código muy expresivo, pero no todas las formas de hacer lo mismo son igual de eficientes. A continuación se ilustran tres buenas prácticas habituales, empleando el paquete microbenchmark para comparar tiempos de ejecución (más fiable que una única medición con proc.time(), ya que repite cada alternativa varias veces).
Preasignar en lugar de crecer un vector dentro de un bucle. Cada vez que se amplía un vector elemento a elemento (a[i] <- i sin haber fijado antes su longitud), R puede tener que reservar memoria nueva y copiar todo el vector; si se preasigna con numeric() (o vector()), el tamaño ya es el definitivo y no hay redimensionamientos:
library(microbenchmark)
n <- 3e4
microbenchmark(
crecer = { a <- numeric(); for (i in seq_len(n)) a[i] <- i },
preasignado = { d <- numeric(n); for (i in seq_len(n)) d[i] <- i },
times = 10
)## Unit: milliseconds
## expr min lq mean median uq
## crecer 21.8519 23.8182 27.75937 25.98785 30.9824
## preasignado 7.8367 11.0907 16.39377 16.95415 20.2057
## max neval cld
## 39.1828 10 a
## 26.0591 10 b
Con un n mucho mayor la diferencia se dispara (no se evalúa en el libro por el tiempo de cómputo que implicaría repetirlo varias veces):
n <- 1e8
a <- numeric()
system.time(for (i in seq_len(n)) a[i] <- i)
d <- numeric(n)
system.time(for (i in seq_len(n)) d[i] <- i)Usar funciones vectorizadas en lugar de apply(). Funciones como colMeans() o rowSums() están implementadas internamente en C y evitan el bucle (implícito) de apply():
m <- matrix(rnorm(1e6), nrow = 1000)
microbenchmark(
apply_mean = apply(m, 2, mean),
colMeans = colMeans(m),
apply_sum = apply(m, 1, sum),
rowSums = rowSums(m),
times = 20
)## Unit: milliseconds
## expr min lq mean median uq
## apply_mean 27.2368 30.70620 43.623030 35.08620 42.34490
## colMeans 1.5414 1.64550 1.971330 1.72520 2.08605
## apply_sum 16.3958 17.21525 21.302610 19.80010 23.61280
## rowSums 1.2935 1.37060 1.581515 1.44975 1.67865
## max neval cld
## 169.3949 20 a
## 3.9990 20 b
## 37.2324 20 c
## 2.5217 20 b
Calcular desfases (lag/lead) sin bucles. Para comparar cada valor con el anterior (o el siguiente) de un vector, en R base basta con desplazar el vector y alinearlo con cbind(), en lugar de recorrerlo con un bucle:
## [1] 1 1 1 1 1 1 1 1 1 1
y <- c(NA, a[-length(a)]) # "lag": a desplazado una posición hacia atrás
x <- c(a[-1], NA) # "lead": a desplazado una posición hacia adelante
cbind(original = a, lag = y, lead = x)## original lag lead
## [1,] 1 NA 2
## [2,] 2 1 3
## [3,] 3 2 4
## [4,] 4 3 5
## [5,] 5 4 6
## [6,] 6 5 7
## [7,] 7 6 8
## [8,] 8 7 9
## [9,] 9 8 10
## [10,] 10 9 11
## [11,] 11 10 NA
En el Capítulo 5 se retoma este mismo desfase con dplyr::lag()/lead() (Sección 5.6), comprobando que su sintaxis más legible no penaliza el rendimiento.