Udtræk fra LPR

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

Published

July 2, 2026

Denne side viser, hvordan du trækker diagnoser ud af LPR med kode. Den bygger på strukturen fra 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

Cirkulær sammenhæng med Fase 10 - bevidst. For at udtrække diagnoser (denne side) tænker vi, at du allerede har en kohorte; men du bygger kohorten i Fase 10 med præcis det udtræksmønster, du lærer her. Læs faserne i rækkefølge, og kom tilbage til koden, når din kohorte er klar. Koden bruger inner_join() og bind_rows(); er de nye, forklares de grundigt i Fase 12.

Warning

LPR3-filer og kolonnenavne - tjek før du loader.

  • Brug LPR_A-filerne konsekvent. DST har skiftet LPR3 fra det gamle LPR_F-format (kontakter, diagnoser, forloeb) til det nye LPR_A-format (lpr_a_kontakt, lpr_a_diagnose). Begge versioner kan ligge i din mappe og dække de samme år - loader du begge (eller blander gammelt og nyt), får du duplikerede rækker. Brug lpr_a_* som i eksemplerne her, og lad LPR_F-filerne ligge. Nogle projekter har desuden ældre data inde i lpr_a_kontakt, som allerede findes i LPR2; filtrér dem fra med lprindberetningssystem == "LPR3". Det er projekt-specifikt - på DARTER er det håndteret i faldgrube 5; ellers afklar med din datamanager.
  • Verificér kolonnenavne. Skiftet til LPR_A har ændret en række variabelnavne (ofte uigennemsigtige danske forkortelser). Eksemplerne bruger de navne registrene typisk har, men tjek altid, før du stoler på et kolonnenavn. På et dovent open_dataset()-objekt kan du bruge arrow::schema(din_data) - den viser kolonnenavnene og deres typer uden at læse data ind i RAM; colnames(din_data) virker også (og er det, du bruger til de DuckDB-baserede objekter fra read_register()/read_register()). Slå navnene op i Register-overblik.
  • Vær opmærksom på diagnose-spiket 2019-2020. LPR3’s kontaktmodel registrerer langt flere ambulante diagnoser end LPR2 gjorde, hvilket giver et spike i antallet af diagnoser omkring 2019-2020, som betyder noget for enhver optællings- eller tendensbaseret analyse. Se diagnose-spiket.

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 en data.frame med pnr og index_date per person - se Fase 10.

Tilpas stien i open_dataset(...) til dit projekts datamappe (eksemplernes E:/workdata/[projektnummer]/... er projektspecifik). OBS: LPR er et af de største registre, så data skal ligge i parquet. Så kan Arrow/DuckDB filtrere før indlæsning og kun hente de rækker du beder om; læser du LPR direkte fra fx SAS, hentes hele registret i RAM. Konverter SAS til parquet først: Fase 4 - SAS til parquet.

Tip

Hvorfor semi_join(tibble(pnr = ...)) og ikke filter(pnr %in% ...)? Begge beholder kun kohortens rækker, men semi_join mod en lille tibble af pnr’er skubbes mere effektivt og pålideligt ned i Arrow/DuckDB. Et stort %in%-filter med en R-vektor kan blive langsomt eller helt afvist (især med en ældre duckplyr). Du slipper samtidig for !!: semi_join tager en almindelig lokal tabel som argument. !! skal du stadig bruge, når du sender en lokal R-vektor ind i et filter(), fx en kodeliste (substr(c_diag, 2, 4) %in% !!KODER).

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.

#=====================================================
# Udtræk diabetes-diagnoser fra LPR (Tilgang 1)
#=====================================================
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 + lpr_diag
#-----------------------------------------------------
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)

# Her samler vi LPR2's to tabeller: kontaktregistret lpr_adm (pnr + datoer) og diagnoseregistret
# lpr_diag (ICD-koden), filtrerer på dine koder/diagnosetyper, og vælger de kolonner du skal bruge.
# Du kan trække så mange koder ud på én gang, du vil (via KODER_REGEX), og du vælger SELV objektnavnet
# (her 'lpr2_dm' = LPR2 + diabetes mellitus, fordi eksemplet er diabetes).
lpr2_dm <- lpr_adm %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%   # KUN din kohorte. UDELAD linjen, hvis du vil have HELE befolkningen
  select(pnr, recnum, date_contact = d_inddto) %>%   # vælg kolonner fra LPR2; d_inddto OMDØBES til date_contact (se noten under koden)
  inner_join(
    lpr_diag %>%                                     # diagnoseregistret: har recnum + ICD-kode, men hverken pnr eller dato
      filter(c_diagtype %in% c("A", "B"),            # A + B = aktions-/bidiagnoser; tilføj "G" (-> c("A","B","G")) hvis du også vil have grundmorbus
             grepl(KODER_REGEX, c_diag)) %>%          # behold kun dine koder - filtrér FØR collect (husk D-præfiks i regex)
      select(recnum, c_diag, c_diagtype),            # de diagnose-kolonner vi skal bruge
    by = "recnum"                                    # recnum = nøglen, der binder kontakt og diagnose sammen i LPR2
  ) %>%
  collect() %>%                                       # FØRST her hentes data i RAM (alt ovenfor kører i databasen)
  mutate(icd3 = substr(c_diag, 2, 4))                 # strip D-præfikset: "DE11" -> "E11"

