9  Partiendo de datos crudos

9.1 Objetivos de aprendizaje

En esta sesión vamos a investigar la estructura de las bases de datos del registro de diabetes de Dinamarca, basándonos en datos simulados. Esto quiere decir que todos los datos que veremos son generados aleatóreamente y no pertenecen a ninguna persona real.

9.2 Explorando el paquete OSDC

Para éste taller usaremos el paquete “osdc” (Open Source Diabetes Classifier). Antes de iniciar esta sesión debes haber completado las tareas pre-taller, incluyendo la instalación de paquetes.

Podemos consultar la documentación del paquete osdc (en Inglés).

Para usar paquetes en una sesión debemos cargarlos:

library(osdc)
library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.2.1     ✔ readr     2.2.0
✔ forcats   1.0.1     ✔ stringr   1.6.0
✔ ggplot2   4.0.3     ✔ tibble    3.3.1
✔ lubridate 1.9.5     ✔ tidyr     1.3.2
✔ purrr     1.2.2     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(purrr)
library(Epi)
library(DBI)
library(duckdb)
library(knitr)
library(rmarkdown)
library(ggplot2)

El paquete ‘osdc’ contiene varias funciones, pero también documentación en forma de guías o viñetas. Podemos revisarlas dentro del ambiente R.

La primera nos da la documentación del algoritmo

vignette("algorithm")
starting httpd help server ... done

El siguiente paso es revisar todas las fuentes de datos que son necesarias para crear el registro.

vignette("data-sources")

9.3 Registros y variables

Como se puede ver, hay 5 registros, pero en algunos casos un solo registro tiene varias tablas temáticas que se acceden como bases separadas (Landspatientregisterets) o separadas en el tiempo (Sygesikringsregisteret)

La segunda tabla nos muestra todas las variables de cada registro que son necesarias para construir el registro de diabetes.

Podemos consultar estos datos al detalle generando una variable que contiene la lista de registros.

reg_list <- names(registers())
print(reg_list)
 [1] "bef"             "lmdb"            "lpr_adm"         "lpr_diag"       
 [5] "lpr3a_kontakt"   "lpr3a_diagnose"  "lpr3f_kontakter" "lpr3f_diagnoser"
 [9] "sysi"            "sssy"            "lab_forsker"    

9.4 Simulación de datos

Como vamos a trabajar con datos simulados, el siguiente paso es simular el registro de la población total.

sim_bef <- simulate_registers("bef", n = 10000)
sim_bef <- sim_bef$bef
head(sim_bef)
# A tibble: 6 × 3
   koen pnr          foed_dato 
  <int> <chr>        <date>    
1     2 164409653234 1923-03-01
2     1 952443913885 1933-03-21
3     1 679714832266 1959-10-20
4     1 447393958677 1939-10-13
5     2 447559464315 2022-01-10
6     2 120172052967 2016-12-07

Después simulamos el registro de laboratorios.

sim_lab_forsker <- simulate_registers("lab_forsker", n = 10000)
sim_lab_forsker <- sim_lab_forsker$lab_forsker
head(sim_lab_forsker)
# A tibble: 6 × 4
  pnr          samplingdate analysiscode  value
  <chr>        <date>       <chr>         <dbl>
1 164409653234 2017-11-05   NPU15168      86.4 
2 952443913885 2011-09-25   NPU86660       1.43
3 679714832266 2021-10-15   NPU27300      74.9 
4 447393958677 2022-12-06   NPU27300     111.  
5 447559464315 2025-12-10   NPU44101      70.1 
6 120172052967 2011-12-21   NPU60312      43.9 

9.5 Datos crudos

9.5.1 Preparación de los datos

El paquete requiere que los datos estén en formato DuckDB. Esta es una base de datos de alta performancia que puede manejar volúmenes grandes de datos sin que sea necesario cargarlos todos a memoria en una sola vez.

Para este ejemplo generaremos datos simulados del formato de registro con el comando simulate_registers() y luego los convertiremos al formato DuckDB:

register_data <- registers() |>
  names() |>
  simulate_registers(n=10000) |>
  purrr::map(duckplyr::as_duckdb_tibble) |>
  # Convert to a DuckDB connection, as duckplyr is still
  # in early development, while the DBI-DuckDB connection
  # is more stable.
  purrr::map(duckplyr::as_tbl)
duckdb keeps downloaded extensions and secrets in a temporary directory:
ℹ /tmp/RtmpHXttXR/duckdb
This is removed when the R session ends.
• Extensions are re-downloaded each session.
• Secrets are lost.
ℹ Run duckdb(shared_home = TRUE) (or create ~/.duckdb) to keep them (suitable for most users).
ℹ Run duckdb(shared_home = FALSE) to accept the temporary directory (and silence this message).
ℹ See ?duckdb_storage for details and alternatives.

El resultado es una lista con nombres, donde cada elemento corresponde a un registro en forma de una tabla de DuckDB. Los datos del LPR abarcan múltiples versiones (LPR2 y LPR3), que deben prepararse y unirse en una única tabla antes de utilizarse como entrada para classify_diabetes(). Lo mismo ocurre con los registros de los servicios de salud (SSSY y SYSI). osdc proporciona funciones auxiliares para realizar esta tarea.

lpr <- list(
  prepare_lpr2(register_data$lpr_adm, register_data$lpr_diag),
  prepare_lpr3f(register_data$lpr3f_kontakter, register_data$lpr3f_diagnoser),
  prepare_lpr3a(register_data$lpr3a_kontakt, register_data$lpr3a_diagnose)
) |>
  join_registers()

