Skip to content

Instantly share code, notes, and snippets.

@andrewheiss
Created November 2, 2023 21:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andrewheiss/6b8f7c33b2adb0ecc8df07e6138264c4 to your computer and use it in GitHub Desktop.
Save andrewheiss/6b8f7c33b2adb0ecc8df07e6138264c4 to your computer and use it in GitHub Desktop.
library(tidyverse)
library(lme4)
library(marginaleffects)

# ?ChickWeight
# weight = body weight in grams
# Time = days since birth
# Chick = chick ID
# Diet = one of 4 possible diets


# This is a model similar to what you have
model <- lmer(weight ~ Time + I(Time^2) + Diet*Time + (1 | Chick), data = ChickWeight)
summary(model)
#> Linear mixed model fit by REML ['lmerMod']
#> Formula: weight ~ Time + I(Time^2) + Diet * Time + (1 | Chick)
#>    Data: ChickWeight
#> 
#> REML criterion at convergence: 5442.3
#> 
#> Scaled residuals: 
#>     Min      1Q  Median      3Q     Max 
#> -3.7484 -0.5503 -0.0059  0.5446  3.3252 
#> 
#> Random effects:
#>  Groups   Name        Variance Std.Dev.
#>  Chick    (Intercept) 545.6    23.36   
#>  Residual             608.9    24.68   
#> Number of obs: 578, groups:  Chick, 50
#> 
#> Fixed effects:
#>              Estimate Std. Error t value
#> (Intercept)  41.04289    6.30890   6.506
#> Time          3.63070    0.60860   5.966
#> I(Time^2)     0.14567    0.02621   5.557
#> Diet2        -2.29496   10.47291  -0.219
#> Diet3       -12.67823   10.47291  -1.211
#> Diet4        -0.00622   10.48080  -0.001
#> Time:Diet2    1.84912    0.41691   4.435
#> Time:Diet3    4.66286    0.41691  11.184
#> Time:Diet4    2.93290    0.42224   6.946
#> 
#> Correlation of Fixed Effects:
#>            (Intr) Time   I(T^2) Diet2  Diet3  Diet4  Tm:Dt2 Tm:Dt3
#> Time       -0.413                                                 
#> I(Time^2)   0.272 -0.911                                          
#> Diet2      -0.555  0.091  0.010                                   
#> Diet3      -0.555  0.091  0.010  0.336                            
#> Diet4      -0.556  0.094  0.007  0.336  0.336                     
#> Time:Diet2  0.237 -0.230 -0.021 -0.423 -0.146 -0.146              
#> Time:Diet3  0.237 -0.230 -0.021 -0.146 -0.423 -0.146  0.364       
#> Time:Diet4  0.237 -0.239 -0.008 -0.144 -0.144 -0.423  0.359  0.359
# The time coefficient is split across two different parts: Time and Time^2. You
# have to combine both parts somehow to get one overall slope. You can't
# interpret just one part on its own.

# You can see how the slope isn't consistent across time. It's shallower early
# on, then gets steeper as time progresses.
plot_predictions(model, condition = "Time")

# We can find the slope of that line at specific points, like 5, 10, 15, and 20
# (or any arbitrary points, really) If we feed hypothetical values into the
# model using datagrid(), marginaleffects will feed all the other necessary
# values (means or modes other coefficents, like Diet here)
slopes(model, newdata = datagrid(Time = c(5, 10, 15, 20)), variables = "Time")
#> 
#>  Term Estimate Std. Error    z Pr(>|z|)     S 2.5 % 97.5 % Diet Chick Time
#>  Time     5.09      0.385 13.2   <0.001 129.8  4.33   5.84    1     1    5
#>  Time     6.54      0.253 25.9   <0.001 487.1  6.05   7.04    1     1   10
#>  Time     8.00      0.342 23.4   <0.001 399.1  7.33   8.67    1     1   15
#>  Time     9.46      0.555 17.1   <0.001 214.2  8.37  10.54    1     1   20
#> 
#> Columns: rowid, term, estimate, std.error, statistic, p.value, s.value, conf.low, conf.high, predicted, predicted_hi, predicted_lo, weight, Diet, Chick, Time

# ↑ Those are the coefficients that you want. At 5 days since birth, the effect
# of time is 5.09: a one-day increase in time is associated with a 5 gram
# increase in weight. At 15 days since birth, a one-day increase in time is
# associated with an 8 gram increase in weight; at 20 days, a one-day increase
# leads to 9 grams in weight, and so on. All those slopes are what you'd see in
# the plot of predictions:
plot_predictions(model, condition = "Time") +
  geom_vline(xintercept = c(5, 10, 15, 20), linewidth = 0.5, color = "grey50")

# If you don't want to calculate the slope at specific points, you can find an
# overall average slope across the whole range of the line
avg_slopes(model, variables = "Time")
#> 
#>  Term Estimate Std. Error    z Pr(>|z|)   S 2.5 % 97.5 %
#>  Time      8.7      0.153 56.8   <0.001 Inf   8.4      9
#> 
#> Columns: term, estimate, std.error, statistic, p.value, s.value, conf.low, conf.high
# ↑ Here the average marginal effect is 8.7. Across the average of days since
# birth, a one-day increase in time is associated with 8.7 more grams
# The neat thing about this approach is that you can find marginal effects
# across all sorts of combinations of covariates. In your model you have
# MedianAge---you can use that to look at the slope of days_since for caes with
# a high median age and a low median age, something like this:
# slopes(
#   Model_5, 
#   newdata = datagrid(
#     days_since = c(10, 50, 100),
#     Median_Age = c(30, 60)
#   )
# )

