R: Modelado Analítico

De los datos a los grupos:
análisis exploratorio y clustering

Act. Casandra Sofía Pejcovich

22 de julio de 2026

Objetivos de la sesión

Instalación de R y RStudio

Paso 1: Instalar R

  1. Ingresar al sitio oficial de R CRAN (Comprehensive R Archive Network): cran.r-project.org
  2. Seleccionar el sistema operativo (Windows, macOS o Linux).
  3. Descargar la versión más reciente de R (archivo .exe en Windows o .pkg en macOS).
  4. Ejecutar el instalador y seguir el asistente, aceptando las opciones predeterminadas.

Paso 2: Instalar RStudio

  1. Ingresar al sitio oficial de Posit: posit.co/download/rstudio-desktop
  2. Descargar la versión gratuita de RStudio Desktop para el sistema operativo correspondiente (.exe o .dmg).
  3. Ejecutar el instalador y seguir las instrucciones hasta finalizar.

Importante: R debe instalarse antes que RStudio, ya que RStudio necesita detectar una instalación de R para funcionar correctamente.

Paso 3: Verificar la instalación

  1. Abrir RStudio.
  2. En la consola, escribir:
print("Hola Mundo")
  1. Si el programa devuelve la frase en un nuevo renglón, la instalación fue exitosa.

1. ¿Qué es R y RStudio?

R: el lenguaje

R es un lenguaje orientado a objetos

# edad es el nombre del objeto, 25 el valor almacenado,
# <- es el operador de asignación (equivalente conceptual a "=")
edad <- 25
edad
## [1] 25

R es software libre, i.e. que la comunidad lo puede enriquecer, para manipular datos, hacer cálculos y generar gráficos. Esa combinación es justamente lo que lo hace tan potente en análisis estadístico.

RStudio: el entorno (IDE)

R vs RStudio, en resumen

R RStudio
¿Qué es? Lenguaje / motor de cálculo Entorno de desarrollo (interfaz)
¿Se puede usar sin el otro? Sí, desde la terminal No, necesita R instalado detrás
Rol Ejecuta el código Facilita escribir y organizar el código
Analogía El chef La cocina equipada

2. Auge de estas herramientas en la actuaría

¿Por qué R (y no solo Excel)?

Papel en la actuaría

3. R Script vs R Markdown

R Script (.R)

# Esto es un R Script típico
datos <- read.csv("archivo.csv")   # cargar datos
mean(datos$variable)               # calcular un promedio

R Markdown (.Rmd)

En resumen,

R Script R Markdown
Contenido Solo código Texto + código + resultados
Público objetivo Uno mismo / equipo técnico Cualquier audiencia (incluye no técnicos)
Salida Nada (o resultados en consola) Documento final (HTML, PDF, slides, etc.)
Uso típico Desarrollo y exploración Reportes, presentaciones, documentación

4. Pequeña guía de uso de Markdown

Sintaxis básica de texto

# Encabezado nivel 1
## Encabezado nivel 2

**texto en negrita**
*texto en cursiva*

- elemento de lista
- otro elemento

1. lista numerada
2. segundo elemento

