Udtræk fra LPR
Praktiske opskrifter - de to tilgange, helper-funktioner og integration med kohorten
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.
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.
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. Bruglpr_a_*som i eksemplerne her, og lad LPR_F-filerne ligge. Nogle projekter har desuden ældre data inde ilpr_a_kontakt, som allerede findes i LPR2; filtrér dem fra medlprindberetningssystem == "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 brugearrow::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 fraread_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.
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.
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 | icd3Hvad 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.
Mange specifikke koder? Byg regex programmatisk:
koder <- c("E10", "E11", "E12", "E13", "E14")
KODER_REGEX <- paste0("^D(", paste(koder, collapse = "|"), ")")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:
- 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. left_joini stedet forinner_join: Beholder alle kontakter fralpr_adm- også dem uden diagnose. Unødvendigt tungt på nationale registre.- 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 | icd3Har 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)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 udfaldEkskludering 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)
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")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 mappePak 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
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.
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"
)
)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"
)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:
Se også
- Forstå LPR: struktur, perioder, D-præfiks og diagnosetyper
- Fase 6 - Første udtræk: trin-for-trin introduktion til open_dataset, collect og saveRDS
- Register-overblik: bekræftede kolonnenavne for alle LPR-registre
- Faldgruber på DST: kendte problemer med LPR på DST