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:
Diagnosticar no es ejecutar summary() y copiar la
salida. Un diagnóstico útil responde, como mínimo, cinco preguntas:
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.
Un diagnóstico inicial debe revisar:
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.
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 |
Suponga que una aseguradora observa un aumento del costo de siniestros. Antes de analizar la base deben distinguirse varias explicaciones posibles:
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.
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 |
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.
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.
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:
Esta distinción es didáctica. No se afirmará que existe una metodología canónica adicional con fases distintas.
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.
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.
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.
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.
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.
Cada decisión debe conectarse con una necesidad del negocio, una evidencia en los datos y un producto verificable.
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 |
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.
¿La base tiene calidad suficiente para calcular aportes esperados y en qué grupos deben priorizarse las verificaciones?
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 |
Antes de ver los resultados se plantean explicaciones que puedan contrastarse:
Estas hipótesis no son conclusiones. Sirven para orientar los controles y evitar una exploración sin propósito.
| 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 |
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…
| 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")| 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.
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 |
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.
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 |
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"
)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”.
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.
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 |
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.
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.
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.
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.
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.
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.
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.
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.
| 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.
| 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.
| 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.
| 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.
| 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.
| 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.
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:
El síntoma “subió la siniestralidad” debe descomponerse:
| 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 |
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
| 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")| 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 |
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")| 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 |
| control | casos |
|---|---|
| Duplicados de id_siniestro | 3 |
| Pago negativo | 1 |
| Pago faltante | 1 |
| Causa faltante | 91 |
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.
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…
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.
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 |
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 |
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"
)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 |
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.
La evaluación debería incluir:
| 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.
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.
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.
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.
| 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
La unidad de observación será una reclamación. Para evitar mezclar procesos incomparables, se analizarán reclamaciones pagadas dentro del periodo de estudio.
| 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…
| 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.
Se separan tres conceptos:
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 |
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.
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
)| 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.
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:
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.
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 |
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 |
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"
)Un patrón no se convierte automáticamente en conocimiento. Debe revisarse si:
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 |
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.
El producto no será una acusación automática. Se propone:
| 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.
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.
| 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 | Sí | Sí |
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 |
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.
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:
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.