R para el análisis de datos en Ciencias Sociales

Gonzalo Almedariz Villanueva

¡Bienvenidos!

Estructura del taller

  • Sobre el libro

  • Un breve introducción

  • El tidyverse con el World Wealth and Income Database

  • Flujo descriptivo e inferencial con GSS

  • Un pequeño acercamiento a la Regresión Lineal

Sobre el libro

Este libro está dirigido a estudiantes e investigadores de las ciencias sociales que buscan una comprensión clara y práctica de la estadística aplicada. Su propósito es ofrecer una base sólida para el análisis de datos empíricos mediante el uso del lenguaje de programación R, integrando teoría, técnica y contexto.

Una breve introducíon

La interfaz

El código

Conociendo nuestro data set

La función str() nos da su estructura

str(iris)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Con head() vemos las primeras filas. Puedes controlar cuantas con n

head(iris, n = 5)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa

El tidyverse

El análisis de datos suele seguir un flujo: importar, limpiar, transformar, visualizar y comunicar resultados.

Cada paquete está diseñado para integrarse de forma intuitiva y reproducible.

Tidyr

El paquete tidyr nos brinda herramientas esenciales para transformar y reorganizar datos, asegurando que estén en un formato limpio, ordenado y estructurado.

library(tidyr)
library(dplyr) # Para hacer uso de %>% 

pivot_longer(): transforma datos del formato ancho al largo, reuniendo varias columnas en dos: una con los nombres originales y otra con sus valores.

# Agregar columna ID con el número de fila
iris = iris %>%
  mutate(id = row_number())  

# Convertir el dataset a formato largo
iris_long = iris %>%
  pivot_longer(
    # Seleccionar columnas que empiezan con "Sepal" o "Petal"
    cols = starts_with("Sepal") | starts_with("Petal"),  
    # Nombre para la nueva columna que guardará los nombres de las variables originales
    names_to = "measurement",  
    # Nombre para la nueva columna que guardará los valores numéricos
    values_to = "value")

Original

head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species id
1          5.1         3.5          1.4         0.2  setosa  1
2          4.9         3.0          1.4         0.2  setosa  2
3          4.7         3.2          1.3         0.2  setosa  3
4          4.6         3.1          1.5         0.2  setosa  4
5          5.0         3.6          1.4         0.2  setosa  5
6          5.4         3.9          1.7         0.4  setosa  6

Formato largo

head(iris_long, 9) 
# A tibble: 9 × 4
  Species    id measurement  value
  <fct>   <int> <chr>        <dbl>
1 setosa      1 Sepal.Length   5.1
2 setosa      1 Sepal.Width    3.5
3 setosa      1 Petal.Length   1.4
4 setosa      1 Petal.Width    0.2
5 setosa      2 Sepal.Length   4.9
6 setosa      2 Sepal.Width    3  
7 setosa      2 Petal.Length   1.4
8 setosa      2 Petal.Width    0.2
9 setosa      3 Sepal.Length   4.7

pivot_wider(): transforma datos del formato largo al ancho, expandiendo los valores de una columna (como categorías o variables medidas) en nuevas columnas.

# Volver al formato ancho
iris_wide = iris_long %>%
  pivot_wider(
    # Nombres de columnas a partir de 'measurement'
    names_from = measurement,  
    # Valores que llenan esas columnas desde 'value'
    values_from = value)
# Ver las primeras filas
head(iris_wide)
# A tibble: 6 × 6
  Species    id Sepal.Length Sepal.Width Petal.Length Petal.Width
  <fct>   <int>        <dbl>       <dbl>        <dbl>       <dbl>
1 setosa      1          5.1         3.5          1.4         0.2
2 setosa      2          4.9         3            1.4         0.2
3 setosa      3          4.7         3.2          1.3         0.2
4 setosa      4          4.6         3.1          1.5         0.2
5 setosa      5          5           3.6          1.4         0.2
6 setosa      6          5.4         3.9          1.7         0.4

separate(): permite separar una columna con valores compuestos en múltiples columnas, usando un delimitador. específico.

# Separar la columna 'measurement' en dos columnas: 'part' y 'dimension'
iris_sep = iris_long %>%
  separate(
    # Columna a dividir
    col = measurement,             
    # Nuevos nombres de columnas
    into = c("part", "dimension"), 
    # Separador usado (en este caso, el punto)
    sep = "\\.")
head(iris_long)
# A tibble: 6 × 4
  Species    id measurement  value
  <fct>   <int> <chr>        <dbl>
1 setosa      1 Sepal.Length   5.1
2 setosa      1 Sepal.Width    3.5
3 setosa      1 Petal.Length   1.4
4 setosa      1 Petal.Width    0.2
5 setosa      2 Sepal.Length   4.9
6 setosa      2 Sepal.Width    3  

Separado

head(iris_sep)
# A tibble: 6 × 5
  Species    id part  dimension value
  <fct>   <int> <chr> <chr>     <dbl>
1 setosa      1 Sepal Length      5.1
2 setosa      1 Sepal Width       3.5
3 setosa      1 Petal Length      1.4
4 setosa      1 Petal Width       0.2
5 setosa      2 Sepal Length      4.9
6 setosa      2 Sepal Width       3  

unite(): combina dos o más columnas en una nueva, colocando un separador entre los valores. Es el inverso de separate().

# Unir las columnas 'part' y 'dimension' en una sola llamada 'measurement'
iris_unite <- iris_sep %>%
  unite(
    # Nombre de la nueva columna resultante
    col = "measurement", 
    # Columnas a unir
    part, dimension, 
    # Separador entre los valores
    sep = ".")
# Ver las primeras filas
head(iris_unite) 
# A tibble: 6 × 4
  Species    id measurement  value
  <fct>   <int> <chr>        <dbl>
1 setosa      1 Sepal.Length   5.1
2 setosa      1 Sepal.Width    3.5
3 setosa      1 Petal.Length   1.4
4 setosa      1 Petal.Width    0.2
5 setosa      2 Sepal.Length   4.9
6 setosa      2 Sepal.Width    3  

drop_na(): elimina filas con valores faltantes

# Crear un tibble con valores faltantes (NA)
tb = tibble(
  pais = c("Peru", "Mexico", "Colombia", NA),
  casos = c(2, NA, 5, 7))
# Mostrar la tabla original
tb
# A tibble: 4 × 2
  pais     casos
  <chr>    <dbl>
1 Peru         2
2 Mexico      NA
3 Colombia     5
4 <NA>         7
# Eliminar filas que contengan al menos un NA
drop_na(tb)
# A tibble: 2 × 2
  pais     casos
  <chr>    <dbl>
1 Peru         2
2 Colombia     5

replace_na(): permite sustituir valores NA por un valor específico.

tb %>% 
              # Reemplazar los NA de la columna 'casos' por 0
  replace_na(list(casos = 0, 
              # y los NA de la columna 'pais' por "ind"
                  pais = "ind"))
# A tibble: 4 × 2
  pais     casos
  <chr>    <dbl>
1 Peru         2
2 Mexico       0
3 Colombia     5
4 ind          7

World Wealth and Income Database

El paquete WDI permite acceder desde R a una amplia base de datos del Banco Mundial.

library(WDI)

En este ejemplo, vamos a centrarnos en tres indicadores fundamentales para comparar países:

  • PIB per cápita (NY.GDP.PCAP.CD)

  • Esperanza de vida (SP.DYN.LE00.IN)

  • Población total (SP.POP.TOTL)

df = WDI(
  country = "all",      # Pedimos datos para todos los países
  indicator = c(
    "NY.GDP.PCAP.CD",    # PIB per cápita
    "SP.DYN.LE00.IN",    # Esperanza de vida
    "SP.POP.TOTL"        # Población total
  ),
  start = 2008,          # Año inicial
  end = 2022,            # Año final
  extra = TRUE           # Información adicional (como región, ingreso, etc.)
) %>%
  as_tibble()            # Convertir el resultado en un tibble (formato más cómodo para trabajar)
