Byg din studiepopulation
Identificér din studiepopulation — og byg sammenligningskohorte hvis relevant
Alle udtræk i de øvrige faser — udfald (Fase 9), kovariater (Fase 6) og socioøkonomi (Fase 13) — forudsætter at du allerede har en kohorte: en tabel med pnr og index_date per person. Denne side viser, hvordan du bygger den fra bunden.
Kort fortalt: Du bygger din studiepopulation i seks trin — identificér de eksponerede (1), ekskludér prævalente tilfælde (2), byg en matchpool (3), anvend inklusionskriterier (4), match en sammenligningskohorte (5) og gem kohorten (6). Resultatet er en tabel med pnr + index_date per person.
Denne side er stadig under udvikling. Koden skal gennemgås og testes yderligere inden den bruges direkte. Brug den som strukturel vejledning og tilpas til dit eget projekt.
Studiedesignet afgør fremgangsmåden. Denne side viser et aktivt kohortestudie med matching — én eksponeret gruppe og én sammenligningskohorte. Tilpas trin 1 til din eksponeringsdefinition: SKS-kode, ICD-diagnose, ATC-kode, klinisk mål eller andet. Se Fase 1 — hvilken type studie for overblikket over case-control vs. kohorte.
Hvad er index-dato?
Index-dato er det tidspunkt der markerer starten på opfølgning for en given person.
- Eksponerede: den dato personen fik eksponeringen (fx operationsdato, diagnosedato, første receptudstedelse)
- Sammenligningskohorte: den index-dato der er tildelt fra den matchede eksponerede
Alt efterfølgende — udfaldsdato, kovariater ved baseline, opfølgningstid — beregnes relativt til index-dato. Definitionen af index-dato er afgørende for studievaliditeten.
Trin 1 — Identificér de eksponerede
Du scanner det register der definerer din eksponering. Du har endnu ingen kohort_pnrs-liste — du forespørger hele registret og filtrerer på eksponeringskriteriet.
Eksponeringen kan defineres på mange måder:
| Type | Eksempel | Register |
|---|---|---|
| Operation / procedure (SKS-kode) | Bariatrisk kirurgi KJDF10/KJDF11 | lpr_sksopra (LPR2), procedurer_kirurgia (LPR3) |
| Hospitaldiagnose (ICD-kode) | Type 2-diabetes E11 | lpr_diag + lpr_adm, lpr_a_diagnose + lpr_a_kontakt |
| Medicineksponering (ATC-kode) | Metformin A10BA02 | LMDB |
| Klinisk mål / biomarkør | BMI > 35, HbA1c > 75 mmol/mol | Projektspecifikke data / OSDC / DBSO |
a lpr_sksopr og procedurer_kirurgi er navnene på DARTER-projektet (708421) — se Registerreference. Navnene kan variere på andre projekter.
Eksempel A: SKS-koder (operation/procedure)
library(arrow) # open_dataset
library(dplyr) # filter, select, group_by, slice, ungroup, bind_rows, mutate
# Tilpas disse koder til dit studie
RYGB <- c("KJDF10", "KJDF11") # Roux-en-Y gastric bypass
SG <- c("KJDF40", "KJDF41", "KJDF96", "KJDF97") # sleeve gastrectomy
BS_KODER <- c(RYGB, SG) # samlet vektor
# ── LPR2: indgreb frem til 2018/2019 ────────────────────────────────────
lpr_sksopr <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_sksopr/") %>%
rename_with(tolower)
lpr_adm <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_adm/") %>%
rename_with(tolower)
eksp_lpr2 <- lpr_sksopr %>%
filter(c_opr %in% !!BS_KODER) %>% # kun bariatri-indgreb
select(recnum, sks_kode = c_opr) %>% # recnum er join-nøgle til lpr_adm
inner_join(
lpr_adm %>% select(pnr, recnum, index_date = d_inddto), # tilknyt pnr og dato
by = "recnum"
) %>%
collect()
# ── LPR3: indgreb fra 2019 og frem ──────────────────────────────────────
proc_kir <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/procedurer_kirurgi/") %>%
rename_with(tolower)
lpr_a_k <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_a_kontakt/") %>%
rename_with(tolower)
eksp_lpr3 <- proc_kir %>%
filter(procedurekode %in% !!BS_KODER) %>% # kun bariatri-indgreb
select(dw_ek_forloeb, sks_kode = procedurekode) %>%
inner_join(
lpr_a_k %>% select(pnr, dw_ek_forloeb, index_date = kont_starttidspunkt),
by = "dw_ek_forloeb"
) %>%
collect() %>%
mutate(index_date = as.Date(index_date)) # datetime → dato
# ── Kombinér og tag ét indgreb per person (det første) ──────────────────
# Resultatet hedder "eksponerede" — kun den eksponerede gruppe, IKKE den fulde kohorte endnu
eksponerede <- bind_rows(eksp_lpr2, eksp_lpr3) %>%
group_by(pnr) %>% # gruppér per person
arrange(index_date) %>% # ældste dato øverst
slice(1) %>% # ét indgreb per person (det første)
ungroup() %>% # frigiv gruppering (se Fase 11)
mutate(exposed = 1L) # markér som eksponeret (1 = ja)
nrow(eksponerede) # antal unikke opereredeeksponerede indeholder kun de opererede. Den fulde kohorte (eksponerede + sammenligningskohorte) bygges i trin 5 og gemmes som kohort. Det er kohort — ikke eksponerede — du bruger som kohort_pnrs i de øvrige faser.
Eksempel B: ICD-diagnose som eksponeringskriterium
# Samme LPR-mønster som Fase 9 — men uden filter(pnr %in% !!kohort_pnrs),
# da kohorte endnu ikke er bygget. Du søger hele befolkningens data for at finde eksponerede.
lpr_diag <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_diag/") %>%
rename_with(tolower)
lpr_adm <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/lpr_adm/") %>%
rename_with(tolower)
eksponerede <- lpr_adm %>%
inner_join(
lpr_diag %>%
filter(c_diagtype %in% c("A", "B"),
substr(c_diag, 2, 4) == "E11") %>% # T2D: "DE11" → strip D-præfiks
select(recnum, c_diag),
by = "recnum"
) %>%
select(pnr, index_date = d_inddto) %>%
collect() %>%
group_by(pnr) %>%
arrange(index_date) %>%
slice(1) %>% # første diagnose per person
ungroup() %>%
mutate(exposed = 1L)Hvad skal du efter trin 1?
- Prævalensstudie — du har nu din studiepopulation med en index-dato. Spring trin 2–6 over og gå direkte til Fase 6 — Udtræk kovariater og Fase 11 — Saml datasættet.
- Kohortestudie eller nested case-control — fortsæt med trin 2–6 nedenfor.
Bemærk til case-control: din “eksponerede” gruppe i trin 1 er dine cases (dem der fik udfaldet, identificeret fra LPR), ikke en eksponering. Trin 3–6 sampler herefter kontroller — folk uden udfaldet der var i risiko på det tidspunkt casen fik sin diagnose.
Trin 2 — Ekskluder prævalente tilfælde
Ekskluder personer der allerede havde diagnosen inden index-dato — ellers tæller de som nye tilfælde, selvom de ikke er det. Dette trin skal ske inden matching, så sammenligningskohorte ikke samples fra en forurenet pool.
Du skal bruge din udtrukne LPR-diagnosetabel fra Fase 9 — enten alle_dx eller dit direkte udtræk.
Vis kode
EKSKL_KODER <- c("G30", "F00", "F01", "F02", "F03") # skift til dine egne eksklusionskoder
# diagnoser = alle_dx (Tilgang 2) eller dit direkte udtræk (Tilgang 1) fra Fase 9
praevalente <- diagnoser %>%
filter(icd3 %in% EKSKL_KODER) %>%
inner_join(eksponerede %>% select(pnr, index_date), by = "pnr") %>%
filter(date_contact < index_date) %>%
distinct(pnr)
cat("Eksponerede inden ekskludering: ", nrow(eksponerede), "\n")
eksponerede_renset <- eksponerede %>%
anti_join(praevalente, by = "pnr")
cat("Eksponerede efter ekskludering: ", nrow(eksponerede_renset), "\n")
cat("Ekskluderede: ", nrow(eksponerede) - nrow(eksponerede_renset), "\n")Lookback-periode: Overvej at begrænse date_contact < index_date yderligere — fx kun de 5 år inden index — for at undgå falske eksklusioner baseret på meget gamle diagnoser.
filter(date_contact >= index_date - 365*5,
date_contact < index_date)Prævalensekskludering gælder også matchpoolen (trin 3) — ekskluder tilsvarende folk med prævalent udfald inden sampling, ellers matches dine eksponerede til folk der ikke var i risiko.
Trin 3 — Byg matchpool fra BEF
Deltagere til sammenligningskohorten hentes fra BEF — alle i befolkningen der endnu ikke er eksponerede. Tilføj de variable du vil matche på (fødselsår, køn, kalenderår mv.).
Vis kode
bef <- open_dataset("E:/workdata/[projektnummer]/cleaned-data/parquet-registers/bef/") %>%
rename_with(tolower)
matchpool <- bef %>%
filter(aar == 2015) %>% # ét BEF-snapshot — juster til dit studievindue
select(pnr, foed_dag, koen) %>%
collect() %>%
anti_join(eksponerede, by = "pnr") %>% # behold kun pnr der IKKE er i eksponerede
# anti_join = omvendt inner_join: ingen match = behold
# se Fase 11 for fuld forklaring af join-typer
mutate(
foedselsaar = as.integer(format(as.Date(foed_dag), "%Y")), # matchingvariabel
exposed = 0L # markér som potentiel sammenligningsperson
)
nrow(matchpool) # antal potentielle sammenligningspersonerTrin 4 — Anvend inklusionskriterier FØR sampling
Inklusionskriterier skal anvendes FØR matching — ikke efter. Eksklusion af fx personer under 18 år eller ikke-bosiddende i Danmark skal ske på matchpoolen, inden sammenligningskohorte samples. At ekskludere efter matching introducerer systematisk bias, fordi du ændrer den population som de eksponerede er matchet til.
Se: Lund et al., Clinical Epidemiology 2015 for en gennemgang af bias ved forkert eksklusionsrækkefølge.
Vis kode
# Tilknyt BEF-variabler til matchpoolen og anvend inklusionskriterier
bef_baseline <- bef %>%
filter(aar == 2015) %>% # brug samme snapshot-år
select(pnr, alder, opr_land) %>%
collect()
matchpool_renset <- matchpool %>%
inner_join(bef_baseline, by = "pnr") %>%
filter(
alder >= 18, # kun voksne
opr_land == 5100 # dansk bopæl
)
# Anvend de samme kriterier på eksponerede (prævalensekskludering er allerede gjort i Trin 2):
eksponerede_renset <- eksponerede_renset %>%
inner_join(bef_baseline, by = "pnr") %>%
filter(alder >= 18, opr_land == 5100)Trin 5 — Match sammenligningskohorte
heaven::riskSetMatch() implementerer risk-set sampling / incidence-density sampling — standarden i registerbaserede kohorter.
Vis kode
library(heaven) # riskSetMatch
# Kombinér til ét datasæt: exposed = 1 (eksponeret) og exposed = 0 (potentiel sammenligningsperson)
pool <- bind_rows(
eksponerede_renset %>% select(pnr, index_date, exposed, koen, foedselsaar),
matchpool_renset %>% select(pnr, exposed, koen, foedselsaar)
)
# Risk-set matching — 1:5 på køn og fødselsår
# Resultatet "matched" bliver din fulde kohorte: eksponerede + matchede sammenligningspersoner
matched <- riskSetMatch(
ptid = "pnr", # personidentifikator
event = "exposed", # 1 = eksponeret, 0 = potentiel sammenligningsperson
terms = c("koen", "foedselsaar"), # matchingvariable — tilpas til dit studie
dat = pool,
ratio = 5 # op til 5 sammenligningspersoner per eksponeret
)Kronologisk sampling og replacement. riskSetMatch() sampler kronologisk — personer kan bidrage til sammenligningskohorte frem til det tidspunkt de selv eksponeres. Tag eksplicit stilling til om der skal være replacement (en person kan matches til flere eksponerede) eller ej. Replacement øger effektiv stikprøvestørrelse men giver korrelerede observationer — det skal håndteres i analysen. Se Lund et al. 2015 og ?riskSetMatch for detaljer.
riskSetMatch() er forklaret i Fase 14a — Pakker og designvalget bag matching i Fase 1.
Matchede sammenligningspersoner får automatisk tildelt den matchede eksponeredes index-dato.
Mere i dybden — faldgruber ved sammenligningskohorten
- Immortal time bias: en person i sammenligningskohorten skal være i live, bosat i Danmark og opfylde inklusionskriterierne på sin tildelte index-dato — ikke kun ved studiestart.
- Risk-set / incidence-density sampling: en person kan optræde i sammenligningskohorten og senere selv blive eksponeret og blive case. Beslut hvordan du håndterer det (
riskSetMatch()understøtter risk-set-matching). - Matching-ratio: fx 1:5 eksponeret:sammenligningskohorte — flere kontroller giver mere præcision, men aftagende udbytte.
- Samme eksklusioner anvendes på begge grupper, ellers introducerer du selektionsbias.
Trin 6 — Gem din kohorte
# "matched" fra riskSetMatch() er nu din fulde kohorte:
# eksponerede (exposed = 1) + matchede sammenligningspersoner (exposed = 0)
kohort <- matched # omdøb til "kohort" — det er denne du bruger fremover
saveRDS(kohort, "datasets/full_cohort.rds") # gem
# Verificér:
nrow(kohort) # antal personer i alt
table(kohort$exposed) # 0 = sammenligningskohorte, 1 = eksponerede
names(kohort) # hvilke kolonner er med?Hvad nu?
Du har full_cohort.rds — én række per person med pnr og index_date for begge grupper (eksponerede + sammenligningskohorte). Brug den i alle efterfølgende udtræk:
kohort <- readRDS("datasets/full_cohort.rds")
kohort_pnrs <- unique(kohort$pnr) # vektor med alle pnr'er — det er denne der sættes ind
# overalt hvor de øvrige faser bruger "kohort_pnrs"kohort_pnrs indeholder altså pnr’erne for både eksponerede og sammenligningspersoner — ikke kun de eksponerede. Det er vigtigt: udtræk af udfald og kovariater skal dække hele studiegruppen.
| Udtræk | Fase |
|---|---|
| Udfald (diagnoser, dødsdato, emigration) | Fase 9, Fase 11 |
| Kovariater fra BEF (alder, køn) | Fase 6 |
| Socioøkonomiske variable | Fase 13 |
| Komorbiditet (NMI, Charlson) | Fase 14c |
| Saml til ét analysedatasæt | Fase 11 |
Se også
- Fase 1 — Studieforberedelse — designvalg bag kohorte og matching
- Fase 9 — Hospitalskontakter (LPR) — diagnosemønster for eksponeringsidentifikation og prævalensekskludering
- Fase 11 — Joins og pivots — saml alle udtræk til ét datasæt
- Fase 15d — Registerreference — kolonnenavne for lpr_sksopr, procedurer_kirurgi m.fl.
- Lund et al. (2015), Clinical Epidemiology — bias ved forkert eksklusionsrækkefølge i risk-set sampling