Propósito de la clase

En un proyecto actuarial, comenzar por el modelo suele ser una mala decisión. Antes de estimar, clasificar o predecir, es necesario entender qué decisión se quiere apoyar, qué representan los datos, qué problemas contienen y qué transformaciones son defendibles.

En esta clase se estudiarán CRISP-DM y KDD como metodologías para organizar proyectos de análisis de datos. También se aclarará el uso del término CRISP, que con frecuencia aparece separado de CRISP-DM aunque no corresponde a un tercer ciclo canónico independiente.

Al finalizar, el estudiante estará en capacidad de:

  • distinguir entre problema del negocio, problema analítico y tarea computacional;
  • explicar las fases de CRISP-DM y las etapas de KDD;
  • diagnosticar bases actuariales con problemas de calidad;
  • registrar de forma reproducible los hallazgos y decisiones de preparación;
  • proponer análisis posteriores sin construir todavía modelos predictivos.

1. Diagnóstico y registro de procesos

1.1 ¿Qué significa diagnosticar?

Diagnosticar no es ejecutar summary() y copiar la salida. Un diagnóstico útil responde, como mínimo, cinco preguntas:

  1. ¿Cuál es la decisión actuarial que se debe apoyar?
  2. ¿Cuál es la unidad de análisis? Una póliza, un asegurado, un siniestro, una reclamación, un afiliado o un periodo.
  3. ¿Qué debería significar cada variable?
  4. ¿Qué impide confiar en los datos?
  5. ¿Qué puede corregirse y qué debe consultarse con el responsable del dato?

Un valor extraño no siempre es un error. Un siniestro muy costoso puede ser completamente real y, precisamente por eso, relevante para el actuario. En cambio, una edad de 240 años difícilmente necesita un modelo sofisticado para generar sospechas.

1.2 Controles mínimos

Un diagnóstico inicial debe revisar:

  • dimensiones y estructura de la base;
  • llave o identificador esperado;
  • duplicados;
  • valores faltantes;
  • tipos de datos;
  • categorías inconsistentes;
  • rangos imposibles;
  • coherencia entre fechas;
  • coherencia entre variables relacionadas;
  • valores extremos plausibles;
  • cobertura temporal y poblacional.

1.3 Una bitácora sencilla y reproducible

La bitácora evita que la limpieza se convierta en una colección de cambios imposibles de explicar.

bitacora <- tibble(
  paso = integer(),
  fecha = as.Date(character()),
  objeto = character(),
  hallazgo = character(),
  decision = character(),
  registros_afectados = integer()
)

registrar <- function(bitacora, paso, objeto, hallazgo,
                      decision, registros_afectados) {
  bind_rows(
    bitacora,
    tibble(
      paso = paso,
      fecha = Sys.Date(),
      objeto = objeto,
      hallazgo = hallazgo,
      decision = decision,
      registros_afectados = registros_afectados
    )
  )
}

bitacora <- registrar(
  bitacora,
  paso = 1,
  objeto = "base de afiliados",
  hallazgo = "Identificadores repetidos",
  decision = "Conservar un registro solo después de verificar cuál es vigente",
  registros_afectados = 12
)

bitacora
## # A tibble: 1 × 6
##    paso fecha      objeto            hallazgo       decision registros_afectados
##   <dbl> <date>     <chr>             <chr>          <chr>                  <dbl>
## 1     1 2026-09-11 base de afiliados Identificador… Conserv…                  12

Regla práctica: cada modificación debe dejar evidencia de qué se encontró, qué se hizo, cuántos registros fueron afectados y por qué la decisión es razonable.

1.4 El diagnóstico como proceso y no como comando

El diagnóstico actuarial puede organizarse en cinco momentos. Cada uno responde una pregunta diferente y produce una evidencia concreta.

Momento Pregunta Evidencia que debe quedar
Diagnóstico del problema ¿Qué decisión está en riesgo y qué ocurre si se decide mal? Formulación del problema y consecuencias
Diagnóstico de la fuente ¿De dónde procede cada dato y quién responde por él? Inventario de fuentes, responsables y periodicidad
Diagnóstico estructural ¿La unidad de análisis, las llaves y las relaciones son correctas? Pruebas de unicidad, cardinalidad e integridad
Diagnóstico estadístico ¿Las distribuciones y relaciones son plausibles para el fenómeno? Tablas, gráficos, tasas y medidas robustas
Diagnóstico operativo ¿Qué debe corregirse, consultarse o monitorearse? Bitácora, responsables y plan de acción

Diagnóstico del problema

Suponga que una aseguradora observa un aumento del costo de siniestros. Antes de analizar la base deben distinguirse varias explicaciones posibles:

  • aumentó realmente la frecuencia de los eventos;
  • aumentó el costo promedio de cada evento;
  • cambió la composición del portafolio;
  • disminuyó la exposición, pero se compararon conteos absolutos;
  • ingresaron pagos atrasados de periodos anteriores;
  • existen duplicados o cambios en el sistema de información.

Todas podrían producir un indicador aparentemente mayor, pero cada una exige una decisión distinta. Por eso, el diagnóstico comienza con hipótesis de trabajo, no con una técnica estadística elegida de antemano.

Diagnóstico de la unidad de análisis

Una de las fallas más peligrosas es mezclar unidades. Una tabla de pólizas debería tener una fila por póliza y periodo; una tabla de siniestros, una fila por siniestro; y una tabla de pagos podría tener varias filas por siniestro. Si se unen sin revisar la cardinalidad, la prima puede repetirse tantas veces como pagos tenga el siniestro.

Tabla Unidad esperada Llave candidata Relación habitual
Pólizas Póliza-periodo id_poliza y periodo Una póliza puede tener varios siniestros
Siniestros Siniestro id_siniestro Un siniestro puede tener varios pagos
Pagos Movimiento de pago id_pago Varios pagos pertenecen a un siniestro

Diagnóstico de calidad por dimensiones

No basta con decir que una base está “limpia” o “sucia”. La calidad debe describirse por dimensiones.

Dimensión Qué se verifica Ejemplo actuarial
Completitud Presencia de datos necesarios Pólizas sin exposición
Unicidad Ausencia de duplicados indebidos Dos filas con el mismo siniestro
Validez Cumplimiento de reglas y dominios Exposición negativa
Consistencia Coherencia entre variables y tablas Siniestro anterior al inicio de vigencia
Exactitud Correspondencia con la realidad o fuente oficial Pago distinto al sistema contable
Oportunidad Disponibilidad en el momento requerido Reservas reportadas con retraso

La exactitud no puede probarse mirando una sola base. Requiere contraste con documentos, sistemas fuente o responsables del proceso.

1.5 Perfil automatizado de una base

El siguiente recurso no reemplaza el análisis del actuario. Sirve para obtener una primera fotografía reproducible.

perfil_base <- function(datos) {
  tibble(
    variable = names(datos),
    clase = vapply(datos, function(x) class(x)[1], character(1)),
    faltantes = vapply(datos, function(x) sum(is.na(x)), numeric(1)),
    porcentaje_faltantes = vapply(
      datos,
      function(x) mean(is.na(x)),
      numeric(1)
    ),
    valores_unicos = vapply(
      datos,
      function(x) n_distinct(x, na.rm = TRUE),
      numeric(1)
    )
  )
}

La lectura correcta del perfil exige contexto. Una variable id_siniestro debería presentar alta unicidad; una variable tipo_poliza no. El mismo resultado numérico puede ser correcto o problemático dependiendo del significado de la variable.

2. Aclaración necesaria: CRISP y CRISP-DM

CRISP-DM significa Cross-Industry Standard Process for Data Mining. En muchos materiales se usa informalmente la palabra CRISP como abreviación, pero las fuentes metodológicas no presentan dos ciclos independientes llamados CRISP y CRISP-DM.

Por eso, en esta clase se trabajará de la siguiente manera:

  • Enfoque CRISP: la idea general de organizar un proyecto desde el problema del negocio, con entregables, responsables e iteraciones.
  • CRISP-DM: el proceso completo y formal de seis fases para desarrollar el proyecto.

Esta distinción es didáctica. No se afirmará que existe una metodología canónica adicional con fases distintas.

2.1 ¿Por qué surgió CRISP-DM?

Los proyectos de análisis solían organizarse alrededor del algoritmo: se recibía una base, se aplicaba una técnica y después se intentaba encontrar una utilidad para el resultado. El proyecto CRISP buscó establecer un proceso común, independiente de una industria y de una herramienta específica.

La palabra cross-industry es central. La metodología debía servir en seguros, banca, salud, mercadeo o manufactura. Lo que cambia es el conocimiento especializado del dominio; la lógica del proceso permanece.

2.2 Los cuatro niveles de abstracción

CRISP-DM no es únicamente un dibujo con seis círculos. Su modelo de referencia puede entenderse en cuatro niveles:

Nivel Qué representa Ejemplo actuarial
Fase Gran momento del proyecto Comprensión de los datos
Tarea genérica Acción aplicable a distintos sectores Verificar la calidad de los datos
Tarea especializada Forma concreta de ejecutar la tarea en un dominio Validar que la exposición se encuentre entre 0 y 1
Instancia del proceso Ejecución real, con responsables y resultados Informe de calidad del portafolio de automóviles de 2025

Esta jerarquía explica por qué dos proyectos pueden seguir CRISP-DM sin utilizar las mismas variables, reglas o técnicas.

2.3 Principios que atraviesan todo el proceso

Orientación a decisiones

El análisis se justifica por la decisión que mejora, no por la complejidad del código. “Construir un modelo” no es un objetivo del negocio.

Independencia tecnológica

R es la herramienta de esta clase, pero las fases no dependen de R. Cambiar de software no elimina la necesidad de comprender el negocio, documentar la preparación o evaluar el resultado.

Iteración

Las fases no forman una escalera rígida. Un problema de calidad puede obligar a redefinir el alcance; un resultado poco útil puede exigir nuevas variables; y el despliegue puede revelar cambios que requieren reiniciar el ciclo.

Trazabilidad

Cada decisión debe conectarse con una necesidad del negocio, una evidencia en los datos y un producto verificable.

3. Enfoque CRISP: organizar antes de analizar

3.1 La lógica fundamental

Antes de tocar los datos se deben definir cuatro elementos:

Elemento Pregunta central Producto esperado
Decisión ¿Qué decisión se debe apoyar? Decisión claramente formulada
Alcance ¿Qué población, periodo y proceso se estudiarán? Límites del proyecto
Criterio de éxito ¿Cómo sabremos que el análisis fue útil? Indicadores verificables
Riesgos y restricciones ¿Qué información, tiempo, reglas o recursos limitan el trabajo? Registro de supuestos y riesgos

Antes del análisis conviene construir una ficha de proyecto. Esta ficha es la traducción práctica de la lógica CRISP.

Componente Contenido mínimo
Situación Qué está ocurriendo y cómo se detectó
Decisión Qué persona o comité utilizará el resultado
Objetivo del negocio Qué resultado organizacional se espera mejorar
Objetivo analítico Qué debe medirse, describirse o descubrirse en los datos
Unidad de análisis Qué representa exactamente cada fila
Población y periodo A quiénes y a qué fechas se refiere el estudio
Criterio de éxito Cómo se evaluará la utilidad del análisis
Restricciones Tiempo, información, regulación, privacidad y recursos
Riesgos Posibles usos incorrectos, sesgos y fallas de implementación
Entregables Productos concretos que recibirá el usuario

3.2 Caso guiado: suficiencia de aportes pensionales

Situación actuarial

Un fondo ficticio desea revisar la consistencia de los aportes registrados durante un año. El propósito no es acusar a un afiliado ni estimar una pensión. Se busca identificar registros que deben ser auditados antes de calcular indicadores de suficiencia.

Para el ejercicio se define una tasa técnica de aporte del 16 %. Es un parámetro del caso académico, no una recomendación normativa.

Decisión que se debe apoyar

¿La base tiene calidad suficiente para calcular aportes esperados y en qué grupos deben priorizarse las verificaciones?

Paso 1. Traducir la solicitud en un problema analítico

La frase “revisar aportes” todavía es ambigua. Debe transformarse en una formulación verificable.

Elemento Definición para el caso
Usuario del resultado Comité de calidad de información y equipo actuarial
Decisión Priorizar registros y fuentes que requieren verificación
Objetivo del negocio Reducir el riesgo de calcular indicadores con aportes inconsistentes
Objetivo analítico Comparar el aporte registrado con un valor técnico esperado y cuantificar problemas de calidad
Unidad de análisis Afiliado durante el periodo anual
Criterio de éxito Identificar y documentar todas las reglas que impiden usar un registro
Resultado esperado Base preparada, indicadores de calidad y listado de casos para revisión

Paso 2. Formular hipótesis de diagnóstico

Antes de ver los resultados se plantean explicaciones que puedan contrastarse:

  1. las diferencias se deben a errores de formato en el ingreso;
  2. existen registros duplicados por fallas de consolidación;
  3. algunos aportes corresponden a periodos incompletos;
  4. hay diferencias materiales que requieren información adicional;
  5. una sola fila anual no representa adecuadamente los cambios mensuales.

Estas hipótesis no son conclusiones. Sirven para orientar los controles y evitar una exploración sin propósito.

Paso 3. Definir reglas antes de observar los casos

Variable Regla operativa del ejercicio Acción ante incumplimiento
id_afiliado No faltante y único para la base anual Revisar duplicados antes de conservar una fila
edad Entre 18 y 100 años Excluir del cálculo y consultar la fuente
meses_cotizados Entero entre 0 y 12 Excluir del cálculo
ingreso_base Convertible a número positivo Solicitar corrección o dato fuente
aporte_reportado No faltante y no negativo Excluir del indicador de suficiencia
Diferencia relativa Máximo 5 % en valor absoluto Marcar para investigación, no corregir automáticamente

Construcción de una base con problemas

set.seed(410)

n_afiliados <- 900
ingreso_numerico <- sample(
  seq(1300000, 12000000, by = 10000),
  n_afiliados,
  replace = TRUE
)
meses_vector <- sample(0:12, n_afiliados, replace = TRUE)
aporte_base <- ingreso_numerico * 0.16 * meses_vector
aporte_observado <- round(
  aporte_base * runif(n_afiliados, 0.98, 1.02),
  -2
)

# Algunos casos presentan diferencias materiales que deberán investigarse.
indices_diferencia <- sample(which(aporte_base > 0), 45)
aporte_observado[indices_diferencia] <- round(
  aporte_base[indices_diferencia] * runif(45, 0.45, 0.80),
  -2
)

base_pensiones_sucia <- tibble(
  id_afiliado = sprintf("AF-%05d", seq_len(n_afiliados)),
  edad = sample(18:75, n_afiliados, replace = TRUE),
  sexo = sample(
    c("F", "M", "femenino", "Masculino", "SIN DATO"),
    n_afiliados,
    replace = TRUE,
    prob = c(0.36, 0.36, 0.10, 0.10, 0.08)
  ),
  ingreso_base = paste0(
    "$",
    format(ingreso_numerico, big.mark = ",", scientific = FALSE, trim = TRUE)
  ),
  meses_cotizados = meses_vector,
  aporte_reportado = aporte_observado,
  tipo_afiliado = sample(
    c("Dependiente", "Independiente", "dependiente", "INDEPENDIENTE"),
    n_afiliados,
    replace = TRUE
  )
)

# Errores deliberados
base_pensiones_sucia$edad[c(18, 208)] <- c(7, 240)
base_pensiones_sucia$meses_cotizados[c(41, 309)] <- c(15, -2)
base_pensiones_sucia$aporte_reportado[c(77, 550)] <- c(-450000, NA)
base_pensiones_sucia$ingreso_base[c(33, 601)] <- c("sin reporte", NA)

base_pensiones_sucia <- bind_rows(
  base_pensiones_sucia,
  slice(base_pensiones_sucia, 25, 26, 27)
)

glimpse(base_pensiones_sucia)
## Rows: 903
## Columns: 7
## $ id_afiliado      <chr> "AF-00001", "AF-00002", "AF-00003", "AF-00004", "AF-0…
## $ edad             <dbl> 40, 57, 51, 62, 73, 50, 60, 69, 22, 58, 51, 70, 64, 2…
## $ sexo             <chr> "F", "M", "F", "F", "M", "M", "F", "femenino", "femen…
## $ ingreso_base     <chr> "$6,330,000", "$5,090,000", "$10,840,000", "$3,020,00…
## $ meses_cotizados  <dbl> 6, 12, 6, 11, 10, 10, 10, 7, 3, 6, 2, 7, 11, 3, 7, 11…
## $ aporte_reportado <dbl> 6194800, 9896100, 10278800, 5236200, 17616500, 160850…
## $ tipo_afiliado    <chr> "Independiente", "INDEPENDIENTE", "INDEPENDIENTE", "D…

Paso 4. Reconocer la estructura antes de limpiar

perfil_base(base_pensiones_sucia) |>
  kable(digits = 3)
variable clase faltantes porcentaje_faltantes valores_unicos
id_afiliado character 0 0.000 900
edad numeric 0 0.000 60
sexo character 0 0.000 5
ingreso_base character 1 0.001 614
meses_cotizados numeric 0 0.000 15
aporte_reportado numeric 1 0.001 824
tipo_afiliado character 0 0.000 4
base_pensiones_sucia |>
  count(id_afiliado, name = "numero_registros") |>
  filter(numero_registros > 1) |>
  kable(caption = "Identificadores con más de un registro")
Identificadores con más de un registro
id_afiliado numero_registros
AF-00025 2
AF-00026 2
AF-00027 2

El resultado confirma que la base contiene más filas que afiliados. Sin una fecha de actualización o un número de versión no es posible saber cuál registro es el correcto. Para continuar el ejemplo se conservará la primera fila, pero la bitácora dejará constancia de que esta es una decisión provisional.

Diagnóstico inicial

diagnostico_pensiones <- tibble(
  control = c(
    "Filas",
    "Afiliados únicos",
    "Identificadores duplicados",
    "Edades fuera de 18 a 100",
    "Meses fuera de 0 a 12",
    "Aportes negativos",
    "Ingresos no interpretables"
  ),
  resultado = c(
    nrow(base_pensiones_sucia),
    n_distinct(base_pensiones_sucia$id_afiliado),
    sum(duplicated(base_pensiones_sucia$id_afiliado)),
    sum(!between(base_pensiones_sucia$edad, 18, 100), na.rm = TRUE),
    sum(!between(base_pensiones_sucia$meses_cotizados, 0, 12), na.rm = TRUE),
    sum(base_pensiones_sucia$aporte_reportado < 0, na.rm = TRUE),
    sum(is.na(parse_number(
      base_pensiones_sucia$ingreso_base,
      locale = locale(grouping_mark = ",")
    )))
  )
)

kable(diagnostico_pensiones)
control resultado
Filas 903
Afiliados únicos 900
Identificadores duplicados 3
Edades fuera de 18 a 100 2
Meses fuera de 0 a 12 2
Aportes negativos 1
Ingresos no interpretables 2

Paso 5. Medir la calidad antes de modificar

reglas_pensiones <- base_pensiones_sucia |>
  mutate(
    ingreso_num_temporal = parse_number(
      ingreso_base,
      locale = locale(grouping_mark = ",")
    ),
    falla_edad = is.na(edad) | !between(edad, 18, 100),
    falla_meses = is.na(meses_cotizados) |
      !between(meses_cotizados, 0, 12),
    falla_ingreso = is.na(ingreso_num_temporal) |
      ingreso_num_temporal <= 0,
    falla_aporte = is.na(aporte_reportado) |
      aporte_reportado < 0,
    duplicado_id = duplicated(id_afiliado) |
      duplicated(id_afiliado, fromLast = TRUE)
  )

informe_calidad_pensiones <- tibble(
  regla = c(
    "Edad inválida",
    "Meses inválidos",
    "Ingreso inválido",
    "Aporte inválido",
    "Identificador repetido"
  ),
  registros = c(
    sum(reglas_pensiones$falla_edad),
    sum(reglas_pensiones$falla_meses),
    sum(reglas_pensiones$falla_ingreso),
    sum(reglas_pensiones$falla_aporte),
    sum(reglas_pensiones$duplicado_id)
  )
) |>
  mutate(
    porcentaje = registros / nrow(base_pensiones_sucia)
  )

kable(informe_calidad_pensiones, digits = 3)
regla registros porcentaje
Edad inválida 2 0.002
Meses inválidos 2 0.002
Ingreso inválido 2 0.002
Aporte inválido 2 0.002
Identificador repetido 6 0.007

Este informe se calcula antes de eliminar observaciones. De lo contrario, la organización recibiría una base aparentemente perfecta, pero perdería evidencia sobre la magnitud y el origen de sus problemas.

Estandarización y reglas de validez

pensiones_preparada <- base_pensiones_sucia |>
  distinct(id_afiliado, .keep_all = TRUE) |>
  mutate(
    sexo_limpio = case_when(
      str_to_lower(sexo) %in% c("f", "femenino") ~ "Femenino",
      str_to_lower(sexo) %in% c("m", "masculino") ~ "Masculino",
      TRUE ~ "Sin dato"
    ),
    tipo_afiliado_limpio = case_when(
      str_to_lower(tipo_afiliado) == "dependiente" ~ "Dependiente",
      str_to_lower(tipo_afiliado) == "independiente" ~ "Independiente",
      TRUE ~ NA_character_
    ),
    ingreso_base_num = parse_number(
      ingreso_base,
      locale = locale(grouping_mark = ",")
    ),
    edad_valida = between(edad, 18, 100),
    meses_validos = between(meses_cotizados, 0, 12),
    aporte_valido = !is.na(aporte_reportado) & aporte_reportado >= 0,
    ingreso_valido = !is.na(ingreso_base_num) & ingreso_base_num > 0
  )

pensiones_auditoria <- pensiones_preparada |>
  filter(edad_valida, meses_validos, aporte_valido, ingreso_valido) |>
  mutate(
    aporte_esperado = ingreso_base_num * 0.16 * meses_cotizados,
    diferencia_aporte = aporte_reportado - aporte_esperado,
    diferencia_relativa = if_else(
      aporte_esperado > 0,
      diferencia_aporte / aporte_esperado,
      NA_real_
    ),
    requiere_revision = abs(diferencia_relativa) > 0.05
  )

pensiones_auditoria |>
  summarise(
    registros_validos = n(),
    casos_para_revision = sum(requiere_revision, na.rm = TRUE),
    proporcion_revision = mean(requiere_revision, na.rm = TRUE)
  ) |>
  kable(digits = 3)
registros_validos casos_para_revision proporcion_revision
892 45 0.055

Paso 6. Analizar la materialidad de las diferencias

Una diferencia pequeña puede ser producto del redondeo. Una diferencia grande requiere investigación. Además de contar casos, interesa cuantificar su impacto monetario.

resumen_diferencias <- pensiones_auditoria |>
  mutate(
    clasificacion = case_when(
      is.na(diferencia_relativa) ~ "No evaluable",
      abs(diferencia_relativa) <= 0.05 ~ "Dentro de tolerancia",
      diferencia_relativa < -0.05 ~ "Aporte inferior al esperado",
      diferencia_relativa > 0.05 ~ "Aporte superior al esperado"
    )
  ) |>
  group_by(tipo_afiliado_limpio, clasificacion) |>
  summarise(
    afiliados = n(),
    aporte_reportado = sum(aporte_reportado),
    diferencia_total = sum(diferencia_aporte),
    .groups = "drop"
  )

kable(resumen_diferencias, digits = 0)
tipo_afiliado_limpio clasificacion afiliados aporte_reportado diferencia_total
Dependiente Aporte inferior al esperado 18 40863300 -30911100
Dependiente Dentro de tolerancia 407 2837351200 453600
Dependiente No evaluable 33 0 0
Independiente Aporte inferior al esperado 27 130138400 -67388000
Independiente Dentro de tolerancia 368 2619119100 341500
Independiente No evaluable 39 0 0
pensiones_auditoria |>
  filter(!is.na(diferencia_relativa)) |>
  ggplot(aes(x = diferencia_relativa)) +
  geom_histogram(bins = 35, fill = "#00A6A6", color = "white") +
  geom_vline(xintercept = c(-0.05, 0.05),
             linetype = "dashed", color = "#F28E2B") +
  scale_x_continuous(labels = percent_format(accuracy = 1)) +
  labs(
    title = "Distribución de la diferencia relativa del aporte",
    x = "Diferencia frente al valor técnico esperado",
    y = "Número de afiliados"
  )

Paso 7. Cerrar el diagnóstico con límites claros

Con esta base se puede identificar inconsistencia, pero no explicar definitivamente su causa. Faltan variables como mes, novedad laboral, días cotizados, correcciones retroactivas y fecha de actualización. La conclusión profesional no es “el aporte está mal”, sino “el registro requiere verificación con estas evidencias”.

Interpretación metodológica

El análisis no termina diciendo que los casos marcados son incorrectos. La diferencia puede deberse a novedades laborales, periodos parciales, ajustes retroactivos o variables no incluidas. El resultado correcto es una lista priorizada para verificación y una solicitud concreta de información adicional.

Registro de una decisión

bitacora_pensiones <- tibble(
  paso = 1:4,
  hallazgo = c(
    "Existen identificadores repetidos",
    "Hay edades y meses fuera del rango operativo",
    "El ingreso fue recibido como texto",
    "Algunos aportes difieren del valor técnico esperado"
  ),
  decision = c(
    "Conservar una fila por afiliado para el ejercicio; solicitar criterio de vigencia",
    "Excluir del cálculo y remitir a validación con la fuente",
    "Convertir con separador de miles explícito",
    "Marcar diferencias superiores al 5 %, sin corregirlas automáticamente"
  )
)

kable(bitacora_pensiones)
paso hallazgo decision
1 Existen identificadores repetidos Conservar una fila por afiliado para el ejercicio; solicitar criterio de vigencia
2 Hay edades y meses fuera del rango operativo Excluir del cálculo y remitir a validación con la fuente
3 El ingreso fue recibido como texto Convertir con separador de miles explícito
4 Algunos aportes difieren del valor técnico esperado Marcar diferencias superiores al 5 %, sin corregirlas automáticamente

Lectura profesional del caso

La llave id_afiliado solo sería suficiente si existiera una única observación por afiliado en todo el periodo. Si los aportes cambian mensualmente, la granularidad correcta probablemente sería afiliado-mes. Tampoco es defendible resolver duplicados sin fecha de actualización, origen o estado del registro. El caso muestra que la estructura disponible puede ser insuficiente incluso cuando el código se ejecuta sin errores.

4. Metodología CRISP-DM

4.1 Idea central

CRISP-DM organiza un proyecto de minería de datos en seis fases. Las flechas entre ellas indican que el proceso es iterativo: un hallazgo en los datos puede obligar a redefinir el objetivo del negocio y una evaluación insatisfactoria puede exigir una nueva preparación.

4.2 Las seis fases

1. Comprensión del negocio

Se define el problema real, la decisión que debe apoyarse, los actores, las restricciones, los riesgos, los recursos y los criterios de éxito.

Entregables: objetivo del negocio, objetivo analítico, plan del proyecto y criterios de éxito.

2. Comprensión de los datos

Se recopilan los datos iniciales, se describen, se exploran y se verifica su calidad. Aquí se identifican problemas capaces de invalidar el proyecto.

Entregables: inventario de fuentes, diccionario, exploración inicial e informe de calidad.

3. Preparación de los datos

Se seleccionan registros y variables, se limpian errores, se integran fuentes, se crean variables y se deja una tabla analítica reproducible.

Entregables: base analítica, reglas de transformación y bitácora.

4. Modelamiento

Se seleccionan técnicas compatibles con el objetivo y la estructura de los datos. También se diseña la evaluación y se revisan los supuestos.

En esta clase: solo se formulará el plan de modelamiento. Los modelos se estudiarán posteriormente.

5. Evaluación

Se verifica tanto el desempeño técnico como la utilidad actuarial. Un resultado estadísticamente atractivo puede ser inútil si no responde la pregunta, viola restricciones o genera decisiones no defendibles.

6. Despliegue

Se decide cómo usar, comunicar, monitorear y actualizar el resultado. El despliegue puede ser un tablero, un informe periódico, una regla de revisión o un proceso automatizado; no necesariamente una aplicación compleja.

4.3 Estructura completa de tareas y productos

Fase 1. Comprensión del negocio

Tarea Desarrollo Producto
Determinar objetivos del negocio Identificar necesidad, usuario, decisión e impacto esperado Objetivos y criterios de éxito del negocio
Evaluar la situación Revisar recursos, restricciones, riesgos, supuestos, costos y beneficios Inventario de recursos y evaluación de riesgos
Determinar objetivos analíticos Traducir la necesidad a resultados que puedan obtenerse de los datos Objetivos analíticos y criterios técnicos de éxito
Elaborar el plan Organizar fases, tiempos, responsables, dependencias y entregables Plan del proyecto

Un objetivo del negocio podría ser reducir el deterioro técnico del portafolio. El objetivo analítico asociado podría ser determinar si el cambio proviene de frecuencia, severidad o composición de la exposición. No son la misma frase con sinónimos.

Fase 2. Comprensión de los datos

Tarea Desarrollo Producto
Recopilar datos iniciales Identificar fuentes, permisos, periodos y responsables Informe de recolección
Describir los datos Revisar formato, volumen, campos, llaves y granularidad Diccionario e inventario
Explorar los datos Estudiar distribuciones, relaciones y segmentos Informe exploratorio
Verificar calidad Medir completitud, validez, unicidad y consistencia Informe de calidad

Esta fase no debe anticipar la limpieza. Primero se conserva evidencia de cómo llegaron los datos; después se decide qué transformar.

Fase 3. Preparación de los datos

Tarea Pregunta de control Ejemplo actuarial
Seleccionar ¿Qué registros y variables responden al objetivo? Conservar pólizas con vigencia en el periodo
Limpiar ¿Qué errores se corrigen, excluyen o consultan? Separar pagos negativos para revisión
Construir ¿Qué variables deben derivarse? Frecuencia, severidad y siniestralidad
Integrar ¿Cómo se relacionan fuentes con diferente granularidad? Agregar siniestros antes de unir con pólizas
Formatear ¿Qué estructura necesita el análisis posterior? Factores, fechas, unidades y tabla analítica

La preparación suele consumir gran parte del trabajo porque obliga a convertir conocimiento del dominio en reglas reproducibles.

Fase 4. Modelamiento

Tarea Pregunta de control
Seleccionar técnica ¿La variable, la exposición y el objetivo son compatibles con la técnica?
Diseñar la prueba ¿Cómo se separarán periodos o muestras sin contaminar la evaluación?
Construir ¿Qué especificación, parámetros y variables se utilizarán?
Evaluar técnicamente ¿Se cumplen supuestos y el comportamiento es estable?

