Presentación

Esta publicación tiene dos objetivos, uno personal y otro general. El objetivo personal es tener en limpio y siempre disponible un script de consulta para manipulaciones y cálculos básicos de salario promedio y empleo con los Datos Abiertos del Instituto Mexicano del Seguro Social (IMSS), todo usando la librería {dplyr}. El segundo objetivo, el general, es simplemente compartirlo a todo aquel que guste.

No asumo que mi código sea el más eficiente o esté libre de errores, así que cualquier corrección, sugerencia o comentario házmelo saber por este medio o a través de mi twitter ecodiegoale.

Además, nunca está de más revisar stackoverflow, github, rpubs o preguntarle a ChatGPT.

Aviso: Si algún link no abre con click izquierdo, trata con el derecho y abrir en otra ventana o pestaña.

Contenido

En este blog usaré los Datos Abiertos del IMSS sobre empleo de las personas trabajadoras aseguradas y salarios promedio, que comúnmente se le llama salario base de cotización promedio (sbc). Estas son bases de datos que contienen miles de registros, por lo que no es posible abrirlo enteramente en una hoja de cálculo de excel, por esto el motivo de hacerlo con R y específicamente yo lo haré con {dplyr}.

Así que en este blog mostraré cómo calcular el empleo asegurado total a nivel nacional y el sbc.

Adicionalmente, mostraré cómo calcular esas variables no solo a nivel nacional, sino por sector económico y género. El nivel de desagregación ya dependerá de tus propias necesidades.

Preparación

Lo primero será definir tu directorio de trabajo y cargar las librerías necesarias para el análisis.

#setwd("C:/Users/tu carpeta")

rm(list = ls()) #para borrar todos los objetos en la memoria de la consola
options(scipen=999) #para desactivar la notación científica

#Si no cuentas con los paquetes, usa el comando install.packages("ejemplo")

library(dplyr)
library(ggplot2)
library(scales)

Conforme avancemos en el blog haré mención de cuando estemos usando las librerías cargadas.

Buscar y descargar los Datos Abiertos

En esta sección vamos a descargar los datos a la antigua, es decir, navegando el sitio web oficial. Si bien podríamos escribir una función que lo haga no es objetivo de este blog, además creo que es más educativo aprender la ruta de navegación. El sitio web oficial para descargar los datos es https://datos.imss.gob.mx/sin embargo, muchas veces sucede que el sitio está en mantenimiento o simplemente está caído. Así que lo haremos con otra ruta.

  1. Lo primero será entrar al portal de Datos Abiertos del Gobierno de México. El enlace es https://www.datos.gob.mx/. En su buscador escribiremos asegurados del imss.

  1. Luego, saldrán los resultados de búsqueda. Seleccionaremos este dando click:

  1. Se desplegarán todos los archivos disponibles mes con mes, para fines de este blog usaremos el más reciente. Damos click sobre el nombre:

  1. Luego, damos click derecho sobre descargar y copiamos el vínculo.

  1. Finalmente, pegamos dicho vínculo en otra pestaña y la descarga iniciará.

  1. Por último, recomiendo descargar el Diccionario de datos asegurados que se encuentra también en el conjunto de datos de asegurados. Este es un archivo excel bastante útil que contiene todo lo necesario para entender el contenido de los Datos Abiertos. La manera de descargar es igual, click derecho sobre descargar, copiar vínculo y pegarlo en otra pestaña.

El contenido de los Datos Abiertos

Antes de iniciar la carga y análisis de los Datos Abiertos vamos a explorar su contenido con un ejemplo. En el archivo del Diccionario de Datos viene una hoja que se llama ejemplo archivo. Vamos a revisarla y aclarar el significado de las variables más importantes, una vez hecho esto, seguimos con los Datos Abiertos reales.

Así lucen los datos en una hoja de excel. Voy a cargarlos al ambiente de R y usaré algunos comandos básicos. Además, como ya dije, vamos a revisar los conceptos y elementos más importantes.

