Udtræk fra LPR

Praktiske opskrifter — de to tilgange, helper-funktioner og integration med kohorten

Published

June 6, 2026

Denne side viser, hvordan du trækker diagnoser ud af LPR med kode. Den bygger på strukturen fra Fase 9a — Forstå LPR: perioderne (LPR2/LPR3), D-præfikset, diagnosetyperne (A/B/G) og filtret for tilbagekaldte diagnoser. Læs den side først, hvis du ikke allerede har.

Du bruger det samme udtræksmønster som i Fase 5 og Fase 6 — bare anvendt på LPR’s to generationer. Det er den vigtigste og nok den mest komplekse del af guiden.

Note

Denne side og Fase 10 hænger cirkulært sammen — det er bevidst. For at bygge din kohorte (Fase 10) skal du vide, hvordan man udtrækker diagnosekoder og procedurer fra LPR. For at udtrække diagnosekoder fra LPR (denne side) har du brug for en kohorte. Vi løser det sådan: denne side lærer dig udtræksmønstret — vi antager at du allerede har en kohorte. Fase 10 viser dig, hvordan du bygger den kohorte ved at bruge præcis det mønster du lige har lært. Læs faserne i rækkefølge, og kom tilbage til koden her, når du har din kohorte klar.

Note

Kodeeksemplerne bruger de kolonnenavne DST’s parquet-registre typisk har. Dine kolonner kan hedde noget andet — tjek med names(din_data) eller slå dem op i Fase 15 — Registerreference.

Note

Koden bruger inner_join() og bind_rows(). Er det nye begreber, er de forklaret grundigt i Fase 11 — Joins og pivots — du kan sagtens følge koden her og vende tilbage til Fase 11 bagefter.


Hent diagnoser fra LPR — vælg tilgang

Vælg én af to tilgange afhængigt af dit studie:

Tilgang 1 — direkte udtræk Tilgang 2 — alle_dx
Bedst når Du har et mindre antal udfald Du har flere udfald fra LPR
Workflow Hent specifikke koder → Ekskluder → færdig Hent alle → Ekskluder → filtrer per udfald
Fordel Simplere og hurtigere ved enkeltudtræk LPR forespørges kun én gang; genbrug til alle udfald

Tilgang 1 er bedst ved et mindre antal diagnoser du vil trække ud. Du filtrerer på specifikke ICD-koder direkte i filter()-steget inden collect(). DuckDB/Arrow sender filteret ned til lagringsformatet — kun relevante rækker hentes i RAM.

Tilgang 2 er bedst når du har mange udfald du vil trække ud. Du forespørger LPR én enkelt gang og bygger alle_dx: en fælles tabel med alle A- og B-diagnoser. For hvert nyt udfald filtrerer du blot alle_dx på koderne — den eneste linje du ændrer er kode-listen.

Note

Eksemplerne kræver parquet-filer og en færdigbygget studiepopulation. kohort er data.frame med pnr og index_date per person — se Fase 10. Er registrene i SAS, konverter dem først: Fase 4 — SAS til parquet.

Tilgang 1 — hent bestemte diagnoser direkte (start her ved ét udfald)

Filtrer på specifikke koder inden collect(). Eksemplet henter diabetes mellitus (E10–E14) — erstat KODER_REGEX med dine egne koder.

library(arrow)
library(dplyr)

kohort_pnrs <- unique(kohort$pnr)
KODER_REGEX <- "^DE1[0-4]"   # diabetes mellitus (E10–E14) — med D-præfiks

# ── LPR2 somatisk ────────────────────────────────────────────────────────
lpr_adm  <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_adm/")  %>% rename_with(tolower)
lpr_diag <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_diag/") %>% rename_with(tolower)

lpr2_dm <- lpr_adm %>%
  filter(pnr %in% !!kohort_pnrs) %>%
  select(pnr, recnum, date_contact = d_inddto) %>%
  inner_join(
    lpr_diag %>%
      filter(c_diagtype %in% c("A", "B"),
             grepl(KODER_REGEX, c_diag)) %>%   # filtrer FØR collect — D-præfiks i regex
      select(recnum, c_diag, c_diagtype),
    by = "recnum"
  ) %>%
  collect() %>%
  mutate(icd3 = substr(c_diag, 2, 4))

# ── LPR3 ─────────────────────────────────────────────────────────────────
lpr3_k <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_a_kontakt/")  %>% rename_with(tolower)
lpr3_d <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_a_diagnose/") %>% rename_with(tolower)

lpr3_dm <- lpr3_k %>%
  filter(pnr %in% !!kohort_pnrs) %>%
  select(pnr, dw_ek_kontakt, date_contact = kont_starttidspunkt) %>%
  inner_join(
    lpr3_d %>%
      filter(diag_kode_type %in% c("A", "B"),
             is.na(senere_afkraeftet) | senere_afkraeftet != "Ja",
             grepl(KODER_REGEX, diag_kode)) %>%
      select(dw_ek_kontakt, c_diag = diag_kode, c_diagtype = diag_kode_type),
    by = "dw_ek_kontakt"
  ) %>%
  collect() %>%
  mutate(date_contact = as.Date(date_contact), icd3 = substr(c_diag, 2, 4))

dm_dx <- bind_rows(lpr2_dm, lpr3_dm)
# Kolonner: pnr | date_contact | c_diag | c_diagtype | icd3
Tip

Mange specifikke koder? Byg regex programmatisk:

koder <- c("E10", "E11", "E12", "E13", "E14")
KODER_REGEX <- paste0("^D(", paste(koder, collapse = "|"), ")")
Note

Har du F-koder (fx demens, depression)? Tilpas regex til at inkludere dem, fx "^DE1[0-4]|^DF0[0-3]|^DG30", og tilføj psykiatrisk LPR2 — se Tilgang 2 nedenfor for kode.

Alternativ: kompakt udtræk (éntabel-tilgang)

En kollega kan have vist dig denne kortere tilgang:

lpr <- left_join(lpr_adm, lpr_diag, by = "RECNUM") |>
  filter(C_DIAGTYPE == "A",
         grepl("^S72", C_DIAG)) |>
  group_by(PNR) |>
  filter(D_INDDTO == min(D_INDDTO)) |>
  slice(1) |>
  ungroup()

Den er kortere, men har tre faldgruber på DST:

  1. D-præfiks-fejl: "^S72" matcher IKKE "DS72..." i DST’s data — returnerer nul rækker uden fejlmeddelelse. Brug "^DS72" (med D) eller strip præfikset først.
  2. left_join i stedet for inner_join: Beholder alle kontakter fra lpr_adm — også dem uden diagnose. Unødvendigt tungt på nationale registre.
  3. Ingen pnr-filter: Henter hele befolkningens data. Rigtigt ved kohorteopbygning (Fase 10), ikke ved udtræk fra eksisterende kohorte.
Tilgang 2 — hent alle diagnoser + filtrer udfald (ved flere udfald)

Del 1 — byg alle_dx

library(arrow)
library(dplyr)

kohort_pnrs <- unique(kohort$pnr)

# ── LPR2 somatisk (frem til marts 2019) ─────────────────────────────────
lpr_adm  <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_adm/")  %>% rename_with(tolower)
lpr_diag <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_diag/") %>% rename_with(tolower)

lpr2_dx <- lpr_adm %>%
  filter(pnr %in% !!kohort_pnrs) %>%
  select(pnr, recnum, date_contact = d_inddto) %>%
  inner_join(
    lpr_diag %>%
      filter(c_diagtype %in% c("A", "B")) %>%
      select(recnum, c_diag, c_diagtype),
    by = "recnum"
  ) %>%
  collect() %>%
  mutate(icd3 = substr(c_diag, 2, 4))