head(df)
# A tibble: 6 × 15
  country     iso2c iso3c  year status lastupdated NY.GDP.PCAP.CD SP.DYN.LE00.IN
  <chr>       <chr> <chr> <int> <chr>  <chr>                <dbl>          <dbl>
1 Afghanistan AF    AFG    2022 ""     2025-06-05            357.           65.6
2 Afghanistan AF    AFG    2013 ""     2025-06-05            637.           62.2
3 Afghanistan AF    AFG    2012 ""     2025-06-05            651.           61.7
4 Afghanistan AF    AFG    2021 ""     2025-06-05            356.           60.4
5 Afghanistan AF    AFG    2009 ""     2025-06-05            452.           60.2
6 Afghanistan AF    AFG    2017 ""     2025-06-05            525.           62.4
# ℹ 7 more variables: SP.POP.TOTL <dbl>, region <chr>, capital <chr>,
#   longitude <chr>, latitude <chr>, income <chr>, lending <chr>
str(df)
tibble [3,990 × 15] (S3: tbl_df/tbl/data.frame)
 $ country       : chr [1:3990] "Afghanistan" "Afghanistan" "Afghanistan" "Afghanistan" ...
 $ iso2c         : chr [1:3990] "AF" "AF" "AF" "AF" ...
 $ iso3c         : chr [1:3990] "AFG" "AFG" "AFG" "AFG" ...
 $ year          : int [1:3990] 2022 2013 2012 2021 2009 2017 2008 2014 2015 2016 ...
 $ status        : chr [1:3990] "" "" "" "" ...
 $ lastupdated   : chr [1:3990] "2025-06-05" "2025-06-05" "2025-06-05" "2025-06-05" ...
 $ NY.GDP.PCAP.CD: num [1:3990] 357 637 651 356 452 ...
  ..- attr(*, "label")= chr "GDP per capita (current US$)"
 $ SP.DYN.LE00.IN: num [1:3990] 65.6 62.2 61.7 60.4 60.2 ...
  ..- attr(*, "label")= chr "Life expectancy at birth, total (years)"
 $ SP.POP.TOTL   : num [1:3990] 40578842 31622704 30560034 40000412 27466101 ...
  ..- attr(*, "label")= chr "Population, total"
 $ region        : chr [1:3990] "South Asia" "South Asia" "South Asia" "South Asia" ...
 $ capital       : chr [1:3990] "Kabul" "Kabul" "Kabul" "Kabul" ...
 $ longitude     : chr [1:3990] "69.1761" "69.1761" "69.1761" "69.1761" ...
 $ latitude      : chr [1:3990] "34.5228" "34.5228" "34.5228" "34.5228" ...
 $ income        : chr [1:3990] "Low income" "Low income" "Low income" "Low income" ...
 $ lending       : chr [1:3990] "IDA" "IDA" "IDA" "IDA" ...

Dplyr

Se especializa en la manipulación eficiente de datos, permitiendo filtrar, ordenar, agrupar y resumir información.

glimpse(): vista rápida y compacta del data frame. Muestra los nombres de las variables, sus tipos y los primeros valores.

df %>%  
  glimpse()
Rows: 3,990
Columns: 15
$ country        <chr> "Afghanistan", "Afghanistan", "Afghanistan", "Afghanist…
$ iso2c          <chr> "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "…
$ iso3c          <chr> "AFG", "AFG", "AFG", "AFG", "AFG", "AFG", "AFG", "AFG",…
$ year           <int> 2022, 2013, 2012, 2021, 2009, 2017, 2008, 2014, 2015, 2…
$ status         <chr> "", "", "", "", "", "", "", "", "", "", "", "", "", "",…
$ lastupdated    <chr> "2025-06-05", "2025-06-05", "2025-06-05", "2025-06-05",…
$ NY.GDP.PCAP.CD <dbl> 357.2612, 637.0871, 651.4171, 356.4962, 452.0537, 525.4…
$ SP.DYN.LE00.IN <dbl> 65.61700, 62.18800, 61.73500, 60.41700, 60.24800, 62.40…
$ SP.POP.TOTL    <dbl> 40578842, 31622704, 30560034, 40000412, 27466101, 35688…
$ region         <chr> "South Asia", "South Asia", "South Asia", "South Asia",…
$ capital        <chr> "Kabul", "Kabul", "Kabul", "Kabul", "Kabul", "Kabul", "…
$ longitude      <chr> "69.1761", "69.1761", "69.1761", "69.1761", "69.1761", …
$ latitude       <chr> "34.5228", "34.5228", "34.5228", "34.5228", "34.5228", …
$ income         <chr> "Low income", "Low income", "Low income", "Low income",…
$ lending        <chr> "IDA", "IDA", "IDA", "IDA", "IDA", "IDA", "IDA", "IDA",…

La función filter() nos ayuda a filtrar filas bajo una condición.

Quitamos regiones agregadas

df = df %>%
  filter(region != "Aggregates") 

Filtrar al año 2022

df_22 = df %>%
  filter(year == 2022) 
# A tibble: 5 × 15
  country     iso2c iso3c  year status lastupdated NY.GDP.PCAP.CD SP.DYN.LE00.IN
  <chr>       <chr> <chr> <int> <chr>  <chr>                <dbl>          <dbl>
1 Afghanistan AF    AFG    2022 ""     2025-06-05            357.           65.6
2 Albania     AL    ALB    2022 ""     2025-06-05           6846.           78.8
3 Algeria     DZ    DZA    2022 ""     2025-06-05           4962.           76.1
4 American S… AS    ASM    2022 ""     2025-06-05          18017.           72.8
5 Andorra     AD    AND    2022 ""     2025-06-05          42414.           84.0
# ℹ 7 more variables: SP.POP.TOTL <dbl>, region <chr>, capital <chr>,
#   longitude <chr>, latitude <chr>, income <chr>, lending <chr>

select() nos permite elegir solo las columnas que queremos conservar, en el orden que deseamos.

df_22 = df_22 %>% 
  select(country, year, NY.GDP.PCAP.CD:capital, income)

En este caso, seleccionamos variables específicas y también un rango de columnas contiguas (NY.GDP.PCAP.CD hasta capital).

Rows: 217
Columns: 8
$ country        <chr> "Afghanistan", "Albania", "Algeria", "American Samoa", …
$ year           <int> 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2022, 2…
$ NY.GDP.PCAP.CD <dbl> 357.2612, 6846.4261, 4961.5526, 18017.4589, 42414.0590,…
$ SP.DYN.LE00.IN <dbl> 65.61700, 78.76900, 76.12900, 72.75200, 84.01600, 64.24…
$ SP.POP.TOTL    <dbl> 40578842, 2777689, 45477389, 48342, 79705, 35635029, 92…
$ region         <chr> "South Asia", "Europe & Central Asia", "Middle East & N…
$ capital        <chr> "Kabul", "Tirane", "Algiers", "Pago Pago", "Andorra la …
$ income         <chr> "Low income", "Upper middle income", "Upper middle inco…

Podemos eliminar columnas también

df_22 = df_22 %>% 
  select(-year)
Rows: 217
Columns: 7
$ country        <chr> "Afghanistan", "Albania", "Algeria", "American Samoa", …
$ NY.GDP.PCAP.CD <dbl> 357.2612, 6846.4261, 4961.5526, 18017.4589, 42414.0590,…
$ SP.DYN.LE00.IN <dbl> 65.61700, 78.76900, 76.12900, 72.75200, 84.01600, 64.24…
$ SP.POP.TOTL    <dbl> 40578842, 2777689, 45477389, 48342, 79705, 35635029, 92…
$ region         <chr> "South Asia", "Europe & Central Asia", "Middle East & N…
$ capital        <chr> "Kabul", "Tirane", "Algiers", "Pago Pago", "Andorra la …
$ income         <chr> "Low income", "Upper middle income", "Upper middle inco…

arrange(): ordena las filas de un data frame según los valores de una o más variables.

