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.
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
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:
## 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
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
mapVeamos 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
## $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
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