Register paths and datastores
Confirmed paths and access methods for all registers on project 708421
Check the modification date on cleaned-data before running the pipeline.
file.info("E:/workdata/708421/cleaned-data/parquet-registers/")$mtimeThe registers are not necessarily updated to today. Confirm that coverage matches your study period.
Base paths
# All paths used as constants at the top of scripts
path_parquet_reg <- "E:/workdata/708421/cleaned-data/parquet-registers/"
path_parquet_ext <- "E:/workdata/708421/cleaned-data/parquet-external/"
path_output <- "E:/workdata/708421/workspaces/[yourName]/BS_demens/datasets/"Overview - all registers on project 708421
This overview is written from the project’s own documentation and from what the DARTER team has passed on, last reviewed 2026-05-15. Most registers are updated to end of 2024 as of 2026 (Anders Aasted Isaksen/DARTER team). Column names are shown after rename_with(tolower). Check your own delivery before you rely on any row here.
| Register | Access | Join key | Period | Critical column |
|---|---|---|---|---|
| BEF | read_register("bef") |
pnr |
All years | koen, foed_dag, familie_id |
| DOD | read_sas() from rawdata - see below |
pnr |
1970–2024 | doddato (use this for death) |
| DODSAARS | read_register("dodsaars") |
pnr |
~1970–2001 | d_dodsdto (stops in 2001 - see below) |
| VNDS | read_register("vnds") |
pnr |
All years | indud_kode, haend_dato |
| LPR2 contacts | read_register("lpr_adm") |
recnum |
Up to March 2019 | d_inddto, c_pattype |
| LPR2 diagnoses | read_register("lpr_diag") |
recnum |
Up to March 2019 | c_diag, c_diagtype |
| LPR2 psych contacts | read_register("t_psyk_adm") |
k_recnum → recnum |
1995–March 2019 | v_cpr → pnr |
| LPR2 psych diagnoses | read_register("t_psyk_diag") |
v_recnum → recnum |
1995–March 2019 | c_diag, c_diagtype |
| LPR3 contacts | read_register("lpr_a_kontakt") |
dw_ek_kontakt |
March 2019+ | kont_starttidspunkt (datetime) |
| LPR3 diagnoses | read_register("lpr_a_diagnose") |
dw_ek_kontakt |
March 2019+ | diag_kode, diag_kode_type, senere_afkraeftet |
| LPR3 procedures | read_register("procedurer_kirurgi") |
no working key - see below | 2019+ | procedurekode, dato_start |
| LPR2 procedures | read_register("lpr_sksopr") |
recnum |
1996–2018 | c_opr, d_odto |
| LPR2 examinations | read_register("lpr_sksube") |
recnum |
1999–2018 | none delivered - see below |
| LMDB | read_register("lmdb") |
pnr |
Approx. 1994+ | atc, eksd |
| UDDA | read_register("udda") |
pnr |
All years | hfaudd, year |
| FAIK | read_register("faik") |
familie_id |
All years | famaekvivadisp_13 |
| AKM | read_register("akm") |
pnr |
All years | socio13, year |
| DBSO | read_register("dbso") |
pnr |
2010+ | datoper_prim, surgery flags |
| OSDC | read_register("osdc") |
pnr |
1977–2024 | has_t1d/has_t2d, stable_inclusion_date |
| Laboratory results | read_register("laboratorieproevesvar") |
cprnummer |
2008+ | analysiscode, samplingdate, samplevalue (character) |
Critical notes
lpr_sksube has no procedure code on DARTER. The parquet holds three columns and no more:
colnames(read_register("lpr_sksube") %>% rename_with(tolower))
# "recnum" "d_odto" "year"DST documents this register with c_opr, c_oprart, c_osgh and c_tilopr for every year 1999-2019, the same shape as lpr_sksopr. None of them are here, so the table can tell you that a procedure happened and on what date, but not which procedure. There is no filtering trick that recovers them.
If you need examinations and non-surgical treatments, ask for the columns to be added to the delivery. Surgical procedures are unaffected: lpr_sksopr has c_opr and d_odto as documented.
Deaths - the convenient register is the wrong one. dodsaars is in cleaned-data, so read_register("dodsaars") just works and it is the one you reach for first. It also stops at the end of 2001. dod (Døde i Danmark) is the register that covers deaths, 1970-2025 at DST, but on DARTER it is not in cleaned-data: it sits as a raw SAS file. Use dod and its doddato column for censoring at death, and see pitfall 1 - three death registers for the general version.
You do not need a separate extraction on DARTER: the raw file reads directly.
library(haven) # read_sas() - reads .sas7bdat straight into R
library(dplyr) # rename_with, select, mutate, semi_join, summarise
# DARTER-SPECIFIC PATH (project 708421). On another project the raw files sit
# somewhere else, may be named differently, and access to rawdata can depend on
# your project role - ask your data manager for the path to the death register.
dod <- read_sas("E:/rawdata/708421/Grunddata/dod2024.sas7bdat") %>%
rename_with(tolower)
# This is a whole-population file read straight into RAM, so cut it to your
# cohort as soon as you have one.
deaths <- dod %>%
semi_join(cohort, by = "pnr") %>%
select(pnr, death_date = doddato) %>%
mutate(death_date = as.Date(death_date))
# Check how far back the file actually reaches before you trust it:
deaths %>% summarise(min(death_date), max(death_date))Do the range check. The file is named dod2024, and the row above says 1970-2024, but nobody on the DARTER team has confirmed the start year of this extract. If it turns out to begin in 2001, and your study period reaches further back, you need dodsaars for the early years as well:
dodsaars <- read_sas("E:/rawdata/708421/Grunddata/dodsaars2001.sas7bdat") %>%
rename_with(tolower) %>%
select(pnr, death_date = d_dodsdto) # note: different column name
deaths <- bind_rows(deaths, mutate(dodsaars, death_date = as.Date(death_date))) %>%
distinct(pnr, .keep_all = TRUE) # one death date per personOnly do this if the range check says you have to. If both files cover 1970-2001, stacking them puts every pre-2001 death in twice, and distinct() then keeps whichever came first rather than the one you meant.
Why it matters: run on dodsaars alone and everyone who died after 2001 is treated as alive for the past 25 years. That skews censoring and matching in 01_build_cohorts.R, with no error message. See pitfall 1.
LPR3 - duplicate risk: lpr_a_kontakt and lpr_a_diagnose contain data from two formats (LPR_F and LPR_A). Always filter on lprindberetningssystem == "LPR3". See pitfall 5.
Laboratory results - use only one source: laboratorieproevesvar_ (>2.2 billion rows) replaces lab_forsker/lab_dm_forsker. The old files still exist and cover the same data - use only one to avoid duplicates. Because the register is so large, semi_join(tibble(pnr = cohort$pnr), by = "pnr") and select() before collect() are essential. Two things to watch when extracting:
- Tests are identified by NPU codes in the
analysiscodecolumn - filter on the NPU codes your analysis needs. samplevalueis a character column - it can contain text like “not detected” or “negative”, not just numbers. Convert with care (as.numeric()returnsNAon text values).
See pitfall 7 - Laboratory results for a code example.
procedurer_kirurgi - the pnr join is not solved. This is a project-specific delivery of the LPR3 procedures; it is not a DST register, and it does not appear in DST’s register list. Like every procedure table it carries no pnr: it identifies procedures, not people, so you have to join to a contact table to get one. On DARTER neither route works cleanly.
| Column | Type | Contents |
|---|---|---|
procedurekode |
character | SKS procedure code - match on this (e.g. "KJDF10") |
dato_start |
Date | Procedure date |
dw_ek_kontakt |
character | The intended join key - but NA for every row here (confirmed 2026-06-02) |
dw_ek_forloeb |
character | Course-level key - usable, but many NA |
proceduretype |
character | "P" = procedure, "+" = add-on code |
procedurekode_parent |
character | Parent procedure code |
dato_slut |
Date | Procedure end date |
lprindberetningssystem |
character | LPR reporting system |
procedureregistrering_id |
character | Internal registration ID |
The empty keys are by design, but the pattern here is still wrong. Sundhedsdatastyrelsen’s guide to LPR3 for researchers states that LPR3 has four real join keys - dw_ek_helbredsforloeb, dw_ek_forloeb, dw_ek_kontakt and dw_ek_procedureregistrering - and that in a table where more than one of them could apply, “only the key that is relevant to join on is filled in”. A procedure attaches either to a single contact or to a whole course, and the other key is deliberately left empty.
So NA on one key is not a defect. What does not fit is the proportion: the same guide says there will be “relatively few rows with course procedures” in procedurer_kirurgi, yet here dw_ek_kontakt is NA on every row (confirmed 2026-06-02), which is the opposite of what the register should look like. That points at the extract rather than at LPR3.
The two keys are not the same level either: dw_ek_kontakt points at one contact, dw_ek_forloeb at a whole course, which can span several contacts. Swapping one for the other changes what a row means.
The documented route for course-level procedures, if you do not have the forloeb table, is to join on dw_ek_forloeb in the contacts table. That gets you to a person, but at course level, not contact level.
So there is still no verified way to attach pnr to a single LPR3 procedure through this file. Use lpr_a_procregistrering instead: it is the real DST register, it joins on dw_ek_kontakt, and DST even flags rows with no contact in flag_proc_uden_kont. Column names differ:
procedurer_kirurgi |
lpr_a_procregistrering |
|---|---|
procedurekode |
proc_kode |
dato_start |
proc_starttidspunkt (datetime) |
dw_ek_forloeb |
dw_ek_kontakt (a working join key) |
Confirm lpr_a_procregistrering is in your delivery before you rely on it.
Two more things from the same source, worth knowing before you plan around this table. procedurer_kirurgi is not a DARTER invention: it is Forskerservice’s own table name, and it holds surgical procedures only. Non-surgical ones, including indication codes, are in a separate table called procedurer_andre. And add-on codes get their own rows, with procedurekode_parent and proceduretype_parent saying which procedure they belong to, so counting rows counts add-ons as procedures.
DBSO: The identifier column is cpr in raw parquet - renamed to pnr by 00_prepare_dbso.R. All code uses pnr after that.
OSDC: a classified diabetes population is already computed and lies ready on the project, so you normally do not run the algorithm yourself - but you can, if you need to change the definitions. read_register("osdc") finds it, but only once fastreg has been pointed at the project folders (see loading templates); without that it will not resolve the name. Both routes are described on Diabetes population (OSDC); the package itself is documented at steno-aarhus.github.io/osdc.
The one thing worth knowing here: the output has two inclusion dates, and picking between them is a study decision, not a formality. raw_inclusion_date is the person’s second inclusion event; stable_inclusion_date is the same date but NA when it falls before 1998, where the underlying data is not considered reliable. Filter on the stable one and your early cases disappear without a word.
Loading templates
fastreg replaces dstDataPrep on DARTER. Registers are now loaded with fastreg::read_register("name") - the same function as in the general guide. If you have been using dstDataPrep::load_database(), that code still runs, but write new code with fastreg. You point fastreg at DARTER’s folders once per script - both of them, per fastreg’s own documentation:
options(
fastreg.project_workdata_dir = "E:/workdata/708421/cleaned-data/",
fastreg.project_rawdata_dir = "E:/rawdata/708421/"
)Then read_register("bef") works by name. See “Didn’t convert the data yourself?” in Parquet and fastreg for the general version.
library(fastreg) # read_register() - access to DST registers
library(dplyr) # rename_with, rename, left_join, select
# Standard register - via read_register:
bef <- read_register("bef") %>% rename_with(tolower) # lazy connection; lowercase columns
# Psychiatric LPR2 - requires renaming v_cpr and k_recnum:
psyk_adm <- read_register("t_psyk_adm") %>%
rename_with(tolower) %>% # lowercase columns
rename(pnr = v_cpr, recnum = k_recnum) # v_cpr → pnr; k_recnum → recnum
# DBSO - parquet-external (converted from SAS via 00_prepare_dbso.R):
dbso <- read_register("dbso") %>% rename_with(tolower) # lazy connection
# OSDC - pre-computed diabetes classification (see osdc.qmd):
dm_pop <- read_register("osdc") # pnr, has_t1d, has_t2d, raw_/stable_inclusion_date
# LPR3 procedures - join via dw_ek_forloeb (NOT dw_ek_kontakt - is NA for all rows):
proc <- read_register("procedurer_kirurgi") %>%
rename_with(tolower) %>% # lowercase columns
left_join(
read_register("lpr_a_kontakt") %>%
rename_with(tolower) %>%
select(dw_ek_forloeb, pnr), # fetch pnr via the forloeb key
by = "dw_ek_forloeb" # join key - dw_ek_kontakt does not work
)
# proc is still lazy - add filter() and collect() before useSee also
- Overview of registers: full confirmed column names
- DARTER pitfalls: project-specific issues