df_22 %>% 
  arrange(SP.DYN.LE00.IN) %>%  # Ordena por esperanza de vida (de menor a mayor)
  head(10)                     # Muestra los 10 países con menor esperanza de vida
# A tibble: 10 × 7
   country       NY.GDP.PCAP.CD SP.DYN.LE00.IN SP.POP.TOTL region capital income
   <chr>                  <dbl>          <dbl>       <dbl> <chr>  <chr>   <chr> 
 1 Central Afri…           467.           18.8     5098039 Sub-S… Bangui  Low i…
 2 Somalia                 573.           53.9    17801897 Sub-S… Mogadi… Low i…
 3 Nigeria                2139.           54.1   223150896 Sub-S… Abuja   Lower…
 4 Chad                    672.           54.5    18455316 Sub-S… N'Djam… Low i…
 5 Lesotho                1030.           56.8     2286110 Sub-S… Maseru  Lower…
 6 South Sudan              NA            57.2    11021177 Sub-S… Juba    Low i…
 7 Mali                    814.           60.0    23072640 Sub-S… Bamako  Low i…
 8 Niger                   610.           60.4    25311973 Sub-S… Niamey  Low i…
 9 Guinea                 1417.           60.4    14055137 Sub-S… Conakry Lower…
10 Benin                  1265.           60.5    13759501 Sub-S… Porto-… Lower…
df_22 %>% 
  arrange(desc(SP.POP.TOTL)) %>%  # Ordena por población total, de mayor a menor
  head(10)                        # Muestra los 10 países más poblados
# A tibble: 10 × 7
   country       NY.GDP.PCAP.CD SP.DYN.LE00.IN SP.POP.TOTL region capital income
   <chr>                  <dbl>          <dbl>       <dbl> <chr>  <chr>   <chr> 
 1 India                  2353.           71.7  1425423212 South… New De… Lower…
 2 China                 12663.           78.2  1412175000 East … Beijing Upper…
 3 United States         78035.           77.4   333271411 North… Washin… High …
 4 Indonesia              4731.           70.9   278830529 East … Jakarta Upper…
 5 Pakistan               1538.           67.4   243700667 South… Islama… Lower…
 6 Nigeria                2139.           54.1   223150896 Sub-S… Abuja   Lower…
 7 Brazil                 9281.           74.9   210306415 Latin… Brasil… Upper…
 8 Bangladesh             2716.           74.3   169384897 South… Dhaka   Lower…
 9 Russian Fede…         15445.           72.5   144236933 Europ… Moscow  High …
10 Mexico                11385.           74.0   128613117 Latin… Mexico… Upper…

mutate(): crear nuevas variables o modificar las existentes dentro de un data frame, manteniendo todas las demás columnas.

df_22 %>%
  # Calcular el logaritmo natural del PIB per cápita
  mutate(log_gdp_percap = log(NY.GDP.PCAP.CD)) %>%
  # Seleccionar solo país, nivel de ingreso y la nueva variable
  select(country, income, log_gdp_percap) %>% 
  # Mostrar las primeras 10 filas
  head(10)
# A tibble: 10 × 3
   country             income              log_gdp_percap
   <chr>               <chr>                        <dbl>
 1 Afghanistan         Low income                    5.88
 2 Albania             Upper middle income           8.83
 3 Algeria             Upper middle income           8.51
 4 American Samoa      High income                   9.80
 5 Andorra             High income                  10.7 
 6 Angola              Lower middle income           7.98
 7 Antigua and Barbuda High income                   9.91
 8 Argentina           Upper middle income           9.54
 9 Armenia             Upper middle income           8.79
10 Aruba               High income                  10.3 

reframe(): reframe() crea un nuevo data frame a partir de un resumen.

df_22 %>%
  reframe(
    # Etiquetas para cada estadístico
    statistic = c("min", "mean", "median", "max", "sd"),  
    # Cálculo de cada valor correspondiente
    value = c(
      min(SP.DYN.LE00.IN, na.rm = TRUE),     # Valor mínimo
      mean(SP.DYN.LE00.IN, na.rm = TRUE),    # Media
      median(SP.DYN.LE00.IN, na.rm = TRUE),  # Mediana
      max(SP.DYN.LE00.IN, na.rm = TRUE),     # Valor máximo
      sd(SP.DYN.LE00.IN, na.rm = TRUE)       # Desviación estándar
    ))
# A tibble: 5 × 2
  statistic value
  <chr>     <dbl>
1 min       18.8 
2 mean      73.1 
3 median    74.2 
4 max       85.7 
5 sd         7.94

group_by(): agrupa los datos según una o más variables. A partir de eso, cualquier operación (como summarise(), mutate(), etc.) se aplica por grupo.

df_22 %>%
  # Agrupa los datos por región
  group_by(region) %>%
  # Resume por grupo: media y desviación estándar de esperanza de vida
  summarise(
    mean_life = mean(SP.DYN.LE00.IN, na.rm = TRUE),
    sd_life = sd(SP.DYN.LE00.IN, na.rm = TRUE)
  ) %>%
  # Ordena de mayor a menor según la media
  arrange(desc(mean_life))
# A tibble: 7 × 3
  region                     mean_life sd_life
  <chr>                          <dbl>   <dbl>
1 North America                   80.2    2.47
2 Europe & Central Asia           78.5    4.27
3 Middle East & North Africa      76.4    4.60
4 Latin America & Caribbean       74.8    3.59
5 East Asia & Pacific             73.6    6.34
6 South Asia                      72.5    4.99
7 Sub-Saharan Africa              63.1    7.98

Se extraen los datos de Perú y Chile, mostrando únicamente su esperanza de vida y PIB per cápita.

df_22 %>%
  # Filtra solo Perú y Chile
  filter(country %in% c("Peru", "Chile")) %>%
  
  # Selecciona solo las columnas de interés
  select(
    country,              # Nombre del país
    SP.DYN.LE00.IN,       # Esperanza de vida
    NY.GDP.PCAP.CD        # PIB per cápita
  )
# A tibble: 2 × 3
  country SP.DYN.LE00.IN NY.GDP.PCAP.CD
  <chr>            <dbl>          <dbl>
1 Chile             79.2         15451.
2 Peru              76.8          7363.

Se listan los cinco países con menor esperanza de vida en Europa y Asia Central.

df_22 %>%
  filter(region == "Europe & Central Asia") %>%      # Filtra países de Europa y Asia Central
  arrange(SP.DYN.LE00.IN) %>%                        # Ordena por esperanza de vida ascendente
  select(country, life_expectancy = SP.DYN.LE00.IN) %>% # Selecciona país y renombra la variable
  head(5)                                            # Muestra los primeros 5
# A tibble: 5 × 2
  country         life_expectancy
  <chr>                     <dbl>
1 Turkmenistan               69.9
2 Greenland                  71.5
3 Moldova                    71.5
4 Tajikistan                 71.6
5 Kyrgyz Republic            72.0

Los cinco países con menor esperanza de vida en Europa y Asia Central.

df_22 %>%
  filter(region == "Europe & Central Asia") %>%       # Filtra por región
  arrange(SP.DYN.LE00.IN) %>%                         # Ordena de menor a mayor esperanza de vida
  select(country, life_expectancy = SP.DYN.LE00.IN) %>% # Selecciona país y renombra variable
  head(5)                                             # Muestra los 5 primeros
# A tibble: 5 × 2
  country         life_expectancy
  <chr>                     <dbl>
1 Turkmenistan               69.9
2 Greenland                  71.5
3 Moldova                    71.5
4 Tajikistan                 71.6
5 Kyrgyz Republic            72.0

Se muestra el país más poblado de cada región, ordenado de mayor a menor población total.

df_22 %>%
  # Agrupar por región
  group_by(region) %>%  
  # Ordenar por población dentro de cada grupo
  arrange(desc(SP.POP.TOTL), .by_group = TRUE) %>%  
  # Tomar el país más poblado por región
  slice_head(n = 1) %>% 
  # Seleccionar región, país y población
  select(region, country, SP.POP.TOTL) %>%  
  # Ordenar el resultado final por población
  arrange(desc(SP.POP.TOTL))                         
