Proyecciones de población

El Instituto Nacional de Estadística y Censos (INDEC) es el encargado de elaborar la proyecciones de población en Argentina, en base a los censos de población, hogares y vivienda.

La serie de proyecciones más actualizada es la desarrollada en base al Censo Nacional de Población, Hogares y Viviendas 2010 y se encuentra disponible en https://www.indec.gob.ar/indec/web/Nivel4-Tema-2-24-85

El material publicado en el sitio web del Instituto aparece en dos formatos disponibles para descarga:

  • Documento .pdf
  • Planilla de cálculos .xls

Ambos formatos resultan habitualmente poco amigables para docentes, investigadores y usuarios relacionados con la estadística y la ciencia de datos. En primer lugar, el formato .pdf resulta adecuado para la impresión del documento y su visualización, pero no para la utilización de los datos en softwares de procesamiento. Por otro lado, las planillas .xls disponibles presentan un diseño en tabulados contiguos, fragmentados en distintas hojas (cada una representa a las jurisdicción diferente), lo que torna muy laborioso reconstruir series temporales sin un trabajo arduo de copiado y pegado que aumenta el riesgo de cometer errores.


Objetivo

El objetivo de este documento es presentar una forma programática de procesamiento de las proyecciones de poblacion 2010-2040 publicadas por el INDEC, usando el software estadístico R (R Core Team 2019) con sus librerías dplyr (Wickham et al. 2023), shiny (Chang et al. 2022), openxlsx (Schauberger and Walker 2023) y readxl (Wickham and Bryan 2023).


Desarrollo

1- Instalar y cargar librerías

Como se mencionó más arriba, se utilizarán las librerías dplyr, openxl y readxl, que deben estar previamente instaladas.

# chequeamos si las librerías están instaladas y si es necesario se instalan
if ("readxl" %in% installed.packages()[,"Package"]) {library(readxl)} else {install.packages("readxl");library(readxl)}
if ("openxlsx" %in% installed.packages()[,"Package"]) {library(openxlsx)} else {install.packages("openxlsx");library(openxlsx)}
if ("highcharter" %in% installed.packages()[,"Package"]) {library(highcharter)} else {install.packages("highcharter");library(highcharter)}

2- Descargar proyecciones de población en formato .xls de la web del INDEC

# descarga del archivo
url = "https://www.indec.gob.ar/ftp/cuadros/poblacion/c2_proyecciones_prov_2010_2040.xls"
download.file(url, destfile = "poblacion.xls", mode="wb")

3- Crear una variable con los nombres de las hojas de la planilla descargada

Esto se puede hacer a través de la función excel_sheets de readxl

sheets = readxl::excel_sheets("poblacion.xls")
print(sheets)
##  [1] "GraphData"              "01-TOTAL DEL PAÍS"      "02-CABA"               
##  [4] "06-BUENOS AIRES"        "10-CATAMARCA"           "14-CÓRDOBA"            
##  [7] "18-CORRIENTES"          "22-CHACO"               "26-CHUBUT"             
## [10] "30-ENTRE RÍOS"          "34-FORMOSA"             "38-JUJUY"              
## [13] "42-LA PAMPA"            "46-LA RIOJA"            "50-MENDOZA"            
## [16] "54-MISIONES"            "58-NEUQUÉN"             "62-RÍO NEGRO"          
## [19] "66-SALTA"               "70-SAN JUAN"            "74-SAN LUIS"           
## [22] "78-SANTA CRUZ"          "82-SANTE FE"            "86-SANTIAGO DEL ESTERO"
## [25] "90-TUCUMÁN"             "94-TIERRA DEL FUEGO"

Eliminar el nombre de la primera hoja de la planilla en la variable (ya que no contiene información)

sheets = sheets[sheets!="GraphData"]

4 - Explorar la planilla descargada y preparar las variables que nos van a ayudar a tabular los datos

Todas las hojas presentan cuadros con la información de interés. Cada cuadro ocupa 21 filas y 3 columnas más una vacía utilizada como margen, comenzando el primero en la celda “B8”. En cada cuadro se presenta la información de la proyección (por grupos de edad y sexo) para una jurisdicción seleccionada (la de la hoja) y un año seleccionado (indicado en el encabezado de cada cuadro).

El diseño de cada hoja (que representa a una jurisdicción) se compone de 5 bloques horizontales de 6 cuadros cada uno y un bloque final (el sexto) de un cuadro. Con una secuencia vamos a obtener la columna en la que empieza cada cuadro. Identificamos la letra “B” en el vector LETTERS que incluye R. En este caso, la letra “B” es la segunda del vector. Usando esa posición, iremos agregando los múltiplos de 4 hasta completar las 6 columnas.

orden_letra_b = 2
columnas = LETTERS[seq(orden_letra_b,length(LETTERS), by = 4)][1:6]

