Substitutionsanalyser

Kodeeksempler til “hvad nu hvis vi erstattede X med Y” - diætsubstitutionsmodeller på en koblet kohorte

Published

July 21, 2026

Under udvikling. Dette er kodeeksempler, ikke en metodegennemgang - se referencen nederst for teori, antagelser og fortolkning.

Substitutionsanalyse besvarer et spørgsmål, som almindelig regression ikke kan: hvad sker der, hvis vi erstatter én enhed af A med én enhed af B, mens totalen holdes konstant? A og B kan fx være fødevarer (A = rødt kød, B = fisk) eller lægemidler (A = NSAID, B = paracetamol).

Det giver mening, når eksponeringen er en samtidig komposition - delene findes på samme tid og er bundet af en helhed, så du ikke kan tilføje mere af én uden at fjerne noget fra en anden (fødevarer inden for totalt energiindtag, eller minutter inden for et døgn). Lægemiddelklasser er undtagelsen: skift sker over tid, ikke som en samtidig komposition, så koden nedenfor gælder ikke for dem - se pointen under tabellen.

Kodeeksemplerne nedenfor bruger kost-eksempler, men den samme substitutionsidé overføres til andre kompositionelle eksponeringer. Det, der ændrer sig, er constraint’en (“totalen”) og hvordan du bygger eksponeringen - og for lægemidler overtager studiedesignet helt (se pointen under tabellen):

Diætsubstitution Fysisk aktivitet / tidsforbrug Skift af lægemiddelklasse
Dele fødevarer (g / portioner) minutter i aktivitetstyper lægemiddelklasser (DDD, indløsninger, dage på middel)
“Total” der holdes fast totalt energiindtag samlet tid (1440 min/døgn) samlet behandlingstid / eksponering
Eksponeringstidspunkt typisk baseline-spørgeskema baseline eller gentaget tidsvarierende (LMDB-indløsninger -> episoder)
Naturligt design tværsnit + logistisk/Cox regression / tidsforbrugsmodeller aktiv komparator, ny bruger / target trial

Pointe. Substitutionsregressionen overføres rent til en samtidig komposition, der holdes ved en total - fødevarer inden for totalt energiindtag, aktivitetsminutter inden for et døgn. Lægemidler er anderledes: skift af lægemiddelklasse er en tidsvarierende behandlingsændring, ikke en samtidig komposition, så koden ovenfor gælder ikke (den ville ignorere hvornår folk skifter - immortal time, behandlings-confounder-feedback). Her besvares substitutionsspørgsmålet (“effekten af at bruge lægemiddel B i stedet for A”) af designet: et aktiv-komparator-, ny-bruger-studie, ideelt en target-trial-emulering, hvor B-vs-A-kontrasten i udfaldsmodellen allerede er substitutionseffekten. Partitions-/leave-one-out-koden overføres kun, hvis du reelt har en lægemiddel-komposition - samtidige andele af flere klasser inden for den samlede behandling - hvilket er ualmindeligt.

Kompositionelle data (CoDA). Fysisk aktivitet over et døgn er en lukket komposition (søvn + stillesiddende + let + hård aktivitet = 1440 min), og her bruger man formel kompositionel dataanalyse (CoDA) med log-ratios (fx isometrisk log-ratio, ilr; R-pakken compositions). Kost er derimod ikke strengt lukket - totalt energiindtag varierer mellem personer - så ernæringsepidemiologi bruger de energi-justerede substitutionsmodeller nedenfor frem for formel ilr-CoDA.

Dette ligger i udkanten af guidens scope - læs forbeholdene.

  • DST har ingen kost- (eller fysisk aktivitets-)data. Disse eksponeringer kommer fra en koblet kohorte eller survey (fx et kostspørgeskema), koblet til register-udfald og -kovariater i Fase 12. Koden nedenfor kører på det samlede analysedatasæt - den åbner altså en .rds/.dta- fil, ikke et register (readRDS, ikke read_register).
  • Dette er kodeeksempler, ikke en metodegennemgang. For teori, antagelser og fortolkning, se Ibsen et al. (reference nederst).
  • Estimaterne er modelbaserede. De antager, at du har justeret for den rigtige total (her totalt energiindtag), ingen residual confounding, linearitet og ingen væsentlig målefejl i kostvariablene.