glimpse(base_ejemplo)
## Rows: 52
## Columns: 29
## $ cve_delegacion     <dbl> 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20,…
## $ cve_subdelegacion  <dbl> 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34, 34,…
## $ cve_entidad        <dbl> 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19,…
## $ cve_municipio      <chr> "D42", "D42", "D42", "D42", "D42", "D42", "D42", "D…
## $ sector_economico_1 <dbl> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, …
## $ sector_economico_2 <dbl> 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63,…
## $ sector_economico_4 <dbl> 6303, 6303, 6303, 6303, 6303, 6303, 6303, 6303, 630…
## $ tamaño_patron      <chr> "S2", "S2", "S2", "S2", "S2", "S2", "S2", "S2", "S2…
## $ sexo               <dbl> 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, …
## $ rango_edad         <chr> "E6", "E6", "E7", "E8", "E8", "E9", "E2", "E3", "E3…
## $ rango_salarial     <chr> "W24", "W5", "W3", "W2", "W5", "W5", "W3", "W2", "W…
## $ rango_uma          <chr> "W25", "W6", "W3", "W3", "W5", "W6", "W4", "W3", "W…
## $ asegurados         <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ no_trabajadores    <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ ta                 <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ teu                <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ tec                <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ tpu                <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ tpc                <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ ta_sal             <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ teu_sal            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ tec_sal            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ tpu_sal            <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ tpc_sal            <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ masa_sal_ta        <dbl> 1126.42, 197.14, 119.45, 83.62, 206.92, 220.39, 131…
## $ masa_sal_teu       <dbl> 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.00, 0.0…
## $ masa_sal_tec       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …
## $ masa_sal_tpu       <dbl> 1126.42, 197.14, 119.45, 83.62, 206.92, 220.39, 131…
## $ masa_sal_tpc       <dbl> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, …

Hay algunas variables cuyos significados son evidentes, además en el Diccionario de Datos viene la definición a detalle de cada una. Antes de proceder con las variables de interés, vamos a enunciar algunos puntos importantes sobren la naturaleza de estos Datos Abiertos.

  1. Cada fila u observación no son individuos ni delegaciones en sí. Cada fila es una suma de personas aseguradas y la masa salarial asociada a estos; estos asegurados comparten ciertas características laborales y sociodemográficas. Si bien , no es como en las encuestas donde cada fila es una muestra y tienen su respectivo factor de expansión, cada fila puede entenderse bajo esta lógica.

  2. Estas bases de datos incluyen total de asegurados, es decir, tanto a la persona trabajadora como a las personas a las que tiene derecho a asegurar u otras que sin trabajar tienen seguridad social, como estudiantes universitarios, etc. A nosotros solo nos interesan las personas trabajadoras.

  3. Si los asegurados son trabajadores o trabajadoras se indica si son eventuales o permanentes, urbanos o del campo. Nosotros solo nos fijaremos en los trabajadores asegurados totales, es decir, sin distinguir si son permanentes o eventuales ni de campo o urbanos.

Demos lectura a la primera fila del ejemplo (solo seleccionaré 6 de las 29 variables):

## # A tibble: 1 × 6
##   cve_municipio sector_economico_1 no_trabajadores    ta ta_sal masa_sal_ta
##   <chr>                      <dbl>           <dbl> <dbl>  <dbl>       <dbl>
## 1 D42                            6               0     1      1       1126.

Donde:

  • no_trabajadores: Asegurados sin un empleo asociado.
  • ta_sal: Puestos de trabajo afiliados con un salario asociado.
  • masa_sal_ta: Masa salarial asociada a puestos de trabajo afiliados.

Entonces, en una de las delegaciones del municipio D42 (San Pedro Garza García en el estado de Nuevo León), en el sector económico 6 (Sector Comercio), donde todos los asegurados tienen un empleo asociado (no_trabajadores igual a 0), el total de asegurados (ta) es 1 con un salario registrado para ese puesto (ta_sal es igual a 1), donde la masa salarial es 1,126.42 pesos. Como solo hay un puesto de trabajo (ta_sal es igual a 1) el salario promedio será 1,126.42 pesos dividido por 1.

Cuando no_trabajadores es diferente de 0, esto indica el número de asegurados que no trabajan pero aún así tienen ciertos derechos o beneficios sociales. Si los puestos de trabajo afiliados con un salario son iguales a 0 (ta_sal es igual a 0), implica que no hay salario registrado para ese puesto, esto puede tener diversos motivos, como que el asegurado no está trabajando activamente o no tiene un vínculo formal de empleo en ese momento, por poner algunos ejemplos.

