A voluntary exam-prep workshop for first-year students seems to help exam performance, but the programme coordinator wants to know whether that apparent benefit just stands in for how much a student studies and how well a student sleeps.
You have anonymized records for 150 first-year students: weekly study hours, a sleep-quality score, workshop attendance, and exam score. Build a regression model that answers this in stages, and check that it can be trusted before reporting it.
data/exam_performance_lab.csv contains 150 rows, one per
student.
| Variable | Type | Description |
|---|---|---|
| student_id | Integer | Anonymous student identifier, 1-150 |
| study_hours | Continuous | Self-reported hours spent studying for this exam in the two weeks before it |
| sleep_quality | Continuous | Self-rated sleep quality over the same period, on a 1-10 scale (1 = very poor, 10 = excellent) |
| workshop | Categorical (2 levels) | Whether the student attended the exam-prep workshop: “Workshop” or “No Workshop” |
| exam_score | Continuous | Score on the standardized introductory exam, 0-100 |
This practical works through simple regression, multiple regression, dummy coding, and testing whether a continuous predictor’s effect differs across a categorical predictor’s levels (an interaction), using the LINE diagnostics from the lecture. Throughout, \(k\) denotes the number of predictors in a given model.
Work through the tasks below in order. Each part builds on the model
from the part before it. Use
data/exam_performance_lab.csv.
Part A – Getting to know the data
study_hours,
sleep_quality, and exam_score, separately for
each workshop group.exam_score against
study_hours, coloring or marking the points by
workshop group. Describe, in a sentence or two, what the
plot suggests about the relationship between study hours and exam score,
and about whether that relationship looks similar in the two
groups.study_hours and
sleep_quality. Why does this correlation matter for how you
interpret the multiple regression models in Parts C and D?Part B – Research question 1: does weekly study time predict exam performance?
exam_score
from study_hours (Model 1). Report the intercept, the
slope, and \(R^2\).study_hours statistically significant?
Report the test statistic, the degrees of freedom, and the \(p\)-value, and state your conclusion.Part C – Research question 2: does sleep quality explain exam performance beyond study time?
sleep_quality to the model (Model 2). Report the
new coefficients and \(R^2\).sleep_quality was added? Test whether this increase is
statistically significant, showing the two models being compared, their
residual sums of squares (or \(R^2\)
values) and degrees of freedom, the resulting \(F\)-statistic, and the \(p\)-value. Do not simply quote software
output without showing how the pieces fit together.Part D – Research question 3: is workshop attendance associated with exam performance, controlling for study time and sleep quality?
workshop to the model as a dummy-coded predictor
(Model 3). State explicitly which category is coded 0 and which is coded
1, and what the model’s intercept now represents given that coding.workshop, its significance
test, and the increase in \(R^2\) over
Model 2 (with the supporting \(F\)-test, as in Question 8).study_hours and
sleep_quality has the larger standardized effect on
exam_score? Why is a standardized coefficient not
calculated, or interpreted, in quite the same way for the
workshop dummy variable?Part E – Checking the model
student_id. Is this
observation unduly influential on the regression results? Support your
answer with an appropriate influence statistic, not the residual
alone.Part F – Research question 4: does the benefit of study time depend on workshop attendance? (moderation)
study_hours and workshop (Model 4). Report the
coefficients, and test whether adding the interaction improves the model
significantly over Model 3.study_hours on exam_score separately for
students who did and did not attend the workshop. Explain, in
substantive terms a programme coordinator would understand, what these
two slopes mean.Part G – Reporting
This walkthrough works through the exam-performance scenario from start to finish: understanding the problem, inspecting the data, choosing a method, conducting the analysis, examining the output, interpreting it, and reporting it. The reasoning matters more than the numbers themselves.
The programme coordinator’s real concern has three layers. First, does study time predict exam performance at all. Second, does sleep quality add anything once study time is accounted for. Third, and most importantly, is workshop attendance associated with exam performance once study time and sleep quality are already in the model, and does the workshop’s apparent benefit hold up, or even grow, once we allow for the possibility that the workshop changes how much studying helps a student rather than simply adding a flat bonus. Each of these is a separate research question, and each should be answered by adding exactly one thing to the model and asking what changed.
d <- read.csv("data/exam_performance_lab.csv", stringsAsFactors = TRUE)
d$workshop <- relevel(d$workshop, ref = "No Workshop")
str(d)
## 'data.frame': 150 obs. of 5 variables:
## $ student_id : int 1 2 3 4 5 6 7 8 9 10 ...
## $ study_hours : num 5.7 9.2 4.8 7.6 4.3 8.9 8.6 8.8 10.7 8.2 ...
## $ sleep_quality: num 2.9 6.4 8.5 8 5.7 4.7 7 4.3 5.1 6.6 ...
## $ workshop : Factor w/ 2 levels "No Workshop",..: 1 1 2 1 1 1 2 1 1 1 ...
## $ exam_score : num 67.5 71.7 62 73.7 51.4 62.3 100 73.6 76.4 78.9 ...
d %>%
group_by(workshop) %>%
summarise(n = n(),
study_hours_M = mean(study_hours), study_hours_SD = sd(study_hours),
sleep_quality_M = mean(sleep_quality), sleep_quality_SD = sd(sleep_quality),
exam_score_M = mean(exam_score), exam_score_SD = sd(exam_score)) %>%
kable()
| workshop | n | study_hours_M | study_hours_SD | sleep_quality_M | sleep_quality_SD | exam_score_M | exam_score_SD |
|---|---|---|---|---|---|---|---|
| No Workshop | 84 | 7.955 | 2.751 | 6.204 | 1.664 | 66.45 | 10.73 |
| Workshop | 66 | 8.114 | 3.088 | 6.370 | 1.372 | 82.35 | 14.36 |
The two groups look similar on study hours and sleep quality, but very different on exam score. That is exactly the pattern that makes the coordinator’s question worth asking properly: a raw difference in exam score of that size is not, on its own, evidence that the workshop caused anything, since students were not randomly assigned to attend it.
ggplot(d, aes(study_hours, exam_score, color = workshop)) +
geom_point(alpha = 0.75) +
scale_color_manual(values = c("No Workshop" = "grey40", "Workshop" = ntnublue)) +
labs(x = "Weekly study hours", y = "Exam score",
title = "Study hours and exam score, by workshop attendance", color = NULL)
The scatterplot shows a positive relationship in both groups, with the workshop group sitting visibly above the no-workshop group across most of the range of study hours.
cor(d$study_hours, d$sleep_quality)
## [1] 0.1638
This is a small correlation. It means study_hours and
sleep_quality are close to independent sources of
information about exam performance, so entering both into the same model
should not create serious collinearity, and each coefficient in Model 2
and beyond can be read as close to what it would be if the other
predictor were absent. This will be confirmed formally with a variance
inflation factor (VIF) below.
Every research question here concerns a continuous outcome
(exam_score), and the questions build on each other by
adding one predictor at a time and asking whether it changes the
picture. That is exactly the setting for a sequence of nested multiple
regression models, compared using the \(R^2\) change and its associated \(F\)-test at each step. The categorical
predictor (workshop) enters as a single dummy variable,
since it has only two levels. The fourth question, about whether the
study-hours effect differs by workshop attendance, calls for a product
(interaction) term between a continuous predictor and a dummy
predictor.
m1 <- lm(exam_score ~ study_hours, data = d)
summary(m1)
##
## Call:
## lm(formula = exam_score ~ study_hours, data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -28.838 -7.888 -0.377 7.863 26.285
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 47.522 2.752 17.3 <2e-16 ***
## study_hours 3.231 0.323 10.0 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 11.4 on 148 degrees of freedom
## Multiple R-squared: 0.404, Adjusted R-squared: 0.4
## F-statistic: 100 on 1 and 148 DF, p-value: <2e-16
confint(m1)
## 2.5 % 97.5 %
## (Intercept) 42.083 52.961
## study_hours 2.593 3.869
Every additional weekly study hour is associated with a 3.23-point increase in predicted exam score, on average. Study hours alone accounts for 40.4% of the variance in exam scores in this sample (\(R^2\) = 0.404). This is \(k = 1\) predictor.
m2 <- lm(exam_score ~ study_hours + sleep_quality, data = d)
summary(m2)
##
## Call:
## lm(formula = exam_score ~ study_hours + sleep_quality, data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -27.25 -7.07 1.18 7.29 24.96
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 38.661 4.314 8.96 1.3e-15 ***
## study_hours 3.093 0.321 9.64 < 2e-16 ***
## sleep_quality 1.589 0.603 2.63 0.0094 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 11.2 on 147 degrees of freedom
## Multiple R-squared: 0.431, Adjusted R-squared: 0.423
## F-statistic: 55.6 on 2 and 147 DF, p-value: <2e-16
confint(m2)
## 2.5 % 97.5 %
## (Intercept) 30.1360 47.186
## study_hours 2.4585 3.727
## sleep_quality 0.3963 2.781
anova(m1, m2)
## Analysis of Variance Table
##
## Model 1: exam_score ~ study_hours
## Model 2: exam_score ~ study_hours + sleep_quality
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 148 19259
## 2 147 18391 1 867 6.93 0.0094 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Adding sleep quality significantly improves the model (\(\Delta R^2\) = 0.027). Note that the
study_hours coefficient barely moved, from 3.23 to 3.09:
because study_hours and sleep_quality are only
weakly correlated, controlling for sleep quality does not meaningfully
change what study hours has to say about exam score. This is worth
stating explicitly, because it is a common misreading: study hours does
not “stop mattering” once sleep quality is in the model; its
coefficient, its standard error, and its significance are all still
essentially what they were in Model 1. Sleep quality is simply a second,
largely independent predictor of exam performance.
workshop is dummy-coded with “No Workshop” as the
reference category (coded 0) and “Workshop” as the comparison category
(coded 1), so the model intercept now represents the predicted exam
score for a student who did not attend the workshop, has zero study
hours, and zero sleep quality (an extrapolation beyond the observed
data, as is often true of a raw intercept, but still the correct
algebraic reading of the term).
m3 <- lm(exam_score ~ study_hours + sleep_quality + workshop, data = d)
summary(m3)
##
## Call:
## lm(formula = exam_score ~ study_hours + sleep_quality + workshop,
## data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -35.55 -5.39 0.29 5.59 19.02
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 33.938 3.202 10.60 <2e-16 ***
## study_hours 3.043 0.236 12.89 <2e-16 ***
## sleep_quality 1.340 0.445 3.01 0.003 **
## workshopWorkshop 15.189 1.356 11.20 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 8.23 on 146 degrees of freedom
## Multiple R-squared: 0.694, Adjusted R-squared: 0.687
## F-statistic: 110 on 3 and 146 DF, p-value: <2e-16
confint(m3)
## 2.5 % 97.5 %
## (Intercept) 27.6092 40.267
## study_hours 2.5761 3.509
## sleep_quality 0.4613 2.218
## workshopWorkshop 12.5090 17.869
anova(m2, m3)
## Analysis of Variance Table
##
## Model 1: exam_score ~ study_hours + sleep_quality
## Model 2: exam_score ~ study_hours + sleep_quality + workshop
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 147 18391
## 2 146 9892 1 8500 125 <2e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
Holding study hours and sleep quality constant, students who attended the workshop scored, on average, 15.19 points higher than students who did not. This is the coordinator’s key result: the workshop is associated with a large and highly significant difference in exam performance even after accounting for how much a student studied and how well a student slept, which addresses the original worry that the workshop’s apparent benefit was really just a stand-in for those two things.
lm.beta(m3)
##
## Call:
## lm(formula = exam_score ~ study_hours + sleep_quality + workshop,
## data = d)
##
## Standardized Coefficients::
## (Intercept) study_hours sleep_quality workshopWorkshop
## NA 0.5984 0.1401 0.5138
Standardizing the continuous predictors puts study_hours
and sleep_quality on a comparable scale: study hours has by
far the larger standardized effect on exam score. lm.beta()
still returns a number for workshop, but it is not
meaningfully interpreted the same way as the continuous predictors’
standardized coefficients, because standardizing a 0/1 dummy variable
rescales it by its own standard deviation, which depends only on the
split between groups rather than on any meaningful unit of “amount of
workshop.” The unstandardized coefficient – a group difference in points
– is already the natural, interpretable scale for a categorical
predictor and should be reported as such.
A model is not ready to report until the assumptions behind its \(p\)-values and confidence intervals have been checked.
par(mfrow = c(2, 2))
plot(m3)
par(mfrow = c(1, 1))
durbinWatsonTest(m3)
## lag Autocorrelation D-W Statistic p-value
## 1 -0.01892 2.009 0.96
## Alternative hypothesis: rho != 0
shapiro.test(resid(m3))
##
## Shapiro-Wilk normality test
##
## data: resid(m3)
## W = 0.98, p-value = 0.02
vif(m3)
## study_hours sleep_quality workshop
## 1.028 1.030 1.003
| Assumption | Check used | Verdict |
|---|---|---|
| Linearity | Residuals vs. Fitted | No systematic curve; reasonable |
| Independence | Durbin-Watson statistic | Close to 2; reasonable |
| Normality of residuals | Normal Q-Q plot; Shapiro-Wilk test | Mild departure in the tail; check the printed \(p\)-value |
| Homoscedasticity (equal variance) | Third diagnostic panel (Scale-Location) | Spread of residuals looks roughly constant across fitted values; reasonable |
If the Shapiro-Wilk test comes out significant, look at the Q-Q plot before concluding the model is unusable: with \(N = 150\), this test is sensitive enough to flag small departures that have little practical effect on the coefficients or their standard errors, especially when the departure traces to a bounded outcome (exam scores capped at 100) rather than a genuinely misspecified model. The variance inflation factors confirm what the small pairwise correlation already suggested: multicollinearity is not a concern in this model.
d$resid_m3 <- resid(m3)
d %>% arrange(resid_m3) %>%
select(student_id, study_hours, sleep_quality, workshop, exam_score, resid_m3) %>%
head(1) %>%
kable()
| student_id | study_hours | sleep_quality | workshop | exam_score | resid_m3 |
|---|---|---|---|---|---|
| 106 | 3.5 | 5.8 | Workshop | 32 | -35.55 |
cooks <- cooks.distance(m3)
sort(cooks, decreasing = TRUE)[1:5]
## 106 77 3 1 128
## 0.16078 0.06314 0.02831 0.02713 0.02389
A large residual is worth a second look, but a large residual is not the same thing as an influential point. Compare the largest Cook’s distance above against the conventional concern threshold of 1.0: this student’s exam score is unusual, but removing this single observation would not materially change the conclusions drawn from Model 3.
m4 <- lm(exam_score ~ study_hours * workshop + sleep_quality, data = d)
summary(m4)
##
## Call:
## lm(formula = exam_score ~ study_hours * workshop + sleep_quality,
## data = d)
##
## Residuals:
## Min 1Q Median 3Q Max
## -32.03 -4.45 0.03 5.20 18.24
##
## Coefficients:
## Estimate Std. Error t value Pr(>|t|)
## (Intercept) 38.785 3.430 11.31 < 2e-16 ***
## study_hours 2.280 0.325 7.01 8.3e-11 ***
## workshopWorkshop 3.090 3.895 0.79 0.42890
## sleep_quality 1.537 0.434 3.54 0.00054 ***
## study_hours:workshopWorkshop 1.502 0.455 3.30 0.00122 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
##
## Residual standard error: 7.97 on 145 degrees of freedom
## Multiple R-squared: 0.715, Adjusted R-squared: 0.707
## F-statistic: 91 on 4 and 145 DF, p-value: <2e-16
confint(m4)
## 2.5 % 97.5 %
## (Intercept) 32.0060 45.563
## study_hours 1.6369 2.922
## workshopWorkshop -4.6085 10.789
## sleep_quality 0.6788 2.396
## study_hours:workshopWorkshop 0.6022 2.402
anova(m3, m4)
## Analysis of Variance Table
##
## Model 1: exam_score ~ study_hours + sleep_quality + workshop
## Model 2: exam_score ~ study_hours * workshop + sleep_quality
## Res.Df RSS Df Sum of Sq F Pr(>F)
## 1 146 9892
## 2 145 9201 1 691 10.9 0.0012 **
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
With an interaction in the model, study_hours and
workshop no longer have simple, standalone interpretations;
each is now a conditional effect. The study_hours
coefficient is the simple slope of study hours specifically for students
who did not attend the workshop (the reference group), and the
workshop coefficient is the group difference specifically
at study_hours = 0, which is not a substantively meaningful
value here. This is a common point of confusion: it would be wrong to
conclude from this table that “workshop attendance no longer matters,”
since the workshop’s effect is now spread across the main-effect term
and the interaction term together.
b <- coef(m4)
slope_no_workshop <- b[["study_hours"]]
slope_workshop <- b[["study_hours"]] + b[["study_hours:workshopWorkshop"]]
c(No_Workshop = slope_no_workshop, Workshop = slope_workshop)
## No_Workshop Workshop
## 2.280 3.782
sleep_mean <- mean(d$sleep_quality)
newdat <- expand.grid(
study_hours = seq(min(d$study_hours), max(d$study_hours), length.out = 100),
workshop = levels(d$workshop),
sleep_quality = sleep_mean
)
newdat$predicted <- predict(m4, newdata = newdat)
ggplot(newdat, aes(study_hours, predicted, color = workshop)) +
geom_line(linewidth = 1.2) +
scale_color_manual(values = c("No Workshop" = "grey40", "Workshop" = ntnublue)) +
labs(x = "Weekly study hours", y = "Predicted exam score",
title = "Predicted exam score by study hours and workshop attendance",
subtitle = "Sleep quality held at its sample mean", color = NULL)
Each additional weekly study hour is worth noticeably more for a student who attended the workshop than for one who did not. The workshop does not just add a flat bonus to every student’s score; it appears to make studying itself more effective, which is a substantively richer and, for a programme coordinator deciding whether to keep funding the workshop, more useful finding than a flat group difference alone.
Taken together, the four models answer the coordinator’s question in stages. Study time predicts exam performance on its own. Sleep quality adds a further, largely independent contribution. Workshop attendance is associated with substantially higher exam scores even after both of those are held constant, ruling out the concern that the workshop’s apparent benefit was just a proxy for already-motivated, well-rested students. Model 4 goes one step further and shows that the workshop’s benefit is not simply additive: it appears to amplify the payoff from studying, a pattern a flat group-difference model would have missed entirely.
Weekly study hours significantly predicted exam performance (Model 1). Sleep quality added a small but significant additional contribution (Model 2). Attendance at a voluntary exam-preparation workshop was associated with substantially higher exam scores after accounting for study hours and sleep quality (Model 3), and this association held even though workshop attenders and non-attenders did not differ appreciably in study hours or sleep quality, which argues against the possibility that the workshop’s benefit was solely attributable to those two factors. A significant study-hours by workshop interaction (Model 4) indicated that the benefit of additional study time was larger for students who attended the workshop than for those who did not. Because workshop attendance was voluntary rather than randomly assigned, these results are consistent with, but do not by themselves establish, a causal benefit of the workshop.
Expected answers to the Questions above. The reasoning for each is in the Guided Walkthrough section; this section states the answers directly, reusing the models already fitted there.
Part A
Part B
summary(m1) output above for the
exact \(t\), \(df\), and \(p\).Part C
anova(m1, m2) output above for the residual sums of
squares, \(df\), \(F\), and \(p\) underlying this test – do not just
quote the final numbers, show the comparison.study_hours coefficient in
Model 2 is almost identical to its value in Model 1, and it remains
highly significant. Study hours continues to matter once sleep quality
is controlled for; the two predictors are only weakly correlated, so
adding sleep quality does not meaningfully change what study hours
contributes.Part D
summary(m3) and
anova(m2, m3) output above for the test statistics and
\(\Delta R^2\).lm.beta(m3) output above. Study hours has the
larger standardized effect. A standardized coefficient is not
interpreted the same way for workshop, because
standardizing a 0/1 dummy divides by a standard deviation that depends
only on the size of the two groups, not on any meaningful unit of
“workshop”; the unstandardized group difference is already the natural
way to report a categorical predictor’s effect.Part E
Part F
study_hours:workshop – see the
summary(m4) and anova(m3, m4) output above.
The interaction significantly improves the model.No_Workshop value in exam points; for students who did
attend, it is worth the (larger) Workshop value. The
workshop does not just add a flat score bonus, it appears to make each
hour of studying more productive.Part G
Study hours, sleep quality, and attendance at a voluntary exam-preparation workshop were entered in a sequence of regression models predicting exam score. All three predictors were independently associated with higher exam performance, and workshop attendance remained a large, significant predictor even after accounting for study hours and sleep quality, arguing against the possibility that its apparent benefit was solely a proxy for already better-prepared or better-rested students. A significant interaction further indicated that the benefit of additional study time was greater among workshop attenders than non-attenders. Because students self-selected into the workshop rather than being randomly assigned, these findings are consistent with a benefit of the workshop but cannot, on their own, establish that attending the workshop caused the improvement in exam scores; unmeasured differences between the kinds of students who choose to attend and those who do not (for example, motivation or exam anxiety) remain a plausible alternative explanation.
Common errors and points to watch for
workshop
main-effect coefficient as “the” workshop effect, rather than
recognizing it as the workshop effect specifically at
study_hours = 0.sessionInfo()
## R version 4.4.2 (2024-10-31)
## Platform: aarch64-apple-darwin20
## Running under: macOS 26.6.2
##
## Matrix products: default
## BLAS: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRblas.0.dylib
## LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
##
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
##
## time zone: Europe/Oslo
## tzcode source: internal
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] knitr_1.51 car_3.1-5 carData_3.0-6 lm.beta_1.7-3 lubridate_1.9.5 forcats_1.0.1 stringr_1.6.0
## [8] dplyr_1.2.0 purrr_1.2.1 readr_2.2.0 tidyr_1.3.2 tibble_3.3.1 ggplot2_4.0.2 tidyverse_2.0.0
## [15] pacman_0.5.1
##
## loaded via a namespace (and not attached):
## [1] gtable_0.3.6 jsonlite_2.0.0 compiler_4.4.2 tidyselect_1.2.1 jquerylib_0.1.4 scales_1.4.0
## [7] yaml_2.3.12 fastmap_1.2.0 R6_2.6.1 labeling_0.4.3 generics_0.1.4 Formula_1.2-5
## [13] tzdb_0.5.0 bslib_0.10.0 pillar_1.11.1 RColorBrewer_1.1-3 rlang_1.1.7 stringi_1.8.7
## [19] cachem_1.1.0 xfun_0.57 sass_0.4.10 S7_0.2.1 otel_0.2.0 timechange_0.4.0
## [25] cli_3.6.5 withr_3.0.2 magrittr_2.0.4 digest_0.6.39 grid_4.4.2 xtable_1.8-8
## [31] rstudioapi_0.18.0 hms_1.1.4 lifecycle_1.0.5 vctrs_0.7.2 evaluate_1.0.5 glue_1.8.0
## [37] farver_2.1.2 abind_1.4-8 rmarkdown_2.30 tools_4.4.2 pkgconfig_2.0.3 htmltools_0.5.9