flowchart LR
X[X] --> Y[Y]
Sometimes, we just want to investigate one variable’s effect on an outcome the estimand = smoking on lung cancer
flowchart LR
X[X] --> Y[Y]
\(y ~ x\)
Important
But we know this model is biased! There are known confounding
flowchart LR
X[X] --> Y[Y]
C1[Age] --> Y
C2[Income] --> Y
C3[Education] --> Y
C4[Region] --> Y
C5[...] --> Y
\[ Y = β_0 + β_1Age + β_2Income + β_3Education + β_4Region + β_n... + ε \]
flowchart LR
X((X)) --> Y[Y]
C1[Age] --> Y
C2[Income] --> Y
C3[Education] --> Y
C4[Region] --> Y
C5[...] --> Y
style X stroke:#d62728,stroke-width:4px
No problem! We simply interpret the coefficient of X, keeping all other variables at 0.
Important
What if we add interactions? 😨🫠
We cannot say age have and effect on HD, as this effect is strong in younger age; but the effect dies off at older age.
Often, we don’t care about every possible value of age. We might be interested in a specific age group, or we might be interested in the average effect of all age groups.
Important
This is where marginal effects comes to the rescue 🎉🥳
flowchart LR
X((X)) --> Y[Y]
subgraph Marginalized covariates
C1[Age]
C2[Income]
C3[Education]
C4[Region]
C5[...]
end
C1 --> Y
C2 --> Y
C3 --> Y
C4 --> Y
C5 --> Y
style X stroke:#d62728,stroke-width:4px
Three good way of doing this:
We already learned this for linear regression: What is the predicted max heart rate for:
| Parameter | Coefficient | SE | CI | CI_low | CI_high | t | df_error | p |
|---|---|---|---|---|---|---|---|---|
| (Intercept) | 213.9282010 | 8.5495824 | 0.95 | 197.1032241 | 230.7531779 | 25.022064 | 299 | 0.0000000 |
| Age | -1.0563804 | 0.1600040 | 0.95 | -1.3712570 | -0.7415039 | -6.602215 | 299 | 0.0000000 |
| HDyes, heart disease | -53.8425770 | 14.6636354 | 0.95 | -82.6995802 | -24.9855738 | -3.671844 | 299 | 0.0002852 |
| Age:HDyes, heart disease | 0.6885871 | 0.2627099 | 0.95 | 0.1715925 | 1.2055816 | 2.621093 | 299 | 0.0092130 |
[1] "age50_HD_true = 141.7"
[1] "age50_HD_false = 161.1"
age2use <- 50
age50_HD_true <- as.numeric(
coef(model_interact)[1] + # intercept
coef(model_interact)[2]*age2use + # age
coef(model_interact)[3]*1 + # HD = true
coef(model_interact)[4]*1*age2use # HD = true and age
)
age50_HD_false <- as.numeric(
coef(model_interact)[1] + # intercept
coef(model_interact)[2]*age2use + # age
coef(model_interact)[3]*0 + # HD = false
coef(model_interact)[4]*0*age2use # HD = false
)
Age Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
50 161 1.57 102.4 <0.001 Inf 158 164
50 142 2.15 65.9 <0.001 Inf 137 146
Type: response
predictions() will compute predictionsnewdata to make a new dataset with datagridvariables takes the variable we examineBut is this significantly different? \(E(Y∣HD = yes,Age=50)−E(Y∣HD = no,Age=50)\)
Age Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
50 -19.4 2.66 -7.29 <0.001 41.5 -24.6 -14.2
Term: HD
Type: response
Comparison: yes, heart disease - no heart disease
\(E(Y∣HD = yes,Age=50)−E(Y∣HD = no,Age=50)\)
Important
We need the covariance between the two predicted values to get the correct standard error and p-value. That’s why we should use comparisons() or hypotheses() rather than trying to test it from the two confidence intervals.
Age
52.7862
[,1]
[1,] 14.57242
Suppose your model is
\(η=β0+β1Age+β2HD+β3(Age×HD)\)
and you’re interested in the difference in predictions at Age = 50:
\(g(β)=y^(HD=1,Age=50)−y^(HD=0,Age=50)\)
The delta method says:
\(Var(g(β^))≈∇g(β^)TVar(β^)∇g(β^)\)
The trick is finding the gradient \(∇g\)
The variance is: \(Var(g)=L⊤VL\)
We do this for all combinations of age and HD we have in our data set?
We’d get the average effect of all participants’ age on HD in the current study
Ready for a lot of homework?
# A tibble: 6 × 2
Age HD
<dbl> <fct>
1 63 no heart disease
2 67 yes, heart disease
3 67 yes, heart disease
4 37 no heart disease
5 41 no heart disease
6 56 no heart disease
| rowidcf | Age | no heart disease | yes, heart disease |
|---|---|---|---|
| 1 | 63 | 147.3762 | 136.9146 |
| 2 | 67 | 143.1507 | 135.4435 |
| 3 | 67 | 143.1507 | 135.4435 |
| 4 | 37 | 174.8421 | 146.4773 |
| 5 | 41 | 170.6166 | 145.0061 |
Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
147 2.25 65.4 <0.001 Inf 143 152
143 2.76 51.9 <0.001 Inf 138 149
143 2.76 51.9 <0.001 Inf 138 149
175 2.92 59.9 <0.001 Inf 169 181
171 2.40 71.2 <0.001 Inf 166 175
--- 596 rows omitted. See ?print.marginaleffects ---
144 2.93 49.0 <0.001 Inf 138 149
135 2.89 46.8 <0.001 Inf 129 141
139 1.65 84.3 <0.001 Inf 136 142
139 1.65 84.3 <0.001 Inf 136 142
146 4.22 34.7 <0.001 871.6 138 154
Type: response
HD Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
no heart disease 156 1.55 101.2 <0.001 Inf 153 159
yes, heart disease 140 1.71 81.9 <0.001 Inf 137 143
Type: response
This is the counterfactual estimate, where we predict the maxHR for all participants, had we observed them with and without HD.
# A tibble: 2 × 2
HD n
<fct> <int>
1 no heart disease 164
2 yes, heart disease 139
We could consider
Making equal number of patients with HD
Making age more informative
HD Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
no heart disease 151 1.93 78.2 <0.001 Inf 147 154
yes, heart disease 138 1.79 77.0 <0.001 Inf 135 142
Type: response
Hypothesis Estimate Std. Error z Pr(>|z|) S 2.5 % 97.5 %
b1=b2 12.5 2.63 4.76 <0.001 19.0 7.37 17.7
Type: response
So this model
so we reject the null that HD has no effect on maxHR given the average effect of an age group between 50-70.
However - is this a clinically meaningful difference?
How much functional gain/loss does going from 138 to 151 BMI really mean in this age group?