#-----------------------------------------------------
# LPR3: lpr_a_kontakt + lpr_a_diagnose
#-----------------------------------------------------
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 %>%                                 # samme mønster som LPR2 - men LPR3-tabeller og andre kolonnenavne
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%   # KUN din kohorte. UDELAD linjen for HELE befolkningen
  select(pnr, dw_ek_kontakt, date_contact = kont_starttidspunkt) %>%  # LPR3's datokolonne omdøbes til SAMME navn: date_contact
  inner_join(
    lpr3_d %>%
      filter(diag_kode_type %in% c("A", "B"),         # som LPR2: tilføj "G" hvis du også vil have grundmorbus
             is.na(senere_afkraeftet) | senere_afkraeftet != "Ja",  # drop tilbagekaldte diagnoser (findes kun i LPR3)
             grepl(KODER_REGEX, diag_kode)) %>%        # samme koder; filtrér FØR collect
      select(dw_ek_kontakt, c_diag = diag_kode, c_diagtype = diag_kode_type),  # omdøb LPR3-navne -> samme navne som LPR2
    by = "dw_ek_kontakt"                               # LPR3's kontakt-nøgle (IKKE recnum som i LPR2)
  ) %>%
  collect() %>%
  mutate(date_contact = as.Date(date_contact),         # LPR3-datoen er datetime -> gør den til en ren dato
         icd3 = substr(c_diag, 2, 4))                  # strip D-præfiks

#-----------------------------------------------------
# Kombinér LPR2 + LPR3 til ét samlet udtræk
#-----------------------------------------------------
# Virker, fordi vi gav de to udtræk SAMME kolonnenavne ovenfor (date_contact, c_diag, ...):
dm_dx <- bind_rows(lpr2_dm, lpr3_dm)                   # ét samlet udtræk (LPR2 + LPR3)
# Kolonner: pnr | date_contact | c_diag | c_diagtype | icd3
Note

Hvad er date_contact = d_inddto? (hvorfor farves date_contact?) Inde i select() betyder nyt_navn = gammelt_navn, at du omdøber en kolonne. d_inddto er LPR2’s faktiske datokolonne, og date_contact er et navn, du selv vælger - editoren farver det som et argument, men det er bare det nye kolonnenavn (ikke et funktionsargument). Vi omdøber, fordi LPR3 har en anden datokolonne (kont_starttidspunkt); ved at give begge navnet date_contact får de to udtræk ens kolonnenavne, så bind_rows() kan stable dem.

Skal vs. valg: registernavnene (pnr, recnum, d_inddto, c_diag, c_diagtype, dw_ek_kontakt, kont_starttidspunkt, diag_kode …) skal staves præcis som i registret. De nye navne (date_contact, icd3) og objektnavnet (lpr2_dm) vælger du selv - bare hold de harmoniserede navne ens på tværs af LPR2 og LPR3.

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

#=====================================================
# Hent ALLE diagnoser fra LPR (Tilgang 2)
#=====================================================
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 %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%
  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 %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%
  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))

#-----------------------------------------------------
# Kombinér LPR2 + LPR3 til ét samlet udtræk
#-----------------------------------------------------
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 %>%
  semi_join(tibble(pnr = kohort_pnrs), by = "pnr") %>%
  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, "sti/til/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 Det bruger open_dataset() på lokale synth_data/-mapper; fastregs read_register() er til et konfigureret DST-projekt, ikke ad-hoc lokale mapper.

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: generér syntetiske data (kør 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.

#=====================================================
# Udtræk CVD-diagnoser (samme mønster som Tilgang 1)
#=====================================================
# 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, "sti/til/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. Funktionen beholder diagnosetype-kolonnen (c_diagtype) i sit output, så du senere kan stramme case-definitionen til en sensitivitetsanalyse (se Diagnosetyper) uden at forespørge LPR igen.

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

Bruger du fastreg, eller arbejder du på DARTER? Med fastreg bytter du open_dataset("E:/workdata/.../<register>/") ud med read_register("<register>") (via navn). På DARTER bruger du samme read_register("<register>") (fastreg). 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)

