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 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.
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.
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.
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.
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 | icd3Mange 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
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 | 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 %>%
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)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 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
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")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 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.
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
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:
Se også
- Fase 9a — 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
- Fase 15 — Registerreference — bekræftede kolonnenavne for alle LPR-registre
- Fase 15 — Faldgruber — kendte problemer med LPR på DST