print(columnas)
## [1] "B" "F" "J" "N" "R" "V"

Ahora obtendremos las filas donde empiezan los cuadros. Entre la longitud de los cuadros (21 filas) más los espacios y los totales que no utilizaremos, vemos que cada bloque comienza 28 filas debajo del anterior. Multiplicamos hasta obtener las filas iniciales de cada uno.

fila_primer_bloque = 8
fila_ultimo_bloque = 148
filas = seq(fila_primer_bloque,fila_ultimo_bloque, by = 28)
print(filas)
## [1]   8  36  64  92 120 148

Con el comando expand.grid obtendermos todas las combinaciones de fila y columna donde comienzan los cuadros a partir de los dos vectores creados anteriormente.

columnas_y_filas = list(
  columnas,
  filas
)

columnas_y_filas = expand.grid(columnas_y_filas)
print(columnas_y_filas)
##    Var1 Var2
## 1     B    8
## 2     F    8
## 3     J    8
## 4     N    8
## 5     R    8
## 6     V    8
## 7     B   36
## 8     F   36
## 9     J   36
## 10    N   36
## 11    R   36
## 12    V   36
## 13    B   64
## 14    F   64
## 15    J   64
## 16    N   64
## 17    R   64
## 18    V   64
## 19    B   92
## 20    F   92
## 21    J   92
## 22    N   92
## 23    R   92
## 24    V   92
## 25    B  120
## 26    F  120
## 27    J  120
## 28    N  120
## 29    R  120
## 30    V  120
## 31    B  148
## 32    F  148
## 33    J  148
## 34    N  148
## 35    R  148
## 36    V  148

Eliminamos las ultimas 5 combinaciones ya que el último bloque sólo dispone de un cuadro

columnas_y_filas = columnas_y_filas[1:(nrow(columnas_y_filas)-5),] 

Pegamos ambas columnas para obtener los identificadores de celdas tal como los usa Excel

celdas = paste0(columnas_y_filas$Var1,columnas_y_filas$Var2) 
print(celdas)
##  [1] "B8"   "F8"   "J8"   "N8"   "R8"   "V8"   "B36"  "F36"  "J36"  "N36" 
## [11] "R36"  "V36"  "B64"  "F64"  "J64"  "N64"  "R64"  "V64"  "B92"  "F92" 
## [21] "J92"  "N92"  "R92"  "V92"  "B120" "F120" "J120" "N120" "R120" "V120"
## [31] "B148"

Ya tenemos entonces la lista de todas las celdas que representan la primera celda (arriba y a la izquierda) de cada uno de los bloques donde se encuentran las proyecciones para un año y jurisdicción específicos

Sumamos ahora unos pasos que servirán para agregar las columnas de jurisdicción, sexo y edad en la tabla final. Creamos un vector con los grupos de edad que utiliza INDEC en las proyecciones. Podemos hacerlo desde el rango A8:A28 de la primera hoja, por ejemplo. Luego, hacemos lo mismo con la variables sexo desde B4:D4 y finalmente creamos un vector con la secuencia de años de las proyecciones (2010 a 2040).

grupos_de_edad = readxl::read_xls("poblacion.xls", sheet = sheets[1], range = "A8:A28", col_names = F)[[1]] # vector de grupos de edad
sexo = colnames(readxl::read_xls("poblacion.xls", sheet = sheets[1], range = "B4:D4")) # vector de categorías de sexo
anos = 2010:2040 # vector de años

print(grupos_de_edad)
##  [1] "0-4"       "5-9"       "10-14"     "15-19"     "20-24"     "25-29"    
##  [7] "30-34"     "35-39"     "40-44"     "45-49"     "50-54"     "55-59"    
## [13] "60-64"     "65-69"     "70-74"     "75-79"     "80-84"     "85-89"    
## [19] "90-94"     "95-99"     "100 y más"
print(sexo)
## [1] "Ambos sexos" "Varones"     "Mujeres"
print(anos)
##  [1] 2010 2011 2012 2013 2014 2015 2016 2017 2018 2019 2020 2021 2022 2023 2024
## [16] 2025 2026 2027 2028 2029 2030 2031 2032 2033 2034 2035 2036 2037 2038 2039
## [31] 2040

Ya tenemos toda la información que necesitamos:

  • Las jurisdicciones (a partir de los nombres de las hojas, en la variable sheets)
  • Los grupos de edad (en la variable grupos_de_edad)
  • Las categorías de sexo (en la variable sexo)
  • Las celdas donde empieza cada bloque (en la variable celdas)

Con esa información podemos recorrer el archivo e ir extrayendo la información para generar un data frame. Ésta será la tarea más compleja.

5 - Crear el data frame

