Act. Casandra Sofía Pejcovich
22 de julio de 2026
dplyr, visualización con
ggplot2 y un modelo de K-means,
interpretando cada resultado en un contexto actuarial..exe en
Windows o .pkg en macOS)..exe o
.dmg).Importante: R debe instalarse antes que RStudio, ya que RStudio necesita detectar una instalación de R para funcionar correctamente.
# 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.
| 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 |
lifecontingencies — cálculo de tablas de mortalidad y
valuación de seguros de vida.ChainLadder — reservas técnicas en seguros
generales.actuar — distribuciones y modelos de
severidad/frecuencia para riesgo..R)#.# Esto es un R Script típico
datos <- read.csv("archivo.csv") # cargar datos
mean(datos$variable) # calcular un promedio.Rmd)| 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 |
# 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/)# definen jerarquía de encabezados (y en
slidy_presentation, cada encabezado genera una
nueva diapositiva).
``` 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
```{r ...} indica que es un bloque de código en R.echo=TRUE/FALSE — mostrar u ocultar el código en el
documento final.eval=TRUE/FALSE — ejecutar o no el código.warning=FALSE, message=FALSE — ocultar
avisos/mensajes técnicos en la salida.# 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)## [1] 2938 22
## 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,…
kmeans() solo acepta variables
numéricas y es sensible a valores faltantes (NA) y
a la escala de cada variable.# 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"
janitor::clean_names() estandariza automáticamente:
minúsculas, sin espacios, separador _.## [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"
# 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
## 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
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)))filter() — quita filas según una condición lógica
(aquí, sin dato en la variable objetivo).select() — nos quedamos solo con las columnas que vamos
a usar (justificación: evitar variables redundantes o irrelevantes para
el perfil de salud/desarrollo).mutate(across(...)) — aplica una transformación a
varias columnas a la vez (todas las numéricas),
evitando repetir el mismo código columna por columna.%>% (pipe) encadena pasos de
forma legible: “toma esto, y luego haz esto, y luego esto otro”.## 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
## 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
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.
ggplot2 piensa en capas que se
combinan:
aes): qué variables van en
qué eje, color, tamaño, forma.geom_*): cómo se
representan los datos (puntos, barras, cajas…).facet_*): paneles separados
por una variable categórica.theme): aspecto visual
general.plot() base: cada capa es
independiente y se agrega con +, lo que hace el código más
legible, modular y fácil de modificar sin reescribir todo el
gráfico.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()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")geom_boxplot() resume mediana, rango intercuartílico y
outliers en una sola geometría.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()scale_color_viridis_c()), mostrando que ggplot2 permite
representar más de dos dimensiones en un solo gráfico 2D.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?
\[ 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\} \]
\[ f: \mathbb{R}^p \rightarrow \mathbb{R}, \quad y_i \approx f(x_i) \]
\[ 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 \]
\[ \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 \]
| 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 |
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.
# 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)gdp), sin que eso refleje mayor importancia real.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\"")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")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
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")fviz_cluster() (de factoextra) está
construido sobre ggplot2, por eso el resultado se ve y se
personaliza igual que los gráficos ya vistos (mismas capas: tema,
título, paleta de color).# 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)
)scale() no fue opcional, sino un requisito del método.status o country quedaron fuera del
modelo numérico), aunque sí existen alternativas como k-modes o
k-prototypes para datos mixtos.