Idéen, samlet ét sted

En substitutionsmodel estimerer effekten af at udskifte én del med en anden, mens totalen holdes konstant. Efter Ibsen et al. 2021 findes der to ækvivalente familier:

  • Leave-one-out (ikke-specificeret) model: læg fødevaren af interesse i modellen sammen med totalt energiindtag og en sammensat total for fødevaregruppen. Dens koefficient er effekten af et højere indtag af den fødevare og et tilsvarende lavere indtag af den udeladte fødevare i gruppen.
  • Partitionsmodel (specificeret): læg alle delfødevarer i modellen plus totalt energiindtag. Substitutionen af fødevare A for fødevare B er så forskellen mellem deres koefficienter (bA − bB), hvis varians er var(bA) + var(bB) − 2·cov(bA, bB).

Begge veje rammer samme estimand - en udskiftning, hvor totalen holdes fast. Partitionsmodellen gør “for hvad?” eksplicit; gjort rigtigt giver de to samme svar.

Opsætning

library(dplyr) # mutate, %>%
# library(haven) # kun hvis den koblede kohorte leverer Stata-filer -> read_dta()

# Det koblede analysedatasæt fra Fase 12: kost (fra kohorten/surveyet) plus
# udfald og kovariater (fra registre), én række per person.
df <- readRDS("sti/til/analysedatasaet.rds")
# df <- haven::read_dta("sti/til/analysedatasaet.dta")  # hvis det er en Stata-.dta

Metode 1 - partition (forskel mellem koefficienter)

Læg hver delfødevare i modellen, og aflæs substitutionen som forskellen mellem to koefficienter. Det er R-modstykket til Statas lincom.

library(dplyr)

# 1. Sæt delfødevarerne på en fælles substitutionsenhed (her per 100 g/dag).
df <- df %>%
  mutate(
    redmeat100 = redmeat_g / 100, # rødt kød, per 100 g/dag
    fish100 = fish_g / 100, # fisk
    poultry100 = poultry_g / 100, # fjerkræ
    mixedmeat100 = mixedmeat_g / 100 # blandet kød
  )

# 2. Partitionsmodel: ALLE delfødevarer + totalt energiindtag i samme model.
#    Justér for dine confoundere (alder, køn, ...); logistisk her, men for et
#    overlevelsesudfald udskift glm() med survival::coxph() - logikken er den samme.
partition <- glm(
  diabetes ~ redmeat100 +
    fish100 +
    poultry100 +
    mixedmeat100 +
    total_energy +
    age +
    sex, # + dit confounder-sæt
  family = binomial(link = "logit"),
  data = df
)

# 3. Substitution af FISK for RØDT KØD = forskellen mellem deres koefficienter.
#    Var(fisk - kød) = Var(fisk) + Var(kød) - 2*Cov(fisk, kød)
b <- coef(partition)
vc <- vcov(partition)

diff_beta <- b["fish100"] - b["redmeat100"] # log-OR for udskiftningen
se_diff <- sqrt(
  vc["fish100", "fish100"] +
    vc["redmeat100", "redmeat100"] -
    2 * vc["fish100", "redmeat100"]
)

# 4. Odds ratio + 95% CI: at erstatte 100 g/dag rødt kød med 100 g/dag fisk.
ci_logit <- diff_beta + c(-1, 1) * qnorm(0.975) * se_diff
cbind(OR = exp(diff_beta), LCL = exp(ci_logit[1]), UCL = exp(ci_logit[2]))

Metode 2 - leave-one-out

Byg en sammensat total for fødevaregruppen, og inkludér hver fødevare undtagen den, du substituerer for. Den udeladte fødevare (her rødt kød) bliver den implicitte reference, så en fødevares koefficient er dens effekt relativt til rødt kød.

library(dplyr)