En actuaría, la exposición, la censura, la inflación, el desarrollo temporal y la asimetría de los costos no son detalles decorativos. Condicionan la técnica que puede utilizarse.

Fase 5. Evaluación

Tarea Pregunta de control
Evaluar resultados ¿El resultado cumple el objetivo analítico y el objetivo del negocio?
Revisar el proceso ¿Se omitió una fuente, una regla o un riesgo importante?
Determinar pasos siguientes ¿Se despliega, se itera o se detiene el proyecto?

La evaluación técnica pregunta si el resultado funciona; la evaluación del negocio pregunta si sirve y si puede utilizarse responsablemente.

Fase 6. Despliegue

Tarea Producto
Planear el despliegue Usuarios, canal, frecuencia y procedimiento de uso
Planear monitoreo y mantenimiento Indicadores, umbrales, responsables y periodicidad
Elaborar informe final Hallazgos, límites, decisiones y documentación técnica
Revisar el proyecto Lecciones aprendidas y mejoras del proceso

Un archivo guardado en una carpeta no es despliegue. El resultado debe incorporarse a una decisión, tener un responsable y contar con reglas de seguimiento.

4.4 Caso guiado CRISP-DM: aumento de la siniestralidad en automóviles

Comprensión del negocio

Una aseguradora ficticia observa que la razón entre siniestros pagados y prima devengada parece haber aumentado. La gerencia solicita “un modelo”, pero todavía no existe evidencia de que el problema sea predictivo.

Decisión: identificar si el deterioro se concentra en segmentos específicos o si puede explicarse por problemas de registro.

Unidad de análisis: póliza anual.

Criterios de éxito:

  • calcular indicadores con una base reconciliada;
  • identificar segmentos con exposición suficiente;
  • separar errores operativos de señales de riesgo;
  • proponer acciones verificables.

Fase 1. Comprensión del negocio aplicada

Del síntoma a preguntas analizables

El síntoma “subió la siniestralidad” debe descomponerse:

  1. ¿aumentó el número de siniestros por unidad de exposición?
  2. ¿aumentó el costo promedio por siniestro?
  3. ¿disminuyó la prima devengada?
  4. ¿cambió el peso de regiones o usos de mayor riesgo?
  5. ¿los siniestros pertenecen realmente a las vigencias analizadas?

Riesgos del proyecto

Riesgo Consecuencia Control previsto
Duplicar primas al unir tablas Subestimar o sobreestimar indicadores Verificar cardinalidad y agregar siniestros antes de unir
Excluir grandes pérdidas por parecer atípicas Ocultar el riesgo de cola Separar plausibilidad de extremidad
Comparar segmentos con poca exposición Conclusiones inestables Mostrar volumen y fijar mínimo de exposición
Usar siniestros fuera de vigencia Asignar costo a una póliza incorrecta Validar fechas después de integrar llaves
Convertir una asociación en causalidad Recomendar tarifas injustificadas Presentar resultados como diagnóstico, no como efecto causal

Criterios analíticos de éxito

  • reconciliar el número de registros originales, válidos y excluidos;
  • calcular frecuencia, severidad y siniestralidad con denominadores correctos;
  • informar el volumen detrás de cada indicador;
  • documentar todas las exclusiones;
  • producir hipótesis que puedan verificarse con información adicional.

Datos iniciales

Se reciben dos fuentes: pólizas y siniestros.

Fuente Granularidad Llave esperada Variables principales
Pólizas Una fila por póliza y vigencia id_poliza Fechas, exposición, prima, región y uso
Siniestros Una fila por siniestro id_siniestro Póliza asociada, fecha, causa y pago

Antes de unirlas se debe comprobar si las llaves cumplen la granularidad declarada.

set.seed(411)

n_polizas <- 1200
inicio_poliza <- as.Date("2025-01-01") + sample(0:180, n_polizas, replace = TRUE)
exposicion_real <- runif(n_polizas, 0.25, 1)

polizas_auto_sucias <- tibble(
  id_poliza = sprintf("POL-%05d", seq_len(n_polizas)),
  fecha_inicio = inicio_poliza,
  fecha_fin = inicio_poliza + round(365 * exposicion_real),
  exposicion = round(exposicion_real, 3),
  region = sample(
    c("Bogotá", "BOGOTA", "Medellín", "medellin", "Cali", "Costa"),
    n_polizas,
    replace = TRUE
  ),
  uso = sample(c("Particular", "Comercial"), n_polizas, replace = TRUE,
               prob = c(0.82, 0.18)),
  antiguedad_vehiculo = sample(0:25, n_polizas, replace = TRUE),
  prima_devengada = round(runif(n_polizas, 600000, 4500000), -2)
)

polizas_auto_sucias$exposicion[c(15, 203)] <- c(1.7, -0.2)
polizas_auto_sucias$antiguedad_vehiculo[c(99, 701)] <- c(80, NA)
polizas_auto_sucias$prima_devengada[455] <- -900000
polizas_auto_sucias <- bind_rows(
  polizas_auto_sucias,
  slice(polizas_auto_sucias, 100, 101)
)

n_siniestros <- 620
ids_poliza_siniestro <- sample(
  polizas_auto_sucias$id_poliza[1:n_polizas],
  n_siniestros,
  replace = TRUE
)

siniestros_auto_sucios <- tibble(
  id_siniestro = sprintf("SIN-%05d", seq_len(n_siniestros)),
  id_poliza = ids_poliza_siniestro,
  fecha_siniestro = as.Date("2025-01-01") + sample(0:545, n_siniestros, replace = TRUE),
  causa = sample(
    c("Choque", "choque", "Hurto", "Daños", "DANOS", NA),
    n_siniestros,
    replace = TRUE
  ),
  pago = round(rlnorm(n_siniestros, log(3500000), 1), -2)
)

siniestros_auto_sucios$pago[c(21, 390)] <- c(-2500000, NA)
siniestros_auto_sucios <- bind_rows(
  siniestros_auto_sucios,
  slice(siniestros_auto_sucios, 77, 78, 79)
)

list(
  polizas = dim(polizas_auto_sucias),
  siniestros = dim(siniestros_auto_sucios)
)
## $polizas
## [1] 1202    8
## 
## $siniestros
## [1] 623   5
perfil_base(polizas_auto_sucias) |>
  kable(digits = 3, caption = "Perfil de la fuente de pólizas")
Perfil de la fuente de pólizas
variable clase faltantes porcentaje_faltantes valores_unicos
id_poliza character 0 0.000 1200
fecha_inicio Date 0 0.000 181
fecha_fin Date 0 0.000 366
exposicion numeric 0 0.000 605
region character 0 0.000 6
uso character 0 0.000 2
antiguedad_vehiculo numeric 1 0.001 27
prima_devengada numeric 0 0.000 1178
perfil_base(siniestros_auto_sucios) |>
  kable(digits = 3, caption = "Perfil de la fuente de siniestros")
Perfil de la fuente de siniestros
variable clase faltantes porcentaje_faltantes valores_unicos
id_siniestro character 0 0.000 620
id_poliza character 0 0.000 479
fecha_siniestro Date 0 0.000 367
causa character 91 0.146 5
pago numeric 1 0.002 616

Comprensión de los datos

Primero se revisan llaves, rangos y categorías. No conviene unir las tablas antes de saber si las llaves son confiables.

control_polizas <- tibble(
  control = c(
    "Duplicados de id_poliza",
    "Exposición fuera de (0, 1]",
    "Prima no positiva",
    "Antigüedad faltante",
    "Antigüedad mayor de 60"
  ),
  casos = c(
    sum(duplicated(polizas_auto_sucias$id_poliza)),
    sum(polizas_auto_sucias$exposicion <= 0 |
          polizas_auto_sucias$exposicion > 1, na.rm = TRUE),
    sum(polizas_auto_sucias$prima_devengada <= 0, na.rm = TRUE),
    sum(is.na(polizas_auto_sucias$antiguedad_vehiculo)),
    sum(polizas_auto_sucias$antiguedad_vehiculo > 60, na.rm = TRUE)
  )
)

control_siniestros <- tibble(
  control = c(
    "Duplicados de id_siniestro",
    "Pago negativo",
    "Pago faltante",
    "Causa faltante"
  ),
  casos = c(
    sum(duplicated(siniestros_auto_sucios$id_siniestro)),
    sum(siniestros_auto_sucios$pago < 0, na.rm = TRUE),
    sum(is.na(siniestros_auto_sucios$pago)),
    sum(is.na(siniestros_auto_sucios$causa))
  )
)

kable(control_polizas, caption = "Controles sobre pólizas")
Controles sobre pólizas
control casos
Duplicados de id_poliza 2
Exposición fuera de (0, 1] 2
Prima no positiva 1
Antigüedad faltante 1
Antigüedad mayor de 60 1
kable(control_siniestros, caption = "Controles sobre siniestros")
Controles sobre siniestros
control casos
Duplicados de id_siniestro 3
Pago negativo 1
Pago faltante 1
Causa faltante 91

Integridad referencial y fechas

siniestros_sin_poliza <- anti_join(
  siniestros_auto_sucios,
  polizas_auto_sucias |>
    distinct(id_poliza),
  by = "id_poliza"
)

tibble(
  control = c(
    "Siniestros sin póliza asociada",
    "Pólizas sin ningún siniestro registrado"
  ),
  casos = c(
    nrow(siniestros_sin_poliza),
    nrow(anti_join(
      polizas_auto_sucias |>
        distinct(id_poliza),
      siniestros_auto_sucios |>
        distinct(id_poliza),
      by = "id_poliza"
    ))
  )
) |>
  kable()
control casos
Siniestros sin póliza asociada 0
Pólizas sin ningún siniestro registrado 721

Que una póliza no tenga siniestros es normal; que un siniestro no encuentre su póliza puede ser un problema de integridad, un desfase temporal o una fuente incompleta.

Preparación de los datos

polizas_auto <- polizas_auto_sucias |>
  distinct(id_poliza, .keep_all = TRUE) |>
  mutate(
    region = case_when(
      str_detect(str_to_lower(region), "bog") ~ "Bogotá",
      str_detect(str_to_lower(region), "med") ~ "Medellín",
      str_to_lower(region) == "cali" ~ "Cali",
      str_to_lower(region) == "costa" ~ "Costa",
      TRUE ~ "Sin clasificar"
    ),
    poliza_valida = exposicion > 0 & exposicion <= 1 &
      prima_devengada > 0 &
      !is.na(antiguedad_vehiculo) &
      antiguedad_vehiculo <= 60 &
      fecha_fin >= fecha_inicio
  )

siniestros_auto <- siniestros_auto_sucios |>
  distinct(id_siniestro, .keep_all = TRUE) |>
  mutate(
    causa = case_when(
      str_to_lower(causa) == "choque" ~ "Choque",
      str_to_lower(causa) == "hurto" ~ "Hurto",
      str_to_lower(causa) %in% c("daños", "danos") ~ "Daños",
      TRUE ~ "Sin clasificar"
    )
  ) |>
  filter(!is.na(pago), pago >= 0)

siniestros_validados <- siniestros_auto |>
  inner_join(
    polizas_auto |>
      select(id_poliza, fecha_inicio, fecha_fin, poliza_valida),
    by = "id_poliza"
  ) |>
  mutate(
    fecha_coherente = fecha_siniestro >= fecha_inicio &
      fecha_siniestro <= fecha_fin
  )

tabla_analitica_auto <- polizas_auto |>
  filter(poliza_valida) |>
  left_join(
    siniestros_validados |>
      filter(fecha_coherente) |>
      group_by(id_poliza) |>
      summarise(
        numero_siniestros = n(),
        costo_siniestros = sum(pago),
        .groups = "drop"
      ),
    by = "id_poliza"
  ) |>
  mutate(
    numero_siniestros = replace_na(numero_siniestros, 0L),
    costo_siniestros = replace_na(costo_siniestros, 0),
    frecuencia = numero_siniestros / exposicion,
    costo_por_exposicion = costo_siniestros / exposicion,
    siniestralidad = costo_siniestros / prima_devengada
  )

glimpse(tabla_analitica_auto)
## Rows: 1,195
## Columns: 14
## $ id_poliza            <chr> "POL-00001", "POL-00002", "POL-00003", "POL-00004…
## $ fecha_inicio         <date> 2025-04-12, 2025-05-20, 2025-05-16, 2025-03-20, …
## $ fecha_fin            <date> 2025-08-24, 2025-08-30, 2026-04-28, 2025-07-26, …
## $ exposicion           <dbl> 0.366, 0.280, 0.951, 0.350, 0.826, 0.607, 0.599, …
## $ region               <chr> "Bogotá", "Costa", "Medellín", "Bogotá", "Bogotá"…
## $ uso                  <chr> "Particular", "Comercial", "Comercial", "Particul…
## $ antiguedad_vehiculo  <dbl> 14, 2, 22, 18, 0, 5, 0, 23, 13, 16, 7, 16, 19, 15…
## $ prima_devengada      <dbl> 1838800, 3535600, 1803600, 1872900, 2329500, 2936…
## $ poliza_valida        <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, T…
## $ numero_siniestros    <int> 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0…
## $ costo_siniestros     <dbl> 0, 0, 3200400, 0, 0, 0, 0, 0, 0, 0, 0, 9758100, 0…
## $ frecuencia           <dbl> 0.000000, 0.000000, 1.051525, 0.000000, 0.000000,…
## $ costo_por_exposicion <dbl> 0.0, 0.0, 3365299.7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0…
## $ siniestralidad       <dbl> 0.0000000, 0.0000000, 1.7744511, 0.0000000, 0.000…

Reconciliación de registros

reconciliacion_auto <- tibble(
  objeto = c(
    "Pólizas recibidas",
    "Pólizas únicas",
    "Pólizas válidas para análisis",
    "Siniestros recibidos",
    "Siniestros únicos con pago válido",
    "Siniestros dentro de la vigencia"
  ),
  registros = c(
    nrow(polizas_auto_sucias),
    n_distinct(polizas_auto_sucias$id_poliza),
    sum(polizas_auto$poliza_valida, na.rm = TRUE),
    nrow(siniestros_auto_sucios),
    nrow(siniestros_auto),
    sum(siniestros_validados$fecha_coherente, na.rm = TRUE)
  )
)

kable(reconciliacion_auto)
objeto registros
Pólizas recibidas 1202
Pólizas únicas 1200
Pólizas válidas para análisis 1195
Siniestros recibidos 623
Siniestros únicos con pago válido 618
Siniestros dentro de la vigencia 276

La reconciliación permite explicar por qué el número final no coincide con el número recibido. Sin esta tabla, una exclusión razonable puede parecer pérdida accidental de información.

Diagnóstico del portafolio

Los indicadores se calculan de forma agregada. Promediar la siniestralidad individual puede dar demasiado peso a pólizas pequeñas o con baja exposición.

resumen_auto <- tabla_analitica_auto |>
  group_by(region, uso) |>
  summarise(
    polizas = n(),
    exposicion = sum(exposicion),
    prima = sum(prima_devengada),
    siniestros = sum(numero_siniestros),
    costo = sum(costo_siniestros),
    frecuencia = siniestros / exposicion,
    severidad = if_else(siniestros > 0, costo / siniestros, NA_real_),
    siniestralidad = costo / prima,
    .groups = "drop"
  ) |>
  arrange(desc(siniestralidad))

kable(resumen_auto, digits = 2)
region uso polizas exposicion prima siniestros costo frecuencia severidad siniestralidad
Cali Comercial 40 27.02 96589400 10 105333500 0.37 10533350 1.09
Costa Comercial 41 24.43 99677500 12 100272800 0.49 8356067 1.01
Medellín Comercial 53 34.75 139033200 22 122711100 0.63 5577777 0.88
Costa Particular 171 110.76 412941800 36 258973500 0.33 7193708 0.63
Medellín Particular 346 225.31 882642200 85 488914500 0.38 5751935 0.55
Bogotá Particular 320 201.29 806352400 61 337723800 0.30 5536456 0.42
Bogotá Comercial 66 41.91 167142800 15 67626600 0.36 4508440 0.40
Cali Particular 158 101.37 393100800 34 148162600 0.34 4357724 0.38

Separar frecuencia, severidad y prima

La siniestralidad puede aumentar por mecanismos diferentes:

\[ \text{Frecuencia} = \frac{\text{Número de siniestros}}{\text{Exposición}}, \qquad \text{Severidad} = \frac{\text{Costo de siniestros}}{\text{Número de siniestros}}, \]

\[ \text{Siniestralidad} = \frac{\text{Costo de siniestros}}{\text{Prima devengada}}. \]

Por eso, dos segmentos pueden tener la misma siniestralidad y requerir decisiones distintas: uno puede presentar muchos eventos pequeños y otro pocos eventos muy costosos.

resumen_auto |>
  select(
    region, uso, polizas, exposicion,
    frecuencia, severidad, siniestralidad
  ) |>
  arrange(desc(frecuencia)) |>
  kable(digits = 2)
region uso polizas exposicion frecuencia severidad siniestralidad
Medellín Comercial 53 34.75 0.63 5577777 0.88
Costa Comercial 41 24.43 0.49 8356067 1.01
Medellín Particular 346 225.31 0.38 5751935 0.55
Cali Comercial 40 27.02 0.37 10533350 1.09
Bogotá Comercial 66 41.91 0.36 4508440 0.40
Cali Particular 158 101.37 0.34 4357724 0.38
Costa Particular 171 110.76 0.33 7193708 0.63
Bogotá Particular 320 201.29 0.30 5536456 0.42

Estabilidad por volumen

resumen_auto <- resumen_auto |>
  mutate(
    evidencia = case_when(
      exposicion >= 100 ~ "Mayor respaldo",
      exposicion >= 50 ~ "Interpretar con cautela",
      TRUE ~ "Exploratorio"
    )
  )

resumen_auto |>
  select(region, uso, exposicion, siniestros, siniestralidad, evidencia) |>
  kable(digits = 2)
region uso exposicion siniestros siniestralidad evidencia
Cali Comercial 27.02 10 1.09 Exploratorio
Costa Comercial 24.43 12 1.01 Exploratorio
Medellín Comercial 34.75 22 0.88 Exploratorio
Costa Particular 110.76 36 0.63 Mayor respaldo
Medellín Particular 225.31 85 0.55 Mayor respaldo
Bogotá Particular 201.29 61 0.42 Mayor respaldo
Bogotá Comercial 41.91 15 0.40 Exploratorio
Cali Particular 101.37 34 0.38 Mayor respaldo

Los límites de exposición utilizados aquí son didácticos. En un proyecto real deben definirse según la volatilidad, el tamaño del portafolio y el uso que tendrá el indicador.

ggplot(
  resumen_auto,
  aes(x = reorder(region, siniestralidad), y = siniestralidad, fill = uso)
) +
  geom_col(position = "dodge") +
  coord_flip() +
  scale_y_continuous(labels = percent_format(accuracy = 1)) +
  labs(
    title = "Siniestralidad agregada por región y uso",
    x = NULL,
    y = "Costo de siniestros / prima devengada",
    fill = "Uso"
  )

Plan de modelamiento, evaluación y despliegue

No se construirá un modelo en esta clase. Sin embargo, CRISP-DM exige anticipar qué se haría después.

Fase Propuesta para el caso
Modelamiento Comparar posteriormente la frecuencia y la severidad mediante técnicas apropiadas para exposición y costos asimétricos
Evaluación Validar supuestos, estabilidad temporal, desempeño fuera de muestra, interpretabilidad y utilidad para suscripción
Despliegue Tablero mensual de calidad y siniestralidad, con alertas por segmento y responsables de revisión

Fase 4. Qué se modelaría después

La fase de modelamiento podría separar dos componentes: frecuencia y severidad. Antes de elegir una técnica se necesitaría revisar exposición, sobredispersión, concentración de ceros, inflación de costos, límites de cobertura y grandes pérdidas. Nombrar un algoritmo sin revisar estos elementos no constituye un plan de modelamiento.

Fase 5. Cómo se evaluaría

La evaluación debería incluir:

  • desempeño sobre periodos no usados en el ajuste;
  • estabilidad de los resultados por región y uso;
  • calibración entre valores observados y estimados;
  • sensibilidad frente a siniestros de gran cuantía;
  • coherencia actuarial de los efectos;
  • impacto de errores y faltantes;
  • utilidad para la decisión de suscripción o tarifa.

Fase 6. Cómo se desplegaría

