4.3 Iteración
En análisis de datos es común implementar rutinas iteraivas, esto es, cuando debemos aplicar los mismos pasos a distintas entradas. Veremos que hay dos paradigmas de iteración:
Programación imperativa: ciclos
for
y cicloswhile
.Programación funcional: los ciclos se implmentan mediante funciones,
La ventaja de la programación imperativa es que hacen la iteración de manera clara, sin embargo, veremos que una vez que nos familiarizamos con el paradigma de programación funcional, resulta en código más fácil de mantener y menos propenso a errores.
Ciclos for
Supongamos que tenemos una base de datos y queremos calcular la media de sus columnas numéricas.
df <- data.frame(id = 1:10, a = rnorm(10), b = rnorm(10, 2), c = rnorm(10, 3),
d = rnorm(10, 4))
df
#> id a b c d
#> 1 1 0.5598207 1.3720047 0.6421145 2.645210
#> 2 2 1.1314733 2.8122895 3.3702844 3.605112
#> 3 3 0.8244794 2.3415211 1.9923689 1.193666
#> 4 4 -0.3886844 1.0820243 3.2171555 2.670401
#> 5 5 -1.2365158 2.0925798 3.2488987 4.604377
#> 6 6 -1.7493224 2.2699081 3.9050203 3.810621
#> 7 7 0.4631877 3.6581228 3.6444229 3.660447
#> 8 8 -1.4091554 1.2304471 3.4405145 5.057545
#> 9 9 0.3065948 3.0581159 2.7483301 3.450854
#> 10 10 0.2481053 0.7824258 2.0184303 4.380003
Podemos crear el código para cada columna pero esto involucra copy-paste y no será muy práctico si aumenta el número de columnas:
mean(df$a)
#> [1] -0.1250017
mean(df$b)
#> [1] 2.069944
mean(df$c)
#> [1] 2.822754
mean(df$d)
#> [1] 3.507823
Con un ciclo for
sería:
salida <- vector("double", 4)
for (i in 1:4) {
salida[[i]] <- mean(df[[i + 1]])
}
salida
#> [1] -0.1250017 2.0699439 2.8227540 3.5078234
Los ciclos for
tienen 3 componentes:
La salida:
salida <- vector("double", 4)
. Es importante especificar el tamaño de la salida antes de iniciar el ciclofor
, de lo contrario el código puede ser muy lento.La secuencia: determina sobre que será la iteración, la función
seq_along
puede resultar útil.
salida <- vector("double", 5)
for (i in seq_along(df)) {
salida[[i]] <- mean(df[[i]])
}
seq_along(df)
#> [1] 1 2 3 4 5
- El cuerpo:
salida[[i]] <- mean(df[[i]])
, el código que calcula lo que nos interesa sobre cada objeto en la iteración.
Calcula el valor máximo de cada columna numérica de los
datos de ENLACE 3o de primaria enlacep_2013_3
.
library(estcomp)
head(enlacep_2013_3)
#> # A tibble: 6 x 6
#> CVE_ENT PUNT_ESP_3 PUNT_MAT_3 PUNT_FCE_3 ALUM_NOCONFIABLE_3 ALUM_EVAL_3
#> <chr> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 01 513 536 459 0 40
#> 2 01 472 472 404 2 36
#> 3 01 496 526 426 0 96
#> 4 01 543 586 495 5 74
#> 5 01 554 560 506 0 30
#> 6 01 505 546 460 0 67
- Recordando la limpieza de datos de la sección anterior en uno de los
últimos ejercicios leíamos archivos de manera iteativa. En este ejercicio
descargaremos un archivo zip con archivos csv que contienen información
de monitoreo de contaminantes en ciudad de México (RAMA), en particular
PM2.5. Y juntaremos la información en una sola tabla, la siguiente instrucción
descarga los datos en una carpeta
data
.
library(usethis)
use_directory("data") # crea carpeta en caso de que no exista ya
usethis::use_zip("https://github.com/tereom/estcomp/raw/master/data-raw/19RAMA.zip",
"data") # descargar y descomprimir zip
- Enlistamos los archivos xls en la carpeta.
Tu turno, implementa un ciclo for
para leer los
archivos y crear una única tabla de datos. Si pegas los data.frames de manera
iterativa sugerimos usar la función bind_rows()
.
Programación funcional
Ahora veremos como abordar iteración usando programación funcional.
Regresando al ejemplo inicial de calcular la media de las columnas de una tabla de datos:
salida <- vector("double", 4)
for (i in 1:4) {
salida[[i]] <- mean(df[[i + 1]])
}
salida
#> [1] -0.1250017 2.0699439 2.8227540 3.5078234
Podemos crear una función que calcula la media de las columnas de un
data.frame
:
col_media <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- mean(df[[i]])
}
salida
}
col_media(df)
#> [1] 5.5000000 -0.1250017 2.0699439 2.8227540 3.5078234
col_media(select(iris, -Species))
#> [1] 5.843333 3.057333 3.758000 1.199333
Y podemos extender a crear más funciones que describan los datos:
col_mediana <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- median(df[[i]])
}
salida
}
col_sd <- function(df) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- sd(df[[i]])
}
salida
}
Podemos hacer nuestro código más general y compacto escribiendo una función que reciba los datos sobre los que queremos iterar y la función que queremos aplicar:
col_describe <- function(df, fun) {
salida <- vector("double", length(df))
for (i in seq_along(df)) {
salida[i] <- fun(df[[i]])
}
salida
}
col_describe(df, median)
#> [1] 5.500000 0.277350 2.181244 3.233027 3.632779
col_describe(df, mean)
#> [1] 5.5000000 -0.1250017 2.0699439 2.8227540 3.5078234
Ahora utilizaremos esta idea de pasar funciones a funciones para eliminar los
ciclos for
.
La iteración a través de funciones es muy común en R, hay funciones para hacer
esto en R base (lapply()
, apply()
, sapply()
). Nosotros utilizaremos las
funciones del paquete purrr
,
La familia de funciones del paquete iteran siempre sobre un vector (vector atómico o lista), aplican una función a cada parte y regresan un nuevo vector de la misma longitud que el vector entrada. Cada función especifica en su nombre el tipo de salida:
map()
devuelve una lista.map_lgl()
devuelve un vector lógico.map_int()
devuelve un vector entero.map_dbl()
devuelve un vector double.map_chr()
devuelve un vector caracter.map_df()
devuelve un data.frame.
En el ejemplo de las medias, map
puede recibir un data.frame
(lista de
vectores) y aplicará las funciones a las columnas del mismo.
library(purrr)
map_dbl(df, mean)
#> id a b c d
#> 5.5000000 -0.1250017 2.0699439 2.8227540 3.5078234
map_dbl(select(iris, -Species), median)
#> Sepal.Length Sepal.Width Petal.Length Petal.Width
#> 5.80 3.00 4.35 1.30
Usaremos map
para ajustar un modelo lineal a subconjuntos de los datos
mtcars
determinados por el cilindraje del motor.
Podemos usar la notación .
para hacer código más corto:
Usemos map_**
para unir tablas de datos que están almacenadas en múltiples
archivos csv.
En este caso es más apropiado usar map_df
Ejercicio
Usa la función
map_**
para calcular el número de valores únicos en las columnas deiris
.Usa la función
map_**
para extraer el coeficiete de la variablewt
para cada modelo: