Objetivos de aprendizaje

Al terminar esta sesión, serás capaz de:

  • Diseñar y ejecutar una metodología de transformación de datos reproducible en R con dplyr, tidyr y janitor.
  • Distinguir y aplicar las etapas descriptiva, prescriptiva, predictiva y prospectiva sobre un mismo conjunto de datos.
  • Realizar un diagnóstico de calidad de datos (estadístico y procedimental), dejando evidencia de lo hecho (registro de procesos data lineage básico).

Metodología de transformación de datos

Para un actuario, la Metodología de Transformación de Datos constituye un pilar fundamental en su trabajo diario, ya que proporciona el marco sistemático y reproducible para convertir datos crudos, frecuentemente desestructurados o incompletos, en información depurada, organizada y confiable lista para el análisis. Un actuario utiliza esta metodología para preparar los datos sobre los cuales aplicará modelos estadísticos y matemáticos avanzados para cuantificar riesgos, calcular primas, provisiones técnicas o capitales económicos, y proyectar escenarios financieros. La capacidad de documentar y replicar cada paso de la transformación—desde la limpieza de nombres de variables con janitor y la manipulación de tablas con dplyr hasta la reorganización de datos con tidyr—no solo garantiza la transparencia y auditabilidad del proceso, crucial en un entorno regulatorio estricto, sino que también asegura que los resultados de sus complejos análisis predictivos y prescriptivos se basen en datos íntegros y de calidad, minimizando así el riesgo de errores en decisiones de alta consecuencia económicas y actuariales.

2) Aspectos-teoría

¿Qué es “transformar datos”?

Cambiar la forma de los datos (filtrar, seleccionar columnas, crear variables, tratar faltantes, unir tablas, pasar de largo a ancho y viceversa) para dejarlos listos para analizar/modelar.


3) Paquetes

# Instalamos paquetes si no están
# install.packages(c("tidyverse", "janitor"))  

# Cargamos las librerías que usaremos
library(dplyr)   # Transformaciones de datos
library(tidyr)   # Cambiar la forma (pivot_longer/pivot_wider)
library(janitor) # Limpieza de nombres y chequeos

4) Flujo sugerido

Usaremos starwars (viene con dplyr) para no depender de archivos externos.

# 0. Traemos la base de ejemplo
base <- dplyr::starwars

names(base)
##  [1] "name"       "height"     "mass"       "hair_color" "skin_color"
##  [6] "eye_color"  "birth_year" "sex"        "gender"     "homeworld" 
## [11] "species"    "films"      "vehicles"   "starships"
head(base)
# 1. Limpiamos nombres de columnas para trabajar más fácil (snake_case)
base <- base %>% janitor::clean_names()

# 2. Nos quedamos solo con las columnas que necesitamos
base <- base %>% select(name, species, height, mass, homeworld, gender)

# 3. Aseguramos que height y mass sean numéricos, y species/gender sean factores
base <- base %>% mutate(
  height = as.numeric(height),
  mass   = as.numeric(mass),
  species = as.factor(species),
  gender  = as.factor(gender)
)

# 4. Tratamos valores faltantes: reemplazamos mass NA por la mediana de su especie
base <- base %>% group_by(species) %>%
  mutate(mass = ifelse(is.na(mass), median(mass, na.rm = TRUE), mass)) %>%
  ungroup()

# 5. Eliminamos duplicados si hay nombres repetidos
base <- base %>% distinct(name, .keep_all = TRUE)

# 6. Creamos nuevas variables: IMC aproximado y categoría de talla
base <- base %>% mutate(
  imc_aprox = mass / ((height/100)^2),
  talla_cat = case_when(
    height < 160 ~ "baja",
    height < 180 ~ "media",
    TRUE ~ "alta"
  )
)

# 7. Cambiamos la forma de los datos: de ancho a largo
base_largo <- base %>% select(name, species, height, mass) %>%
  pivot_longer(cols = c(height, mass), names_to = "variable", values_to = "valor")

# 8. Hacemos un join: añadimos info de regiones de algunos planetas
mundos <- tibble::tribble(
  ~homeworld, ~region,
  "Naboo",   "Mid Rim",
  "Tatooine","Outer Rim",
  "Alderaan","Core"
)

base_join <- base %>% left_join(mundos, by = "homeworld")

# 9. Resumimos: cuántos personajes y el IMC mediano por región y categoría de talla
resumen <- base_join %>% group_by(region, talla_cat) %>%
  summarise(
    n = n(),
    mediana_imc = median(imc_aprox, na.rm = TRUE)
  ) %>% arrange(region, talla_cat)

resumen

5) Ejercicios básicos

Ejercicio 1. Selección y filtrado

# Paso 1: limpiamos nombres
# Paso 2: seleccionamos columnas de interés
# Paso 3: filtramos solo filas con height >= 170 y mass no faltante
sol_1 <- starwars %>%
  janitor::clean_names() %>%
  select(name, species, height, mass) %>%
  filter(!is.na(mass), height >= 170)

Ejercicio 2. Variables derivadas y ordenamiento

# Paso 1: a sol_1 le agregamos la variable imc_aprox
# Paso 2: ordenamos de mayor a menor IMC
sol_2 <- sol_1 %>%
  mutate(imc_aprox = mass / ((height/100)^2)) %>%
  arrange(desc(imc_aprox))

Ejercicio 3. Tratamiento simple de faltantes

# Paso 1: seleccionamos columnas clave
# Paso 2: convertimos height y mass a numéricos
# Paso 3: imputamos mass faltante con mediana por especie
# Paso 4: calculamos el IMC aproximado
sol_3 <- starwars %>%
  janitor::clean_names() %>%
  select(name, species, height, mass) %>%
  mutate(across(c(height, mass), as.numeric)) %>%
  group_by(species) %>%
  mutate(mass = ifelse(is.na(mass), median(mass, na.rm = TRUE), mass)) %>%
  ungroup() %>%
  mutate(imc_aprox = mass / ((height/100)^2))

Ejercicio 4. Cambiar entre ancho y largo

# Paso 1: llevamos height y mass a formato largo
largo <- sol_3 %>%
  pivot_longer(cols = c(height, mass), names_to = "variable", values_to = "valor")

# Paso 2: regresamos a formato ancho
ancho <- largo %>% pivot_wider(names_from = variable, values_from = valor)

Ejercicio 5. Join con diccionario

# Paso 1: construimos tabla auxiliar de mundos
mundos <- tibble::tribble(
  ~homeworld, ~region,
  "Naboo",   "Mid Rim",
  "Tatooine","Outer Rim",
  "Alderaan","Core"
)

# Paso 2: unimos con starwars por homeworld
# Paso 3: contamos cuántos personajes por región
sol_5 <- starwars %>%
  janitor::clean_names() %>%
  left_join(mundos, by = "homeworld") %>%
  count(region, sort = TRUE)

Etapas descriptiva, prescriptiva, predictiva y prospectiva

Teoría (siguiente etapa)

  • Descriptiva (¿qué pasó?)

Resume y caracteriza los datos: conteos, medias, medianas, rangos, tablas de frecuencia. No prescribe acciones ni predice el futuro; describe el estado actual o histórico.

  • Prescriptiva (¿qué deberíamos hacer?)

Toma lo descrito y propone reglas/decisiones. Puede ir desde heurísticas simples (reglas si-entonces) hasta optimización. No “adivina” el futuro: recomienda acciones con base en reglas/criterios.

  • Predictiva (¿qué pasará?)

Construye modelos para estimar una variable de interés a partir de otras (p. ej., regresión). No decide por ti, pero provee pronósticos.

  • Prospectiva (¿qué podría pasar?)

Explora escenarios hipotéticos (simulación, “what-if”). Usa el modelo predictivo o supuestos para proyectar resultados bajo cambios en condiciones.

# ============================
# 0) Cargar paquetes y datos
# ============================