Componente Definición para el caso
Usuario Comité técnico y equipo de suscripción
Producto Tablero mensual y reporte de excepciones
Frecuencia Actualización mensual con cierre contable
Indicadores Exposición, frecuencia, severidad, siniestralidad y calidad
Alertas Cambios materiales, baja exposición y deterioro de calidad
Responsable Propietario del dato y responsable actuarial
Monitoreo Comparación temporal y trazabilidad de correcciones

El despliegue termina de conectar el análisis con el proceso de decisión. También crea nueva información sobre errores, cambios de comportamiento y necesidades de mantenimiento, por lo que el ciclo vuelve a comenzar.

Lectura profesional del caso

Los valores imposibles y las incoherencias temporales deben separarse del conjunto analítico y devolverse a la fuente; las grandes pérdidas plausibles deben conservarse y analizarse como parte del riesgo. Los segmentos con baja exposición solo permiten hallazgos exploratorios. Antes de recomendar cambios de tarifa todavía se necesitarían coberturas, sumas aseguradas, deducibles, canal, desarrollo de siniestros y varios periodos de observación.

5. Proceso KDD

5.1 Idea central

KDD significa Knowledge Discovery in Databases. Su objetivo es transformar datos de bajo nivel en conocimiento válido, novedoso, útil y comprensible. La minería de datos es una etapa dentro de KDD, no un sinónimo de todo el proceso.

5.2 Del dato al conocimiento

KDD distingue varios niveles que suelen confundirse:

Nivel Significado Ejemplo en salud
Dato Registro elemental sin interpretación suficiente Una reclamación pagada por 4 millones
Información Dato organizado en contexto El pago pertenece a cirugía y supera la mediana del servicio
Patrón Regularidad encontrada en un conjunto de datos Un prestador presenta repetidamente pagos superiores a lo facturado
Conocimiento Patrón validado, comprensible y útil La diferencia se relaciona con una regla contractual mal parametrizada
Acción Uso del conocimiento en el proceso Corregir la parametrización y monitorear su efecto

No todo patrón es conocimiento. Puede ser producto del azar, un error de captura, una definición equivocada, una población demasiado pequeña o una característica ya conocida que no aporta ninguna decisión.

Criterios para considerar útil un patrón

  • Validez: se sostiene bajo controles y no depende de un error evidente.
  • Novedad: aporta algo que el usuario no conocía de la misma forma.
  • Utilidad: permite comprender o mejorar una decisión.
  • Comprensibilidad: puede explicarse a quienes deben utilizarlo.
  • Estabilidad: no desaparece ante cambios pequeños o segmentaciones razonables.
  • Materialidad: su magnitud justifica atención actuarial u operativa.

5.3 Las nueve etapas

Etapa Pregunta principal
1 ¿Qué se conoce del dominio y qué conocimiento se busca?
2 ¿Qué conjunto objetivo de datos es pertinente?
3 ¿Qué ruido, errores y faltantes deben tratarse?
4 ¿Cómo reducir o representar mejor la información?
5 ¿Qué tipo de tarea de descubrimiento responde al objetivo?
6 ¿Qué técnica y qué parámetros son apropiados?
7 ¿Qué patrones aparecen al aplicar la técnica?
8 ¿Cuáles patrones son válidos, comprensibles y útiles?
9 ¿Cómo se usará, comunicará o incorporará el conocimiento?

KDD también es iterativo. Si el patrón descubierto no es útil o se explica por un error de captura, se regresa a etapas anteriores.

Etapa 1. Comprender el dominio y definir el objetivo

Se incorpora conocimiento previo, se identifica al usuario y se define qué tipo de conocimiento sería valioso. En actuaría esto exige comprender contratos, coberturas, exposición, periodos, procesos de pago y restricciones regulatorias.

Producto: objetivo de descubrimiento y conjunto inicial de preguntas.

Etapa 2. Crear el conjunto objetivo

Se eligen población, periodo, fuentes, variables y unidad de análisis. La selección debe responder al objetivo, no a la simple disponibilidad.

Producto: conjunto objetivo y criterios de inclusión y exclusión.

Etapa 3. Limpiar y preprocesar

Se tratan duplicados, faltantes, errores, ruido y cambios conocidos en el proceso generador de datos. También se conserva información que permita explicar las exclusiones.

Producto: datos preprocesados e informe de calidad.

Etapa 4. Reducir y transformar

Se construye una representación útil: agregaciones, tasas, grupos, escalas, ventanas temporales o selección de atributos. Reducir no significa borrar sin criterio; significa representar el fenómeno con la complejidad necesaria.

Producto: datos transformados y definición de atributos derivados.

Etapa 5. Elegir la tarea de minería

Se decide si se busca describir, clasificar, estimar, agrupar, detectar anomalías o descubrir asociaciones. Esta decisión conecta el objetivo con la familia de métodos.

Producto: tarea de descubrimiento claramente formulada.

Etapa 6. Elegir la técnica y la estrategia de búsqueda

Se seleccionan algoritmos, parámetros, restricciones y forma de evaluación. La técnica debe ser compatible con la representación de los datos y con la necesidad de interpretación.

Producto: diseño analítico.

Etapa 7. Ejecutar la minería de datos

Se buscan patrones utilizando la técnica seleccionada. En esta clase se utilizarán agregaciones, reglas y exploración descriptiva para no anticipar modelos predictivos.

Producto: conjunto de patrones candidatos.

Etapa 8. Interpretar y evaluar

Los patrones se contrastan con el dominio, la calidad de los datos, el volumen, la estabilidad y la utilidad. Muchos patrones candidatos se descartan en esta etapa.

Producto: patrones aceptados, descartados y justificados.

Etapa 9. Actuar sobre el conocimiento

El conocimiento se comunica, documenta o incorpora a un proceso. También se definen responsables y seguimiento.

Producto: decisión, intervención, reporte o sistema de monitoreo.

5.4 Caso guiado KDD: patrones de costo en reclamaciones de salud

Etapa 1. Comprensión del dominio y objetivo

Una aseguradora de salud ficticia desea descubrir combinaciones de servicio, plan y prestador asociadas con costos inusuales. El propósito es orientar una auditoría clínica y contractual, no etiquetar automáticamente fraude.

El conocimiento previo del dominio sugiere varias posibilidades: diferencias contractuales, errores de parametrización, concentración de procedimientos complejos, pagos duplicados o una mezcla de servicios que hace incomparables a los prestadores.

Objetivo de descubrimiento: encontrar patrones persistentes y materialmente relevantes en la relación entre valor facturado y valor pagado, controlando al menos por servicio, plan, prestador y volumen observado.

Criterio de utilidad: un patrón será candidato a conocimiento si tiene volumen suficiente, aparece en más de un mes, es económicamente material y puede interpretarse a partir del proceso de pago.

Etapa 2. Selección del conjunto objetivo

La unidad de observación será una reclamación. Para evitar mezclar procesos incomparables, se analizarán reclamaciones pagadas dentro del periodo de estudio.

Definición del conjunto objetivo

Elemento Decisión
Población Reclamaciones asociadas a beneficiarios del portafolio analizado
Periodo Servicios realizados entre enero y diciembre de 2025
Unidad Una reclamación
Inclusión Valor facturado positivo y pago informado no negativo
Exclusión provisional Fechas fuera del periodo, edades imposibles y valores incompatibles
Segmentación Servicio, plan, prestador, grupo de edad y mes
Variable central Proporción reconocida: valor pagado dividido por valor facturado
set.seed(412)

n_reclamaciones <- 2500
facturado_vector <- round(
  rlnorm(n_reclamaciones, log(450000), 1.25),
  -2
)

reclamaciones_salud_sucias <- tibble(
  id_reclamacion = sprintf("REC-%06d", seq_len(n_reclamaciones)),
  id_beneficiario = sprintf(
    "BEN-%05d",
    sample(1:1100, n_reclamaciones, replace = TRUE)
  ),
  id_prestador = sprintf(
    "IPS-%03d",
    sample(1:65, n_reclamaciones, replace = TRUE)
  ),
  fecha_servicio = as.Date("2025-01-01") +
    sample(0:364, n_reclamaciones, replace = TRUE),
  edad = sample(0:95, n_reclamaciones, replace = TRUE),
  plan = sample(c("Básico", "BASICO", "Plus", "Premium", NA),
                n_reclamaciones, replace = TRUE,
                prob = c(0.40, 0.10, 0.28, 0.20, 0.02)),
  servicio = sample(
    c("Consulta", "CONSULTA", "Urgencias", "Cirugía", "cirugia",
      "Medicamentos", "Laboratorio"),
    n_reclamaciones,
    replace = TRUE,
    prob = c(0.18, 0.07, 0.18, 0.10, 0.04, 0.25, 0.18)
  ),
  valor_facturado = facturado_vector,
  valor_pagado = round(
    facturado_vector * runif(n_reclamaciones, 0.65, 0.98),
    -2
  )
)

reclamaciones_salud_sucias$edad[c(19, 1400)] <- c(-4, 160)
reclamaciones_salud_sucias$valor_facturado[c(88, 1201)] <- c(-800000, 0)
reclamaciones_salud_sucias$valor_pagado[c(777, 1900)] <- c(-250000, NA)
reclamaciones_salud_sucias$fecha_servicio[222] <- as.Date("2035-01-15")

# Un grupo de reclamaciones con una relación pagado/facturado inusual
indices_inusuales <- sample(seq_len(n_reclamaciones), 35)
reclamaciones_salud_sucias$valor_pagado[indices_inusuales] <-
  reclamaciones_salud_sucias$valor_facturado[indices_inusuales] * 1.35

reclamaciones_salud_sucias <- bind_rows(
  reclamaciones_salud_sucias,
  slice(reclamaciones_salud_sucias, 51, 52, 53, 54)
)

glimpse(reclamaciones_salud_sucias)
## Rows: 2,504
## Columns: 9
## $ id_reclamacion  <chr> "REC-000001", "REC-000002", "REC-000003", "REC-000004"…
## $ id_beneficiario <chr> "BEN-00841", "BEN-00514", "BEN-00037", "BEN-00255", "B…
## $ id_prestador    <chr> "IPS-061", "IPS-006", "IPS-022", "IPS-059", "IPS-026",…
## $ fecha_servicio  <date> 2025-12-27, 2025-02-17, 2025-12-10, 2025-02-28, 2025-…
## $ edad            <dbl> 73, 79, 2, 49, 11, 10, 76, 27, 15, 60, 19, 14, 11, 30,…
## $ plan            <chr> "Básico", "Premium", "Básico", "BASICO", "Plus", "BASI…
## $ servicio        <chr> "Laboratorio", "Laboratorio", "Urgencias", "Laboratori…
## $ valor_facturado <dbl> 254000, 766400, 168700, 40600, 7517700, 3338100, 43200…
## $ valor_pagado    <dbl> 235800, 645200, 142200, 34600, 10148895, 2710300, 3100…
perfil_base(reclamaciones_salud_sucias) |>
  kable(digits = 3)