En conclusión, a nosotros nos interesa que el asegurado trabaje (no_trabajadores sea igual a 0), el número de puestos de trabajo (ta_sal) por el que vamos a dividir la masa salarial (masa_sal_ta), por lo que necesitamos que los puestos de trabajo sean mayores a 0 (o la división masa_sal_ta/ta_sal se indetermina). Más adelante veremos en el dataset real que para las filas donde no_trabajadores es diferente de 0, la información como del sector económico, el rango salarial o el tamaño del patrón es NA y el número de trabajadores asegurados (ta) es 0, porque justamente, si no_trabajadores es diferente de 0, estamos hablando de asegurados sin un empleo asociado.

Los Datos Abiertos

Cargando los Datos

El tipo de archivo que descargamos del portal de datos es .csv.

Primero, hay que averiguar cuál es el separador del archivo. En el contexto de un archivo .csv, un separador es el carácter que delimita o separa los diferentes campos o columnas dentro de una fila de datos. Los separadores más comunes son la coma (,), el punto y coma (;), la barra vertical (|), o el tabulador. Es importante identificar correctamente el separador antes de cargar el archivo en R para que los datos se estructuren de forma adecuada.

Hay varias formas de hacerlo y la eficiencia dependerá del tamaño del archivo. Por ahora con el siguiente comando basta:

readLines("asg-2024-07-31.csv", n = 5)
## [1] "cve_delegacion|cve_subdelegacion|cve_entidad|cve_municipio|sector_economico_1|sector_economico_2|sector_economico_4|tama\xf1o_patron|sexo|rango_edad|rango_salarial|rango_uma|asegurados|no_trabajadores|ta|teu|tec|tpu|tpc|ta_sal|teu_sal|tec_sal|tpu_sal|tpc_sal|masa_sal_ta|masa_sal_teu|masa_sal_tec|masa_sal_tpu|masa_sal_tpc"
## [2] "1|1|1|A01||||NA|1|E1|NA|NA|147|147|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0"                                                                                                                                                                                                                                                                  
## [3] "1|1|1|A01||||NA|1|E10|NA|NA|1248|1248|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0"                                                                                                                                                                                                                                                               
## [4] "1|1|1|A01||||NA|1|E11|NA|NA|1062|1062|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0"                                                                                                                                                                                                                                                               
## [5] "1|1|1|A01||||NA|1|E12|NA|NA|521|521|0|0|0|0|0|0|0|0|0|0|0|0|0|0|0"

De lo anterior, queda en evidencia que el separador es |. No hay una única forma de abrir este tipo de archivos con este separador, sin embargo, yo utilizo {data.table}, pues tiene funciones muy concretas. Si no cuentas con este paquete, instálalo y luego cárgalo. De este paquete vamos a usar la función fread(). La función fread() pertenece al paquete {data.table} en R y es ampliamente utilizada para leer archivos grandes de texto delimitados de manera eficiente. A diferencia de otras funciones como read.csv(), fread() está optimizada para manejar grandes volúmenes de datos de forma más rápida y flexible. A menudo puede detectar el delimitador (separador) y el tipo de las columnas automáticamente, lo que facilita su uso cuando no se conoce la estructura exacta del archivo.

library(data.table)
datos <- fread(input = "asg-2024-07-31.csv", sep = "|", encoding = "Latin-1")

Vamos a desglosar cada argumento usado:

  • input: especifica la ruta del archivo o una conexión de entrada desde donde leer los datos.
  • sep: Define el carácter delimitador que separa las columnas en el archivo de texto. Si no se especifica, fread() intentará detectar el separador automáticamente.
  • encoding: Establece la codificación de caracteres del archivo. Esto es importante para leer correctamente caracteres especiales y acentos. encoding = “Latin-1” indica que el archivo está codificado en la codificación Latin-1, que es común en archivos de texto en español y otros idiomas europeos. Del Diccionario sabemos que existe una columna llamada tamaño_patron, la ñ a veces puede ser problemática, por lo que Latin-1 nos ayuda a manejarla.