# Cargamos paquetes para transformar y limpiar
library(dplyr)    # verbos de manipulación (select, filter, mutate, summarise, etc.)
library(tidyr)    # reshape (pivot_longer/pivot_wider) si lo necesitáramos
library(janitor)  # clean_names() y utilidades de limpieza

# Tomamos la base de ejemplo incluida en dplyr
datos <- dplyr::starwars

# Estandarizamos nombres de columnas a snake_case para facilitar el trabajo
datos <- janitor::clean_names(datos)

# Nos quedamos con variables sencillas y útiles para el ejemplo
datos <- datos %>%
  select(name, species, height, mass, homeworld, gender)

# Aseguramos tipos correctos (height y mass numéricos; species/gender factores)
datos <- datos %>%
  mutate(
    height  = as.numeric(height),
    mass    = as.numeric(mass),
    species = as.factor(species),
    gender  = as.factor(gender)
  )

# Imputamos mass faltante con la mediana por especie (sencillo y reproducible)
datos <- datos %>%
  group_by(species) %>%
  mutate(mass = ifelse(is.na(mass), median(mass, na.rm = TRUE), mass)) %>%
  ungroup()

# Creamos una variable derivada simple: IMC aproximado (masa / talla^2) con talla en metros
datos <- datos %>%
  mutate(imc_aprox = mass / ((height / 100)^2))

1) Etapa Descriptiva (¿qué pasó?)

# Resumen general de las variables numéricas (rápido y útil)
summary(select(datos, height, mass, imc_aprox))
##      height           mass           imc_aprox     
##  Min.   : 66.0   Min.   :  15.00   Min.   : 12.89  
##  1st Qu.:167.0   1st Qu.:  65.00   1st Qu.: 21.60  
##  Median :180.0   Median :  79.00   Median : 24.67  
##  Mean   :174.6   Mean   :  91.55   Mean   : 31.03  
##  3rd Qu.:191.0   3rd Qu.:  83.00   3rd Qu.: 27.45  
##  Max.   :264.0   Max.   :1358.00   Max.   :443.43  
##  NA's   :6       NA's   :6         NA's   :12
# Conteo de registros por especie (tabla de frecuencias)
conteo_especie <- datos %>% count(species, sort = TRUE)
head(conteo_especie, 10)  # mostramos las 10 más frecuentes
# Medidas por especie (n, media de altura y masa, IMC mediano)
desc_por_especie <- datos %>%
  group_by(species) %>%
  summarise(
    n            = n(),
    altura_media = mean(height, na.rm = TRUE),
    masa_media   = mean(mass,   na.rm = TRUE),
    imc_mediano  = median(imc_aprox, na.rm = TRUE)
  ) %>%
  arrange(desc(n))

head(desc_por_especie, 10)  # vistazo rápido

Aquí solo describimos: ¿cuántos hay de cada especie?, ¿cómo lucen altura/masa?, ¿qué tan dispersos están?

2) Etapa Prescriptiva (¿qué deberíamos hacer?)

Construimos una regla simple para recomendar talla de traje según altura. Es una decisión basada en la etapa descriptiva (no es un pronóstico).

# Definimos una regla: asignar talla de traje por rangos de altura
datos <- datos %>%
  mutate(
    talla_traje = case_when(
      is.na(height)        ~ "Revisar",     # si no hay altura, pedimos revisión
      height < 160         ~ "S",
      height >= 160 & height < 180 ~ "M",
      height >= 180        ~ "L"
    )
  )

# Resumen de la política: cuántos personajes por talla recomendada
datos %>% count(talla_traje, sort = TRUE)
# Otra regla prescriptiva: prioridad de revisión si IMC está fuera de rango “esperado”
# (Nota: umbrales ficticios para ejemplo básico)
datos <- datos %>%
  mutate(
    flag_revision = case_when(
      is.na(imc_aprox)        ~ TRUE,               # sin IMC, revisar
      imc_aprox < 15          ~ TRUE,               # demasiado bajo, revisar
      imc_aprox > 45          ~ TRUE,               # demasiado alto, revisar
      TRUE                    ~ FALSE               # en rango, sin revisión
    )
  )

