Skip to contents

Una medición aislada describe una entrega. Varias corridas permiten saber si la calidad mejora y si cambió la estructura que el modelo esperaba.

Serie de evaluaciones

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
#> 1   2026-02-28                1     1

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.

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] TRUE

La persistencia usa RDS y no agrega dependencias. El archivo no se sobrescribe sin consentimiento.

archivo <- tempfile(fileext = ".rds")
guardar_historico(historico, archivo)
recuperado <- leer_historico(archivo)
identical(historico, recuperado)
#> [1] TRUE
unlink(archivo)

Deriva estructural entre entregas

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 nueva_columna     hallazgo     aparecido sospechoso
#> 4          <NA>     hallazgo no_comparable      error

Una 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.

Las 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.

nrow(comparar_perfiles(perfil_enero, perfil_enero))
#> [1] 0

Equivalencia de resúmenes

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.01

La 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.