Capítulo26 Iteración: purrr

El tema proviene de los siguientes sitios.

English: https://r4ds.had.co.nz/iteration.html

Español: https://es.r4ds.hadley.nz/iteración.html


Fecha de la última revisión

## [1] "2026-07-29"

26.1 Temas: El paquete purrr

  • La idea de iterar (repetir una acción)
  • El bucle for de base R
  • La familia map() de purrr
  • Fórmulas anónimas con ~ y .x
  • Iterar sobre dos listas con map2()
  • Un modelo por grupo

purrr es parte de tidyverse, así que se activa con library(tidyverse).

library(tidyverse)
library(datos)

26.2 ¿Qué es iterar?

Iterar es repetir la misma acción sobre cada elemento de una lista, vector o columna. Igual que con las funciones, la meta es no repetir código. Si las funciones evitan copiar y pegar código dentro de una tarea, la iteración evita copiar y pegar la misma tarea muchas veces.

Supón que queremos la mediana de cada columna de este data frame:

df <- tibble(
  a = rnorm(10),
  b = rnorm(10),
  c = rnorm(10),
  d = rnorm(10)
)

# Repetir a mano es tedioso y propenso a errores:
median(df$a)
## [1] -0.2364174
median(df$b)
## [1] 0.2332814
median(df$c)
## [1] 1.136037
median(df$d)
## [1] 0.4082237

26.3 El bucle for de base R

La forma clásica de iterar es con un bucle for:

resultado <- vector("double", ncol(df))   # espacio para guardar la salida
for (i in seq_along(df)) {                # para cada columna
  resultado[[i]] <- median(df[[i]])       # calcular y guardar
}
resultado
## [1] -0.2364174  0.2332814  1.1360375  0.4082237

Funciona, pero hay que escribir bastante “andamiaje” (crear el vector de salida, manejar el índice i). purrr simplifica esto.


26.4 La familia map()

Las funciones map() toman un vector o lista, aplican una función a cada elemento, y devuelven un resultado. Hay una versión por tipo de salida:

  • map() devuelve una lista
  • map_dbl() devuelve un vector de dobles
  • map_int() devuelve un vector de enteros
  • map_chr() devuelve un vector de texto
  • map_lgl() devuelve un vector lógico
map_dbl(df, median)   # la mediana de cada columna, en una sola línea
##          a          b          c          d 
## -0.2364174  0.2332814  1.1360375  0.4082237
map_dbl(df, mean)
##          a          b          c          d 
## -0.3534311  0.1467468  1.0998756  0.2888905
map_dbl(df, sd)
##         a         b         c         d 
## 0.9753734 0.8503055 0.5548967 1.0877976

Compara: el bucle for completo de arriba se reduce a map_dbl(df, median).


26.5 Fórmulas anónimas con ~ y .x

A veces necesitas una pequeña operación que no tiene nombre de función. Puedes escribirla como una fórmula empezando con ~, y usar .x para representar “el elemento actual”.

# El rango (máximo - mínimo) de cada columna
map_dbl(df, ~ max(.x) - min(.x))
##        a        b        c        d 
## 3.042304 2.203092 1.832097 3.338961
# ¿Cuántos valores positivos hay en cada columna?
map_int(df, ~ sum(.x > 0))
##  a  b  c  d 
##  3  5 10  6

Esto es muy útil con datos reales. Por ejemplo, el promedio de atraso de salida por mes en los vuelos:

vuelos %>%
  split(.$mes) %>%                                   # una lista: un data frame por mes
  map_dbl(~ mean(.x$atraso_salida, na.rm = TRUE))    # promedio de atraso por mes
##         1         2         3         4         5         6         7         8 
## 10.036665 10.816843 13.227076 13.938038 12.986859 20.846332 21.727787 12.611040 
##         9        10        11        12 
##  6.722476  6.243988  5.435362 16.576688

26.6 Iterar sobre dos listas con map2()

Cuando necesitas recorrer dos listas en paralelo, usa map2(). Los elementos se representan con .x (de la primera) y .y (de la segunda).

medias <- c(0, 10, 100)
desv   <- c(1, 2, 3)

# Generar 5 números al azar de cada combinación de media y desviación
map2(medias, desv, ~ rnorm(5, mean = .x, sd = .y))
## [[1]]
## [1]  1.01720062 -0.45401384  1.11807753  0.02410736 -0.30014527
## 
## [[2]]
## [1] 13.09959 10.12764 10.11323  9.00243 10.06829
## 
## [[3]]
## [1]  99.31260  99.88415  98.67224 100.17103 103.52231

26.7 Un modelo por grupo

Un uso poderoso es ajustar un modelo por grupo. Aquí ajustamos una regresión del atraso de llegada contra el de salida, por separado para cada aerolínea.

Nota: como el paquete maps también se carga en este libro, escribimos purrr::map() de forma explícita para no confundirla con maps::map().

modelos <- vuelos %>%
  split(.$aerolinea) %>%
  purrr::map(~ lm(atraso_llegada ~ atraso_salida, data = .x))

# Ver el R^2 de cada modelo
modelos %>%
  purrr::map(summary) %>%
  map_dbl("r.squared") %>%
  head()
##        9E        AA        AS        B6        DL        EV 
## 0.8622935 0.7952061 0.7012039 0.8369837 0.8192728 0.9080101

Este patrón —dividir, aplicar un modelo a cada parte, y resumir— es la base del tema de muchos modelos con purrr y broom.


  1. Ejercicios:

Hacer los ejercicios en la sección 21.5 del libro en español.


  1. Ejercicios:

Usa map_dbl() para calcular el número de valores únicos (n_distinct()) en cada columna del data frame diamantes.


  1. Ejercicios:

Hacer los ejercicios en la sección 21.9 del libro en español.