# Resumen de la regla de revisión
datos %>% count(flag_revision)

Idea didáctica: la prescripción aplica reglas: asigna acciones a casos. No intenta predecir valores futuros; decide con base en criterios.

3) Etapa Predictiva (¿qué pasará?)

Construimos un modelo sencillo para predecir mass a partir de height. No buscamos un “modelo ganador”, solo mostrar el flujo.

# Preparamos un subconjunto sin NA en height/mass para modelar
dat_modelo <- datos %>% filter(!is.na(height), !is.na(mass))

# Ajustamos una regresión lineal simple: mass ~ height
modelo_lm <- lm(mass ~ height, data = dat_modelo)

# Revisamos el resumen del modelo (coeficientes, R^2, etc.)
summary(modelo_lm)
## 
## Call:
## lm(formula = mass ~ height, data = dat_modelo)
## 
## Residuals:
##     Min      1Q  Median      3Q     Max 
##  -55.86  -23.78  -16.98  -12.35 1264.49 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)
## (Intercept)  -7.1303    91.9243  -0.078    0.938
## height        0.5751     0.5190   1.108    0.272
## 
## Residual standard error: 150.1 on 73 degrees of freedom
## Multiple R-squared:  0.01654,    Adjusted R-squared:  0.003066 
## F-statistic: 1.228 on 1 and 73 DF,  p-value: 0.2715
# Hacemos predicciones para nuevas alturas (escenario de producción de trajes, por ejemplo)
nuevas_alturas <- data.frame(height = c(150, 165, 180, 200))
predicciones <- predict(modelo_lm, newdata = nuevas_alturas)

# Mostramos la tabla de alturas nuevas y sus masas estimadas
cbind(nuevas_alturas, mass_pred = round(predicciones, 2))

Idea didáctica: la predictiva estima un valor futuro/desconocido (masa) usando un modelo entrenado con datos históricos (altura).

4) Etapa Prospectiva (¿qué podría pasar?)

Usamos el modelo predictivo para explorar escenarios (what-if). Ejemplo: si la altura promedio aumenta 10 cm, ¿cómo cambiaría la masa esperada?

# Calculamos la altura promedio actual (para tener una línea base)
altura_promedio_actual <- mean(dat_modelo$height, na.rm = TRUE)

# Definimos un escenario hipotético: +10 cm a la altura promedio
altura_promedio_escenario <- altura_promedio_actual + 10

# Estimamos la masa esperada en la altura promedio actual (baseline)
masa_esperada_actual <- predict(modelo_lm, data.frame(height = altura_promedio_actual))

# Estimamos la masa esperada en el escenario (+10 cm)
masa_esperada_escenario <- predict(modelo_lm, data.frame(height = altura_promedio_escenario))

# Comparamos ambos valores en una tablita
data.frame(
  escenario        = c("Actual", "Altura +10cm"),
  height_promedio  = c(altura_promedio_actual, altura_promedio_escenario),
  mass_esperada    = c(masa_esperada_actual,  masa_esperada_escenario)
)

Idea didáctica: la prospectiva no afirma que sucederá; explora qué pasaría si cambian las condiciones (útil para planeación y decisiones).

Teoría: Diagnóstico de calidad de datos

Antes de confiar en los datos, debemos preguntarnos:

  • Completitud: ¿faltan valores? ¿cuántos? ¿dónde?

  • Duplicados: ¿hay filas repetidas que puedan inflar resultados?

  • Consistencia de tipos: ¿cada columna tiene el tipo correcto (numérico, texto, factor)?

  • Rangos y reglas de negocio: ¿los valores son razonables (ejemplo: alturas negativas, masas imposibles)?

  • Documentación (data lineage): dejar registro de qué transformaciones hicimos y por qué, para que otro pueda reproducir el proceso.

Usamos la base starwars ya transformada.

