Propósito

Dominar lo básico de data frames y listas: crear, inspeccionar, indexar, transformar, unir y resumir. 30–40 minutos. Sin humo.


1) Crear un data frame desde vectores (5 min)

set.seed(123)
id         <- paste0("P", 1:12)
edad       <- sample(18:70, 12, replace = TRUE)
riesgo     <- factor(sample(c("Bajo","Medio","Alto"), 12, replace = TRUE),
                     levels = c("Bajo","Medio","Alto"), ordered = TRUE)
siniestros <- sample(c(0,0,0,1,2, NA), 12, replace = TRUE)
prima      <- round(runif(12, 200, 600), 2)

polizas <- data.frame(id, edad, riesgo, siniestros, prima)
polizas
str(polizas)
nrow(polizas); ncol(polizas); names(polizas)

Mini-reto 1.1: ¿Qué clase tiene riesgo? ¿Y siniestros? Verifica con class() y typeof().


2) Selección e indexación (7 min)

polizas$edad                # seleccionar una columna
polizas[ , "prima"]         # por nombre
polizas[1:3, c("id","prima")]   # filas 1:3 y columnas elegidas

# Filtrado lógico (base R)
polizas[polizas$riesgo == "Alto", ]
polizas[polizas$edad >= 40 & polizas$prima > 400, ]

# Ordenar
polizas_orden <- polizas[order(polizas$riesgo, -polizas$edad), ]
head(polizas_orden, 5)

Mini-reto 2.1: Crea polizas_top_edad con las 5 edades más altas (pista: order(..., decreasing = TRUE)).


3) Transformación básica (7 min)

# Nueva columna: prima mensual
polizas$prima_mensual <- round(polizas$prima / 12, 2)

# Tasa de siniestro (con NA -> 0)
polizas$siniestros_clean <- ifelse(is.na(polizas$siniestros), 0, polizas$siniestros)
polizas$tiene_siniestro  <- polizas$siniestros_clean > 0

# Renombrar y reordenar
names(polizas)[names(polizas) == "tiene_siniestro"] <- "tiene_sin"
polizas <- polizas[ , c("id","edad","riesgo","siniestros","siniestros_clean","tiene_sin","prima","prima_mensual")]
head(polizas)

Mini-reto 3.1: Elimina la columna siniestros dejando solo siniestros_clean (pista: asigna NULL).


4) Uniones: cbind/rbind y merge (8 min)

# cbind / rbind (cuando dimensiones coinciden)
nueva_col <- sample(LETTERS[1:3], nrow(polizas), replace = TRUE)
polizas2  <- cbind(polizas, grupo = nueva_col)
nrow(polizas2); ncol(polizas2)

# Creamos pagos por id (varias filas por id)
set.seed(456)
pagos <- data.frame(
  id    = sample(polizas$id, 20, replace = TRUE),
  fecha = as.Date("2025-01-01") + sample(0:200, 20, replace = TRUE),
  valor = round(runif(20, 50, 500), 2)
)
head(pagos, 5)

# Resumen de pagos por id
pagos_sum <- aggregate(valor ~ id, data = pagos, FUN = sum)
head(pagos_sum)

# merge: traemos el total de pagos a polizas2
polizas3 <- merge(polizas2, pagos_sum, by = "id", all.x = TRUE)
polizas3$valor[is.na(polizas3$valor)] <- 0
head(polizas3, 8)

Mini-reto 4.1: ¿Cuántas pólizas no tienen pagos? (pista: sum(polizas3$valor == 0)).


5) Resumen por grupo (base R) (5 min)

# Tasa de siniestro por nivel de riesgo
tasa_por_riesgo <- aggregate(tiene_sin ~ riesgo, data = polizas3, FUN = mean)
tasa_por_riesgo

# Prima promedio por riesgo
prima_prom_riesgo <- aggregate(prima ~ riesgo, data = polizas3, FUN = mean)
prima_prom_riesgo

Opcional (dplyr)
Si usas dplyr, el mismo resumen sería:

# install.packages("dplyr")  # si hace falta
library(dplyr)
polizas3 %>%
  group_by(riesgo) %>%
  summarise(tasa = mean(tiene_sin), prima_prom = mean(prima), .groups = "drop")

6) Listas: objetos heterogéneos (8 min)

# Construimos una lista "cartera" con todo junto
cartera <- list(
  meta    = list(fecha = Sys.Date(), n_polizas = nrow(polizas3)),
  polizas = polizas3,
  pagos   = pagos,
  util    = list(
    calcular_loading = function(prima, loading = 0.15) { round(prima * (1 + loading), 2) }
  )
)

# Acceso
cartera$meta$n_polizas
head(cartera$polizas[, c("id","prima","valor")], 3)
cartera$util$calcular_loading(c(200, 350), loading = 0.1)

# Añadir un elemento nuevo
cartera$resumen_riesgo <- aggregate(tiene_sin ~ riesgo, data = cartera$polizas, mean)
cartera$resumen_riesgo

Mini-reto 6.1: Extrae desde la lista cartera solo los id con valor > 300 en pagos totales (pista: trabaja sobre cartera$polizas).


7) lapply/sapply con listas y data frames (5 min)

# Medias de columnas numéricas del data frame (base R)
num_cols <- sapply(cartera$polizas, is.numeric)
lapply(cartera$polizas[, num_cols], mean)

# Sobre una lista de vectores
mis_vectores <- list(a = 1:5, b = c(10, 20, 30), c = c(2, 2, 8, 4))
sapply(mis_vectores, mean)

Mini-reto final (10 min)

  1. Crea un data frame riesgos_por_grupo con la tasa de siniestro y la prima promedio por combinación de riesgo y grupo.
  2. Guárdalo dentro de cartera como cartera$riesgos_por_grupo.
  3. Escribe una función dentro de cartera$util que, dada una tasa (0–1), retorne "Alta" si > 0.4, "Media" si 0.2–0.4, "Baja" si < 0.2. Aplícala a la tasa por grupo para crear una columna categoria.
# (Posible solución guía)
riesgos_por_grupo <- aggregate(cbind(tiene_sin, prima) ~ riesgo + grupo, data = cartera$polizas, FUN = mean)
names(riesgos_por_grupo)[names(riesgos_por_grupo) == "tiene_sin"] <- "tasa"

cartera$riesgos_por_grupo <- riesgos_por_grupo

cartera$util$categorizar_tasa <- function(tasa){
  ifelse(tasa > 0.4, "Alta",
         ifelse(tasa >= 0.2, "Media", "Baja"))
}

cartera$riesgos_por_grupo$categoria <- cartera$util$categorizar_tasa(cartera$riesgos_por_grupo$tasa)
cartera$riesgos_por_grupo

Chequeo rápido

Próximo: lectura/escritura (readr/readxl), factores y fechas al detalle, y joins modernos con dplyr.