Vamos a hacer ahora un loop que recorra hoja por hoja el archivo y a medida que pase por cada cuadro lo agregue a un data frame debajo del cuadro anterior, identificando la jurisdicción y el grupo de edad en filas y el sexo en columnas

resultado = data.frame() # creamos en data.frame vacío donde se guardarán los resultados

for (i in sheets) { # recorre las hojas del archivo original
  for (j in celdas) { # recorre cada una de las celdas donde comienza un bloque de datos
    ano=anos[which(celdas==j)] # identifica el año del bloque que está capturando
    rango = c(j,
              paste0(LETTERS[which(LETTERS==substring(j,1,1))+2],as.numeric(substring(j,2,4))+20)) 
    rango = paste(rango,collapse = ":") # obtiene el rango completo del bloque
    cuadro = readxl::read_xls("poblacion.xls", sheet = i, range = rango, col_names = F) # lee el bloque
    colnames(cuadro) = sexo # pone nombre de columnas a los datos obtenidos
    cuadro$ano = ano # agrega el año de los datos
    cuadro$juri = i # agrega la jurisdicción a los datos
    cuadro$edad = grupos_de_edad # agrega las etiquetas de los grupos de edad
    resultado = rbind(
      resultado,
      cuadro[,c(4,5,6,1,2,3)]
    ) # une los datos obtenidos al data frame donde se almacenarán todos (resultado)
  }
}

Ahora sí, contamos con un data frame que contiene los datos en un formato amigable:

print(resultado)
## # A tibble: 16,275 × 6
##      ano juri              edad  `Ambos sexos` Varones Mujeres
##    <int> <chr>             <chr>         <dbl>   <dbl>   <dbl>
##  1  2010 01-TOTAL DEL PAÍS 0-4         3571540 1838771 1732769
##  2  2010 01-TOTAL DEL PAÍS 5-9         3507135 1794531 1712604
##  3  2010 01-TOTAL DEL PAÍS 10-14       3541954 1801750 1740204
##  4  2010 01-TOTAL DEL PAÍS 15-19       3559813 1797164 1762649
##  5  2010 01-TOTAL DEL PAÍS 20-24       3346483 1674870 1671613
##  6  2010 01-TOTAL DEL PAÍS 25-29       3166874 1575889 1590985
##  7  2010 01-TOTAL DEL PAÍS 30-34       3112375 1540513 1571862
##  8  2010 01-TOTAL DEL PAÍS 35-39       2711144 1334655 1376489
##  9  2010 01-TOTAL DEL PAÍS 40-44       2347809 1149610 1198199
## 10  2010 01-TOTAL DEL PAÍS 45-49       2212137 1076990 1135147
## # ℹ 16,265 more rows

Podemos mejorar la presentación de los datos.

# separamos los códigos de jurisdicción de los nombres
resultado$juri_nombre = substring(resultado$juri,4,max(nchar(resultado$juri)))
resultado$juri = substring(resultado$juri,1,2)

También podemos usar tidyr (Wickham, Vaughan, and Girlich 2023) para pasar la variable sexo a las filas y DT (Xie, Cheng, and Tan 2023).

if ("tidyr" %in% installed.packages()[,"Package"]) {library(tidyr)} else {install.packages("tidyr");library(tidyr)}
if ("DT" %in% installed.packages()[,"Package"]) {library(DT)} else {install.packages("DT");library(DT)}

resultado = resultado %>% pivot_longer(cols = 4:6,
                                       names_to = "sexo_nombre",
                                       values_to = "poblacion") # pasa sexo a filas

# codifica sexo
resultado$sexo_codigo = ""
resultado$sexo_codigo[resultado$sexo_nombre=="Ambos sexos"] = "0"
resultado$sexo_codigo[resultado$sexo_nombre=="Varones"] = "1"
resultado$sexo_codigo[resultado$sexo_nombre=="Mujeres"] = "2"

resultado = resultado[,c(1,2,4,7,5,3,6)] # ordena columnas
DT::datatable(resultado)

Podemos observar que generamos un data frame de 48.825 filas. Si tenemos en cuenta que tabulamos información de 31 años, en 25 jurisdicciones (incluyendo “total país”), para 3 categorías de sexo (incluyendo “ambos sexos”) y 21 grupos de edad, podemos comprobar si nuestro trabajo fue correcto:

filas_data_frame = 48825
n_anos = 31
n_jurisdicciones = 25
n_categorias_sexo = 3
n_grupos_de_edad = 21

filas_esperadas = n_anos * n_jurisdicciones * n_categorias_sexo * n_grupos_de_edad

print(filas_data_frame == filas_esperadas)
## [1] TRUE

6 - Visualización

Finalmente, podemos hacer una visualización sencilla de los datos usando los paquetes shiny (Chang et al. 2022), highcharter (Kunst 2022) y htmlwidgets (Vaidyanathan et al. 2023).