# ============================
# 0) Base limpia inicial
# ============================
library(dplyr)
library(janitor)

datos <- dplyr::starwars %>%
  janitor::clean_names() %>%
  select(name, species, height, mass, homeworld, gender) %>%
  mutate(
    height = as.numeric(height),
    mass   = as.numeric(mass),
    species = as.factor(species),
    gender  = as.factor(gender)
  )

1) Chequeo de valores faltantes (completitud)

# Conteo rápido de NA por variable
faltantes <- datos %>% summarise(across(everything(), ~sum(is.na(.))))
faltantes
# Porcentaje de NA
faltantes_pct <- datos %>% summarise(across(everything(), ~mean(is.na(.))*100))
faltantes_pct

Nos dice qué variables están incompletas y en qué proporción.

2) Duplicados

# Revisamos si hay filas duplicadas completas
duplicados <- datos %>% get_dupes()
duplicados

Si hay duplicados, deben decidir si eliminarlos, consolidarlos o dejarlos (depende de la lógica de negocio).

3) Consistencia de tipos

# Ver estructura general: tipo de dato en cada columna
str(datos)
## tibble [87 × 6] (S3: tbl_df/tbl/data.frame)
##  $ name     : chr [1:87] "Luke Skywalker" "C-3PO" "R2-D2" "Darth Vader" ...
##  $ species  : Factor w/ 37 levels "Aleena","Besalisk",..: 11 6 6 11 11 11 11 6 11 11 ...
##  $ height   : num [1:87] 172 167 96 202 150 178 165 97 183 182 ...
##  $ mass     : num [1:87] 77 75 32 136 49 120 75 32 84 77 ...
##  $ homeworld: chr [1:87] "Tatooine" "Tatooine" "Naboo" "Tatooine" ...
##  $ gender   : Factor w/ 2 levels "feminine","masculine": 2 2 2 2 1 2 1 2 2 2 ...
# Tabla rápida de clases
tipos <- sapply(datos, class)
tipos
##        name     species      height        mass   homeworld      gender 
## "character"    "factor"   "numeric"   "numeric" "character"    "factor"

Asegura que números son numéricos, categorías son factores, etc.

4) Rango y reglas de negocio

# Resumen estadístico de height y mass
summary(select(datos, height, mass))
##      height           mass        
##  Min.   : 66.0   Min.   :  15.00  
##  1st Qu.:167.0   1st Qu.:  55.60  
##  Median :180.0   Median :  79.00  
##  Mean   :174.6   Mean   :  97.31  
##  3rd Qu.:191.0   3rd Qu.:  84.50  
##  Max.   :264.0   Max.   :1358.00  
##  NA's   :6       NA's   :28
# Detectamos valores fuera de rango razonable
# Ejemplo: altura negativa o exagerada > 300 cm
outliers_altura <- datos %>% filter(height < 0 | height > 300)

# Ejemplo: masa negativa o exagerada > 5000 kg
outliers_masa <- datos %>% filter(mass < 0 | mass > 5000)

outliers_altura
outliers_masa

Esto no elimina, solo marca posibles problemas para revisarlos.

5) Data Lineage básico (registro de procesos)

Una forma simple es mantener un tibble de bitácora con los pasos aplicados:

# Creamos una bitácora manual de procesos aplicados
lineage <- tibble::tibble(
  paso = 1:5,
  descripcion = c(
    "Importación de datos starwars",
    "Limpieza de nombres con janitor::clean_names()",
    "Selección de variables relevantes (name, species, height, mass, homeworld, gender)",
    "Conversión de tipos (numéricos y factores)",
    "Diagnóstico de calidad: NA, duplicados, rangos"
  ),
  fecha = Sys.Date()
)

lineage

Esto es un mini data lineage: documenta qué se hizo, cuándo y en qué orden.

Conclusión

  • Diagnóstico estadístico: NA, duplicados, tipos, rangos.

  • Diagnóstico procedimental: registrar los pasos aplicados, para poder rastrear y repetir el proceso.

  • Esto convierte un análisis en algo confiable y auditable: cualquiera puede seguir el mismo flujo y obtener los mismos resultados.