# Or with the chick weights, we can look at the slopes for the different diets:
plot_predictions(model, condition = c("Time", "Diet")) +
  geom_vline(xintercept = c(5, 10, 15, 20), linewidth = 0.5, color = "grey50")

slopes(model, newdata = datagrid(Time = c(5, 10, 15, 20), Diet = unique), variable = "Time")
#> 
#>  Term Estimate Std. Error    z Pr(>|z|)     S 2.5 % 97.5 % Chick Time Diet
#>  Time     5.09      0.385 13.2   <0.001 129.8  4.33   5.84     1    5    1
#>  Time     6.94      0.448 15.5   <0.001 176.9  6.06   7.82     1    5    2
#>  Time     9.75      0.448 21.7   <0.001 345.8  8.87  10.63     1    5    3
#>  Time     8.02      0.450 17.8   <0.001 233.8  7.14   8.90     1    5    4
#>  Time     6.54      0.253 25.9   <0.001 487.1  6.05   7.04     1   10    1
#>  Time     8.39      0.335 25.1   <0.001 458.6  7.74   9.05     1   10    2
#>  Time    11.21      0.335 33.5   <0.001 814.2 10.55  11.86     1   10    3
#>  Time     9.48      0.341 27.8   <0.001 562.9  8.81  10.15     1   10    4
#>  Time     8.00      0.342 23.4   <0.001 399.1  7.33   8.67     1   15    1
#>  Time     9.85      0.401 24.6   <0.001 441.3  9.07  10.64     1   15    2
#>  Time    12.66      0.400 31.6   <0.001 726.6 11.88  13.45     1   15    3
#>  Time    10.93      0.409 26.7   <0.001 520.0 10.13  11.74     1   15    4
#>  Time     9.46      0.555 17.1   <0.001 214.2  8.37  10.54     1   20    1
#>  Time    11.31      0.588 19.2   <0.001 271.0 10.15  12.46     1   20    2
#>  Time    14.12      0.588 24.0   <0.001 420.3 12.97  15.27     1   20    3
#>  Time    12.39      0.597 20.8   <0.001 315.6 11.22  13.56     1   20    4
#> 
#> Columns: rowid, term, estimate, std.error, statistic, p.value, s.value, conf.low, conf.high, predicted, predicted_hi, predicted_lo, weight, Chick, Time, Diet
# ↑ This is some really rich detail! For chicks that are 5 days old on diet 1, a
# one-day increase in time is associated with a 5 gram increase in weight, but
# for chicks that are 5 days old on diet 3, a one-day increase is associated
# with a 9.75 gram increase in weight

# And finally, you can play with the re.form argument and set it to NULL or NA
# for conditional and marginal effects, where you control how to deal with the
# group-specific offsets in intercepts and slopes:
#
# Conditional effect = average chick; re.form = NA
# Marginal effect = chicks on average; re.form = NULL
#
# See this for more: https://www.andrewheiss.com/blog/2022/11/29/conditional-marginal-marginaleffects/

slopes(model, newdata = datagrid(Time = c(5, 10, 15, 20), Diet = unique), variable = "Time", re.form = NA)
#> 
#>  Term Estimate Std. Error    z Pr(>|z|)     S 2.5 % 97.5 % Chick Time Diet
#>  Time     5.09      0.385 13.2   <0.001 129.8  4.33   5.84     1    5    1
#>  Time     6.94      0.448 15.5   <0.001 176.9  6.06   7.82     1    5    2
#>  Time     9.75      0.448 21.7   <0.001 345.8  8.87  10.63     1    5    3
#>  Time     8.02      0.450 17.8   <0.001 233.8  7.14   8.90     1    5    4
#>  Time     6.54      0.253 25.9   <0.001 487.1  6.05   7.04     1   10    1
#>  Time     8.39      0.335 25.1   <0.001 458.6  7.74   9.05     1   10    2
#>  Time    11.21      0.335 33.5   <0.001 814.1 10.55  11.86     1   10    3
#>  Time     9.48      0.341 27.8   <0.001 562.9  8.81  10.15     1   10    4
#>  Time     8.00      0.342 23.4   <0.001 399.1  7.33   8.67     1   15    1
#>  Time     9.85      0.401 24.6   <0.001 441.3  9.07  10.64     1   15    2
#>  Time    12.66      0.400 31.6   <0.001 726.6 11.88  13.45     1   15    3
#>  Time    10.93      0.409 26.7   <0.001 520.0 10.13  11.74     1   15    4
#>  Time     9.46      0.555 17.1   <0.001 214.2  8.37  10.54     1   20    1
#>  Time    11.31      0.588 19.2   <0.001 271.0 10.15  12.46     1   20    2
#>  Time    14.12      0.588 24.0   <0.001 420.4 12.97  15.27     1   20    3
#>  Time    12.39      0.597 20.8   <0.001 315.6 11.22  13.56     1   20    4
#> 
#> Columns: rowid, term, estimate, std.error, statistic, p.value, s.value, conf.low, conf.high, predicted, predicted_hi, predicted_lo, weight, Chick, Time, Diet

Created on 2023-11-02 with reprex v2.0.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment