Introducción

Este documento introduce brevemente a la utilidad de los loops o bucles en el entorno de programación R.

Se describe brevemente la lógica, una forma alternativa de uso a partir de la librería purrr y un ejemplo de uso con datos de población.

1. ¿Qué es un loop?

Se trata de una estructura de programación que sirve para repetir el mismo proceso sobre múltiples objetos contenidos, por ejemplo, en una lista o vector simple. También sirve para reproducir un proceso en múltiples columnas de un data frame.

Hay dos formas sencillas de realizar loops en R:

  • Usando el comando for del paquete base de R

  • Usando la función map de la librería purrr

2. Loops en vectores

Supongamos por ejemplo que tenemos una lista llamada datos que contiene dos data frames: iris y mtcars:

# creamos la lista
datos = list(
  iris = iris,
  mtcars = mtcars
)

# visualizamos los objetos de la lista
print(names(datos))
## [1] "iris"   "mtcars"

Supongamos ahora que queremos obtener estadísticas descriptivas básicas de las columnas de ambos data frames. Sin usar loops, debiéramos repetir el mismo comando para cada uno a través de la función summary(), de esta forma:

summary(datos$iris) # descriptivos de iris
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
## 
summary(datos$mtcars) # descriptivos de mtcars
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

El procedimiento aplicado es correcto, sin embargo es común enfrentarse a situaciones donde los objetos sobre los que hay que aplicar un procedimiento no son sólo dos. En esos casos, repetir el mismo procedimiento no es una opción correcta ya que hace que el código sea menos eficiente, más dificil de reproducir, más proclive a contener errores, etc. En esta situaciones, la opción de usar loops aparece como una alternativa adecuada.

Podemos usar el comando for de R base para repetir una acción en múltiples conjuntos de datos. Para usar esta estructura programática, debe definirse un índice al que llamaremos i y que identificará al objeto sobre el que apliquemos la operación en cada repetición. Luego, entre llaves, el proceso que queremos aplicar.

for (i in datos) { # i representa a iris en la primera iteración y a mtcars en la segunda
  print(summary(i)) # en cada iteración muestra los estadísticos de i
}
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
##                 
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

3. Función map

Veamos ahora cómo lo haríamos usando la función map, de la librería purrr. Esta función tiene dos argumentos:

1- La lista o vector sobre el que aplicaremos la acción

2- La acción que aplicaremos expresada como función

library(purrr)

map(
  datos, # primer argumento
  summary # segundo argumento
)
## $iris
##   Sepal.Length    Sepal.Width     Petal.Length    Petal.Width   
##  Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100  
##  1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300  
##  Median :5.800   Median :3.000   Median :4.350   Median :1.300  
##  Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199  
##  3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800  
##  Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500  
##        Species  
##  setosa    :50  
##  versicolor:50  
##  virginica :50  
##                 
##                 
##                 
## 
## $mtcars
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000

4. Ejemplo de uso de loops con datos de población

Supongamos que nos interesa conocer cómo estima el Instituto Nacional de Estadística y Centos (INDEC) el avance del envejecimiento poblacional por sexo entre los años 2022 y 2040, según sus proyecciones de población. Para eso, necesitamos elaborar un gráfico de barras combinadas por año donde cada par de barras represente los valores del porcentaje de población de 65 años y más para varones y mujeres.

Como primer paso, descargamos las proyecciones de población de la página del INDEC:

# ubicación del archivo en la web
url = "https://www.indec.gob.ar/ftp/cuadros/poblacion/proyecciones_jurisdicciones_2022_2040_base.csv"

# descargamos el dataset y reemplazamos por cero el indicador de población inexistente
datosIndec = read.csv2(url)
datosIndec$Poblacion[datosIndec$Poblacion==" -   "] = 0

# visualizamos la estructura del data frame.
str(datosIndec)
## 'data.frame':    19152 obs. of  5 variables:
##  $ Jurisdiccion: int  2 2 2 2 2 2 2 2 2 2 ...
##  $ Sexo        : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ Edad.q.     : int  0 5 10 15 20 25 30 35 40 45 ...
##  $ Poblacion   : chr  "72804" "96389" "96818" "95087" ...
##  $ Fecha       : int  2022 2022 2022 2022 2022 2022 2022 2022 2022 2022 ...

Ahora usamos dplyr para crear el indicador de “Porcentaje de población de 65 años y más”

library(dplyr)

datosIndec <- datosIndec %>%
  mutate(
    Poblacion = as.numeric(Poblacion),
    es_65_mas = ifelse(Edad.q. >= 65, "pob_65_mas", "pob_menor")
  ) %>%
  group_by(Jurisdiccion, Sexo, Fecha) %>%
  summarise(
    poblacion_total = sum(Poblacion, na.rm = TRUE),
    poblacion_65_mas = sum(Poblacion[es_65_mas == "pob_65_mas"], na.rm = TRUE),
    .groups = "drop"
  ) %>%
  mutate(
    porcentaje_65_mas = (poblacion_65_mas / poblacion_total) * 100
  )

# visualizamos el resultado
head(datosIndec)
## # A tibble: 6 × 6
##   Jurisdiccion  Sexo Fecha poblacion_total poblacion_65_mas porcentaje_65_mas
##          <int> <int> <int>           <dbl>            <dbl>             <dbl>
## 1            2     1  2022         1680354           370970              22.1
## 2            2     1  2023         1672333           371317              22.2
## 3            2     1  2024         1663356           371822              22.4
## 4            2     1  2025         1654308           372016              22.5
## 5            2     1  2026         1645518           372333              22.6
## 6            2     1  2027         1636875           372828              22.8

Una ves que disponemos de los datos modelados de la forma adecuada, aplicamos el loop para obtener los gráficos. Veamos primero la forma nativa, usando for.

Previamente, crearemos el gráfico para un año cualquiera con el objetivo de ver cómo sería el procedimiento individual. Tomaremos como ejemplo el año 2025:

library(highcharter) # librería para graficar

anioSeleccionado = 2025 # el año que usaremos como ejemplo

datosGrafico = subset(datosIndec, Fecha == 2025) # subset del año 2025 para crear el gráfico

# gráfico  
hchart(datosGrafico %>% arrange(-porcentaje_65_mas), type = "column",
       hcaes(x = as.factor(Jurisdiccion),
             y = round(porcentaje_65_mas,1),
             group = Sexo))

Ahora bien, si quisiéramos reproducir el procedimiento para para el período 2020-2040, no parece una buena opción reproducir este procedimiento por más de 20 veces. El código resultante sería excesivamente largo y podríamos cometer un error al copiar y pegar si no modificamos el valor del año correctamente. Veamos ahora cómo podríamos resolver este problema usando el comando for

anios = unique(datosIndec$Fecha) # creamos un vector con los años a incluir

graficos = list() # creamos una lista vacía donde iremos guardando los gráficos año a año

for (i in anios) { # creamos un loop para iterar por cada año
 
  datosGrafico = subset(datosIndec, Fecha == i) # creamos el subset de datos para el año i
 
  # agregamos el gráfico al objeto i de nuestra lista
  graficos[[as.character(i)]] = hchart(datosGrafico %>% arrange(-porcentaje_65_mas), type = "column",
                         hcaes(x = as.factor(Jurisdiccion),
                               y = round(porcentaje_65_mas,1),
                               group = Sexo))
}

# vemos uno de los gráficos resultantes, por ejemplo el del año 2030
graficos$`2030`

Debe notarse que en este caso no usamos una lista de data frames para iterar, sino que la iteración se realizó a partir de un vector de años, que luego usamos para filtrar un dataset.

Con la librería purr es más sencillo ya que podemos crear la lista resultados en el mismo comando del bucle

graficosConMap = map(anios, function(i) {
  datosGrafico = subset(datosIndec, Fecha == i)
  hchart(datosGrafico %>% arrange(-porcentaje_65_mas), type = "column",
       hcaes(x = as.factor(Jurisdiccion),
             y = round(porcentaje_65_mas,1),
             group = Sexo))
  }
)

names(graficosConMap) = anios # nombramos los objetos de la lista con el año del gráfico

graficosConMap$`2030`

Para comprobas que hemos hecho el procedimiento correctamente, podemos generar un grid para visualizar la totalidad a través de la función hw_grid

hw_grid(graficosConMap, ncol = 3, rowheight = 300)
Shiny applications not supported in static R Markdown documents