library(shiny)
library(dplyr)
library(highcharter)
library(shinyWidgets)

ui <- fluidPage(
  column(3,
         br(),
         selectizeInput(inputId = "ano", 
                        label = "Seleccionar año:", 
                        choices = unique(resultado$ano),
                        selected = substring(Sys.Date(),1,4)),
         selectizeInput(inputId = "juri", 
                        label = "Seleccionar jurisdicción:", 
                        choices = unique(resultado$juri_nombre))
  ),
  column(6,
         br(),
         highchartOutput("grafico")),
  column(3)
) 

# definimos la lógica para elaborar el gráfico de pirámides a partir de la información ingresada en la ui
server <- function(input, output, session) {
  output$grafico = renderHighchart({
    datos_grafico = resultado[
      resultado$ano==input$ano &
        resultado$sexo_codigo!="0" &
        resultado$juri_nombre==input$juri,]
    
    highchart() %>%
      hc_chart(type = "bar") %>%
      hc_title(text = paste("Pirámide de población", "-", input$juri, "-", input$ano)) %>%
      hc_xAxis(categories = rev(unique(datos_grafico$edad))) %>%
      hc_yAxis(title = list(text = "Población"),
               labels = list(formatter = JS( 
                 "function() {    
                    return Math.abs(this.value); 
                  }"
               )),
               max = max(datos_grafico$poblacion)*1.1,
               min = max(datos_grafico$poblacion)*1.1*-1) %>%
      hc_plotOptions(series = list(stacking = "normal",
                                   groupPadding = 0,
                                   pointPadding = 0,
                                   borderWidth = .1)) %>%
      hc_add_series(name = "Varones", data = rev(datos_grafico$poblacion[datos_grafico$sexo_codigo=="1"])*-1, color = "#d8b365") %>%
      hc_add_series(name = "Mujeres", data = rev(datos_grafico$poblacion[datos_grafico$sexo_codigo=="2"]), color = "#5ab4ac") %>%
      hc_legend(align = "right", verticalAlign = "top", reversed = TRUE) %>%
      hc_tooltip(formatter = JS("function () {
                                  if (this.series.name === 'Varones') {
                                    return `<b>${this.series.name}</b></br>${this.y*-1}`
                                  } else if (this.series.name === 'Mujeres') {
                                    return `<b>${this.series.name}</b></br>${this.y}`}}")) %>%
      hc_exporting(enabled = TRUE)
  })  
}

# mostramos la aplicación en el servidor local
shinyApp(ui, server)

Conclusiones

Los formatos en los cuales se puede acceder a la información sobre proyecciones de población de Argentina resultan poco amigables para los procesamientos con herramientas informáticas. Este documento muestra una metodología sencilla para dar a esos datos un formato acorde a la ciencia de datos que aumenta las posibilidades de uso de esta información en casos que requieren procesamientos complejos (series temporales, cálculo de indicadores en lote, desarrollo de visualizaciones, etc.). El software estadístico R (R Core Team 2019) se presenta como una alternativa eficiente para esta tarea.

Referencias

Chang, Winston, Joe Cheng, JJ Allaire, Carson Sievert, Barret Schloerke, Yihui Xie, Jeff Allen, Jonathan McPherson, Alan Dipert, and Barbara Borges. 2022. Shiny: Web Application Framework for r. https://CRAN.R-project.org/package=shiny.
Kunst, Joshua. 2022. Highcharter: A Wrapper for the ’Highcharts’ Library. https://CRAN.R-project.org/package=highcharter.
R Core Team. 2019. R: A Language and Environment for Statistical Computing. Vienna, Austria: R Foundation for Statistical Computing. https://www.R-project.org.
Schauberger, Philipp, and Alexander Walker. 2023. Openxlsx: Read, Write and Edit Xlsx Files. https://CRAN.R-project.org/package=openxlsx.
Vaidyanathan, Ramnath, Yihui Xie, JJ Allaire, Joe Cheng, Carson Sievert, and Kenton Russell. 2023. Htmlwidgets: HTML Widgets for r. https://CRAN.R-project.org/package=htmlwidgets.
Wickham, Hadley, and Jennifer Bryan. 2023. Readxl: Read Excel Files. https://CRAN.R-project.org/package=readxl.
Wickham, Hadley, Romain François, Lionel Henry, Kirill Müller, and Davis Vaughan. 2023. Dplyr: A Grammar of Data Manipulation. https://CRAN.R-project.org/package=dplyr.
Wickham, Hadley, Davis Vaughan, and Maximilian Girlich. 2023. Tidyr: Tidy Messy Data. https://CRAN.R-project.org/package=tidyr.
Xie, Yihui, Joe Cheng, and Xianying Tan. 2023. DT: A Wrapper of the JavaScript Library ’DataTables’. https://CRAN.R-project.org/package=DT.