# 1. Fødevarer per 100 g + en sammensat total for hele gruppen (alle fire fødevarer).
df <- df %>%
  mutate(
    fish100 = fish_g / 100,
    poultry100 = poultry_g / 100,
    mixedmeat100 = mixedmeat_g / 100,
    totalmeat_g = redmeat_g + poultry_g + mixedmeat_g + fish_g, # gruppens total
    meat100 = totalmeat_g / 100
  )

# 2. Leave-one-out-model: sammensat total + hver enkelt fødevare UNDTAGEN den, du
#    substituerer for (rødt kød udelades). Totalt energiindtag justeres stadig for.
leaveoneout <- glm(
  diabetes ~ fish100 +
    poultry100 +
    mixedmeat100 +
    meat100 +
    total_energy +
    age +
    sex, # + dit confounder-sæt
  family = binomial(link = "logit"),
  data = df
)

# 3. fish100 = at erstatte 100 g/dag fisk med 100 g/dag af den udeladte fødevare
#    (rødt kød) - samme udskiftning som Metode 1, så estimaterne bør stemme overens.
OR <- exp(coef(leaveoneout)["fish100"])
CI <- exp(confint(leaveoneout)["fish100", ])
cbind(OR = OR, LCL = CI[1], UCL = CI[2])

Flere udskiftninger på én gang, og overlevelsesudfald

Begge udvidelser bygger på partitionsmodellen fra Metode 1: substitutionen af A for B er altid b_A - b_B, så én tilpasset model kan besvare mange udskiftninger.

En genbrugelig hjælpefunktion laver enhver tilpasset model (glm eller coxph) om til et udskiftnings-estimat med 95% CI:

library(purrr) # map()
library(dplyr) # tibble(), bind_rows()

# Effekten af at udskifte fødevare `a` FOR fødevare `b`, fra en partitionsmodel.
# Returnerer en odds ratio (glm) eller en hazard ratio (coxph) med 95% CI.
swap_effect <- function(model, a, b) {
  est <- coef(model)[a] - coef(model)[b] # forskel mellem koefficienter
  vc <- vcov(model)
  se <- sqrt(vc[a, a] + vc[b, b] - 2 * vc[a, b])
  tibble(
    swap = paste(a, "for", b),
    est = exp(est), # OR (glm) eller HR (coxph)
    lcl = exp(est - qnorm(0.975) * se),
    ucl = exp(est + qnorm(0.975) * se)
  )
}

# Flere fødevarer udskiftet for rødt kød, fra den ENE partitionsmodel (Metode 1):
pairs <- list(
  c("fish100", "redmeat100"),
  c("poultry100", "redmeat100"),
  c("mixedmeat100", "redmeat100")
)
map(pairs, ~ swap_effect(partition, .x[1], .x[2])) %>% bind_rows()

Overlevelsesudfald (Cox). Tilpas den samme partitionsmodel med coxph(), så returnerer hjælpefunktionen hazard ratios i stedet for odds ratios:

library(survival) # Surv(), coxph()

partition_cox <- coxph(
  Surv(followup_years, event) ~ redmeat100 +
    fish100 +
    poultry100 +
    mixedmeat100 +
    total_energy +
    age +
    sex, # + dit confounder-sæt
  data = df,
  ties = "breslow" # Breslow-håndtering af sammenfaldende hændelsestider
)

swap_effect(partition_cox, "fish100", "redmeat100") # HR: 100 g/dag fisk for rødt kød

Fortolkning og finjustering

  • Substitutionsenhed. Estimatet er per udskiftet enhed (her 100 g/dag). Vælg en enhed, der er meningsfuld og realistisk, og rapportér altid både enheden og parringen (“100 g/dag rødt kød erstattet af 100 g/dag fisk”).
  • Hvad justering for totalt energiindtag gør. At holde totalt energiindtag fast er det, der gør “mere fisk” til “mere fisk i stedet for noget andet” i stedet for “mere fisk oven i den nuværende kost”.
  • Ikke-linearitet. Et lineært led antager en konstant udskiftningseffekt over hele intervallet. Hvis det er urimeligt, modellér fødevarerne med splines (splines::ns(), se Regression) og evaluér udskiftningen ved valgte værdier.

Se også

TipLæs mere
Back to top