Una medición aislada describe una entrega. Varias corridas permiten saber si la calidad mejora y si cambió la estructura que el modelo esperaba.
El identificador y la fecha se fijan al medir. Aquí se evalúa la completitud de la misma columna en dos meses.
nucleo <- metricas_nucleo()
instancia <- instanciar(
especializar(nucleo$NoNulo, nombre_especifico = "NoNuloDato"),
"entrega", "dato"
)
modelo_calidad <- modelo(instancia)
enero <- medir(
modelo_calidad, data.frame(dato = c("A", NA, "C", NA)),
id_medicion = "enero",
fecha = as.POSIXct("2026-01-31", tz = "UTC")
)
febrero <- medir(
modelo_calidad, data.frame(dato = c("A", "B", "C", NA)),
id_medicion = "febrero",
fecha = as.POSIXct("2026-02-28", tz = "UTC")
)
enero <- agregar(enero, "atributo", "ratio")
febrero <- agregar(febrero, "atributo", "ratio")
regla <- regla_evaluacion("Completitud mayor al 60 %", function(x) x > 0.6)
perfil <- perfil_evaluacion("Operativo", regla)
evaluacion_enero <- evaluar(enero, perfil)
evaluacion_febrero <- evaluar(febrero, perfil)
comparar_evaluaciones(evaluacion_enero, evaluacion_febrero)
#> perfil id_medicion_anterior fecha_anterior resultado_anterior id_medicion_actual
#> 1 Operativo enero 2026-01-31 0 febrero
#> fecha_actual resultado_actual delta comparacion
#> 1 2026-02-28 1 1 Cambió el resultado de la evaluación.historico_calidad() normaliza las corridas en un
data.frame plano y versionado. Puede escribirse como CSV o
llevarse a una tabla institucional sin desarmar listas anidadas. La
configuración de cada corrida —la tabla, el marco, los tipos de
resultado— viaja en las columnas identidad_tabla y
configuracion_* de las filas
evaluacion_perfil, así que un histórico releído de un CSV
separa las tablas y corta los cambios de marco igual que el objeto.
comparar_evaluaciones() publica el mismo delta que la
deriva, y en comparacion dice por qué falta cuando las
corridas no se pueden comparar.
historico <- historico_calidad(evaluacion_enero, evaluacion_febrero)
historico[, c("id_medicion", "fecha", "nivel", "resultado")]
#> id_medicion fecha nivel resultado
#> 1 enero 2026-01-31 evaluacion_regla 0
#> 2 enero 2026-01-31 evaluacion_perfil 0
#> 3 febrero 2026-02-28 evaluacion_regla 1
#> 4 febrero 2026-02-28 evaluacion_perfil 1
detectar_deriva_calidad(historico, umbral = 0.05)
#> nivel perfil regla identidad_tabla id_medicion_anterior fecha_anterior
#> 1 perfil Operativo <NA> entrega enero 2026-01-31
#> resultado_anterior id_medicion_actual fecha_actual resultado_actual delta
#> 1 0 febrero 2026-02-28 1 1
#> cambio_absoluto significativo direccion severidad cambio aspecto
#> 1 1 TRUE mejora ok <NA> resultado
#> descripcion evidencia
#> 1 Cambió el resultado de la evaluación. <NA>acumular_historico() existe para el flujo incremental:
amplía una serie ya guardada y no duplica una corrida idéntica.
historico_incremental <- acumular_historico(
historico_calidad(evaluacion_enero), evaluacion_febrero
)
identical(historico, historico_incremental)
#> [1] TRUELa persistencia usa RDS y no agrega dependencias. El archivo no se sobrescribe sin consentimiento.
comparar_perfiles() distingue cambios de esquema y
cambios sobre columnas comparables. Informa columnas nuevas o retiradas,
tipos, ausencias, cardinalidad, rangos, patrones y hallazgos.
data(datos_administrativos)
entrega_enero <- datos_administrativos
entrega_febrero <- datos_administrativos
entrega_febrero$cedula[1] <- "12345678"
entrega_febrero$nueva_columna <- "nuevo"
perfil_enero <- perfilar(
entrega_enero,
fecha = as.POSIXct("2026-01-31", tz = "UTC")
)
perfil_febrero <- perfilar(
entrega_febrero,
fecha = as.POSIXct("2026-02-28", tz = "UTC")
)
deriva <- comparar_perfiles(perfil_enero, perfil_febrero)
deriva[, c("columna", "aspecto", "cambio", "severidad")]
#> columna aspecto cambio severidad
#> 1 nueva_columna columna aparecida error
#> 2 cedula cardinalidad modificado sospechoso
#> 3 cedula patron modificado sospechoso
#> 4 cedula patron modificado sospechoso
#> 5 nueva_columna hallazgo aparecido sospechoso
#> 6 <NA> hallazgo no_comparable errorUna comparación contra el mismo perfil produce cero cambios. Las columnas que sólo existen en un lado se informan como cambios estructurales; no hacen fallar la comparación de las demás.
Lo que la protección de datos personales tapa no se compara, pero
tampoco se calla: el rango de cedula o de
correo queda declarado, por columna y lado, en el atributo
campos_protegidos. Si la protección de una columna cambia
entre las entregas, ese cambio sale con su propia fila,
aspecto = "proteccion".
attr(deriva, "campos_protegidos")
#> columna campo lado
#> 1 cedula rango anterior
#> 2 cedula rango actual
#> 3 fecha_nacimiento rango anterior
#> 4 fecha_nacimiento rango actual
#> 5 correo rango anterior
#> 6 correo rango actualLas configuraciones que contienen nombres o texto del usuario se
convierten en claves por bytes, no por la marca de codificación ni por
la intercalación del locale. Por eso la comparación sigue siendo estable
si el perfil se guarda con saveRDS() y se relee bajo otro
locale.
La misma regla alcanza la clave interna que agrupa hallazgos: dos perfiles idénticos no emiten avisos espurios bajo LC_CTYPE = C.
La misma separación se usa al redactar sugerencias: los nombres de la
tabla se conservan tal como llegaron y sólo la copia destinada al texto
se marca como UTF-8. Así el código publicado no cambia al pasar de un
locale UTF-8 a C.
comparar_equivalencia() devuelve una fila por columna y
campo compartidos. Compara con igualdad exacta los conteos y usa la
tolerancia declarada por quien llama sólo para los campos flotantes.
set.seed(20260831)
datos_equivalencia <- data.frame(
monto = c(10, 20, 30, 40),
stringsAsFactors = FALSE
)
perfil_equivalencia_anterior <- perfilar(
datos_equivalencia, muestra = Inf, analizar_dependencias = FALSE
)
perfil_equivalencia_actual <- perfilar(
datos_equivalencia, muestra = Inf, analizar_dependencias = FALSE
)
fila_monto <- match(
"monto", perfil_equivalencia_actual$columnas$columna
)
perfil_equivalencia_actual$columnas$media[fila_monto] <-
perfil_equivalencia_actual$columnas$media[fila_monto] + 0.001
perfil_equivalencia_actual$columnas$n[fila_monto] <-
perfil_equivalencia_actual$columnas$n[fila_monto] + 1L
tolerancia_llamador <- 0.01
equivalencia <- comparar_equivalencia(
perfil_equivalencia_anterior, perfil_equivalencia_actual,
tolerancia = tolerancia_llamador
)
campos_mostrados <- c("n", "n_faltantes", "media")
equivalencia_monto <- equivalencia[
equivalencia$columna == "monto" & equivalencia$campo %in% campos_mostrados,
, drop = FALSE
]
equivalencia_monto <- equivalencia_monto[
match(campos_mostrados, equivalencia_monto$campo),
c("campo", "valor_anterior", "valor_actual", "veredicto", "motivo",
"tipo_eje", "tolerancia")
]
equivalencia_monto
#> campo valor_anterior valor_actual veredicto motivo
#> 3 n 4 5 materialmente_distinto eje_exacto
#> 8 n_faltantes 0 0 identico igualdad_exacta
#> 34 media 25 25.001 equivalente dentro_de_tolerancia
#> tipo_eje tolerancia
#> 3 exacto 0.01
#> 8 exacto 0.01
#> 34 flotante 0.01La salida muestra los tres veredictos y el motivo de cada fila; la
tolerancia es del llamador y viaja ecoada en tolerancia. En
los ejes exactos, equivalente es inalcanzable: el conteo
cambiado permanece materialmente_distinto con cualquier
tolerancia válida.
tolerancias <- c(0, tolerancia_llamador, 1e12)
veredictos_conteo <- vapply(tolerancias, function(tolerancia) {
prueba <- comparar_equivalencia(
perfil_equivalencia_anterior, perfil_equivalencia_actual,
tolerancia = tolerancia
)
as.character(prueba$veredicto[
prueba$columna == "monto" & prueba$campo == "n"
])
}, character(1))
data.frame(tolerancia = tolerancias, veredicto_n = veredictos_conteo)
#> tolerancia veredicto_n
#> 1 0e+00 materialmente_distinto
#> 2 1e-02 materialmente_distinto
#> 3 1e+12 materialmente_distinto
stopifnot(all(veredictos_conteo == "materialmente_distinto"))El invariante es deliberado: devuelve datos, no decisiones.