# A tibble: 7 × 3
# Groups:   region [7]
  region                     country            SP.POP.TOTL
  <chr>                      <chr>                    <dbl>
1 South Asia                 India               1425423212
2 East Asia & Pacific        China               1412175000
3 North America              United States        333271411
4 Sub-Saharan Africa         Nigeria              223150896
5 Latin America & Caribbean  Brazil               210306415
6 Europe & Central Asia      Russian Federation   144236933
7 Middle East & North Africa Egypt, Arab Rep.     112618250

rename(): permite cambiar el nombre de columnas para facilitar su lectura o adaptación al idioma/contexto.

df_22 = df_22 %>%
  rename(
    pais = country,
    pib_per_capita = NY.GDP.PCAP.CD,
    esperanza_vida = SP.DYN.LE00.IN,
    poblacion_total = SP.POP.TOTL,
    region = region,
    capital = capital,
    ingreso = income,
  )

fct_recode() del paquete forcats para renombrar niveles de factores lo que puede facilitar la interpretación y presentación de los datos.

library(forcats)
df_22 = df_22 %>% 
  mutate(
    region = fct_recode(region,
      "Asia del Sur" = "South Asia",
      "Europa y Asia Central" = "Europe & Central Asia",
      "Medio Oriente y Norte de África" = "Middle East & North Africa",
      "Asia Oriental y Pacífico" = "East Asia & Pacific",
      "África Subsahariana" = "Sub-Saharan Africa",
      "América Latina y el Caribe" = "Latin America & Caribbean",
      "América del Norte" = "North America"
    ),
    
    ingreso = fct_recode(ingreso,
      "Bajo" = "Low income",
      "Medio bajo" = "Lower middle income",
      "Medio alto" = "Upper middle income",
      "Alto" = "High income",
      "No clasificado" = "Not classified"
    )
  )

Limpieza

Se identifican los países que tienen datos faltantes en PIB per cápita o esperanza de vida.

df_22 %>% 
  filter(
    # Falta el PIB per cápita
    is.na(pib_per_capita) |
    # Falta la esperanza de vida
    is.na(esperanza_vida)       
  )
# A tibble: 10 × 7
   pais     pib_per_capita esperanza_vida poblacion_total region capital ingreso
   <chr>             <dbl>          <dbl>           <dbl> <fct>  <chr>   <fct>  
 1 British…             NA           77.2           38319 Améri… "Road … Alto   
 2 Cuba                 NA           77.6        11059820 Améri… "Havan… Medio …
 3 Eritrea              NA           67.8         3409447 Áfric… "Asmar… Bajo   
 4 Gibralt…             NA           83.5           37609 Europ… ""      Alto   
 5 Greenla…             NA           71.5           56661 Europ… "Nuuk"  Alto   
 6 Isle of…             NA           81.0           84132 Europ… "Dougl… Alto   
 7 Korea, …             NA           73.6        26328845 Asia … "Pyong… Bajo   
 8 South S…             NA           57.2        11021177 Áfric… "Juba"  Bajo   
 9 St. Mar…             NA           80.2           28870 Améri… "Marig… Alto   
10 Venezue…             NA           72.6        28213017 Améri… "Carac… No cla…

Podemos asegurarnos de trabajar solo con datos completos usando drop_na(), que elimina las filas con valores faltantes en las variables indicadas.

df_22 =  df_22 %>%
  drop_na(pib_per_capita, esperanza_vida)

Ggplot2

ggplot2 es el paquete central para visualización en el tidyverse. Permite crear gráficos personalizables mediante una lógica de capas: se parte de una base con los datos y las estéticas (aes()), y se añaden elementos como puntos, líneas o etiquetas usando +.

Análisis univariado

En un análisis univariado con ggplot2, podemos explorar la distribución de cada una de nuestras variable usando multitud de gráficos. Estas visualizaciones permiten identificar patrones generales, valores extremos o asimetrías.

El histograma es una herramienta clave para explorar la distribución de una variable numérica. Nos permite ver cómo se agrupan los datos, si la distribución es simétrica, sesgada o presenta varios picos, y si hay valores extremos.

Es especialmente útil al comenzar un análisis, porque nos da una idea visual rápida de la forma general de la variable.

Usamos geom_histogram()

Histograma de la variable PBI per cápita

# Define los datos y la variable de interés 
ggplot(df_22, aes(x = pib_per_capita)) +       
  # Agrega un histograma con color de relleno y borde definidos
  geom_histogram(
    fill = "#800020",           
    color = "black",            
    bins = 30                   
  ) +
  # Título y etiquetas a los ejes
  labs(
    title = "Distribución del PIB per cápita", 
    x = "PIB per cápita", 
    y = "Frecuencia"
  ) +
  # Estilo visual minimalista
  theme_minimal()               

La transformación logarítmica con scale_x_log10() es útil cuando los datos están muy sesgados o tienen una distribución muy amplia, como suele pasar con el PIB.

# Define los datos y la variable de interés
ggplot(df_22, aes(x = pib_per_capita)) +       
  # Agrega un histograma con color de relleno y bordes
  geom_histogram(
    fill = "#800020", 
    bins = 30, 
    color = 'black'
  ) +
  # Escala logarítmica en el eje X para mejorar la visualización
  scale_x_log10() +
  # Título y etiquetas de los ejes
  labs(
    title = "Distribución del PIB per cápita", 
    x = "PIB per cápita (log)", 
    y = "Frecuencia"
  ) +
  theme_minimal()

La transformación logarítmica con scale_x_log10() es útil cuando los datos están muy sesgados o tienen una distribución muy amplia, como suele pasar con el PIB.

Para mejorar el aspecto visual de nuestros gráficos, podemos apoyarnos en paquetes como ggthemes y ggsci, que nos ofrecen estilos y paletas de colores usados en medios y publicaciones profesionales.

library(ggthemes)  
library(ggsci) 

Por ejemplo, con theme_set(theme_economist()) aplicamos de forma global el estilo gráfico de The Economist.

# Aplica el estilo Economist a todos los gráficos
theme_set(theme_economist())  

Histograma de la esperanza de vida

# Histograma de la esperanza de vida en 2022
df_22 %>%
  ggplot(aes(x = esperanza_vida)) +                     
  # Histograma con 30 barras, color celeste y bordes blancos
  geom_histogram(
    bins = 30, 
    fill = "skyblue", 
    color = "white"
  ) +
  # Título y etiquetas de los ejes
  labs(
    title = "Distribución de esperanza de vida (2022)",
    x = "Esperanza de vida", 
    y = "Frecuencia")

El boxplot también es una forma muy útil de explorar cómo se distribuye una variable. Nos muestra de manera clara el rango de valores, dónde se concentra la mayoría de los datos (la mediana y los cuartiles), y si hay valores extremos que se salen de lo común. Usamos geom_boxplot()

Un boxplot de la esperanza de vida en 2022

df_22 %>%
  ggplot(aes(x = esperanza_vida)) +                     
  # Boxplot
  geom_boxplot(
    fill = "skyblue", 
    color = "white"
  ) +
  # Título y etiquetas de los ejes
  labs(
    title = "Distribución de esperanza de vida (2022)",
    x = "Esperanza de vida", 
    y = ""
  )

El paquete patchwork nos permite combinar múltiples gráficos generados con ggplot2 en una misma visualización, de forma intuitiva y sin complicaciones. Es especialmente útil para comparar visualmente distintas variables o perspectivas de un mismo conjunto de datos

library(patchwork)

Podemos ver la distribución de la esperanza de vida em abmos gráficos de forma simultánea

# Histograma de esperanza de vida
hist_pbi = ggplot(df_22, aes(x = esperanza_vida)) +
  geom_histogram(
    fill = "#800020",       # Color de relleno
    bins = 30,              # Número de barras
    color = 'black'         # Borde de las barras
  ) +
  labs(
    title = "Distribución de la esperanza de vida", 
    x = "", 
    y = "Frecuencia"
  )

