4. Estructura no declarada

Una entrega puede llegar sin diccionario, sin claves y sin relaciones declaradas. ¿Qué estructura cumplen de hecho los datos recibidos? ¿Y qué parte todavía hay que confirmar con quien conoce el sistema?

En esta guía:

2 tablas que se pueden seguir fila por fila

Los conjuntos incluidos en lupa sirven para examinar columnas, pero no contienen una clave exacta. En datos_operativos, id_evento tiene 12 valores distintos en 13 filas. Sobre ellos, detectar_claves() y detectar_dependencias() devolverían tablas sin filas.

Para ver una clave, una relación y una dependencia construimos 2 tablas chicas: una de personas y otra de trámites. Cada conclusión se puede contrastar con las filas impresas.

library(lupa)

personas <- data.frame(
  id_persona = 101:108,
  nombre = rep(c("Ana", "Bruno", "Carla", "Diego"), each = 2),
  ciudad = rep(c("Montevideo", "Salto", "Belén", "Melo"), each = 2),
  departamento = rep(
    c("Montevideo", "Salto", "Salto", "Cerro Largo"), each = 2
  ),
  stringsAsFactors = FALSE
)

tramites <- data.frame(
  id_tramite = 1001:1010,
  id_persona = c(101, 101, 102, 103, 104, 104, 105, 106, 107, 108),
  tipo = rep(c("alta", "renovación"), 5),
  stringsAsFactors = FALSE
)

personas
#>   id_persona nombre     ciudad departamento
#> 1        101    Ana Montevideo   Montevideo
#> 2        102    Ana Montevideo   Montevideo
#> 3        103  Bruno      Salto        Salto
#> 4        104  Bruno      Salto        Salto
#> 5        105  Carla      Belén        Salto
#> 6        106  Carla      Belén        Salto
#> 7        107  Diego       Melo  Cerro Largo
#> 8        108  Diego       Melo  Cerro Largo
tramites
#>    id_tramite id_persona       tipo
#> 1        1001        101       alta
#> 2        1002        101 renovación
#> 3        1003        102       alta
#> 4        1004        103 renovación
#> 5        1005        104       alta
#> 6        1006        104 renovación
#> 7        1007        105       alta
#> 8        1008        106 renovación
#> 9        1009        107       alta
#> 10       1010        108 renovación

Una clave candidata

id_persona no falta y no se repite en personas. Limitamos la búsqueda a columnas simples (max_combinacion = 1) para que la salida responda sólo esa pregunta:

claves <- detectar_claves(personas, max_combinacion = 1)
claves[, c(
  "columnas", "n_columnas", "n_filas", "casi_clave", "unicidad_exacta"
)]
#>     columnas n_columnas n_filas casi_clave unicidad_exacta
#> 1 id_persona          1       8      FALSE            TRUE

nombre, ciudad y departamento se repiten, así que no aparecen. Que una columna identifique cada fila en esta entrega la vuelve candidata, no clave: una magnitud que no repite valores también pasaría. Declarar la clave al perfilar, con perfilar(clave = ...), permite ir de un hallazgo a la fila en el sistema de origen. Eso se explica en 2. Leer un perfil.

Una relación entre 2 tablas

id_persona también está en tramites, y ahí se repite: una persona puede tener más de un trámite. detectar_relaciones() recibe las 2 columnas de identificación y compara sus valores:

detectar_relaciones(
  personas["id_persona"], tramites["id_persona"], muestra = Inf
)
#>   columna_tabla1 columna_tabla2 cardinalidad n_valores_comunes cobertura_tabla1_en_tabla2
#> 1     id_persona     id_persona          1:m                 8                          1
#>   cobertura_tabla2_en_tabla1 motivo_poda
#> 1                          1        <NA>

La cardinalidad es 1:m, uno a muchos. Las 2 coberturas valen 1 porque todos los identificadores de cada lado aparecen en el otro. No hay trámites de personas que no estén en personas, ni personas sin trámites. muestra = Inf deja explícito que se comparan todos los valores.

Una dependencia dentro de una tabla

En personas, cada ciudad observada pertenece a un solo departamento: conocer la ciudad determina el departamento. Al revés no vale, porque Salto y Belén comparten departamento. Bajamos el mínimo de observaciones a 4 porque la tabla tiene 8 filas:

dependencias <- detectar_dependencias(
  personas[c("ciudad", "departamento")],
  muestra = Inf,
  min_observaciones = 4
)
dependencias
#>   determinante  dependiente cumplimiento n_evaluados n_grupos n_violaciones exacta
#> 1       ciudad departamento            1           8        4             0   TRUE
#>   evidencia
#> 1

perfilar() busca las mismas dependencias por su cuenta (analizar_dependencias = TRUE, el valor por omisión) y las deja en perfil$dependencias. Una dependencia exacta permite además imputar un valor faltante desde los propios datos, como se ve en 6. Limpiar con un plan.

⚠️ Lo que se cumple no es una regla

Nada de esto modifica los datos ni declara el modelo:

Una entrega futura puede contradecirlas. La documentación del sistema es la que puede confirmarlas como reglas.

Resumen

🧭 Paso siguiente

5. El vacío por diseño (vignette("vacio-por-diseno")): por qué una celda vacía puede ser correcta, y cómo se le dice al paquete.