#=====================================================
# Funktion: get_lpr_diagnoses() - genbrugeligt LPR-udtræk
#=====================================================
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 (DARTER): behold kun rækker fra LPR3-systemet - undgå overlappende rækker
  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 (heldøgn) i LPR2
    lpr3_k  <- lpr3_k  %>% filter(kont_type == "ALCA00")     # ALCA00 = fysisk fremmøde (IKKE = indlagt) - se "Klassificér patienttype" nedenfor
  }

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

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

  # LPR3
  lpr3_dx <- lpr3_k %>%
    semi_join(tibble(pnr = pnr_vector), by = "pnr") %>%
    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, c_diagtype = diag_kode_type),
      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("sti/til/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 | c_diagtype | 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, "sti/til/extract_dementia.rds")

Klassificér patienttype: indlagt, ambulant eller skadestue

Mange studier skal skelne indlagte kontakter fra ambulante besøg og skadestue-besøg (fx kun indlæggelser som udfald, eller akutte kontakter som markør). Der findes ikke ét fælles felt på tværs af LPR2 og LPR3 - du udleder typen.

Warning

Kolonnenavne og koder skal verificeres mod dit eget udtræk. Selve logikken er holdbar (koderne ALCA00 og ATA1 er bekræftede LPR3-værdier pr. 2025), men de præcise kolonnenavne i LPR_A kan afvige fra eksemplet. Tjek med arrow::schema()/colnames() og slå op i Register-overblik. Mønstret er tilpasset fra Plana-Ripoll-gruppens kode på OSF.

LPR2 (frem til marts 2019). Patienttypen ligger i c_pattype, men skadestue-kodningen ændrede sig omkring 2014:

lpr2_type <- lpr_adm %>%                        # lpr_adm fra udtrækket ovenfor
  mutate(patienttype = case_when(
    c_pattype %in% c("0", "1", "4", "5") ~ "indlagt",    # heldøgn/deldøgn/dag/nat
    c_pattype == "3"                     ~ "skadestue",  # eksplicit skadestue (mest før 2014)
    c_pattype == "2" & c_indm == "1"     ~ "skadestue",  # fra ~2014: akut indskrivningsmåde = skadestue
    c_pattype == "2"                     ~ "ambulant",
    TRUE                                 ~ NA_character_
  ))

LPR3 (marts 2019 og frem). Der er ingen direkte c_pattype. kont_type-koden ALCA00 betyder fysisk fremmøde (ikke indlæggelse), og prioritet-koden ATA1 betyder akut. En forskningstilgang er at udlede patienttypen af kontaktens varighed plus en skadestue-/akut-markør:

lpr3_type <- lpr3_k %>%                          # lpr3_k fra udtrækket ovenfor
  filter(kont_type == "ALCA00") %>%              # behold fysiske fremmøder; drop telefon/video
  mutate(
    varighed_timer = as.numeric(
      difftime(kont_sluttidspunkt, kont_starttidspunkt, units = "hours")  # verificér slut-kolonnens navn
    ),
    patienttype = case_when(
      varighed_timer >= 8                                  ~ "indlagt",    # >= 8 timer ~ indlæggelse
      enhedstype_ans == "skadestue" & prioritet == "ATA1"  ~ "skadestue",  # akut skadestue
      TRUE                                                 ~ "ambulant"
    )
  )
Note

8-timers-grænsen er en heuristik, ikke en officiel definition - LPR3 har ikke et “indlagt”-felt. Vælg og dokumentér din egen grænse, og afklar med din datamanager. Kolonnenavnene for start-/sluttidspunkt og enhedstype varierer mellem leverancer; verificér dem før brug.


Rens uønskede diagnoser

Et par koder er administrative artefakter snarere end patientens egen sygdom og bør typisk fjernes fra både udfald og komorbiditet:

  • “Rask ledsager” (en person indlagt som ledsager til en anden, fx en forælder): ICD-10 DZ763. Beslægtede kontakt-/observationskoder uden sygdom: DZ032, DZ038, DZ039.
  • “Diagnose ikke fundet”/uoplyst fra ICD-8-æraen: Y719.
alle_dx <- alle_dx %>%
  filter(
    !substr(toupper(c_diag), 1, 5) %in% c("DZ763", "DZ032", "DZ038", "DZ039"),
    substr(toupper(c_diag), 1, 4) != "Y719"
  )
Note

Hvad der skal renses, afhænger af projekt og spørgsmål - DZ03* (observation pga. mistanke) er fx relevant i nogle studier og bør beholdes der. Mønstret er tilpasset fra Plana-Ripoll-gruppens kode på OSF; verificér mod dine egne data.


Næste skridt

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

Fase 12 - Saml & klargør datasættet

Se også

Back to top