# Boxplot de esperanza de vida
boxp_pbi = ggplot(df_22, aes(x = esperanza_vida)) +
  geom_boxplot(
    fill = "#800020",       # Color de relleno
    color = 'black'         # Borde del boxplot
  ) +
  labs(
    title = "", 
    x = "PIB per cápita", 
    y = ""
  ) +
  # Elimina texto y ejes laterales
  theme(
    axis.text.y = element_blank(),
    axis.ticks.y = element_blank(),
    axis.title.y = element_blank()
  )

# Combina ambos gráficos verticalmente
hist_pbi / boxp_pbi

Los gráficos de barras son ideales para comparar valores entre categorías. A diferencia del histograma, que se usa para variables continuas, las barras permiten visualizar con claridad las diferencias entre grupos definidos, como regiones, países o niveles de ingreso.

Son especialmente útiles cuando queremos comunicar información de manera directa y ordenada, resaltando qué categorías tienen los valores más altos o bajos.

Usamos geom_col

# Filtrar solo los países seleccionados
df_22 %>%
  filter(pais %in% c("Peru", "Chile", "Brazil", "Argentina")) %>%
  # Definir variables y ordenar por PIB per cápita
  ggplot(aes(x = reorder(pais, pib_per_capita), 
             y = pib_per_capita)) +

  # Dibujar barras horizontales con color definido
  geom_col(fill = "darkgreen",
           color = "black") +
  # Invertir los ejes para facilitar la lectura
  coord_flip() +
  # Añadir título y etiquetas a los ejes
  labs(
    title = "PIB per cápita en 2022", 
    x = "", 
    y = "USD")

Análisis bivariado

El análisis bivariado se centra en examinar la relación entre dos variables, permitiendo explorar si existe alguna asociación entre ellas.

Dependiendo del tipo de variables (categóricas, continuas, ordinales), se utilizan diferentes herramientas estadísticas y visuales.

Ordenar por factores una variable como ingreso nos permite visualizar los datos en un orden lógico y significativo, especialmente cuando estamos trabajando con categorías que tienen una jerarquía implícita.

Vamos a ordenar por factores la variable ingreso.

df_22$ingreso = factor(df_22$ingreso, 
                        levels = c("Bajo", "Medio bajo", "Medio alto", 
                                   "Alto", "No clasificado"),
                        ordered = TRUE)

Los gráficos de dispersión son una herramienta esencial para el análisis bivariado cuando queremos explorar la relación entre dos variables numéricas. A nivel teórico, permiten observar patrones, tendencias, agrupamientos y posibles valores atípicos en los datos.

Cada punto en el gráfico representa una observación individual, ubicada según sus valores en los ejes X e Y.

Un gráfico de dispersión entre PIB per cápita y esperanza de vida puede revelar cómo el desarrollo económico se asocia con la calidad de vida, y si hay diferencias notables entre regiones o niveles de ingreso.

# Crear un gráfico de dispersión entre PIB per cápita y esperanza de vida
ggplot(df_22, aes(x = pib_per_capita, 
                  y = esperanza_vida, 
                  color = region)) +
  # Agregar puntos con transparencia, tamaño y borde definidos
  geom_point(alpha = 0.7, 
             size = 2.5,
             stroke = 0.5) +
  # Aplicar escala logarítmica al eje X
  scale_x_log10() +
  # Aplicar paleta de colores bmj
  scale_color_bmj() +
  # Añadir título y etiquetas de ejes y leyenda
  labs(title = "PIB per cápita y esperanza de vida (2022)", 
       x = "PIB per cápita (log)", 
       y = "Esperanza de vida", 
       color = "Ingreso") +
  # Ajustar el tamaño del texto de la leyenda
  theme(
    legend.text = element_text(size = 9)
  )

En R, el paquete gghighlight nos permite resaltar visualmente estos casos sin perder el contexto del conjunto de datos. Al usar gghighlight(), podemos enfocarnos en valores extremos, condiciones específicas o entidades con interés analítico (como países con mayor esperanza de vida o menor PIB per cápita)

Países con esperanza de vida menor a 30

library(gghighlight)
# Crear gráfico de dispersión entre PIB per cápita y esperanza de vida
ggplot(df_22, aes(x = pib_per_capita, 
                  y = esperanza_vida, 
                  color = ingreso)) +
  # Agregar puntos con transparencia, tamaño y borde definidos
  geom_point(alpha = 0.7, size = 2.5, stroke = 0.5) +
  # Aplicar escala logarítmica al eje X
  scale_x_log10() +
  # Aplicar paleta de colores tipo BMJ
  scale_color_bmj() +
  # Resaltar países con esperanza de vida menor a 30 años
  gghighlight(esperanza_vida < 30, label_key = pais) +
  # Añadir título, etiquetas de ejes y leyenda
  labs(
    title = "PIB per cápita y esperanza de vida (2022)", 
    x = "PIB per cápita (log)", 
    y = "Esperanza de vida", 
    color = "Ingreso"
  ) +
  # Ajustar tamaño del texto en la leyenda
  theme(legend.text = element_text(size = 9))

Países con esperanza de vida mayor a 85

library(gghighlight)
# Crear gráfico de dispersión entre PIB per cápita y esperanza de vida
ggplot(df_22, aes(x = pib_per_capita, 
                  y = esperanza_vida, 
                  color = ingreso)) +
  # Agregar puntos con transparencia, tamaño y borde definidos
  geom_point(alpha = 0.7, size = 2.5, stroke = 0.5) +
  # Aplicar escala logarítmica al eje X
  scale_x_log10() +
  # Aplicar paleta de colores tipo NPG
  scale_color_npg() +
  # Resaltar países con esperanza de vida mayor a 80 años
  gghighlight(esperanza_vida > 80, label_key = pais) +
  # Añadir título, etiquetas de ejes y leyenda
  labs(
    title = "PIB per cápita y esperanza de vida (2022)", 
    x = "PIB per cápita (log)", 
    y = "Esperanza de vida", 
    color = "Ingreso"
  ) +
  # Ajustar tamaño del texto en la leyenda
  theme(legend.text = element_text(size = 9))

facet_wrap() es una herramienta de ggplot2 que permite dividir un gráfico en múltiples paneles según los niveles de una variable categórica.

# Crear gráfico de dispersión entre PIB per cápita y esperanza de vida
ggplot(df_22, aes(x = pib_per_capita, 
                  y = esperanza_vida, 
                  colour = ingreso)) +
  # Agregar puntos con transparencia y tamaño definidos
  geom_point(alpha = 0.7, size = 2.5, stroke = 0.5) +
  # Aplicar escala logarítmica al eje X
  scale_x_log10() +
  # Aplicar paleta de colores tipo NPG (aunque se está usando colour, no fill)
  scale_fill_npg() +
  # Añadir título y etiquetas
  labs(
    title = "Relación entre PIB per cápita y esperanza de vida", 
    x = "PIB per cápita (log)", 
    y = "Esperanza de vida"
  ) +
  # Dividir el gráfico por región
  facet_wrap(~region) +
  # Ocultar leyenda de color
  theme(legend.position = "none")

Cando comparamos varios grupos (categorías) en relación a una variable numérica el boxplot nos permite ver rápidamente diferencias en la dispersión.

Distribución del PIB per cápita por región

df_22 %>%
  # Definir variables estéticas
  ggplot(aes(x = region, 
             y = pib_per_capita,
             fill = region)) +
  # Crear boxplot por región
  geom_boxplot() +
  # Aplicar escala de color NPG
  scale_fill_npg() +
  # Invertir los ejes para facilitar la lectura
  coord_flip() +
  # Agregar título y etiquetas a los ejes
  labs(
    title = "Distribución del PIB per cápita por región",
    x = "Región", 
    y = "PIB per cápita (log)"
  )

Aunque el gráfico anterior nos da una idea general de la distribución del PIB per cápita por región, los valores extremos dificultan la lectura. Para resolver esto, aplicamos una transformación logarítmica que permite visualizar mejor las diferencias relativas entre regiones.