# ── LPR3 (marts 2019 og frem) ────────────────────────────────────────────
lpr3_k <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_a_kontakt/")  %>% rename_with(tolower)
lpr3_d <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_a_diagnose/") %>% rename_with(tolower)

lpr3_dx <- lpr3_k %>%
  filter(pnr %in% !!kohort_pnrs) %>%
  select(pnr, dw_ek_kontakt, date_contact = kont_starttidspunkt) %>%
  inner_join(
    lpr3_d %>%
      filter(diag_kode_type %in% c("A", "B"),
             is.na(senere_afkraeftet) | senere_afkraeftet != "Ja") %>%
      select(dw_ek_kontakt, c_diag = diag_kode, c_diagtype = diag_kode_type),
    by = "dw_ek_kontakt"
  ) %>%
  collect() %>%
  mutate(date_contact = as.Date(date_contact), icd3 = substr(c_diag, 2, 4))

alle_dx <- bind_rows(lpr2_dx, lpr3_dx)
# Kolonner: pnr | date_contact | c_diag | c_diagtype | icd3
Note

Har du F-koder (fx demens, depression)? Psykiatriske diagnoser stillet inden marts 2019 er i separate registre. Tilføj dem inden bind_rows():

psyk_adm  <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/t_psyk_adm/") %>%
  rename_with(tolower) %>% rename(pnr = v_cpr, recnum = k_recnum)
psyk_diag <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/t_psyk_diag/") %>%
  rename_with(tolower) %>% rename(recnum = v_recnum)

lpr2_psyk_dx <- psyk_adm %>%
  filter(pnr %in% !!kohort_pnrs) %>%
  select(pnr, recnum, date_contact = d_inddto) %>%
  inner_join(psyk_diag %>% filter(c_diagtype %in% c("A", "B")) %>%
               select(recnum, c_diag, c_diagtype), by = "recnum") %>%
  collect() %>% mutate(icd3 = substr(c_diag, 2, 4))

alle_dx <- bind_rows(lpr2_dx, lpr2_psyk_dx, lpr3_dx)
Tip

Bruger du duckplyr? union_all() kombinerer tabellerne inden collect() og kræver identiske kolonnenavne og -typer. Omdøb LPR3-kolonnerne til LPR2-format inden kombination — se onboarding-dokumentet for eksempel.

Filtrer din udtrukne tabel for specifikke udfald

KODER <- c("G30", "F00", "F01", "F02", "F03")   # demens — skift til dit udfald

udfald <- alle_dx %>%
  filter(icd3 %in% KODER) %>%
  inner_join(kohort %>% select(pnr, index_date), by = "pnr") %>%   # brug kohort_renset efter ekskludering (Fase 10 Trin 2)
  filter(date_contact > index_date) %>%   # post-index; brug < for baseline-kovariat
  group_by(pnr) %>%
  arrange(date_contact) %>%
  slice(1) %>%
  ungroup() %>%
  select(pnr, event_date = date_contact)

# Join til kohort — NA = ingen hændelse (censureres ved studieafslutning)
resultat <- kohort %>%
  select(pnr) %>%
  left_join(udfald, by = "pnr")

saveRDS(resultat, "datasets/extract_demens.rds")   # skift filnavn for hvert nyt udfald
Note

Ekskludering af prævalente tilfælde — personer der allerede havde diagnosen inden index-dato — sker i Fase 10, Trin 2. Brug kohort_renset i stedet for kohort i koden ovenfor, efter at du har gennemført det trin.


Prøv det selv — kørbart eksempel med syntetiske data (Tilgang 1)
Important

Dette eksempel kræver RStudio installeret lokalt på din computer — ikke DST-serveren. Det syntetiske datasæt (fakeregs) er ikke tilgængeligt på DST. Download R: cran.r-project.org · Download RStudio: posit.co/download/rstudio-desktop

Eksemplet trækker CVD-diagnoser (iskæmisk hjertesygdom, ICD-10 I20–I25) fra LPR2 og LPR3 kombineret — det komplette mønster fra teoriafsnittet ovenfor, men kørbart lokalt med syntetiske data. Det følger Tilgang 1: specifikke koder filtreres fra inden collect().

De syntetiske LPR-data genereres med fakeregs-pakken, som du allerede kender fra Fase 6 — Første udtræk. Har du allerede genereret og gemt data der, er synth_data/lpr_adm/ klar og du kan springe forberedelsesblokken over.

Tilpasset fra Anders Aasted Isaksens dev/common_tasks_datatable.qmd i fakeregs (MIT-licens, Steno Diabetes Center Aarhus). Omskrevet til dplyr + arrow og tilpasset denne vejlednings mønster.

# Installer fakeregs første gang:
# install.packages("pak"); pak::pak("steno-aarhus/fakeregs")

library(fakeregs)   # syntetiske DST-registerdata
library(dplyr)      # filter, select, mutate, inner_join, bind_rows
library(arrow)      # open_dataset, write_parquet

# ── Forberedelse: generer syntetiske data og gem som parquet (gøres kun én gang) ────
bp             <- generate_background_pop()
lpr_adm_synth  <- generate_lpr_adm(background_df = bp)
lpr_diag_synth <- generate_lpr_diag(background_df = lpr_adm_synth)
lpr_a_k_synth  <- generate_lpr_a_kontakt(background_df = bp)
lpr_a_d_synth  <- generate_lpr_a_diagnose(background_df = lpr_a_k_synth)

dir.create("synth_data/lpr_adm",        recursive = TRUE, showWarnings = FALSE)
dir.create("synth_data/lpr_diag",       recursive = TRUE, showWarnings = FALSE)
dir.create("synth_data/lpr_a_kontakt",  recursive = TRUE, showWarnings = FALSE)
dir.create("synth_data/lpr_a_diagnose", recursive = TRUE, showWarnings = FALSE)
write_parquet(lpr_adm_synth,  "synth_data/lpr_adm/lpr_adm.parquet")
write_parquet(lpr_diag_synth, "synth_data/lpr_diag/lpr_diag.parquet")
write_parquet(lpr_a_k_synth,  "synth_data/lpr_a_kontakt/lpr_a_kontakt.parquet")
write_parquet(lpr_a_d_synth,  "synth_data/lpr_a_diagnose/lpr_a_diagnose.parquet")
Tip

Stien er relativ til dit working directory — tjek med getwd(). Har du allerede kørt forberedelsesblokken i Fase 6, er synth_data/lpr_adm/ allerede gemt.

# De ICD-koder vi søger — skift disse til dit eget udfald
CVD_KODER <- c("I20", "I21", "I22", "I23", "I24", "I25")   # iskæmisk hjertesygdom

# ── LPR2 somatisk (frem til marts 2019) ──────────────────────────────────
lpr_adm  <- open_dataset("synth_data/lpr_adm/")  %>% rename_with(tolower)   # LPR2 kontakttabel — syntetisk
lpr_diag <- open_dataset("synth_data/lpr_diag/") %>% rename_with(tolower)   # LPR2 diagnosetabel — syntetisk

lpr2_cvd <- lpr_adm %>%
  select(pnr, recnum, date_contact = d_inddto) %>%           # vælg kun nødvendige kolonner
  inner_join(
    lpr_diag %>%
      filter(c_diagtype %in% c("A", "B"),                    # kun aktions- og bidiagnoser
             substr(c_diag, 2, 4) %in% !!CVD_KODER) %>%       # !! sender den lokale R-vektor til DuckDB
      select(recnum, c_diag),                    # kun join-nøgle og diagnosekode
    by = "recnum"                                             # join-nøgle i LPR2
  ) %>%
  collect() %>%                                              # HER hentes data ind i R
  mutate(icd3 = substr(c_diag, 2, 4))                        # gem renset kode som ny kolonne

# ── LPR3 (marts 2019 og frem) ─────────────────────────────────────────────
lpr3_k <- open_dataset("synth_data/lpr_a_kontakt/")  %>% rename_with(tolower)   # LPR3 kontakttabel — syntetisk
lpr3_d <- open_dataset("synth_data/lpr_a_diagnose/") %>% rename_with(tolower)   # LPR3 diagnosetabel — syntetisk

lpr3_cvd <- lpr3_k %>%
  select(pnr, dw_ek_kontakt, date_contact = kont_starttidspunkt) %>%   # dw_ek_kontakt er join-nøgle til lpr_a_diagnose
  inner_join(
    lpr3_d %>%
      filter(diag_kode_type %in% c("A", "B"),
             is.na(senere_afkraeftet) | senere_afkraeftet != "Ja",  # ekskluder tilbagekaldte
             substr(diag_kode, 2, 4) %in% !!CVD_KODER) %>%   # !! sender den lokale R-vektor til DuckDB
      select(dw_ek_kontakt, c_diag = diag_kode),             # omdøb til c_diag for konsistens med LPR2
    by = "dw_ek_kontakt"                                     # join-nøgle i LPR3
  ) %>%
  collect() %>%                                              # hent ind i R
  mutate(
    date_contact = as.Date(date_contact),                    # datetime → dato
    icd3         = substr(c_diag, 2, 4)                      # strip D-præfiks: "DI21" → "I21"
  )

# ── Kombiner og gem ────────────────────────────────────────────────────────
alle_cvd <- bind_rows(lpr2_cvd, lpr3_cvd)                   # sæt LPR2 og LPR3 sammen

nrow(alle_cvd)                                               # tjek: antal diagnoserækker
length(unique(alle_cvd$pnr))                                 # tjek: antal unikke personer
table(alle_cvd$icd3)                                         # fordeling på koder

saveRDS(alle_cvd, "datasets/extract_cvd.rds")                # gem — skift sti til din egen mappe

Pak mønstret i en genanvendelig funktion (ved mange udfald)

Henter du diagnoser for flere udfald, betaler det sig at samle Tilgang 2-mønstret i én genanvendelig funktion fremfor at kopiere ~40 linjer for hvert nyt udfald. Definer den øverst i dit script eller i en separat functions.R-fil.

Fordele: - Ét sted at rette, hvis noget ændres (fx et nyt register eller en ny kolonne) - Kodeblokken til hvert udfald reduceres fra ~40 linjer til ét funktionskald - Fejl introduceres ét sted i stedet for i hver kopi

Tip

Arbejder du på DARTER (eller andet projekt med dstDataPrep)? Byt open_dataset("E:/workdata/.../<register>/") ud med load_database("<register>") — så finder den stien automatisk. Se DARTER — oversigt og pipeline for den fuldt tilpassede variant — den er opdateret med de aktuelle, bekræftede registernavne (pr. juni 2026).

Se den fulde get_lpr_diagnoses()-funktion og brug
library(arrow)
library(dplyr)

get_lpr_diagnoses <- function(pnr_vector, diagtypes = c("A", "B"), inpatient_only = FALSE) {
  base <- "E:/workdata/[projektnummer]/cleaned-data/parquet-registers/"

  # Åbn registre
  lpr_adm   <- open_dataset(paste0(base, "lpr_adm/"))   %>% rename_with(tolower)   # LPR2 somatisk kontakter
  lpr_diag  <- open_dataset(paste0(base, "lpr_diag/"))  %>% rename_with(tolower)   # LPR2 somatisk diagnoser
  psyk_adm  <- open_dataset(paste0(base, "t_psyk_adm/"))  %>% rename_with(tolower) %>%
    rename(pnr = v_cpr, recnum = k_recnum)                            # LPR2 psykiatrisk kontakter
  psyk_diag <- open_dataset(paste0(base, "t_psyk_diag/")) %>% rename_with(tolower) %>%
    rename(recnum = v_recnum)                                          # LPR2 psykiatrisk diagnoser
  lpr3_k    <- open_dataset(paste0(base, "lpr_a_kontakt/"))  %>% rename_with(tolower) %>%
    filter(lprindberetningssystem == "LPR3")                               # KRITISK: undgå duplikerede rækker fra LPR_F-format
  lpr3_d    <- open_dataset(paste0(base, "lpr_a_diagnose/")) %>% rename_with(tolower)  # LPR3 diagnoser

  # Filtrer på indlæggelsestype hvis ønsket
  if (inpatient_only) {
    lpr_adm <- lpr_adm %>% filter(c_pattype == "0")          # "0" = indlagt i LPR2
    lpr3_k  <- lpr3_k  %>% filter(kont_type == "ALCA00")     # "ALCA00" = indlagt i LPR3
  }

  # LPR2 somatisk
  lpr2_dx <- lpr_adm %>%
    filter(pnr %in% !!pnr_vector) %>%
    select(pnr, recnum, date_contact = d_inddto) %>%
    inner_join(
      lpr_diag %>% filter(c_diagtype %in% !!diagtypes) %>% select(recnum, c_diag),
      by = "recnum"
    ) %>%
    collect() %>%
    mutate(icd3 = substr(c_diag, 2, 4))                       # strip D-præfiks

  # LPR2 psykiatrisk
  lpr2_psyk_dx <- psyk_adm %>%
    filter(pnr %in% !!pnr_vector) %>%
    select(pnr, recnum, date_contact = d_inddto) %>%
    inner_join(
      psyk_diag %>% filter(c_diagtype %in% !!diagtypes) %>% select(recnum, c_diag),
      by = "recnum"
    ) %>%
    collect() %>%
    mutate(icd3 = substr(c_diag, 2, 4))

  # LPR3
  lpr3_dx <- lpr3_k %>%
    filter(pnr %in% !!pnr_vector) %>%
    select(pnr, dw_ek_kontakt, date_contact = kont_starttidspunkt) %>%
    inner_join(
      lpr3_d %>%
        filter(diag_kode_type %in% !!diagtypes,
               is.na(senere_afkraeftet) | senere_afkraeftet != "Ja") %>%
        select(dw_ek_kontakt, c_diag = diag_kode),
      by = "dw_ek_kontakt"
    ) %>%
    collect() %>%
    mutate(date_contact = as.Date(date_contact),               # datetime → dato
           icd3 = substr(c_diag, 2, 4))

  bind_rows(lpr2_dx, lpr2_psyk_dx, lpr3_dx)                   # returner samlet tabel
}

Brug funktionen — ét kald pr. udtræk, skift kun KODER:

kohort    <- readRDS("datasets/full_cohort.rds")
pnr_liste <- unique(kohort$pnr)

# Hent alle diagnoser for kohorten (Fase 1 — se hospitalskontakter-siden)
alle_dx <- get_lpr_diagnoses(
  pnr_vector    = pnr_liste,
  diagtypes     = c("A", "B"),
  inpatient_only = FALSE
)
# Returnerer: pnr | date_contact | c_diag | icd3

# Udtræk ét udfald — skift kun KODER (Fase 2)
KODER <- c("F00", "F01", "F02", "F03", "G30", "G31")   # demens

demens <- alle_dx %>%
  filter(icd3 %in% KODER) %>%
  inner_join(kohort %>% select(pnr, index_date), by = "pnr") %>%
  filter(date_contact > index_date) %>%
  group_by(pnr) %>% arrange(date_contact) %>% slice(1) %>% ungroup() %>%
  select(pnr, demens_dato = date_contact)

resultat <- kohort %>% select(pnr) %>% left_join(demens, by = "pnr")
saveRDS(resultat, "datasets/extract_dementia.rds")

Næste skridt

Du har nu trukket diagnoser fra to LPR-generationer. Næste skridt er at forme og kombinere dine udtræk:

Fase 11 — Joins og pivots

Se også

Back to top