hsr <- list(register_data$sssy, register_data$sysi) |> join_registers()

The remaining registers are extracted from the register_data list, so they are ready to be passed as arguments to classify_diabetes():

Los registros restantes se extraen de la lista register_data, y así quedan listos para pasarlos como argumentos a classify_diabetes().

bef <- register_data$bef
lmdb <- register_data$lmdb
lab_forsker <- register_data$lab_forsker

9.5.2 Hacer correr la función de clasificación

Ahora estamos listos para correr el algoritmo de clasificación. Pasamos todos los registros a classify_diabetes():

classified_diabetes <- classify_diabetes(
  lpr = lpr,
  hsr = hsr,
  lab_forsker = lab_forsker,
  bef = bef,
  lmdb = lmdb
)

classified_diabetes
# A query:  ?? x 5
# Database: DuckDB 1.5.5 [unknown@Linux 6.17.0-1022-azure:R 4.6.1//tmp/RtmpHXttXR/duckplyr/duckplyr213d35a731ae.duckdb]
   pnr          stable_inclusion_date raw_inclusion_date has_t1d has_t2d
   <chr>        <date>                <date>             <lgl>   <lgl>  
 1 000968900938 2017-02-13            2017-02-13         FALSE   TRUE   
 2 002118745152 2011-10-30            2011-10-30         FALSE   TRUE   
 3 020930073909 2007-11-26            2007-11-26         FALSE   TRUE   
 4 023901517086 2025-03-24            2025-03-24         FALSE   TRUE   
 5 034983397970 2022-10-09            2022-10-09         FALSE   TRUE   
 6 036188275183 2024-07-25            2024-07-25         FALSE   TRUE   
 7 079844973894 2011-01-20            2011-01-20         FALSE   TRUE   
 8 089028788111 2024-11-08            2024-11-08         FALSE   TRUE   
 9 134624431203 2025-01-17            2025-01-17         FALSE   TRUE   
10 175783911906 2025-11-19            2025-11-19         FALSE   TRUE   
# ℹ more rows

Como se puede ver, este proceso devuelve una tabla de DuckDB con los individuos clasificados como personas con diabetes tipo 1 (DM1) o diabetes tipo 2 (DM2), junto con la fecha de la clasificación. Cada una de las columnas de la salida se explica en la Section 9.5.3.1 más adelante.

9.5.3 Transferencia de los datos a formato R

Por ahora los datos están guardados en formato DuckDB, lo que quiere decir que no han sido cargados directamente a la memoria de R. Para transferir los datos a R usamos dplyr::collect():

classified_diabetes <- classified_diabetes |>
  dplyr::collect()

classified_diabetes
# A tibble: 954 × 5
   pnr          stable_inclusion_date raw_inclusion_date has_t1d has_t2d
   <chr>        <date>                <date>             <lgl>   <lgl>  
 1 000968900938 2017-02-13            2017-02-13         FALSE   TRUE   
 2 002118745152 2011-10-30            2011-10-30         FALSE   TRUE   
 3 020930073909 2007-11-26            2007-11-26         FALSE   TRUE   
 4 023901517086 2025-03-24            2025-03-24         FALSE   TRUE   
 5 034983397970 2022-10-09            2022-10-09         FALSE   TRUE   
 6 036188275183 2024-07-25            2024-07-25         FALSE   TRUE   
 7 079844973894 2011-01-20            2011-01-20         FALSE   TRUE   
 8 089028788111 2024-11-08            2024-11-08         FALSE   TRUE   
 9 134624431203 2025-01-17            2025-01-17         FALSE   TRUE   
10 175783911906 2025-11-19            2025-11-19         FALSE   TRUE   
# ℹ 944 more rows

Ahora vemos que en los datos simulados 954 individuos han sido clasificados con un diagnóstico de diabetes.

9.5.3.1 Cómo entender los datos

El resultado es una tabla con una fila por individuo clasificado y cinco columnas:

Columna Descripción
pnr El número identificador individual pseudonimizado.
stable_inclusion_date La fecha del segundo evento de inclusión, si cae después de stable_inclusion_start_date (valor predeterminado: 1998). NA para fechas anteriores.
raw_inclusion_date La fecha del segundo evento de inclusión sin imponer NA para fechas anteriores a stable_inclusion_start_date.
has_t1d TRUE si el individuo se clasifica como caso de diabetes tipo 1, FALSE en otros casos.
has_t2d TRUE si el individuo se clasifica como caso de diabetes tipo 2, FALSE en otros casos.
Note

9.5.4 Acerca de stable_inclusion_date y raw_inclusion_date

La raw_inclusion_date corresponde simplemente a la fecha del segundo evento que cumple los criterios de clasificación. Sin embargo, para los eventos anteriores a 1998, los datos de los registros pueden no tener una cobertura suficiente para distinguir de forma fiable entre casos nuevos (incidentes) y casos ya existentes (prevalentes). Por ello, la columna stable_inclusion_date se establece como NA para esas fechas más tempranas, con el fin de señalar esta incertidumbre.

La función classify_diabetes() incluye el parámetro stable_inclusion_start_date, cuyo valor predeterminado es 01-01-1998. Esto significa que es posible modificar la fecha a partir de la cual la clasificación se considera estable.

Véase la sección ‘Interface’ bajo vignette("design") para mayor información sobre los datos resultantes.

9.5.5 Borrar variables ya usadas

Antes de continuar vamos a hacer algo de limpieza, eliminando las variables que ya no vamos a necesitar.

rm(bef, hsr, lab_forsker, lmdb, lpr, register_data, reg_list)

9.6