[texto de un link](https://www.r-project.org/)

El “chunk” de código


``` r
# aquí va código R que se ejecuta al compilar
summary(cars)
```

```
##      speed           dist       
##  Min.   : 4.0   Min.   :  2.00  
##  1st Qu.:12.0   1st Qu.: 26.00  
##  Median :15.0   Median : 36.00  
##  Mean   :15.4   Mean   : 42.98  
##  3rd Qu.:19.0   3rd Qu.: 56.00  
##  Max.   :25.0   Max.   :120.00
```

5. Carga de la base de datos (Excel)

El dataset: Life Expectancy (WHO)

¿Qué es Kaggle y por qué es válido tomar datos de ahí?

Cargando el archivo Excel

# readxl es el paquete estándar para leer archivos .xlsx / .xls en R
library(readxl)
library(dplyr)
# read_excel() lee la hoja de cálculo y la convierte en un data frame de R
# path: ubicación del archivo | sheet: nombre u orden de la hoja (opcional)
life_exp <- read_excel("Life_Expectancy_Data.xlsx", sheet = 1)

Primer vistazo a la estructura

# dim(): dimensiones (filas x columnas)
dim(life_exp)
## [1] 2938   22
# glimpse(): vista compacta y legible de cada columna, su tipo y primeros valores
glimpse(life_exp)
## Rows: 2,938
## Columns: 22
## $ Country                           <chr> "Afghanistan", "Afghanistan", "Afgha…
## $ Year                              <dbl> 2015, 2014, 2013, 2012, 2011, 2010, …
## $ Status                            <chr> "Developing", "Developing", "Develop…
## $ `Life expectancy`                 <dbl> 65.0, 59.9, 59.9, 59.5, 59.2, 58.8, …
## $ `Adult Mortality`                 <dbl> 263, 271, 268, 272, 275, 279, 281, 2…
## $ `infant deaths`                   <dbl> 62, 64, 66, 69, 71, 74, 77, 80, 82, …
## $ Alcohol                           <dbl> 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, …
## $ `percentage expenditure`          <dbl> 71.279624, 73.523582, 73.219243, 78.…
## $ `Hepatitis B`                     <dbl> 65, 62, 64, 67, 68, 66, 63, 64, 63, …
## $ Measles                           <dbl> 1154, 492, 430, 2787, 3013, 1989, 28…
## $ BMI                               <dbl> 19.1, 18.6, 18.1, 17.6, 17.2, 16.7, …
## $ `under-five deaths`               <dbl> 83, 86, 89, 93, 97, 102, 106, 110, 1…
## $ Polio                             <dbl> 6, 58, 62, 67, 68, 66, 63, 64, 63, 5…
## $ `Total expenditure`               <dbl> 8.16, 8.18, 8.13, 8.52, 7.87, 9.20, …
## $ Diphtheria                        <dbl> 65, 62, 64, 67, 68, 66, 63, 64, 63, …
## $ `HIV/AIDS`                        <dbl> 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0…
## $ GDP                               <dbl> 584.25921, 612.69651, 631.74498, 669…
## $ Population                        <dbl> 33736494, 327582, 31731688, 3696958,…
## $ `thinness  1-19 years`            <dbl> 17.2, 17.5, 17.7, 17.9, 18.2, 18.4, …
## $ `thinness 5-9 years`              <dbl> 17.3, 17.5, 17.7, 18.0, 18.2, 18.4, …
## $ `Income composition of resources` <dbl> 0.479, 0.476, 0.470, 0.463, 0.454, 0…
## $ Schooling                         <dbl> 10.1, 10.0, 9.9, 9.8, 9.5, 9.2, 8.9,…

Primeras filas

# head(): muestra las primeras n filas, útil para "ver" los datos reales
head(life_exp, 5)

6. Análisis exploratorio y limpieza con dplyr

¿Por qué limpiar antes de graficar o modelar?

Diagnóstico inicial: nombres de columna

# Muchos datasets de Kaggle traen espacios al inicio/final de los nombres
# (ej. " BMI ", "Diphtheria ") impidiendo que hagamos referencias precisas por nombre
names(life_exp)
##  [1] "Country"                         "Year"                           
##  [3] "Status"                          "Life expectancy"                
##  [5] "Adult Mortality"                 "infant deaths"                  
##  [7] "Alcohol"                         "percentage expenditure"         
##  [9] "Hepatitis B"                     "Measles"                        
## [11] "BMI"                             "under-five deaths"              
## [13] "Polio"                           "Total expenditure"              
## [15] "Diphtheria"                      "HIV/AIDS"                       
## [17] "GDP"                             "Population"                     
## [19] "thinness  1-19 years"            "thinness 5-9 years"             
## [21] "Income composition of resources" "Schooling"
library(janitor)

life_exp <- life_exp %>%
  clean_names()

names(life_exp)
##  [1] "country"                         "year"                           
##  [3] "status"                          "life_expectancy"                
##  [5] "adult_mortality"                 "infant_deaths"                  
##  [7] "alcohol"                         "percentage_expenditure"         
##  [9] "hepatitis_b"                     "measles"                        
## [11] "bmi"                             "under_five_deaths"              
## [13] "polio"                           "total_expenditure"              
## [15] "diphtheria"                      "hiv_aids"                       
## [17] "gdp"                             "population"                     
## [19] "thinness_1_19_years"             "thinness_5_9_years"             
## [21] "income_composition_of_resources" "schooling"

Diagnóstico de valores faltantes — ANTES

# colSums(is.na(df)): cuenta NA's por columna
# is.na() marca TRUE/FALSE por celda, colSums() suma esos TRUE (=1) por columna
colSums(is.na(life_exp))
##                         country                            year 
##                               0                               0 
##                          status                 life_expectancy 
##                               0                              10 
##                 adult_mortality                   infant_deaths 
##                              10                               0 
##                         alcohol          percentage_expenditure 
##                             194                               0 
##                     hepatitis_b                         measles 
##                             553                               0 
##                             bmi               under_five_deaths 
##                              34                               0 
##                           polio               total_expenditure 
##                              19                             226 
##                      diphtheria                        hiv_aids 
##                              19                               0 
##                             gdp                      population 
##                             448                             652 
##             thinness_1_19_years              thinness_5_9_years 
##                              34                              34 
## income_composition_of_resources                       schooling 
##                             167                             163
# summary(): además de los NA, muestra rango, media, cuartiles por variable
summary(life_exp)
##    country               year         status          life_expectancy
##  Length:2938        Min.   :2000   Length:2938        Min.   :36.30  
##  Class :character   1st Qu.:2004   Class :character   1st Qu.:63.10  
##  Mode  :character   Median :2008   Mode  :character   Median :72.10  
##                     Mean   :2008                      Mean   :69.22  
##                     3rd Qu.:2012                      3rd Qu.:75.70  
##                     Max.   :2015                      Max.   :89.00  
##                                                       NA's   :10     
##  adult_mortality infant_deaths       alcohol        percentage_expenditure
##  Min.   :  1.0   Min.   :   0.0   Min.   : 0.0100   Min.   :    0.000     
##  1st Qu.: 74.0   1st Qu.:   0.0   1st Qu.: 0.8775   1st Qu.:    4.685     
##  Median :144.0   Median :   3.0   Median : 3.7550   Median :   64.913     
##  Mean   :164.8   Mean   :  30.3   Mean   : 4.6029   Mean   :  738.251     
##  3rd Qu.:228.0   3rd Qu.:  22.0   3rd Qu.: 7.7025   3rd Qu.:  441.534     
##  Max.   :723.0   Max.   :1800.0   Max.   :17.8700   Max.   :19479.912     
##  NA's   :10                       NA's   :194                             
##   hepatitis_b       measles              bmi        under_five_deaths
##  Min.   : 1.00   Min.   :     0.0   Min.   : 1.00   Min.   :   0.00  
##  1st Qu.:77.00   1st Qu.:     0.0   1st Qu.:19.30   1st Qu.:   0.00  
##  Median :92.00   Median :    17.0   Median :43.50   Median :   4.00  
##  Mean   :80.94   Mean   :  2419.6   Mean   :38.32   Mean   :  42.04  
##  3rd Qu.:97.00   3rd Qu.:   360.2   3rd Qu.:56.20   3rd Qu.:  28.00  
##  Max.   :99.00   Max.   :212183.0   Max.   :87.30   Max.   :2500.00  
##  NA's   :553                        NA's   :34                       
##      polio       total_expenditure   diphtheria       hiv_aids     
##  Min.   : 3.00   Min.   : 0.370    Min.   : 2.00   Min.   : 0.100  
##  1st Qu.:78.00   1st Qu.: 4.260    1st Qu.:78.00   1st Qu.: 0.100  
##  Median :93.00   Median : 5.755    Median :93.00   Median : 0.100  
##  Mean   :82.55   Mean   : 5.938    Mean   :82.32   Mean   : 1.742  
##  3rd Qu.:97.00   3rd Qu.: 7.492    3rd Qu.:97.00   3rd Qu.: 0.800  
##  Max.   :99.00   Max.   :17.600    Max.   :99.00   Max.   :50.600  
##  NA's   :19      NA's   :226       NA's   :19                      
##       gdp              population        thinness_1_19_years thinness_5_9_years
##  Min.   :1.681e+00   Min.   :3.400e+01   Min.   : 0.10       Min.   : 0.10     
##  1st Qu.:4.639e+02   1st Qu.:1.958e+05   1st Qu.: 1.60       1st Qu.: 1.50     
##  Median :1.767e+03   Median :1.387e+06   Median : 3.30       Median : 3.30     
##  Mean   :7.483e+03   Mean   :1.275e+07   Mean   : 4.84       Mean   : 4.87     
##  3rd Qu.:5.911e+03   3rd Qu.:7.420e+06   3rd Qu.: 7.20       3rd Qu.: 7.20     
##  Max.   :1.192e+05   Max.   :1.294e+09   Max.   :27.70       Max.   :28.60     
##  NA's   :448         NA's   :652         NA's   :34          NA's   :34        
##  income_composition_of_resources   schooling    
##  Min.   :0.0000                  Min.   : 0.00  
##  1st Qu.:0.4930                  1st Qu.:10.10  
##  Median :0.6770                  Median :12.30  
##  Mean   :0.6276                  Mean   :11.99  
##  3rd Qu.:0.7790                  3rd Qu.:14.30  
##  Max.   :0.9480                  Max.   :20.70  
##  NA's   :167                     NA's   :163

Limpieza con dplyr

life_exp_limpio <- life_exp %>%
  # filter(): nos quedamos solo con filas donde la variable objetivo existe
  filter(!is.na(life_expectancy)) %>%
  # select(): elegimos las variables numéricas relevantes para el clustering
  select(country, year, status, life_expectancy, adult_mortality,
         alcohol, percentage_expenditure, bmi, gdp,
         schooling, income_composition_of_resources) %>%
  # mutate(): imputamos NA's restantes con la mediana de cada columna numérica
  # (la mediana es más robusta a outliers que la media, por eso se prefiere aquí)
  mutate(across(where(is.numeric), ~ ifelse(is.na(.x), median(.x, na.rm = TRUE), .x)))

Diagnóstico de valores faltantes — DESPUÉS

colSums(is.na(life_exp_limpio))
##                         country                            year 
##                               0                               0 
##                          status                 life_expectancy 
##                               0                               0 
##                 adult_mortality                         alcohol 
##                               0                               0 
##          percentage_expenditure                             bmi 
##                               0                               0 
##                             gdp                       schooling 
##                               0                               0 
## income_composition_of_resources 
##                               0
summary(life_exp_limpio)
##    country               year         status          life_expectancy
##  Length:2928        Min.   :2000   Length:2928        Min.   :36.30  
##  Class :character   1st Qu.:2004   Class :character   1st Qu.:63.10  
##  Mode  :character   Median :2008   Mode  :character   Median :72.10  
##                     Mean   :2008                      Mean   :69.22  
##                     3rd Qu.:2011                      3rd Qu.:75.70  
##                     Max.   :2015                      Max.   :89.00  
##  adult_mortality    alcohol       percentage_expenditure      bmi       
##  Min.   :  1.0   Min.   : 0.010   Min.   :    0.000      Min.   : 1.00  
##  1st Qu.: 74.0   1st Qu.: 1.107   1st Qu.:    4.854      1st Qu.:19.40  
##  Median :144.0   Median : 3.770   Median :   65.612      Median :43.35  
##  Mean   :164.8   Mean   : 4.559   Mean   :  740.321      Mean   :38.29  
##  3rd Qu.:228.0   3rd Qu.: 7.400   3rd Qu.:  442.614      3rd Qu.:56.10  
##  Max.   :723.0   Max.   :17.870   Max.   :19479.912      Max.   :77.60  
##       gdp              schooling     income_composition_of_resources
##  Min.   :1.681e+00   Min.   : 0.00   Min.   :0.0000                 
##  1st Qu.:5.788e+02   1st Qu.:10.30   1st Qu.:0.5040                 
##  Median :1.765e+03   Median :12.30   Median :0.6770                 
##  Mean   :6.627e+03   Mean   :12.02   Mean   :0.6301                 
##  3rd Qu.:4.794e+03   3rd Qu.:14.10   3rd Qu.:0.7730                 
##  Max.   :1.192e+05   Max.   :20.70   Max.   :0.9480

7. Gráficos multivariable básicos en ggplot2

Con la base ya limpia, el siguiente paso natural es encontrar qué patrones insinúan los gráficos para así anticipar qué esperar del modelo de clustering.

La filosofía de ggplot2: gramática de gráficos

Construyendo un gráfico, capa por capa

library(ggplot2)

# Capa 1: solo datos + estética -> lienzo vacío, define ejes
ggplot(life_exp_limpio, aes(x = percentage_expenditure, y = life_expectancy))

# Capa 2: agregamos geometría -> ahora sí vemos los puntos
ggplot(life_exp_limpio, aes(x = percentage_expenditure, y = life_expectancy)) +
  geom_point()

# Capa 3: coloreamos por una variable categórica (status: desarrollado/en desarrollo)
ggplot(life_exp_limpio, aes(x = percentage_expenditure, y = life_expectancy,
                             color = status)) +
  geom_point(alpha = 0.5)

# Capa 4: tema, para una presentación más limpia y legible
ggplot(life_exp_limpio, aes(x = percentage_expenditure, y = life_expectancy,
                             color = status)) +
  geom_point(alpha = 0.5) +
  labs(title = "Esperanza de vida vs. gasto en salud",
       x = "Gasto en salud (% del PIB, escala relativa)",
       y = "Esperanza de vida (años)",
       color = "Status") +
  theme_minimal()

Segundo gráfico: distribución por status

ggplot(life_exp_limpio, aes(x = status, y = life_expectancy, fill = status)) +
  geom_boxplot() +
  labs(title = "Distribución de esperanza de vida según status del país",
       x = NULL, y = "Esperanza de vida (años)") +
  theme_minimal() +
  theme(legend.position = "none")

Tercer gráfico: relación entre escolaridad y desarrollo

ggplot(life_exp_limpio, aes(x = schooling, y = income_composition_of_resources,
                             color = life_expectancy)) +
  geom_point(size = 2, alpha = 0.7) +
  scale_color_viridis_c() +
  labs(title = "Escolaridad, desarrollo y esperanza de vida",
       x = "Años de escolaridad", y = "Índice de composición del ingreso",
       color = "Esperanza\nde vida") +
  theme_minimal()

8. Conclusiones de los gráficos

Lo observado

El puente hacia K-means

Los gráficos ya insinúan la existencia de grupos naturales de países según su perfil de desarrollo y salud, pero lo hacen variable por variable, de a pares. La pregunta natural es: ¿podemos encontrar esos grupos usando varias variables al mismo tiempo, de forma sistemática?

9. Introducción al modelado estadístico

¿Qué significa “modelar”?

Aprendizaje supervisado: la idea

\[ i \in \{1, 2, \dots, n\}, \quad (x_i, y_i), \quad x_i \in \mathbb{R}^p, \quad y_i \in \mathbb{R} \;\text{o}\; y_i \in \{0,1\} \]

Aprendizaje supervisado: el objetivo

\[ f: \mathbb{R}^p \rightarrow \mathbb{R}, \quad y_i \approx f(x_i) \]

Aprendizaje no supervisado: la idea

\[ i \in \{1, 2, \dots, n\}, \quad x_i \in \mathbb{R}^p, \quad \{x_1, x_2, \dots, x_n\} \subset \mathbb{R}^p \]

Aprendizaje no supervisado: clustering

\[ \mathcal{C} = \{C_1, C_2, \dots, C_K\}, \quad \bigcup_{k=1}^{K} C_k = \{1,2,\dots,n\}, \quad C_k \cap C_j = \emptyset \quad \text{si } k \neq j \]

Los dos enfoques, en resumen

Supervisado No supervisado
¿Hay variable objetivo (\(y\))? Sí, conocida de antemano No existe una etiqueta previa
Objetivo Predecir o clasificar Descubrir estructura/grupos ocultos
Ejemplos Regresión, árboles de decisión, regresión logística K-means, clustering jerárquico, PCA
Ejemplo actuarial Predecir el monto de un siniestro Segmentar asegurados por perfil de riesgo

¿Dónde está K-means?

K-means es un modelo no supervisado que busca, únicamente a partir de las variables numéricas, la forma de agrupar observaciones similares entre sí y distintas de otros grupos.

10. Análisis de K-means

Algoritmo K-means

  1. Inicialización aleatoria: se asigna, al azar, un número del 1 al K a cada una de las observaciones. Esto define una primera partición en K grupos.
  2. Iterar hasta que las asignaciones dejen de cambiar:
    1. Para cada uno de los K clusters, calcular su centroide: el vector formado por el promedio de cada una de las \(p\) variables, tomado solo sobre las observaciones que pertenecen a ese cluster en el paso actual.
    2. Reasignar cada observación al cluster cuyo centroide esté más cerca, usando distancia euclidiana.
  3. El proceso converge cuando, tras recalcular los centroides y reasignar observaciones, ningún punto cambia de grupo y el algoritmo se detiene.

Paso previo obligatorio: escalar las variables

# Seleccionamos solo las variables numéricas relevantes (sin id/categóricas)
datos_kmeans <- life_exp_limpio %>%
  select(life_expectancy, adult_mortality, alcohol,
         percentage_expenditure, bmi, gdp,
         schooling, income_composition_of_resources)

# scale(): centra (resta la media) y estandariza (divide entre desv. estándar)
# cada variable queda con media 0 y varianza 1 -> todas "pesan" lo mismo
datos_escalados <- scale(datos_kmeans)

Eligiendo k: método del codo

library(factoextra)

# fviz_nbclust calcula, para distintos valores de k, la suma de cuadrados
# dentro de cada cluster (WSS) — buscamos el "codo" donde agregar más
# grupos deja de reducir sustancialmente esa suma
fviz_nbclust(datos_escalados, kmeans, method = "wss", k.max = 10) +
  labs(title = "Método del codo para elegir k",
       subtitle = "Buscamos el punto donde la curva se \"aplana\"")

Un segundo criterio: el método de la silueta

fviz_nbclust(datos_escalados, kmeans, method = "silhouette", k.max = 10) +
  labs(title = "Método de la silueta para elegir k",
       subtitle = "Buscamos el k que maximiza el ancho de silueta promedio")

Ejecutando K-means

set.seed(123)  # semilla fija para que el resultado sea reproducible

# k_elegido: valor observado en el codo/silueta de ESTA corrida de ejemplo.
k_elegido <- 3

# nstart = 25 -> corre el algoritmo completo 25 veces, cada una empezando
# desde una asignación aleatoria inicial distinta (paso 1 del algoritmo)
modelo_kmeans <- kmeans(datos_escalados, centers = k_elegido, nstart = 25)

# tamaño de cada cluster
modelo_kmeans$size
## [1] 1083  238 1607

Visualizando los clusters

fviz_cluster(modelo_kmeans, data = datos_escalados,
             geom = "point", ellipse.type = "norm",
             palette = "jco", ggtheme = theme_minimal()) +
  labs(title = "Clusters de países según perfil de salud y desarrollo")

Interpretando los clusters en contexto

# Agregamos la etiqueta de cluster a la base y resumimos por grupo
life_exp_limpio %>%
  mutate(cluster = modelo_kmeans$cluster) %>%
  group_by(cluster) %>%
  summarise(
    filas = n(),
    paises_distintos = n_distinct(country),
    esperanza_vida_prom = mean(life_expectancy),
    gdp_prom = mean(gdp),
    escolaridad_prom = mean(schooling)
  )

11. Limitaciones y próximos pasos

Cierre

¡Gracias!