Substitutionsanalyser
Kodeeksempler til “hvad nu hvis vi erstattede X med Y” - diætsubstitutionsmodeller på en koblet kohorte
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, ikkeread_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-.dtaMetode 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ødFortolkning 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å
- Fase 12 - Saml & klargør datasættet: hvor det koblede kost + register-datasæt bygges.
- Regression og Tid-til-hændelse: de underliggende modeller (
glm,coxph) og hvordan man tilføjer splines. - Læringsressourcer: henvisning til regressionskalibrering for målefejl i kosteksponeringer.
- Fødevaresubstitution: Ibsen DB, Laursen ASD, Würtz AML, Dahm CC, Rimm EB, Parner ET, Overvad K, Jakobsen MU. “Food substitution models for nutritional epidemiology”, Am J Clin Nutr 2021;113(2):294-303 - partitions- og leave-one-out-modellerne brugt ovenfor, med fortolkning og antagelser. Det supplerende materiale dækker desuden flere udvidelser, som vi ikke gentager her: substitution baseret på ændring mellem to kostmålinger (baseline + ændringsled), effektmodifikation efter baseline-indtag, og modelvalidering (proportionale hazards, linearitet, multikollinearitet mellem de per konstruktion korrelerede fødevareled).
- Substitution og fysisk aktivitet: Mekary RA, Willett WC, Hu FB, Ding EL. “Isotemporal Substitution Paradigm for Physical Activity Epidemiology and Weight Change”, Am J Epidemiol 2009;170(4):519-527 - samme substitutionsidé for fysisk aktivitet: hold den samlede tid konstant og omfordel tid fra én adfærd til en anden.
- Skift af lægemiddelklasse er ikke en substitutionsregression - det er et tidsvarierende, aktiv-komparator-problem. Se Lund JL, Richardson DB, Stürmer T. “The Active Comparator, New User Study Design in Pharmacoepidemiology”, Curr Epidemiol Rep 2015;2(4):221-228, og ram kontrasten kausalt med target-trial-emulering: Hernán MA, Robins JM. “Using Big Data to Emulate a Target Trial When a Randomized Trial Is Not Available”, Am J Epidemiol 2016;183(8):758-764.