DARTER pitfalls

Quirks and known issues specific to project 708421

Published

September 9, 2026

This page supplements the general DST pitfalls with issues specific to the DARTER project.

1. Check that parquet files are up to date

Most registers are as of 2026 updated to end of 2024 (confirmed by Anders Aasted Isaksen/Marie Kempf Frydendahl, DARTER team).

# Check when the parquet folder was last updated:
file.info("E:/workdata/708421/cleaned-data/parquet-registers/bef/")$mtime

Deaths are a special case, and it is not about the update date. dodsaars is a closed register: it ends in 2001 and no update will ever extend it. Deaths belong in dod (Døde i Danmark), which runs to 2025. On DARTER dod is not in cleaned-data, so see Register paths and datastores for how to read it, and pitfall 1 - four death registers for the general version.

The same may apply to other registers. Always confirm that coverage matches your study period before running the pipeline.

If the parquet file does not cover your study period: You need to extract data from the raw SAS file on DST. Contact your data manager - they can help with raw data access and conversion.

library(fastreg) # read_register(): opens a parquet register by name
library(dplyr) # the verbs below, and the pipe

# Any register: check that its coverage reaches the end of your follow-up
akm <- read_register("akm") %>% rename_with(tolower) # lazy connection
akm %>% summarise(min(year), max(year)) %>% collect()

Consequence of missing coverage: comparators and BS patients whose events fall after the parquet file’s end date look like nothing happened to them - this affects censoring and matching in 01_build_cohorts.R, with no error message.

2. Surgery and procedures

Procedure codes are split across two registers by period:

  • lpr_sksopr (parquet-registers) - procedures and surgery 1996–2018, joined to lpr_adm via recnum
  • procedurer_kirurgi (parquet-external) - 2019 and onwards, joined to lpr_a_kontakt via dw_ek_forloeb

dw_ek_kontakt is NA for all rows in procedurer_kirurgi (confirmed 2026-06-02). Use dw_ek_forloeb - not dw_ek_kontakt - to fetch pnr from lpr_a_kontakt.

There is a third table: lpr_a_procregistrering. It is the general LPR3 procedure table, it exists on DARTER, and its dw_ek_kontakt is reported to be populated for the large majority of rows - so it joins to lpr_a_kontakt the same way lpr_a_diagnose does, and avoids the problem above entirely. The column names differ (proc_kode, proc_starttidspunkt), so the code is not interchangeable. See Overview of registers for a code example and the checks to run before you rely on it.

# WRONG - dw_ek_kontakt is NA:
proc %>% left_join(contacts, by = "dw_ek_kontakt") # joins nothing

# CORRECT - use dw_ek_forloeb:
proc <- read_register("procedurer_kirurgi") %>%
  rename_with(tolower) %>%
  left_join(
    read_register("lpr_a_kontakt") %>%
      rename_with(tolower) %>%
      select(dw_ek_forloeb, pnr),
    by = "dw_ek_forloeb"
  )

3. lpr_a_diagnose - “a” does not mean A-type diagnoses

The table is called lpr_a_diagnose - “a” refers to the analysis model designation (LPR_A series). It is not a filter on A-type diagnoses. The table contains A, B and G. You still need to filter on diag_kode_type.

4. nmi_countnmi_score

Variable What it is
nmi_score Weighted score - Nordic Multimorbidity Index (50 predictors with individual weights)
nmi_count Simple count of the number of chronic conditions (33 possible)

If you use nmi_count in your Cox model instead of nmi_score, you are adjusting for something different than you think.

5. LPR3 - filter on lprindberetningssystem == "LPR3"

Besides the actual LPR3 reports, lpr_a_kontakt also contains older data that is already present in LPR2 (lpr_adm), loaded into the new LPR_A table. If you combine LPR2 and LPR3 without filtering, the same contacts are counted twice - you get duplicated rows. lprindberetningssystem == "LPR3" keeps only the rows from the LPR3 system (confirmed by Anders Aasted Isaksen, DARTER team 2026).

# CORRECT - filter to LPR_A format only:
lpr3_k <- read_register("lpr_a_kontakt") %>%
  rename_with(tolower) %>%
  filter(lprindberetningssystem == "LPR3") # keep only rows from the LPR3 system - removes overlapping rows

get_lpr_diagnoses() in darter-index.qmd is updated with this filter. If you have copies of LPR3 code in your own scripts, you must add it manually.

6. FAIK changed structure from 2022

FAIK used to hold one row per family per year, which is why the standard recipe joins income to people via familie_id from BEF. From the 2022 data onward it also carries pnr, and the family’s row is repeated once per family member (confirmed by the DARTER team, August 2026).

If you join on familie_id alone across those years, you get one row per family member instead of one per person. Nothing errors, the dataset just grows, and any average computed afterwards is weighted by household size.

# Check before you join - more than one row per family and year means it applies
faik %>%
  count(familie_id, year) %>%
  filter(n > 1) %>%
  count() %>%
  collect()

# Fix: collapse back to one row per household-year before joining
faik <- faik %>%
  distinct(familie_id, year, famaekvivadisp_13)

Full explanation and the alternative (joining on pnr directly) is in Socioeconomic variables.

7. Laboratory results - use laboratorieproevesvar_

The new laboratory data register is called laboratorieproevesvar_ and contains >2.2 billion rows. The old lab_forsker / lab_dm_forsker still exists but covers the same data - use only one source to avoid duplicates.

lab <- read_register("laboratorieproevesvar") %>%
  rename_with(tolower) %>%
  rename(pnr = cprnummer) %>% # the person column is cprnummer here, patient_cpr in the other two tables
  semi_join(tibble(pnr = cohort$pnr), by = "pnr") %>% # filter BEFORE collect - the register is very large
  select(pnr, analysiscode, samplingdate, samplevalue) %>% # NPU is the coding system, analysiscode is the column
  collect()
# samplevalue is character - can contain "not detected", "negative" etc.

See also

Back to top