Para un actuario, el diagnóstico de calidad de datos no es un trámite, sino la base de todo análisis confiable. Decisiones sobre reservas, primas o proyecciones de riesgo dependen de información sólida: un valor faltante mal tratado puede distorsionar una estimación de probabilidad; un duplicado puede inflar la frecuencia de siniestros; un rango incoherente puede alterar un modelo predictivo. Cada paso del diagnóstico cumple un rol en momentos distintos: la descriptiva ayuda a explicar el pasado (ej. distribución de edades de asegurados), la prescriptiva orienta decisiones inmediatas (ej. asignar categorías de riesgo), la predictiva permite anticipar escenarios probables (ej. estimar la siniestralidad futura) y la prospectiva abre la puerta a evaluar planes estratégicos bajo supuestos hipotéticos (ej. impacto de un cambio regulatorio o de una nueva cobertura). Al dejar evidencia clara del data lineage, el actuario garantiza transparencia, reproducibilidad y confianza en los resultados, cualidades esenciales en un campo donde los números respaldan millones de decisiones financieras.

Problema aplicado: Base simulada de siniestros de seguros de vida

Contexto

Una aseguradora ha recolectado información de 500 pólizas de vida. Los datos incluyen edad del asegurado, género, valor asegurado, número de siniestros reportados y prima pagada. El analista (ustedes 😎) debe preparar y analizar la base para responder a preguntas clave de gestión actuarial.

La base es simulada, pero incluye problemas comunes: valores faltantes, duplicados, y posibles registros fuera de rango.

  1. Simulación de datos en R
set.seed(123)  # reproducibilidad

library(dplyr)

# Generamos 500 registros
n <- 500
siniestros <- tibble(
  id_poliza   = sample(1000:2000, n, replace = TRUE),   # puede tener duplicados
  edad        = sample(18:90, n, replace = TRUE),
  genero      = sample(c("M", "F", NA), n, replace = TRUE, prob = c(0.45, 0.45, 0.10)),
  valor_aseg  = round(rnorm(n, mean = 100000, sd = 20000), 0),
  siniestros  = rpois(n, lambda = 0.3),
  prima       = round(rnorm(n, mean = 3000, sd = 800), 0)
)

# Introducimos problemas:
siniestros$edad[sample(1:n, 5)] <- c(-5, 150, 200, -10, 500)  # edades fuera de rango
siniestros$valor_aseg[sample(1:n, 5)] <- NA                   # valores faltantes
  1. Actividades para el estudiante
  1. Transformación y diagnóstico de calidad
  • Limpia nombres de variables (janitor::clean_names).

  • Detecta duplicados en id_poliza. ¿Qué harías con ellos?

  • Identifica valores faltantes y su porcentaje.

  • Encuentra edades fuera de rango (<18 o >100).

  • Documenta los pasos en una bitácora (data lineage sencillo).

  1. Etapa Descriptiva
  • Calcula la edad promedio y mediana de los asegurados.

  • Calcula el valor asegurado medio y la prima promedio.

  • Muestra la distribución de siniestros (conteo de cuántos tuvieron 0, 1, 2…).

  1. Etapa Prescriptiva

Define una regla de riesgo actuarial según edad:

  • <30 años → riesgo bajo,

  • 30–60 años → riesgo medio,

  • 60 años → riesgo alto.

  • Cuenta cuántas pólizas hay en cada categoría de riesgo.

  • Recomienda si se debe ajustar la prima a los de riesgo alto.

  1. Etapa Predictiva
  • Ajusta un modelo sencillo (lm) para predecir la prima en función de edad y siniestros.

  • Interpreta si la edad y el número de siniestros impactan en la prima estimada.

  1. Etapa Prospectiva
  • Supón que la aseguradora planea aumentar en 20% el valor asegurado promedio.

  • Usando el modelo predictivo, estima cómo cambiaría la prima promedio en ese escenario.