Una vez cargados, podemos empezar a manipularlos.

Manipulando los Datos

Podemos explorar los datos de diversas maneras, aquí ya usamos glimpse() de {dplyr}. Vamos a ver algo que dije anteriormente.

datos %>% 
  filter(no_trabajadores > 0) %>% 
  select(cve_municipio, sector_economico_1, no_trabajadores, 
         ta, ta_sal, masa_sal_ta) %>% 
  head(5)
##    cve_municipio sector_economico_1 no_trabajadores ta ta_sal masa_sal_ta
## 1:           A01                 NA             147  0      0           0
## 2:           A01                 NA            1248  0      0           0
## 3:           A01                 NA            1062  0      0           0
## 4:           A01                 NA             521  0      0           0
## 5:           A01                 NA             309  0      0           0

Como vemos, no_trabajadores diferente de 0 implica a los registros de aquellos asegurados que no trabajan, como los familiares del trabajador, universitarios que gozan de seguro facultativo, incorporados voluntarios, etc. Por esto no hay trabajadores asegurados, ni sector económico en el que se desempeñen, o puestos de trabajo o masa salarial.

Una vez aclarado esto, vamos con las variables que nos importan: empleo formal total y su salario promedio (sbc).

Empleo y salario base de cotización

Primero, vamos con el empleo. El cálculo del empleo no es más que sumar todos los puestos de trabajo. Vamos a revisar el boletín del mes de julio de 2024 del IMSS (link aquí)

Al 31 de julio de 2024 se registraron ante el IMSS 22,331,788 (veintidós millones trescientos treinta y un mil setecientos ochenta y ocho) puestos de trabajo.

datos %>% 
  summarise(
    empleo = sum(ta)
  ) %>% 
  knitr::kable()
empleo
22331788

Si lo queremos por sector, entidad federativa o sexo, solo basta con hacer agrupaciones. Por ejemplo, por sexo luciría así:

datos %>%
  group_by(sexo) %>% 
  summarise(
    empleo = sum(ta)
  ) %>% 
  knitr::kable()
sexo empleo
1 13406298
2 8925490

Lo anterior lo podemos comprobar entrando al dashboard del IMSS y seleccionamos los filtros y criterios deseados. Puedes acceder dando click en este enlace.

Ahora, vamos con el salario promedio. Aplicaremos lo que hemos desarrollado hasta ahorita, esto se verá reflejado en forma de filtros. Como mencioné, las variables más importantes son no_trabajadores, ta, ta_sal, masa_sal_ta. Primero, vamos con los filtros necesarios, ya después haremos cualquier cálculo u operación. Reitero, necesitamos solo a los asegurados que sí trabajan y el número de puestos asociados a un salario. Este último fungirá como un ponderador, pues como ya dije, las filas no son trabajadores individuales, sino un agregado de trabajadores y asegurados que comparten características similares. Si lo quieres ver, puede ser como un factor de expansión, pero repito, no lo es, pues estas bases no fueron hechas a partir de muestreos, sino registros administrativos.

Para saber que lo hicimos bien y que nuestras operaciones van a estar correctas, propongo calcular el salario promedio a nivel nacional de este mes (para fines de este blog usamos la base de julio).

Al 31 de julio de 2024 el salario base de cotización promedio de los puestos de trabajo afiliados alcanzó un monto de $588.7(quinientos ochenta y ocho punto siete pesos).

Para conseguir esto, los filtros lucirán así:

datos <- datos %>% 
    filter(no_trabajadores == 0) %>% #asegurados que sí trabajan
    filter(ta_sal != 0) #el ponderador

Ya tenemos una base con solo datos de trabajadores.

Si nuestros cálculos no dan esta cifra, algo hicimos mal. Lo primero será calcular el sbc.

# Creamos el salario base de cotización
  datos <- datos %>% 
    mutate(
      # dividimos la masa salarial por el número de puestos de trabajo asociados a esta masa
      sbc = masa_sal_ta/ta_sal, 
      # para los casos donde el sbc sea 0,lo sustituimos por missings o NAs
      sbc = ifelse(sbc == 0, NA, sbc) 
    )