df_22 %>%
  # Definir variables estéticas
  ggplot(aes(x = region, 
             y = pib_per_capita,
             fill = region)) +
  # Boxplot por región
  geom_boxplot() +
  # Transformar eje Y a escala logarítmica
  scale_y_log10() +
  # Escala de color con paleta npg
  scale_fill_npg() +
  # Invertir ejes para mejorar legibilidad
  coord_flip() +
  # Título y etiquetas
  labs(
    title = "Distribución del PIB per cápita por región",
    x = "Región", 
    y = "PIB per cápita (log)")

Gráficos listos para publicación

Una vez que entendemos cómo construir gráficos efectivos, el siguiente paso es afinarlos para que cumplan con no solo claridad y precisión, sino también estética y coherencia visual.

Esto implica ajustar temas, tipografías, escalas, colores y leyendas, así como también eliminar elementos innecesarios o añadir detalles sutiles que orienten la lectura. Existen múltiples formas de personalizar y mejorar nuestros gráficos

Gráfico 1

p1 = df_22 %>%
  # Excluir país con valor extremadamente bajo para evitar distorsión visual
  filter(pais != 'Central African Republic') %>% 
  # Iniciar gráfico con eje X como logaritmo base 10 del PIB per cápita
  ggplot(aes(x = log10(pib_per_capita))) +
  # Agregar histograma con 30 divisiones, color de borde negro y relleno violeta oscuro
  geom_histogram(
    bins = 30, 
    color = "black", 
    alpha = 0.9, 
    fill = '#440154'
  ) +
  # Añadir título principal y subtítulo para contextualizar el gráfico
  labs(
    title = "Desigualdad en el ingreso",
    subtitle = "Distribución del PIB per cápita global",
    x = "PIB per cápita (log)", 
    y = ""
  ) +
  # Eliminar leyenda innecesaria
  theme(legend.position = "none")

Gráfico 2

p2 = df_22 %>% 
  # Eliminar países sin clasificación de ingreso
  filter(ingreso != "No clasificado") %>% 
  # Excluir país con valor atípico extremo
  filter(pais != 'Central African Republic') %>% 
  # Definir variables para el gráfico
  ggplot(aes(x = ingreso, 
             y = esperanza_vida, 
             fill = ingreso)) +
  # Agregar boxplot con un poco de transparencia
  geom_boxplot(alpha = 0.8) +
  # Aplicar paleta de color tipo Nature Publishing Group
  scale_fill_npg() +
  # Títulos y etiquetas de los ejes
  labs(
    title = "Nivel de ingreso",
    x = "Nivel de ingreso", 
    y = "Esperanza de vida"
  ) +
  # Eliminar leyenda para simplificar el gráfico
  theme(legend.position = "none")

Gráfico 3

p3 = df_22 %>% 
  # Excluir país con valor extremo
  filter(pais != 'Central African Republic') %>% 
  # Definir variables para el gráfico
  ggplot(aes(x = pib_per_capita, 
             y = esperanza_vida)) +
  # Agregar puntos al gráfico de dispersión
  geom_point(size = 3, alpha = 0.7,
             color = '#440154') +
  # Agregar línea de regresión lineal sin intervalo de confianza
  geom_smooth(method = 'lm', se = F, 
              color = '#27AE60', linetype = 'dashed',
              linewidth = 2) +
  # Transformar el eje X a escala logarítmica
  scale_x_log10() +
  # Escala de relleno (no visible pero definida por consistencia)
  scale_fill_npg() +
  # Título y etiquetas de los ejes
  labs(
    title = "Ingreso y esperanza de vida",
    x = "PIB per cápita (log)", 
    y = "Esperanza de vida"
  ) +
  # Eliminar leyenda
  theme(legend.position = "none")

Los combinamos con patchwork

((p1 / p2) | p3) +
  plot_annotation(
    title = "Una mirada global al ingreso y esperanza de vida (2022)",
    caption = "World Wealth and Income Database (2022)")

Gráfico 1

g1 = df_22 %>%
  # Calcular la esperanza de vida promedio por región
  group_by(region) %>%
  summarise(mean_life = mean(esperanza_vida, na.rm = TRUE)) %>%
  # Definir variables estéticas
  ggplot(aes(x = reorder(region, mean_life), 
             y = mean_life, 
             fill = region)) +
  # Agregar barras horizontales
  geom_col(width = 0.6) +
  # Línea de referencia con la media global
  geom_hline(yintercept = mean(df_22$esperanza_vida, na.rm = TRUE), 
             linetype = "dotted", 
             color = "black") +
  # Escala de color
  scale_fill_npg() +
  # Invertir ejes para mejor lectura
  coord_flip() +
  # Estilo visual
  theme_minimal() +
  # Título y ejes vacíos (para limpiar visual)
  labs(
    title = "Esperanza de vida promedio (2022)",
    x = "", 
    y = ""
  ) +
  # Ajustes de tema para limpiar y estilizar
  theme(
    legend.position = "top",                      # Leyenda arriba
    legend.key.size = unit(0.5, "lines"),         # Íconos pequeños
    legend.text = element_text(size = 7),         # Texto reducido
    legend.title = element_blank(),               # Sin título
    legend.spacing.x = unit(3, "pt"),             # Espacio entre ítems
    axis.text.y = element_blank(),                # Oculta etiquetas Y
    axis.ticks.y = element_blank(),               # Oculta tics eje Y
    axis.text.x = element_text(size = 9),         # Tamaño eje X
    plot.margin = margin(10, 10, 10, 10)          # Márgenes del gráfico
  )

Gráfico 2

g2 = df %>%
  # Calcular promedio de esperanza de vida por región y año
  group_by(region, year) %>% 
  reframe(prom = mean(SP.DYN.LE00.IN, na.rm = TRUE)) %>% 
  # Definir estética del gráfico
  ggplot(aes(x = year, y = prom, color = region)) +
  # Agregar líneas por región
  geom_line(linewidth = 1.2, alpha = 0.9) +
  # Agregar líneas horizontales en múltiplos de 10
  geom_hline(yintercept = c(60, 70, 80),
             color = "black", 
             linetype = "dotted", 
             linewidth = 0.4) +
  # Escala de color por región
  scale_color_npg() +
  # Escala de años cada 2 años
  scale_x_continuous(breaks = seq(2008, 2022, by = 2)) +
  # Estilo visual limpio
  theme_minimal() +
  # Título y ejes sin texto
  labs(
    title = "Evolución de la esperanza de vida",
    x = "",
    y = "",
    fill = "Región"
  ) +
  # Ajustes de tema para remover elementos visuales
  theme(
    panel.grid = element_blank(),     # Quita grillas por defecto
    axis.line = element_blank(),      # Quita líneas de ejes
    axis.ticks = element_blank(),     # Quita tics
    axis.text = element_text(size = 9),
    legend.position = "none",         # Oculta leyenda
    plot.margin = margin(10, 10, 10, 10)
  )
g1 + g2 +
    plot_annotation(
    title = "La esperanza de vida en el mundo",
    caption = "World Wealth and Income Database")

Flujo descriptivo e inferencial

Vamos a aplicar un flujo completo de análisis de datos, desde la exploración inicial hasta pruebas estadísticas básicas. Utilizaremos el conjunto gss_cat del paquete forcats, que contiene información de encuestas sociales en EE.UU. Veremos cómo limpiar, visualizar y analizar datos reales.

La variables que usaremos:

  • marital: estado civil

  • age: edad

  • race: raza

  • rincome: ingreso

  • partyid: identificación partidaria

  • relig: religión

  • denom: denominación religiosa

  • tvhours: horas de TV por día

data(gss_cat) 

glimpse(gss_cat)
Rows: 21,483
Columns: 9
$ year    <int> 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 2000, 20…
$ marital <fct> Never married, Divorced, Widowed, Never married, Divorced, Mar…
$ age     <int> 26, 48, 67, 39, 25, 25, 36, 44, 44, 47, 53, 52, 52, 51, 52, 40…
$ race    <fct> White, White, White, White, White, White, White, White, White,…
$ rincome <fct> $8000 to 9999, $8000 to 9999, Not applicable, Not applicable, …
$ partyid <fct> "Ind,near rep", "Not str republican", "Independent", "Ind,near…
$ relig   <fct> Protestant, Protestant, Protestant, Orthodox-christian, None, …
$ denom   <fct> "Southern baptist", "Baptist-dk which", "No denomination", "No…
$ tvhours <int> 12, NA, 2, 4, 1, NA, 3, NA, 0, 3, 2, NA, 1, NA, 1, 7, NA, 3, 3…

Vamos a utilizar los datos del 2014

gss_14 = gss_cat %>% 
  # Filtrar los datos solo para el año 2014
  filter(year == "2014") %>% 
  # Eliminar la columna de año ya que es constante
  select(-year)

Diagnóstico general

Para evaluar la calidad de nuestros datos y detectar valores faltantes, podemos utilizar el paquete naniar, que nos permite visualizar y resumir de forma rápida los patrones de ausencia en las variables.

library(naniar)

Frecuencia y proporción de valores perdidos

# Diagnóstico de estructura
miss_var_summary(gss_14)
# A tibble: 8 × 3
  variable n_miss pct_miss
  <chr>     <int>    <num>
1 tvhours     869   34.2  
2 age           9    0.355
3 marital       0    0    
4 race          0    0    
5 rincome       0    0    
6 partyid       0    0    
7 relig         0    0    
8 denom         0    0    

Gráficamente

vis_miss(gss_cat)
vis_miss(gss_14)
gss_cat = gss_cat %>% 
  select(-tvhours) %>% 
  drop_na()

vis_miss(gss_cat)

Anque podríamos aplicar otras herramientas para imputar los valores perdidos. Vamos a precindir de la variable en esta ocasión.

Estadística descriptiva

La estadística descriptiva se enfoca en resumir y explorar los datos de forma clara y ordenada. Nos permite entender cómo se distribuyen nuestras variables, detectar patrones generales, identificar valores extremos y comparar grupos. A través de medidas como la media, mediana o desviación estándar, y con el apoyo de gráficos, obtenemos una primera mirada sobre el comportamiento de nuestros datos.

dlookr es un paquete extremadamente útil para el análisis exploratorio de datos. Su mayor ventaja es que automatiza tareas que normalmente haríamos paso a paso, como verificar distribuciones o comparar variables entre grupos.

library(dlookr)

Resumen estadístico de variables numéricas

gss_14 %>% 
  diagnose_numeric()
# A tibble: 1 × 10
  variables   min    Q1  mean median    Q3   max  zero minus outlier
  <chr>     <int> <dbl> <dbl>  <int> <dbl> <int> <int> <int>   <int>
1 age          18    34  49.0     49    62    89     0     0       0

Resumen estadístico de variables categóricas

gss_14 %>% 
  diagnose_category()
# A tibble: 49 × 6
   variables levels             N  freq   ratio  rank
   <chr>     <chr>          <int> <int>   <dbl> <int>
 1 marital   Married         2529  1153 45.6        1
 2 marital   Never married   2529   674 26.7        2
 3 marital   Divorced        2529   410 16.2        3
 4 marital   Widowed         2529   209  8.26       4
 5 marital   Separated       2529    81  3.20       5
 6 marital   No answer       2529     2  0.0791     6
 7 race      White           2529  1883 74.5        1
 8 race      Black           2529   385 15.2        2
 9 race      Other           2529   261 10.3        3
10 rincome   $25000 or more  2529   951 37.6        1
# ℹ 39 more rows

Gráficos de barras para varibales categóricas

plot_bar_category(gss_14)

Histogramas para variables numéricas

plot_hist_numeric(df_22)

Boxplot para variables numéricas

plot_box_numeric(df_22)

Diagnóstico de normalidad

plot_normality(df_22 %>% 
                 select(pib_per_capita))
plot_normality(df_22 %>% 
                 select(esperanza_vida))
plot_normality(
  df_22 %>%
    filter(esperanza_vida > 30) %>%
    select(esperanza_vida)
)
plot_normality(df_22 %>% 
                 select(poblacion_total))

La función case_when() en R, del paquete dplyr, se utiliza para crear nuevas variables categóricas o reclasificar valores existentes en función de condiciones lógicas múltiples.

gss_sp = gss_14 %>%
  # Recodificar afiliación política en tres grandes grupos
  mutate(
    party3 = case_when(
      partyid %in% c("Strong democrat", 
                     "Not str democrat", 
                     "Ind,near dem") 
      ~ "Demócrata",
      partyid %in% c("Strong republican", 
                     "Not str republican", 
                     "Ind,near rep") 
      ~ "Republicano",
      partyid == "Independent" ~ "Independente",
      TRUE ~ NA_character_
    ),
    # Agrupar afiliación religiosa en cuatro categorías
    relig4 = case_when(
      relig == "Catholic" ~ "Católico",
      relig %in% c("Protestant", "Orthodox-christian") 
      ~ "Protestante",
      relig %in% c("None", "Don't know", "No answer") 
      ~ "No religion",
      TRUE ~ "Otro"
    )
  ) %>%
  # Eliminar casos con categorías sin asignar
  filter(!is.na(party3), !is.na(relig4))

Afiliación política según religión

gss_sp %>%
  # Contar combinaciones de religión y afiliación política
  count(relig4, party3) %>%
  # Agrupar por religión
  group_by(relig4) %>%
  # Calcular porcentaje dentro de cada grupo religioso
  mutate(pct = round(100 * n / sum(n), 1)) %>%
  # Ordenar de mayor a menor proporción por religión
  arrange(relig4, desc(pct))
# A tibble: 12 × 4
# Groups:   relig4 [4]
   relig4      party3           n   pct
   <chr>       <chr>        <int> <dbl>
 1 Católico    Demócrata      295  49.8
 2 Católico    Republicano    171  28.9
 3 Católico    Independente   126  21.3
 4 No religion Demócrata      278  54.3
 5 No religion Independente   148  28.9
 6 No religion Republicano     86  16.8
 7 Otro        Demócrata      117  48.5
 8 Otro        Republicano     72  29.9
 9 Otro        Independente    52  21.6
10 Protestante Demócrata      470  42.8
11 Protestante Republicano    455  41.5
12 Protestante Independente   172  15.7

Podemos usar geom_bar(position = "fill") cuando queremos visualizar proporciones o comparaciones relativas entre categorías, en lugar de frecuencias absolutas. Esto transforma el gráfico de barras en un gráfico de barras apiladas normalizadas, donde cada barra tiene la misma altura (100%), y las secciones internas representan proporciones.

Para mostrar estas proporciones en formato de porcentaje, podemos apoyarnos en el paquete scales, usando su función percent_format().

ggplot(gss_sp, aes(x = relig4, fill = party3)) +
  # Crear barras apiladas por proporción
  geom_bar(position = "fill") +
  # Mostrar el eje Y como porcentaje
  scale_y_continuous(labels = scales::percent_format()) +
  # Aplicar paleta de colores tipo BMJ
  scale_fill_bmj() +  
  # Título y etiquetas
  labs(
    title = "Afiliación política según religión",
    x = "Religión", 
    y = "Proporción dentro del grupo",
    fill = "Afiliación política"
  )

La función describeBy() del paquete psych es especialmente útil cuando queremos obtener un resumen estadístico de una variable numérica segmentada por grupos.

psych::describeBy(gss_sp$age, 
                  group = gss_sp$party3,   # Agrupar por afiliación política
                  mat = TRUE) %>%          # Convertir la salida a data.frame
  arrange(desc(mean))                     # Ordenar por mayor edad promedio
    item       group1 vars    n     mean       sd median  trimmed     mad min
X13    3  Republicano    1  784 51.37117 17.48793   52.0 51.06847 19.2738  18
X11    1    Demócrata    1 1160 49.32069 17.39345   50.0 48.73599 20.7564  18
X12    2 Independente    1  498 44.84940 16.61044   42.5 43.72250 18.5325  19
    max range      skew   kurtosis        se
