Módulo: Análisis Exploratorio de Datos
Docente: Sebastian Moreno Rodríguez
En esta entrega integro dos fuentes de la Gran Encuesta Integrada de Hogares GEIH 2025 (DANE): Características Generales (CG) y Ocupados (OC). Objetivo: unir, preparar y explorar variables clave (edad, sexo, ingreso, horas trabajadas y meses trabajados), reportando descriptivos, covarianzas/correlaciones y gráficos con un análisis breve por visualización.
#====================== 1. INSTALO PAQUETES CARGO LIBRERIAS ===========
install.packages("dplyr") # Pauete de manipulacion de datos
install.packages("ggplot2") # Liberia para usar gráficos
install.packages("e1071") # skewness y kurtosis (Fisher-Pearson)
install.packages("DescTools") # Skew(), Kurt(), BowleySkew()
install.packages("readr") #importar/exportar CSV
library(dplyr)
library(ggplot2)
library(e1071)
library(DescTools)
library(readr)
library(patchwork) # USAR FRAMES PARA GRAFICOS
# *****************ARCHIVOS DANE*************
#>Gran Encuesta Integrada de Hogares - GEIH - 2025 -
#>1. archivo de datos: Caracteristicas generales, seguridad social en salud y educacion
#>2. archivo de datos:
CG <- read_delim("CG.CSV",
delim = ";", locale = locale(encoding = "Latin1"))
OC <- read_delim("OC.CSV",
delim = ";", locale = locale(encoding = "Latin1"))
# Revisar estructura
#glimpse(CG)
#glimpse(OC)
# ************************** ORGANIZAR DF***********
# De Características Generales
CG <- CG %>%
select(DIRECTORIO, SECUENCIA_P, ORDEN,
sexo = P3271, # Sexo al nacer (1=Hombre, 2=Mujer)
edad = P6040) # Edad en años cumplidos
# De Ocupados
OC <- OC %>%
select(DIRECTORIO, SECUENCIA_P, ORDEN,
ingreso = P6500, # Ingreso mensual antes de descuentos
HoraHabitual = P6800, # Horas semanales habituales
HoraSemana = P6850, # Horas efectivas semana pasada
HoraExtra = P6640S1, # Horas extra semana pasada
MesesTrab = P6790, # Meses trabajados últimos 12 meses
peso = FEX_C18) # Factor de expansión
#> Este paso es clave aca uno los dos archivos, pero solo quedan los datos que están en ambas bases Por eso base queda con
#> 30155 registros que son los de Ocupados.
DF_final <- inner_join(CG, OC, by = c("DIRECTORIO", "SECUENCIA_P", "ORDEN"))
# Diagnóstico del join
cat("Filas CG:", nrow(CG), "| Filas OC:", nrow(OC), "| Filas unidas (DF_final):", nrow(DF_final), "\n")
## Filas CG: 68577 | Filas OC: 30155 | Filas unidas (DF_final): 30155
head(DF_final)
## # A tibble: 6 × 11
## DIRECTORIO SECUENCIA_P ORDEN sexo edad ingreso HoraHabitual HoraSemana
## <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 8316823 1 1 1 64 NA 40 40
## 2 8316824 1 1 1 62 1423500 47 47
## 3 8316824 1 2 2 50 1423500 47 47
## 4 8316825 1 1 1 30 1423500 48 50
## 5 8316825 1 3 1 29 NA 40 40
## 6 8316825 1 4 2 55 400000 60 60
## # ℹ 3 more variables: HoraExtra <dbl>, MesesTrab <dbl>, peso <dbl>
#> ====================================== PASO 1. ORGANIZACION DATOS DF ============
DF_final <- DF_final %>%
mutate(
edad = parse_number(as.character(edad), locale = locale(decimal_mark = ",", grouping_mark = ".")),
ingreso = parse_number(as.character(ingreso), locale = locale(decimal_mark = ",", grouping_mark = ".")),
HoraHabitual= parse_number(as.character(HoraHabitual), locale = locale(decimal_mark = ",", grouping_mark = ".")),
HoraSemana = parse_number(as.character(HoraSemana), locale = locale(decimal_mark = ",", grouping_mark = ".")),
HoraExtra = parse_number(as.character(HoraExtra), locale = locale(decimal_mark = ",", grouping_mark = ".")),
MesesTrab = parse_number(as.character(MesesTrab), locale = locale(decimal_mark = ",", grouping_mark = "."))
)
# Visualizo
names(DF_final)
## [1] "DIRECTORIO" "SECUENCIA_P" "ORDEN" "sexo" "edad"
## [6] "ingreso" "HoraHabitual" "HoraSemana" "HoraExtra" "MesesTrab"
## [11] "peso"
dplyr::glimpse(DF_final)
## Rows: 30,155
## Columns: 11
## $ DIRECTORIO <dbl> 8316823, 8316824, 8316824, 8316825, 8316825, 8316825, 831…
## $ SECUENCIA_P <dbl> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, …
## $ ORDEN <dbl> 1, 1, 2, 1, 3, 4, 1, 1, 1, 2, 1, 2, 1, 1, 2, 1, 2, 2, 1, …
## $ sexo <dbl> 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 1, 2, …
## $ edad <dbl> 64, 62, 50, 30, 29, 55, 44, 48, 64, 63, 32, 31, 44, 50, 3…
## $ ingreso <dbl> NA, 1423500, 1423500, 1423500, NA, 400000, NA, 2500000, N…
## $ HoraHabitual <dbl> 40, 47, 47, 48, 40, 60, 60, 60, 24, 40, 16, 56, 47, 36, 4…
## $ HoraSemana <dbl> 40, 47, 47, 50, 40, 60, 60, 60, 24, 40, 16, 56, 47, 36, 4…
## $ HoraExtra <dbl> NA, NA, NA, 2, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ MesesTrab <dbl> 12, 12, 12, 12, 2, 12, 12, 12, 12, 12, 7, 12, 12, 12, 12,…
## $ peso <dbl> 21.33878, 21.31727, 21.31727, 39.31131, 39.31131, 39.3113…
# CAMBIAR ETIQUETA HOMBR EY MUUJER
DF_final <- DF_final %>%
mutate(
sexo = case_when(
sexo == 1 ~ "Hombre",
sexo == 2 ~ "Mujer",
TRUE ~ NA_character_
),
sexo = factor(sexo, levels = c("Hombre", "Mujer"))
)
# *****************************************. DESCRIPTIVOS ******************
#Guardo en vars_num la selección de las variables que les voy a sacar descriptivos
vars_num <- DF_final %>% select(edad, ingreso, HoraHabitual, HoraSemana, HoraExtra, MesesTrab)
# ********************* Estadisticos a todas las variables
# Función simple: centralidad, dispersión y forma
analizar <- function(x, nom) {
x <- as.numeric(x)
x <- x[!is.na(x)]
data.frame(
# LOCALIZACIÓN
Variable = nom,
n = length(x),
Media = mean(x),
Mediana = median(x),
Moda = as.numeric(names(which.max(table(x)))), # moda simple y rápida
#CUARTILES
Q1 = quantile(x, 0.25, na.rm=TRUE),
Q2 = quantile(x, 0.50, na.rm=TRUE),
Q3 = quantile(x, 0.75, na.rm=TRUE),
#DISPERSIÓN
Varianza = var(x),
Desv_Est = sd(x),
Rango = max(x) - min(x),
IQR = IQR(x),
CV = ifelse(mean(x) == 0, NA_real_, sd(x) / mean(x) * 100),
#FORMA
Asimetria = e1071::skewness(x, type = 2), # Fisher–Pearson
Curtosis = e1071::kurtosis(x, type = 2), # exceso de curtosis (normal = 0)
row.names = NULL
)
}
# **************************** Muestro la tabla
TablaEstadisticos <- Map(analizar, vars_num, names(vars_num))
EstadisticosDF <- bind_rows(TablaEstadisticos)
EstadisticosDFinal <- EstadisticosDF %>% mutate(across(-c(Variable, n), ~ sprintf("%.3f", as.numeric(.))))
#EstadisticosDFinal
#View(EstadisticosDFinal)
knitr::kable (EstadisticosDFinal )
| Variable | n | Media | Mediana | Moda | Q1 | Q2 | Q3 | Varianza | Desv_Est | Rango | IQR | CV | Asimetria | Curtosis |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| edad | 30155 | 41.831 | 40.000 | 30.000 | 30.000 | 40.000 | 52.000 | 201.624 | 14.199 | 80.000 | 22.000 | 33.945 | 0.370 | -0.617 |
| ingreso | 15893 | 2788119.883 | 1423500.000 | 1423500.000 | 1400000.000 | 1423500.000 | 2000000.000 | 161015309324169.781 | 12689180.798 | 470000000.000 | 600000.000 | 455.116 | 17.428 | 370.787 |
| HoraHabitual | 30155 | 43.279 | 44.000 | 48.000 | 40.000 | 44.000 | 48.000 | 167.925 | 12.959 | 129.000 | 8.000 | 29.942 | -0.083 | 2.421 |
| HoraSemana | 30155 | 42.149 | 44.000 | 48.000 | 40.000 | 44.000 | 48.000 | 200.181 | 14.149 | 130.000 | 8.000 | 33.568 | -0.325 | 2.049 |
| HoraExtra | 414 | 10.118 | 8.000 | 4.000 | 4.000 | 8.000 | 12.000 | 95.867 | 9.791 | 98.000 | 8.000 | 96.767 | 4.628 | 34.048 |
| MesesTrab | 30155 | 11.213 | 12.000 | 12.000 | 12.000 | 12.000 | 12.000 | 5.049 | 2.247 | 11.000 | 0.000 | 20.039 | -3.120 | 9.055 |
#> ======================================= PASO 2. CORRELACIONES Y COVARIANZAS ==============
# **************** COVARIANZAS ****************
VarCovCo <- DF_final %>%
select(edad, ingreso, HoraHabitual, HoraSemana, HoraExtra, MesesTrab)
#>"complete.obs" --> Solo usa las filas donde todas las variables tienen datos validos
covarianza <- cov(VarCovCo, use = "complete.obs")
round(covarianza, 3)
## edad ingreso HoraHabitual HoraSemana HoraExtra
## edad 144.110 1.275715e+07 -4.021 4.164 18.632
## ingreso 12757148.552 3.538006e+13 -4047976.655 -384738.771 2786141.178
## HoraHabitual -4.021 -4.047977e+06 68.458 62.502 21.850
## HoraSemana 4.164 -3.847388e+05 62.502 104.046 42.829
## HoraExtra 18.632 2.786141e+06 21.850 42.829 96.089
## MesesTrab 1.741 2.287089e+05 0.554 0.501 -0.165
## MesesTrab
## edad 1.741
## ingreso 228708.945
## HoraHabitual 0.554
## HoraSemana 0.501
## HoraExtra -0.165
## MesesTrab 2.083
# ******************* CORELLACIONES **************
cor_pearson <- cor(vars_num, method = "pearson", use = "complete.obs")
cor_spearman <- cor(vars_num, method = "spearman", use = "complete.obs")
round(cor_pearson, 3)
## edad ingreso HoraHabitual HoraSemana HoraExtra MesesTrab
## edad 1.000 0.179 -0.040 0.034 0.158 0.100
## ingreso 0.179 1.000 -0.082 -0.006 0.048 0.027
## HoraHabitual -0.040 -0.082 1.000 0.741 0.269 0.046
## HoraSemana 0.034 -0.006 0.741 1.000 0.428 0.034
## HoraExtra 0.158 0.048 0.269 0.428 1.000 -0.012
## MesesTrab 0.100 0.027 0.046 0.034 -0.012 1.000
round(cor_spearman, 3)
## edad ingreso HoraHabitual HoraSemana HoraExtra MesesTrab
## edad 1.000 0.257 -0.044 0.001 0.130 0.163
## ingreso 0.257 1.000 -0.126 -0.027 0.052 0.122
## HoraHabitual -0.044 -0.126 1.000 0.599 0.257 0.054
## HoraSemana 0.001 -0.027 0.599 1.000 0.573 0.073
## HoraExtra 0.130 0.052 0.257 0.573 1.000 0.044
## MesesTrab 0.163 0.122 0.054 0.073 0.044 1.000
El mapa de correlaciones (coeficiente de Pearson) permite observar la intensidad y dirección de las relaciones lineales entre variables numéricas. Los tonos verdes indican relaciones positivas, mientras que los rojos indican negativas. En la matriz se aprecia una alta correlación positiva entre horas habituales y horas efectivas trabajadas, lo cual es esperable porque representan medidas relacionadas de la jornada laboral. Por el contrario, la relación entre edad y horas extras tiende a ser baja o incluso negativa, lo que sugiere que las personas mayores podrían realizar menos horas adicionales. Este gráfico es útil para identificar patrones de asociación y posibles redundancias entre variables.
library(ggcorrplot)
library(GGally)
library(ggplot2)
library(scales)
# PREPARACION COR MATRIX_
CorMatriz<- cor(vars_num, method = "pearson", use = "complete.obs")
#> 1. Mapa Correlaciones
ggcorrplot(CorMatriz,
hc.order = TRUE, # agrupa variables similares
type = "lower", # muestra mitad inferior
lab = TRUE, # muestra valores numéricos
lab_size = 3,
colors = c("red", "white", "green"),
title = "Mapa de correlaciones (Pearson) - GEIH 2025",
ggtheme = ggplot2::theme_minimal())
El gráfico de dispersión con línea de tendencia OLS (mínimos cuadrados ordinarios) muestra una relación positiva moderada entre edad e ingreso. Esto sugiere que, en promedio, los ingresos aumentan con la edad, probablemente debido a la experiencia laboral acumulada o la antigüedad en los puestos. Sin embargo, la nube de puntos es amplia, lo que refleja una alta variabilidad: existen individuos jóvenes con ingresos altos y adultos con ingresos bajos.
#> 2. Relación edad–ingreso
ggplot(DF_final, aes(x = edad, y = ingreso)) +
geom_point(alpha = 0.3, color = "#1F78B4") +
geom_smooth(method = "lm", color = "#E31A1C", se = FALSE) +
scale_y_continuous(labels = comma) +
labs(title = "Relación entre edad e ingreso - GEIH 2025",
subtitle = "Tendencia lineal y dispersión",
x = "Edad (años cumplidos)",
y = "Ingreso mensual (COP)") +
theme_minimal()
## 4.1.b Horas Habituales y semana
El gráfico de hexágonos (“heatmap”) evidencia una concentración diagonal, lo que significa que las horas efectivas trabajadas son cercanas a las habituales para la mayoría. Los hexágonos más oscuros indican la zona de mayor densidad, donde el número de horas planeadas coincide con las realmente trabajadas. La dispersión hacia arriba o abajo de esa diagonal indica variaciones o incumplimiento de jornadas, posiblemente por informalidad o flexibilidad laboral.
#> 3. Horas Habituales y semana
ggplot(DF_final, aes(x = HoraHabitual, y = HoraSemana)) +
geom_hex(bins = 25) +
scale_fill_viridis_c() +
labs(title = "Concentración de horas trabajadas - GEIH 2025",
subtitle = "Distribución conjunta entre horas habituales y efectivas",
x = "Horas habituales por semana",
y = "Horas efectivas la semana pasada",
fill = "Frecuencia") +
theme_minimal()
La densidad del ingreso revela una distribución altamente asimétrica a la derecha, donde la mayoría de los valores se concentran en los tramos bajos, mientras que un pequeño grupo presenta ingresos muy altos (cola larga). Este comportamiento es típico de variables económicas con desigualdad en la distribución de riqueza.
library(scales)
# 3.1 SIN FILTRO
ggplot(DF_final, aes(x = ingreso)) +
geom_density(fill = "pink", alpha = 0.5) +
scale_x_continuous(labels = scales::comma)+
labs(title = "Distribución del ingreso mensual - GEIH 2025",
subtitle = "Alta asimetría y presencia de valores extremos",
x = "Ingreso mensual (COP)",
y = "Densidad") +
theme_minimal()
Al aplicar el filtro, la forma de la curva se hace más legible, eliminando los extremos. Se distingue un modo principal alrededor de los ingresos medios-bajos, que representan la mayor parte de la población ocupada. Este gráfico permite analizar el comportamiento central sin distorsión por valores extremos.
# FIltrado
DF_filtrado <- DF_final %>%
filter(ingreso >= 100000 & ingreso <= 20000000)
ggplot(DF_filtrado, aes(x = ingreso)) +
geom_density(fill = "orange", alpha = 0.5) +
scale_x_continuous(labels = scales::comma) +
labs(title = "Distribución del ingreso mensual (filtrado) - GEIH 2025",
subtitle = "Ingresos entre $100.000 y $20.000.000 COP",
x = "Ingreso mensual (COP)",
y = "Densidad") +
theme_minimal()
El uso de la escala logarítmica en el eje X reduce la asimetría y comprime los valores altos, permitiendo visualizar mejor la concentración de los ingresos bajos. Se observa que la mayoría de la población percibe ingresos en un rango reducido respecto al total. Esta transformación es útil para representar variables con diferencias de magnitud amplias y para comparaciones entre grupos.
# genero otro grafico por que no permite uuna visualizacion adecuada
ggplot(DF_final, aes(x = ingreso)) +
geom_density(fill = "aquamarine", alpha = 0.5) +
scale_x_log10(labels = scales::comma) + # cambio la escala
labs(title = "Distribución del ingreso mensual (escala logarítmica) - GEIH 2025",
subtitle = "La mayoría de los ingresos se concentran en valores bajos",
x = "Ingreso mensual (COP, escala log10)",
y = "Densidad") +
theme_minimal()
El boxplot compara la distribución de ingresos entre hombres y mujeres. Se aprecia que la mediana del ingreso es más alta para los hombres, y la dispersión (IQR) también es mayor, lo cual sugiere mayor heterogeneidad salarial en ese grupo. Además, la presencia de outliers en ambos casos refleja individuos con ingresos muy por encima del promedio, lo que puede asociarse a diferencias sectoriales o jerárquicas en el mercado laboral. Este resultado es coherente con la evidencia de brecha salarial de género observada en encuestas laborales.
# ******************************. POR GENERO ***********************
library(dplyr)
library(ggplot2)
library(scales)
# =====================Boxplot de ingreso por sexo
DF_filtrado <- DF_final %>%
filter(!is.na(sexo),
ingreso >= 100000, ingreso <= 10000000)
ggplot(DF_filtrado, aes(x = sexo, y = ingreso, fill = sexo)) +
geom_boxplot(alpha = 0.6, outlier.colour = "red", outlier.alpha = 0.4) +
scale_y_continuous(labels = comma) +
labs(title = "Ingreso mensual por sexo (filtrado)",
subtitle = "Corte entre $100.000 y $10.000.000 COP para mejorar legibilidad",
x = "Sexo", y = "Ingreso mensual (COP)") +
theme_minimal(base_size = 13) +
theme(legend.position = "none")
Las curvas de densidad permiten visualizar la forma de la distribución para cada grupo. Se observa un desplazamiento hacia la derecha en la curva de los hombres, indicando que en general perciben ingresos más altos. El solapamiento parcial sugiere que, aunque existen diferencias, también hay zonas de coincidencia en los ingresos medios. Este análisis permite visualizar la desigualdad de manera más intuitiva que los estadísticos descriptivos
# =========================Densidades de ingreso por sexo
ggplot(DF_filtrado, aes(x = ingreso, fill = sexo)) +
geom_density(alpha = 0.45) +
scale_fill_manual(values = c("Hombre" = "cyan", "Mujer" = "pink"))+
scale_x_continuous(labels = comma) +
labs(title = "Distribución del ingreso por sexo",
subtitle = "Se observa desplazamiento de la densidad entre grupos",
x = "Ingreso mensual (COP)", y = "Densidad", fill = "Sexo") +
theme_minimal(base_size = 13)
Al separar por sexo, las rectas de tendencia muestran que tanto hombres como mujeres presentan una relación positiva entre edad e ingreso, aunque con pendientes distintas. La curva de los hombres tiende a ubicarse por encima, lo cual refuerza la hipótesis de mayor remuneración promedio. Sin embargo, la dispersión dentro de cada grupo evidencia una gran heterogeneidad: factores como tipo de ocupación, nivel educativo o región podrían explicar las diferencias individuales.
#====================== Dispersión edad–ingreso por sexo + rectas de tendencia
ggplot(DF_filtrado, aes(x = edad, y = ingreso, color = sexo)) +
geom_point(alpha = 0.25, size = 1) +
geom_smooth(method = "lm", se = FALSE, size = 1) +
scale_y_continuous(labels = comma) +
labs(title = "Relación entre edad e ingreso por sexo",
subtitle = "Rectas OLS separadas por grupo",
x = "Edad (años)", y = "Ingreso mensual (COP)", color = "Sexo") +
theme_minimal(base_size = 13)
El gráfico de violín combina la forma de la distribución (ancho del violín) con los estadísticos resumidos (boxplot interno). Se observa que la distribución del ingreso de los hombres tiene una cola más larga hacia valores altos, mientras que la de las mujeres es más compacta y concentrada. En escala logarítmica, esta visualización resalta que los ingresos femeninos tienden a concentrarse en los tramos bajos-medios, mientras que los masculinos se extienden más en los rangos altos. Este tipo de representación es ideal para comparar forma y dispersión de manera simultánea.
# ============================ Violin + boxplot (Ingreso por sexo)
ggplot(DF_final %>% filter(!is.na(sexo), ingreso > 0),
aes(x = sexo, y = ingreso, fill = sexo)) +
geom_violin(alpha = 0.35, trim = TRUE) +
geom_boxplot(width = 0.15, outlier.alpha = 0.25) +
scale_y_log10(labels = comma) +
labs(title = "Ingreso por sexo (escala log10)",
subtitle = "Violin + box: forma y mediana con todos los datos",
x = "Sexo", y = "Ingreso mensual (COP, log10)") +
theme_minimal(base_size = 13) +
theme(legend.position = "none")
La tabla de correlaciones separadas por sexo resume cómo se relacionan las variables principales en cada grupo.
En ambos sexos, la relación entre edad e ingreso es positiva, aunque ligeramente más fuerte en los hombres, indicando que el efecto de la edad sobre el salario podría ser mayor en ellos.
La correlación entre horas habituales y efectivas es alta y similar en ambos grupos, lo que sugiere consistencia en las jornadas laborales.
Finalmente, la relación entre horas extras y horas semanales también es positiva, pero más débil, lo cual indica que las horas adicionales no siempre se traducen en mayores horas totales, posiblemente por compensaciones o límites normativos.
# ============================= Correlaciones separadas por sexo
cor_por_sexo <- DF_final %>%
filter(!is.na(sexo)) %>%
group_by(sexo) %>%
summarise(
r_edad_ingreso = cor(edad, ingreso, use = "complete.obs", method = "pearson"),
r_horas = cor(HoraHabitual, HoraSemana, use = "complete.obs", method = "pearson"),
r_extra_sem = cor(HoraExtra, HoraSemana, use = "complete.obs", method = "pearson"),
.groups = "drop"
)
cor_por_sexo %>%
dplyr::mutate(dplyr::across(where(is.numeric), ~ round(., 3))) %>%
knitr::kable(caption = "Correlaciones clave separadas por sexo (Pearson)")
| sexo | r_edad_ingreso | r_horas | r_extra_sem |
|---|---|---|---|
| Hombre | 0.064 | 0.884 | 0.435 |
| Mujer | 0.046 | 0.910 | 0.369 |
Referencias
Bruce, P., Bruce, A., & Gedeck, P. (2022). Estadística práctica para ciencia de datos con R y Python (2ª ed.). Marcombo.
DataScience Latinoamérica. (2023, 28 de marzo). Mapa de correlaciones en R – ggcorrplot y GGally [Video]. YouTube. https://www.youtube.com/watch?v=sSn5_wYOJrY
Departamento Administrativo Nacional de Estadística – DANE. (2025). Gran Encuesta Integrada de Hogares (GEIH) – Características generales, seguridad social en salud y educación: Diccionario de datos, Formulario F1. Microdatos DANE. https://microdatos.dane.gov.co/index.php/catalog/853/data-dictionary/F1?file_name=Caracteristicas%20generales,%20seguridad%20social%20en%20salud%20y%20educacion
Estadística con R. (2023, 12 de mayo). Correlación y gráficos en R paso a paso [Video]. YouTube. https://www.youtube.com/watch?v=iZBpLucydh4
R-charts. (s. f.). Gráficos de correlación en R. R-charts. Recuperado el 2 de noviembre de 2025, de https://r-charts.com/es/correlacion/
Wickham, H., Çetinkaya-Rundel, M., & Grolemund, G. (2023). R for data science: Import, tidy, transform, visualize, and model data (2nd ed.). O’Reilly Media. https://r4ds.hadley.nz