variable clase faltantes porcentaje_faltantes valores_unicos
id_reclamacion character 0 0.000 2500
id_beneficiario character 0 0.000 956
id_prestador character 0 0.000 65
fecha_servicio Date 0 0.000 366
edad numeric 0 0.000 98
plan character 55 0.022 4
servicio character 0 0.000 7
valor_facturado numeric 0 0.000 2292
valor_pagado numeric 1 0.000 2261

El perfil permite observar tipos, faltantes y cardinalidad, pero todavía no muestra coherencias entre variables. Por ejemplo, una fecha puede estar completa y aun así pertenecer a 2035.

Etapa 3. Limpieza y preprocesamiento

Se separan tres conceptos:

  • error imposible: por ejemplo, edad negativa;
  • dato incompleto: por ejemplo, plan faltante;
  • valor extremo plausible: por ejemplo, una cirugía de alto costo.

Solo el primer grupo puede excluirse con una regla técnica inmediata. Los demás requieren tratamiento explícito.

salud_preprocesada <- reclamaciones_salud_sucias |>
  distinct(id_reclamacion, .keep_all = TRUE) |>
  mutate(
    plan_limpio = case_when(
      str_to_lower(plan) %in% c("básico", "basico") ~ "Básico",
      str_to_lower(plan) == "plus" ~ "Plus",
      str_to_lower(plan) == "premium" ~ "Premium",
      TRUE ~ "Sin dato"
    ),
    servicio_limpio = case_when(
      str_to_lower(servicio) == "consulta" ~ "Consulta",
      str_to_lower(servicio) == "urgencias" ~ "Urgencias",
      str_to_lower(servicio) %in% c("cirugía", "cirugia") ~ "Cirugía",
      str_to_lower(servicio) == "medicamentos" ~ "Medicamentos",
      str_to_lower(servicio) == "laboratorio" ~ "Laboratorio",
      TRUE ~ "Sin clasificar"
    ),
    registro_valido = between(edad, 0, 110) &
      fecha_servicio >= as.Date("2025-01-01") &
      fecha_servicio <= as.Date("2025-12-31") &
      valor_facturado > 0 &
      !is.na(valor_pagado) &
      valor_pagado >= 0
  )

salud_valida <- salud_preprocesada |>
  filter(registro_valido)

salud_preprocesada |>
  count(registro_valido) |>
  kable()
registro_valido n
FALSE 7
TRUE 2493

Informe detallado de reglas

calidad_salud <- reclamaciones_salud_sucias |>
  mutate(
    duplicado = duplicated(id_reclamacion) |
      duplicated(id_reclamacion, fromLast = TRUE),
    edad_invalida = is.na(edad) | !between(edad, 0, 110),
    fecha_invalida = is.na(fecha_servicio) |
      fecha_servicio < as.Date("2025-01-01") |
      fecha_servicio > as.Date("2025-12-31"),
    facturado_invalido = is.na(valor_facturado) |
      valor_facturado <= 0,
    pagado_invalido = is.na(valor_pagado) |
      valor_pagado < 0,
    plan_faltante = is.na(plan)
  )

informe_calidad_salud <- tibble(
  regla = c(
    "Identificador duplicado",
    "Edad inválida",
    "Fecha fuera del periodo",
    "Valor facturado inválido",
    "Valor pagado inválido",
    "Plan faltante"
  ),
  casos = c(
    sum(calidad_salud$duplicado),
    sum(calidad_salud$edad_invalida),
    sum(calidad_salud$fecha_invalida),
    sum(calidad_salud$facturado_invalido),
    sum(calidad_salud$pagado_invalido),
    sum(calidad_salud$plan_faltante)
  )
) |>
  mutate(porcentaje = casos / nrow(reclamaciones_salud_sucias))

kable(informe_calidad_salud, digits = 3)
regla casos porcentaje
Identificador duplicado 8 0.003
Edad inválida 2 0.001
Fecha fuera del periodo 1 0.000
Valor facturado inválido 2 0.001
Valor pagado inválido 2 0.001
Plan faltante 55 0.022

El plan faltante no se excluye automáticamente porque la reclamación conserva información útil para controles generales. Se crea la categoría Sin dato, pero el faltante permanece documentado como problema de completitud.

Etapa 4. Reducción y transformación

Se construyen variables que representan mejor el fenómeno actuarial.

salud_transformada <- salud_valida |>
  mutate(
    grupo_edad = cut(
      edad,
      breaks = c(-Inf, 17, 39, 59, 74, Inf),
      labels = c("0-17", "18-39", "40-59", "60-74", "75 o más")
    ),
    porcentaje_reconocido = valor_pagado / valor_facturado,
    mes = format(fecha_servicio, "%Y-%m")
  )

umbral_costo_alto <- quantile(
  salud_transformada$valor_pagado,
  probs = 0.99,
  na.rm = TRUE
)

salud_transformada <- salud_transformada |>
  mutate(
    costo_alto = valor_pagado >= umbral_costo_alto,
    pago_supera_factura = porcentaje_reconocido > 1.05
  )

Justificación de las variables derivadas

Variable Razón de construcción
grupo_edad Permite estudiar patrones de utilización sin interpretar cada edad por separado
porcentaje_reconocido Hace comparables reclamaciones con montos facturados distintos
mes Permite verificar persistencia temporal
costo_alto Identifica la cola superior sin declarar que sea un error
pago_supera_factura Implementa una regla de auditoría que requiere validación contractual

El percentil 99 se utiliza como criterio exploratorio de costo alto. No es una verdad universal ni convierte automáticamente una reclamación en irregular.

Etapas 5 y 6. Tarea y técnica de descubrimiento

La tarea elegida es descriptiva: descubrir grupos con alta frecuencia de reglas de auditoría y patrones de costo. No se busca predecir el valor de una reclamación.

La técnica será una combinación de:

  • agregación por grupos;
  • tasas y percentiles;
  • reglas de anomalía definidas con conocimiento del dominio;
  • visualización para interpretar patrones.

La elección se justifica porque el objetivo actual es descubrir y describir señales auditables. Una técnica compleja podría encontrar separaciones matemáticas, pero no necesariamente produciría patrones comprensibles para el comité clínico y contractual.

Etapa 7. Búsqueda de patrones

patrones_servicio <- salud_transformada |>
  group_by(servicio_limpio, plan_limpio) |>
  summarise(
    reclamaciones = n(),
    beneficiarios = n_distinct(id_beneficiario),
    pago_total = sum(valor_pagado),
    pago_mediano = median(valor_pagado),
    proporcion_costo_alto = mean(costo_alto),
    proporcion_pago_superior = mean(pago_supera_factura),
    .groups = "drop"
  ) |>
  filter(reclamaciones >= 25) |>
  arrange(desc(proporcion_pago_superior), desc(pago_total))

kable(patrones_servicio, digits = 3)
servicio_limpio plan_limpio reclamaciones beneficiarios pago_total pago_mediano proporcion_costo_alto proporcion_pago_superior
Consulta Plus 192 178 194933080 434750 0.031 0.031
Consulta Básico 271 244 209783175 365000 0.007 0.022
Urgencias Plus 139 131 109415175 433200 0.000 0.022
Urgencias Premium 96 93 86977760 297250 0.021 0.021
Cirugía Básico 169 161 110693835 376800 0.006 0.018
Consulta Premium 114 104 115879660 347350 0.018 0.018
Urgencias Básico 251 221 226276520 430700 0.008 0.016
Cirugía Premium 77 76 49839265 378800 0.000 0.013
Laboratorio Premium 90 88 78430210 336100 0.011 0.011
Cirugía Plus 96 89 85554375 379900 0.021 0.010
Medicamentos Básico 301 268 234477580 361100 0.010 0.010
Medicamentos Premium 117 109 94464530 360100 0.009 0.009
Laboratorio Básico 212 183 164071350 354500 0.000 0.005
Medicamentos Plus 182 164 160829300 420550 0.005 0.000
Laboratorio Plus 131 122 85152100 393400 0.008 0.000
patrones_prestador <- salud_transformada |>
  group_by(id_prestador) |>
  summarise(
    reclamaciones = n(),
    pago_total = sum(valor_pagado),
    pago_mediano = median(valor_pagado),
    pagos_superiores = sum(pago_supera_factura),
    proporcion_pago_superior = mean(pago_supera_factura),
    .groups = "drop"
  ) |>
  filter(reclamaciones >= 20) |>
  arrange(desc(proporcion_pago_superior))

head(patrones_prestador, 12) |>
  kable(digits = 3)
id_prestador reclamaciones pago_total pago_mediano pagos_superiores proporcion_pago_superior
IPS-030 35 19488390 378800 2 0.057
IPS-022 40 51535975 343300 2 0.050
IPS-042 45 28996865 366900 2 0.044
IPS-013 47 41715370 402400 2 0.043
IPS-050 47 38749625 390700 2 0.043
IPS-056 29 17117345 288500 1 0.034
IPS-011 31 29805675 481500 1 0.032
IPS-017 31 18914875 488200 1 0.032
IPS-047 31 37709865 654500 1 0.032
IPS-059 31 49154025 628200 1 0.032
IPS-002 36 26297465 210100 1 0.028
IPS-029 36 31020855 588950 1 0.028

Materialidad económica del patrón

Contar alertas no es suficiente. Diez diferencias pequeñas y diez diferencias grandes no representan el mismo impacto.

materialidad_prestador <- salud_transformada |>
  mutate(
    exceso_pago = pmax(valor_pagado - valor_facturado, 0)
  ) |>
  group_by(id_prestador) |>
  summarise(
    reclamaciones = n(),
    casos_marcados = sum(pago_supera_factura),
    exceso_total = sum(exceso_pago),
    maximo_exceso = max(exceso_pago),
    .groups = "drop"
  ) |>
  filter(reclamaciones >= 20) |>
  arrange(desc(exceso_total))

