Este texto se basa en los siguientes materiales:
> library(tidyverse)
> library(broom)
> library(car)
> library(patchwork)
En general, suele ser importante tratar de escribir código no redundante, en lugar de copiar y pegar. Reducir la duplicación de código tiene tres beneficios principales:
Es más fácil ver el objetivo de tu código; lo diferente llama más atención a la vista que aquello que permanece igual.
Es más sencillo responder a cambios en los requerimientos. A medida que tus necesidades cambian, solo necesitarás realizar cambios en un lugar, en vez de recordar cambiar en cada lugar donde copiaste y pegaste el código.
Es probable que tengas menos errores porque cada línea de código es utilizada en más lugares.
Una herramienta para reducir la duplicación de código son las funciones, que reducen dicha duplicación al identificar patrones repetidos de código y extraerlos en piezas independientes que pueden reutilizarse y actualizarse fácilmente. Otra herramienta para reducir la duplicación es la iteración, que te ayuda cuando necesitas hacer la misma tarea con múltiples entradas: repetir la misma operación en diferentes columnas o en diferentes conjuntos de datos. Vamos a ver una forma de iterar: la programación imperativa. Tiene herramientas como for loops y while loops, que son un gran lugar para comenzar porque hacen que la iteración sea muy explícita, por lo que es obvio qué está pasando. Existe otro paradigma llamado “programación funcional” pero se nos escapa del objetivo de este intermezzo.
for loopsImagimemos que tenemos esta tabla:
> df <- tibble(a = rnorm(10), b = rnorm(10), c = rnorm(10), d = rnorm(10))
y queremos calcular la mediana de cada columna. Podríamos hacerlo copiando y pegando:
> median(df$a)
## [1] 0.5393077
> median(df$b)
## [1] 0.0828265
> median(df$c)
## [1] 0.1109587
> median(df$c)
## [1] 0.1109587
Pero es medio engorroso. También podríamos hacerlo con las
herramientas tidy. Pero la idea hoy es hacerlo con un
for loop.
> output <- vector("double", ncol(df)) # 1. output
> for (i in seq_along(df)) {
+ # 2. secuencia
+ output[[i]] <- median(df[[i]]) # 3. cuerpo
+ }
>
> output
## [1] 0.5393077 0.0828265 0.1109587 -0.3920435
Cada bucle tiene tres componentes:
output <- vector("double", length(x)). Antes
de comenzar el bucle, siempre debes asignar suficiente espacio para la
salida. Esto es muy importante para la eficiencia: si aumentas el bucle
for en cada iteración usando, por ejemplo, c() , el bucle
for será muy lento.Una forma general de crear un vector vacío de longitud dada es la
función vector(). Tiene dos argumentos: el tipo de vector
(“logical”, “integer”, “double”, “character”, etc) y su longitud.
i in seq_along(df). Este código determina
sobre qué iterar: cada ejecución del bucle for asignará a
i un valor diferente de seq_along(df). Es útil
pensar en i como un pronombre, como “eso”.Es posible que no hayas visto seq_along() con
anterioridad. Es una versión segura de la más familiar
1:length(l), con una diferencia importante: si se tiene un
vector de longitud cero, seq_along() hace lo correcto:
> y <- vector("double", 0)
> seq_along(y)
## integer(0)
> 1:length(y)
## [1] 1 0
Probablemente no vas a crear un vector de longitud cero
deliberadamente, pero es fácil crearlos accidentalmente. Si usamos 1:
length(x) en lugar de seq_along(x), es posible
que obtengamos un mensaje de error confuso.
output[[i]] <- median(df[[i]]). Este es
el código que hace el trabajo. Se ejecuta repetidamente, con un valor
diferente para i cada vez. La primera iteración ejecutará
output[[1]] <- median(df[[1]]), la segunda ejecutará
output[[2]] <- median (df [[2]]), y así
sucesivamente.¡Eso es todo lo que hay para el bucle for! Ahora es un buen momento para practicar creando algunos bucles for básicos (y no tan básicos) usando los ejercicios que se encuentran a continuación. Luego avanzaremos en algunas variaciones de este bucle que te ayudarán a resolver otros problemas que surgirán en la práctica.
for loopUna vez que tienes el for loop básico en tu haber, hay
algunas variaciones que debes tener en cuenta. Hay cuatro variaciones
del bucle for básico. Acá vamos a ver las dos primeras. Las siguientes
pueden revisarlas en el link de libro (al principio de este
notebook).
Algunas veces querrás usar un bucle for para modificar un objeto existente. Por ejemplo, recuerda el desafío que teníamos en el capítulo sobre funciones. Queríamos reescalar cada columna en un data frame:
> df <- tibble(a = rnorm(10), b = rnorm(10), c = rnorm(10), d = rnorm(10))
> rescale01 <- function(x) {
+ rng <- range(x, na.rm = TRUE)
+ (x - rng[1])/(rng[2] - rng[1])
+ }
>
> df$a <- rescale01(df$a)
> df$b <- rescale01(df$b)
> df$c <- rescale01(df$c)
> df$d <- rescale01(df$d)
Para resolver esto con un bucle for, volvamos a pensar en los tres componentes:
seq_along(df).rescale01().Esto nos da:
> for (i in seq_along(df)) {
+ df[[i]] <- rescale01(df[[i]])
+ }
Por lo general, se modificará una lista o un data frame con este tipo
de bucle, así que recuerda utilizar [[ y no [.
Te habrás fijado que usamos [[ en todos nuestros bucles
for: creemos que es mejor usar [[ incluso para
vectores atómicos porque deja en claro que queremos trabajar con un solo
elemento.
Hay tres formas básicas de hacer un bucle sobre un vector. Hasta
ahora hemos visto la más general: iterar sobre los índices numéricos con
for(i in seq_along(xs)), y extraer el valor con
x[[i]]. Hay otras dos formas:
Iterar sobre los elementos: for(x in xs). Esta forma
es la más útil si solo te preocupas por los efectos secundarios, como
graficar o grabar un archivo, porque es difícil almacenar el output de
forma eficiente.
Iterar sobre los nombres: for(nm in names(xs)). Esto
te entrega el nombre, que se puede usar para acceder al valor con
x[[nm]]. Esto es útil si queremos utilizar el nombre en el
título de un gráfico o en el nombre de un archivo.
Iterar sobre los índices numéricos es la forma más general, porque dada la posición se puede extraer tanto el nombre como el valor:
> for (i in seq_along(df)) {
+ name <- names(df)[[i]]
+ value <- df[[i]]
+ }
for loop:> vec <- c(3, 4, 6, 2, 6, 8, 9, 2, 3, 4, 5, 7, 8, 9, 4, 2, 3, 5, 7, 8, 9, 1)
>
> ###
> data(iris)
> head(iris)
## Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1 5.1 3.5 1.4 0.2 setosa
## 2 4.9 3.0 1.4 0.2 setosa
## 3 4.7 3.2 1.3 0.2 setosa
## 4 4.6 3.1 1.5 0.2 setosa
## 5 5.0 3.6 1.4 0.2 setosa
## 6 5.4 3.9 1.7 0.4 setosa
> ###
files <- dir("data/", pattern = "\\.csv$", full.names = TRUE),
y ahora quieres leer cada uno con read_csv(). Escribe un bucle for que
los cargue en un solo data frame.> ###
Bueno… por ahora suficiente con loops. Volvamos a nuestro notebook principal.