X13  89    71 0.1076617 -0.8526906 0.6245688
X11  89    71 0.2020732 -0.8544126 0.5106893
X12  89    70 0.5285046 -0.5206750 0.7443318

Un violin plot combina aspectos de un boxplot y un kernel density plot, permitiendo visualizar no solo la mediana, cuartiles y valores atípicos, sino también la forma de la distribución. Usamos geom_violin().

ggplot(gss_sp, aes(x = party3, y = age, fill = party3)) +
  # Violin plot para mostrar la distribución completa
  geom_violin(trim = FALSE, 
              alpha = 0.9) +
  # Boxplot sobrepuesto para resaltar los estadísticos clave
  geom_boxplot(width = 0.1, 
               color = "black", 
               alpha = 0.2, 
               outlier.shape = NA) +
  # Paleta de colores tipo revista
  scale_fill_bmj() +  
  # Título y etiquetas
  labs(
    title = "Distribución de la edad según afiliación política",
    x = "",
    y = "Edad"
  ) +
  # Ocultar leyenda
  theme(legend.position = 'none')

Estadística inferencial

La estadística inferencial nos permite evaluar si los patrones que observamos en una muestra son suficientemente fuertes como para generalizar a toda una población. Es decir, nos ayuda a contrastar hipótesis y determinar si las diferencias o asociaciones observadas son estadísticamente significativas.

En esta sección veremos cómo aplicar algunas pruebas clásicas (chi-cuadrado y ANOVA) para analizar relaciones entre variables categóricas y numéricas.

El paquete broom nos permite organizar los resultados de modelos estadísticos en tablas limpias y fáciles de interpretar. En particular, la función tidy() convierte el output de las pruebas en un data frame estructurado

library(broom)

Chi cuadrado

La prueba de chi-cuadrado se utiliza para analizar si existe una relación entre dos variables categóricas. Evalúa si las frecuencias observadas en una tabla de contingencia difieren significativamente de las que esperaríamos si no hubiera asociación. Usamos chisq.test() para aplicar la prueba.

Asociación entre la afilicaicón partidaria y la creencia religiosa

chi_t = chisq.test(table(gss_sp$relig4, gss_sp$party3))

    Pearson's Chi-squared test

data:  table(gss_sp$relig4, gss_sp$party3)
X-squared = 110.93, df = 6, p-value < 2.2e-16
# A tibble: 1 × 4
  statistic  p.value parameter method                    
      <dbl>    <dbl>     <int> <chr>                     
1      111. 1.30e-21         6 Pearson's Chi-squared test

ANOVA

El ANOVA (Análisis de Varianza) permite comparar las medias de una variable numérica entre tres o más grupos. Su objetivo es determinar si al menos una media difiere significativamente del resto, bajo el supuesto de normalidad y homogeneidad de varianzas. Es útil cuando se quiere estudiar el efecto de una variable categórica sobre una continua. Usmaos aov() para ajustar el modelo.

Asociación entre la afiliación partidaria y la edad.

anova_mod = aov(age ~ party3, data = gss_sp)
              Df Sum Sq Mean Sq F value   Pr(>F)    
party3         2  13096    6548   21.96 3.53e-10 ***
Residuals   2439 727223     298                     
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# A tibble: 2 × 6
  term         df   sumsq meansq statistic   p.value
  <chr>     <dbl>   <dbl>  <dbl>     <dbl>     <dbl>
1 party3        2  13096.  6548.      22.0  3.53e-10
2 Residuals  2439 727223.   298.      NA   NA       

Tukey HSD (Honest Significant Difference) realiza comparaciones múltiples entre todos los pares de grupos y ajusta automáticamente los valores p para controlar el error por comparaciones múltiples.

TukeyHSD(anova_mod)
  Tukey multiple comparisons of means
    95% family-wise confidence level

Fit: aov(formula = age ~ party3, data = gss_sp)

$party3
                              diff        lwr       upr     p adj
Independente-Demócrata   -4.471292 -6.6407163 -2.301868 0.0000043
Republicano-Demócrata     2.050484  0.1782633  3.922704 0.0277248
Republicano-Independente  6.521776  4.2013527  8.842199 0.0000000

Regresión

La regresión es una herramienta estadística que usamos para modelar la relación entre una variable dependiente y una o más variables independientes. Nos permite entender cómo cambia una variable cuando otra varía,.

En su forma más básica, la regresión lineal simple analiza cómo una variable numérica se relaciona con otra .pl

Asociación PBI per cápita y la esperanza de vida

p3

En la regresión lineal simple, el modelo estima una recta que resume la relación entre dos variables numéricas. Esta relación está muy ligada al concepto de correlación.

La correlación mide la fuerza y dirección de la asociación entre dos variables. Su valor va de -1 a 1. Cuando la correlación es cercana a 1 (positiva fuerte), la recta de regresión tendrá una pendiente positiva; si es cercana a -1 (negativa fuerte), la pendiente será negativa.

df_reg = df_22 %>% 
  # Calcular el logaritmo del PIB per cápita
  mutate(pib_log = log(pib_per_capita)) %>% 
  # Seleccionar variables relevantes
  select(pais, pib_per_capita, pib_log, esperanza_vida)
df_reg %>% 
  plot_correlate()
reg_mod = lm(esperanza_vida ~ pib_log, data = df_reg)

summary(reg_mod)

Call:
lm(formula = esperanza_vida ~ pib_log, data = df_reg)

Residuals:
    Min      1Q  Median      3Q     Max 
-41.989  -2.268   0.370   2.816   8.489 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  34.3222     2.0672   16.60   <2e-16 ***
pib_log       4.3085     0.2269   18.99   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 4.81 on 205 degrees of freedom
Multiple R-squared:  0.6375,    Adjusted R-squared:  0.6357 
F-statistic: 360.5 on 1 and 205 DF,  p-value: < 2.2e-16

R-squared indica el porcentaje de variabilidad de la variable dependiente que el modelo logra explicar. Aunque alto no siempre significa buen modelo, es una primera referencia útil.

summary(reg_mod)$r.squared
[1] 0.6374694

Residual Standard Error (RSE) nos da una idea del promedio del error de predicción. Cuanto menor, mejor es el ajuste del modelo.

summary(reg_mod)$sigma
[1] 4.810133

La Cook’s distance es una medida que nos permite identificar observaciones que influyen demasiado en los coeficientes de un modelo de regresión. No se enfoca solo en si un punto es atípico, sino en cuánto cambia el modelo si se elimina. Si su valor es alto, conviene revisar ese caso, ya que podría estar afectando de forma desproporcionada la interpretación de los resultados.

plot(reg_mod, which = 4)
df_reg %>%
  # Calcular la distancia de Cook para cada observación
  mutate(cooksD = cooks.distance(reg_mod)) %>%
  # Filtrar las observaciones con alta influencia
  filter(cooksD > 0.5) %>%
  # Seleccionar variables clave para revisar
  select(pais, pib_per_capita, esperanza_vida, cooksD) %>%
  # Ordenar por influencia descendente
  arrange(desc(cooksD))
# A tibble: 1 × 4
  pais                     pib_per_capita esperanza_vida cooksD
  <chr>                             <dbl>          <dbl>  <dbl>
1 Central African Republic           467.           18.8  0.910

¿Y si filtramos el valor influyente?

df_regni= df_reg %>% 
  filter(pais != "Central African Republic")

Call:
lm(formula = esperanza_vida ~ pib_log, data = df_regni)

Residuals:
     Min       1Q   Median       3Q      Max 
-13.8488  -2.2038   0.3457   2.6237   7.9139 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  36.9737     1.6483   22.43   <2e-16 ***
pib_log       4.0367     0.1807   22.34   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 3.796 on 204 degrees of freedom
Multiple R-squared:  0.7098,    Adjusted R-squared:  0.7084 
F-statistic:   499 on 1 and 204 DF,  p-value: < 2.2e-16