Ahora, debemos calcular el salario a nivel nacional. El salario, por su naturaleza, debe ser un promedio, el salario del comunicado es el salario promedio a nivel nacional, pero no un simple promedio, como hay personas que ganan mucho y otras que ganan poco, un promedio aritmético sería sensible a la desigualdad de ingresos, además, ya dijimos que los registros no son a nivel a individual, sino que son agregados de personas con características similares, una de estas es el nivel salarial, por lo tanto, el salario promedio debe estar ponderado.

datos %>% 
  summarise(
    sbc_prom = weighted.mean(sbc, w = ta_sal)
  ) %>% 
  knitr::kable()
sbc_prom
588.711

Nuevamente, si lo quisieramos desagregado o con base en diferentes características basta con usar group_by().

datos %>% 
  group_by(sector_economico_1, sexo) %>% 
  summarise(
    sbc_prom = weighted.mean(sbc, w = ta_sal)
  ) %>% 
  knitr::kable()
sector_economico_1 sexo sbc_prom
0 1 418.5857
0 2 398.1723
1 1 962.3881
1 2 882.4479
3 1 693.5957
3 2 534.3245
4 1 446.1412
4 2 432.4897
5 1 1197.7364
5 2 1106.8438
6 1 561.9996
6 2 484.2616
7 1 615.7314
7 2 596.4706
8 1 632.2735
8 2 549.6160
9 1 720.9222
9 2 645.4375

Del cuadro anterior podemos ver que sin importar el sector económico, las mujeres (donde sexo es igual a 2) ganan menos que los hombres en promedio. O que la Industria eléctrica, y captación y suministro de agua potable (donde sector_economico_1 es igual a 5) es la que tiene los salarios más altos. Muchas conclusiones pueden hacerse, ya será cuestión de tu pregunta de investigación.

Un gráfico de paletas

Finalmente, vamos a visualizar la última tabla. No me detendré a explicar las funciones empleadas con {ggplot2}, pues sobre esto tengo ya varias entradas (click aquí).

datos_resumen <- datos %>% 
  group_by(sector_economico_1, sexo) %>% 
  summarise(sbc_prom = weighted.mean(sbc, w = ta_sal, na.rm = TRUE)) %>%
  filter(!is.na(sector_economico_1)) %>% 
  mutate(
    label_sec = case_when(
      sector_economico_1 == 0 ~ "Sector primario",
      sector_economico_1 == 1 ~ "Industrias extractivas",
      sector_economico_1 == 3 ~ "Industrias de transformación",
      sector_economico_1 == 4 ~ "Industria de construcción",
      sector_economico_1 == 5 ~ "Industria eléctrica y de agua potable",
      sector_economico_1 == 6 ~ "Comercio",
      sector_economico_1 == 7 ~ "Transportes y comunicaciones",
      sector_economico_1 == 8 ~ "Servicios para privados",
      sector_economico_1 == 9 ~ "Servicios sociales y comunales",
      TRUE ~ NA_character_  # Manejo de NA en sector_economico_1
    ),
    label_sex = case_when(
      sexo == 1 ~ "Hombre",
      sexo == 2 ~ "Mujer"  # Corregido
    )
  ) %>% 
  ungroup()
ggplot(data = datos_resumen, 
       aes(x = reorder(label_sec, +sbc_prom), y = sbc_prom)) +
  geom_segment(aes(x = label_sec, xend = label_sec, 
                   y = 0, yend = sbc_prom),
               size = 1,
               alpha = 0.2) +
  geom_point(aes(color = label_sex), size = 3) +
  scale_y_continuous(seq(0, max(datos_resumen$sbc_prom, 
                                na.rm = TRUE), by = 100),
                     labels = scales::label_dollar(),
                     expand = c(0,0),
                     limits = c(0, 1300)) +
  MetBrewer::scale_color_met_d("Hiroshige", direction = -1)+
  labs(title = "Salario base de cotización promedio diario",
       subtitle = "Por sector y sexo",
       caption = "Cifras originales de julio 2024\nElaboración propia con información de IMSS (2024) | @ecodiegale") +
  theme_ecodiegoale +
  coord_flip()