head(materialidad_prestador, 12) |>
  kable(digits = 0)
id_prestador reclamaciones casos_marcados exceso_total maximo_exceso
IPS-025 42 1 3228330 3228330
IPS-026 37 1 2631195 2631195
IPS-011 31 1 2063075 2063075
IPS-050 47 2 1279425 1142575
IPS-012 41 1 1149645 1149645
IPS-030 35 2 1035090 626920
IPS-059 31 1 750225 750225
IPS-022 40 2 451675 421050
IPS-065 42 1 367290 367290
IPS-002 36 1 355565 355565
IPS-021 40 1 294560 294560
IPS-042 45 2 284165 173810

Comparabilidad entre prestadores

Un prestador puede concentrarse en servicios de alta complejidad. Para evitar comparaciones engañosas se revisa el patrón dentro de cada servicio.

comparacion_prestador_servicio <- salud_transformada |>
  group_by(id_prestador, servicio_limpio) |>
  summarise(
    reclamaciones = n(),
    pago_mediano = median(valor_pagado),
    proporcion_pago_superior = mean(pago_supera_factura),
    .groups = "drop"
  ) |>
  filter(reclamaciones >= 8) |>
  arrange(desc(proporcion_pago_superior), desc(reclamaciones))

head(comparacion_prestador_servicio, 15) |>
  kable(digits = 3)
id_prestador servicio_limpio reclamaciones pago_mediano proporcion_pago_superior
IPS-013 Medicamentos 14 538800.0 0.143
IPS-026 Consulta 8 233300.0 0.125
IPS-042 Consulta 8 517377.5 0.125
IPS-042 Laboratorio 8 283950.0 0.125
IPS-011 Consulta 9 561300.0 0.111
IPS-049 Consulta 9 325700.0 0.111
IPS-055 Urgencias 9 363600.0 0.111
IPS-009 Consulta 10 869145.0 0.100
IPS-012 Consulta 10 323150.0 0.100
IPS-022 Laboratorio 10 1572775.0 0.100
IPS-064 Consulta 10 240250.0 0.100
IPS-020 Urgencias 11 670500.0 0.091
IPS-021 Consulta 11 343600.0 0.091
IPS-034 Urgencias 11 239100.0 0.091
IPS-047 Consulta 11 654500.0 0.091
ggplot(
  patrones_prestador,
  aes(x = reclamaciones, y = proporcion_pago_superior, label = id_prestador)
) +
  geom_point(aes(size = pago_total), color = "#6A1B9A", alpha = 0.7) +
  geom_text(check_overlap = TRUE, nudge_y = 0.004, size = 3) +
  scale_y_continuous(labels = percent_format(accuracy = 0.1)) +
  scale_size_continuous(
    labels = label_number(scale_cut = cut_si(""))
  ) +
  labs(
    title = "Prestadores con pagos superiores al valor facturado",
    x = "Número de reclamaciones",
    y = "Proporción de casos marcados",
    size = "Pago total"
  )

Etapa 8. Interpretación de los patrones

Un patrón no se convierte automáticamente en conocimiento. Debe revisarse si:

  • es consecuencia de un error de integración;
  • tiene suficiente número de observaciones;
  • permanece en distintos meses;
  • es clínicamente y contractualmente interpretable;
  • representa una diferencia material;
  • puede orientar una acción sin producir conclusiones injustificadas.
estabilidad_prestador <- salud_transformada |>
  filter(pago_supera_factura) |>
  count(id_prestador, mes, name = "casos") |>
  group_by(id_prestador) |>
  summarise(
    meses_con_alerta = n_distinct(mes),
    total_alertas = sum(casos),
    .groups = "drop"
  ) |>
  arrange(desc(meses_con_alerta), desc(total_alertas))

head(estabilidad_prestador, 10) |>
  kable()
id_prestador meses_con_alerta total_alertas
IPS-013 2 2
IPS-022 2 2
IPS-030 2 2
IPS-042 2 2
IPS-050 2 2
IPS-002 1 1
IPS-008 1 1
IPS-009 1 1
IPS-011 1 1
IPS-012 1 1

Evaluación explícita de patrones candidatos

evaluacion_patrones <- materialidad_prestador |>
  left_join(estabilidad_prestador, by = "id_prestador") |>
  mutate(
    meses_con_alerta = replace_na(meses_con_alerta, 0L),
    total_alertas = replace_na(total_alertas, 0L),
    patron_persistente = meses_con_alerta >= 3,
    patron_material = exceso_total > 0,
    candidato_a_revision = patron_persistente & patron_material
  ) |>
  arrange(desc(candidato_a_revision), desc(exceso_total))

head(evaluacion_patrones, 12) |>
  select(
    id_prestador, reclamaciones, casos_marcados,
    exceso_total, meses_con_alerta, candidato_a_revision
  ) |>
  kable(digits = 0)
id_prestador reclamaciones casos_marcados exceso_total meses_con_alerta candidato_a_revision
IPS-025 42 1 3228330 1 FALSE
IPS-026 37 1 2631195 1 FALSE
IPS-011 31 1 2063075 1 FALSE
IPS-050 47 2 1279425 2 FALSE
IPS-012 41 1 1149645 1 FALSE
IPS-030 35 2 1035090 2 FALSE
IPS-059 31 1 750225 1 FALSE
IPS-022 40 2 451675 2 FALSE
IPS-065 42 1 367290 1 FALSE
IPS-002 36 1 355565 1 FALSE
IPS-021 40 1 294560 1 FALSE
IPS-042 45 2 284165 2 FALSE

La regla anterior reduce el conjunto de patrones candidatos, pero todavía no prueba una causa. El siguiente paso necesita contratos, parametrización del sistema, notas de auditoría y posiblemente revisión clínica.

Etapa 9. Uso del conocimiento

El producto no será una acusación automática. Se propone:

  1. verificar contratos y reglas de reconocimiento;
  2. seleccionar una muestra de reclamaciones para auditoría;
  3. documentar la causa de cada diferencia;
  4. ajustar reglas de captura si el patrón proviene del sistema;
  5. monitorear si la señal persiste después de la corrección.

Cadena completa de conocimiento para el caso

Nivel Resultado del caso
Dato Reclamaciones con facturación, pago, servicio, plan, prestador y fecha
Patrón candidato Prestadores con proporción y materialidad elevadas de pagos superiores
Validación Persistencia mensual, volumen mínimo y comparación dentro del servicio
Conocimiento pendiente Explicación contractual, clínica u operativa confirmada
Acción Auditoría focalizada, corrección del proceso y seguimiento

KDD no termina en la tabla ordenada. Termina cuando el patrón ha sido interpretado y puede incorporarse responsablemente a una decisión.

Lectura profesional del caso

Las reclamaciones de alto costo se conservan porque pueden representar el fenómeno que interesa gestionar. Una anomalía es una observación que se aparta de un patrón esperado; un caso irregular exige evidencia adicional sobre incumplimiento, error o conducta. La interpretación también debe considerar volumen, mezcla de servicios y persistencia. Para convertir la señal en conocimiento se necesitan contratos, reglas de pago, auditoría clínica y trazabilidad del sistema.

6. Comparación entre CRISP-DM y KDD

Dimensión CRISP-DM KDD
Punto de partida Objetivo y decisión del negocio Dominio, conocimiento previo y objetivo de descubrimiento
Estructura Seis fases Nueve etapas en la formulación clásica
Énfasis Gestión completa del proyecto analítico Descubrimiento e interpretación de conocimiento desde datos
Preparación Una fase explícita con selección, limpieza, integración y construcción Selección, preprocesamiento, reducción y transformación aparecen diferenciados
Minería de datos Se desarrolla en la fase de modelamiento Es una etapa dentro del proceso, no el proceso completo
Resultado final Solución evaluada y desplegada Conocimiento interpretado y usado
Iteración

6.1 Correspondencia aproximada

La siguiente relación sirve para comparar, pero no significa que las fases sean idénticas.

CRISP-DM Etapas relacionadas de KDD
Comprensión del negocio Comprensión del dominio y definición del objetivo
Comprensión de los datos Selección del conjunto objetivo y exploración inicial
Preparación de los datos Limpieza, preprocesamiento, reducción y transformación
Modelamiento Definición de la tarea, selección de técnica y minería de datos
Evaluación Interpretación y evaluación de patrones
Despliegue Uso del conocimiento

6.2 ¿Cuándo resulta más natural cada enfoque?

CRISP-DM es especialmente útil cuando existe una necesidad organizacional concreta y debe gestionarse el ciclo completo, desde la definición del problema hasta el uso y mantenimiento del resultado. KDD resulta especialmente natural cuando el interés central es descubrir y validar conocimiento en conjuntos de datos, haciendo explícita la transición entre selección, preprocesamiento, transformación, minería e interpretación.

No son metodologías enemigas. Comparten la necesidad de iterar, preparar los datos, incorporar conocimiento del dominio y evaluar los resultados. La diferencia principal está en el énfasis y en la forma de organizar las etapas.

6.3 Síntesis conceptual

La metodología no sustituye el criterio actuarial. Su función es organizarlo, hacerlo visible y permitir que otras personas revisen cómo se pasó de una necesidad a una conclusión.

En los tres casos desarrollados se repite una misma lógica:

  1. definir la decisión antes de calcular;
  2. precisar la unidad de análisis y las fuentes;
  3. conservar evidencia de los problemas encontrados;
  4. transformar mediante reglas justificadas;
  5. usar denominadores y comparaciones coherentes con el riesgo;
  6. distinguir un patrón de una explicación causal;
  7. declarar lo que los datos todavía no permiten concluir;
  8. conectar el resultado con un uso y un responsable.

El modelo puede llegar después. Si estas decisiones previas están mal formuladas, un modelo más sofisticado solo producirá un error más elegante.

7. Referencias

  • Chapman, P., Clinton, J., Kerber, R., Khabaza, T., Reinartz, T., Shearer, C. y Wirth, R. (2000). CRISP-DM 1.0: Step-by-step data mining guide.
  • Fayyad, U., Piatetsky-Shapiro, G. y Smyth, P. (1996). From Data Mining to Knowledge Discovery in Databases. AI Magazine, 17(3), 37-54.
  • IBM. CRISP-DM Help Overview y documentación de las fases de CRISP-DM en SPSS Modeler.