in longitudinal randomised controlled trials
Tuesday, the 19th of March, 2024
Regarding the analysis of longitudinal RCT data there is a debate going on whether an adjustment for the baseline value of the outcome variable should be made. When an adjustment is made, there is a lot of misunderstanding regarding the way this should be done. Therefore, the aims to:
to explain different methods used to estimate treatment effects in RCTs
to illustrate the different methods with a real data example
to give an advise on how to analyse RCT data
Effectiveness of a long-term homocysteine-lowering treatment with folic acid plus pyridoxine in reducing systolic blood.
In this 24 months , randomized, placebo-controlled trial, a baseline measurement and two follow-up measurements (after 12 and after 24 months) were performed. At each time-point systolic blood pressure was measured four times and the average value was used in the analysis.
add_by_n <- function(data, variable, by, tbl, ...) {
data %>%
select(all_of(c(variable, by))) %>%
dplyr::group_by(.data[[by]]) %>%
dplyr::summarise_all(~sum(!is.na(.))) %>%
rlang::set_names(c("by", "variable")) %>%
dplyr::left_join(
tbl$df_by %>% select(by, by_col),
by = "by"
) %>%
mutate(
by_col = paste0("add_n_", by_col),
variable = style_number(variable)
) %>%
select(-by) %>%
tidyr::pivot_wider(names_from = by_col,
values_from = variable)}
tbl_summary(data = dat_twisk_w %>% mutate(TRT01P = fct_rev(TRT01P)),
by = TRT01P ,
include = starts_with('SYS_'),
digit = list(everything() ~ c(1,1)),
statistic = list(all_continuous() ~ "{mean} ({sd})"),
missing = 'no',
label = list( SYS_0 ~ 'Week 0 (Randomization)',
SYS_1 ~ 'Week 12',
SYS_2 ~ 'Week 24')) %>%
add_stat( fns = everything() ~ add_by_n ) %>%
add_difference(test = list(all_continuous() ~ "t.test"),
pvalue_fun = ~style_pvalue(.x, digits = 2),
estimate_fun = list(all_continuous() ~ function(x) style_number(x, digits=1)) ) %>%
bold_labels() %>%
modify_column_merge(pattern = "{estimate} ({ci})") %>%
modify_header(
label = '',
estimate = '**DIFF (95% CI)**',
all_stat_cols(FALSE) ~ "**{level}**<br>n = {n}",
starts_with("add_n_stat") ~ "**n**") %>%
modify_table_body(
~ .x %>%
dplyr::relocate(add_n_stat_1, .before = stat_1) %>%
dplyr::relocate(add_n_stat_2, .before = stat_2)
) %>%
modify_spanning_header(
c(all_stat_cols(F), starts_with("add_n_stat")) ~ "**Treatment**") %>%
as_gt() %>%
gtsave('tbl/tbl_1.png', zoom = 4)
knitr::include_graphics('tbl/tbl_1.png')
pacman::p_load(gtreg)
tbl_reg_summary(data = dat_twisk_w %>% mutate(TRT01P = fct_rev(TRT01P)),
by = TRT01P ,
include = starts_with('SYS_'),
label = list( SYS_0 ~ 'Week 0 (Randomization)',
SYS_1 ~ 'Week 12',
SYS_2 ~ 'Week 24'),
digits = everything() ~ c(0, 1, 1, 0, 0, 0, 0, 0) ) %>%
add_overall() %>%
bold_labels() %>%
modify_header(
label = '',
all_stat_cols(TRUE) ~ "**{level}**" ) %>%
modify_spanning_header(
c(all_stat_cols(F), starts_with("add_n_stat")) ~ "**Treatment**")%>%
as_gt() %>%
gtsave('tbl/tbl_2.png', zoom = 3)
knitr::include_graphics('tbl/tbl_2.png')
ggplot(data = dat_twisk_l %>% drop_na(AVAL),
aes(x = AVISITN, y = AVAL, color = TRT01P)) +
geom_quasirandom(dodge.width = 0.5, alpha = 0.5, size = 2) +
scale_colour_brewer(palette = "Set1",
name = 'Treatment') +
scale_y_continuous(name = 'Systolic Blood Pressure (mmHg)',
limits = c(80, 200)) +
scale_x_continuous(name = '',
breaks = c(0, 1, 2),
labels = c('Week 0\nRandomization', 'Week 12', 'Week 24')) +
theme_bw(base_size = 18) +
theme(panel.grid.minor = element_blank(),
legend.position = c(0.99, 0.99),
legend.justification = c('right','top'))
m <- mmrm(AVAL ~ TRT01P * AVISIT + us(AVISIT | USUBJID), data = dat_twisk_l)
e <- emmeans(m, revpairwise ~ TRT01P | AVISIT, adjust = 'none') %>%
as.data.frame() %>%
filter(contrast != '.') %>%
data.frame()
f_a <- ggplot(data = dat_twisk_l %>% drop_na(AVAL),
aes(x = AVISITN, y = AVAL, color = TRT01P)) +
geom_quasirandom(dodge.width=1, alpha = 0.2, size = 2.5, show.legend = FALSE) +
geom_boxplot(aes(group = interaction(AVISITN, TRT01P)),
fill = 'transparent',
outlier.shape = NA,
show.legend = FALSE) +
scale_colour_brewer(palette = "Set1",
name = 'Treatment') +
stat_summary(fun = mean,
geom = "point",
size = 3,
position = position_dodge(width = 1)) +
scale_y_continuous(name = 'Systolic Blood Pressure (mmHg)',
limits = c(80, 200)) +
scale_x_continuous(name = NULL,
breaks = c(0, 1, 2),
labels = c('Week 0 (R)', 'Week 12', 'Week 24')) +
theme_bw(base_size = 14) +
theme(panel.grid.minor = element_blank(),
legend.position = c(0.99, 0.99),
legend.justification = c('right','top'))
f_b <- ggplot(data = e,
aes(x = AVISIT, y = emmean, group = contrast)) +
geom_hline(yintercept = 0, color = 'gray50') +
geom_point(size = 4) +
geom_line(linewidth = 1) +
geom_linerange(aes(ymin = lower.CL,
ymax = upper.CL),
size = 1.5) +
geom_text(aes( y = -14.5,
label = gtsummary::style_pvalue(p.value, prepend_p = TRUE) ) ) +
scale_color_discrete(name = 'Treatment') +
scale_y_continuous(name = 'DIFF (95% CI)',
limits = c(-15, 2)) +
scale_x_discrete(name = '') +
theme_bw(base_size = 14) +
theme(panel.grid.minor = element_blank(),
axis.text.x=element_blank())
f_a / f_b + plot_layout( heights = c(2, 1))
ggplot(data = dat_twisk_l %>% drop_na(AVAL),
aes(x = AVISIT, y = AVAL, color = TRT01P , group = USUBJID )) +
geom_quasirandom(dodge.width = 0.5, alpha = 0.5, size = 2.5) +
geom_line(alpha = 0.5,
position=position_quasirandom(dodge.width = 0.5)) +
facet_wrap(. ~ TRT01P ) +
scale_colour_brewer(palette = "Set1",
name = 'Treatment') +
scale_y_continuous(name = 'Systolic Blood Pressure (mmHg)',
limits = c(80, 200)) +
labs(x = NULL) +
theme_bw(base_size = 18) +
theme(panel.grid.minor = element_blank(),
legend.position = c(0.99, 0.99),
legend.justification = c('right','top'))
Trelliscope is an open source project that provides tools for data scientists to build and share interactive collections of visualizations for detailed data exploration and more.
pacman::p_load(trelliscope)
summary_df <- dat_twisk_l %>%
select(USUBJID, TRT01P, AVISITN, AVISIT, AVAL, CHG) %>%
drop_na(AVAL) %>%
summarise(
mean_AVAL = mean(AVAL),
sd_AVAL = sd(AVAL),
max_AVAL = max(AVAL),
mean_CHG = mean(CHG),
sd_CHG = sd(CHG),
max_CHG = max(CHG),
.by = c(USUBJID, TRT01P))
panels_df <- (
ggplot(dat_twisk_l %>% drop_na(AVAL) %>% mutate(TRT01P = factor(TRT01P, levels = c('TRT', 'CNT'))),
aes(x = AVISIT, y = AVAL, color = TRT01P, group = USUBJID) ) +
geom_point(size = 4) +
geom_line(linewidth = 1) +
scale_colour_manual(values = c( 'TRT' = '#377eb8',
'CNT' = '#e41a1c') ) +
scale_y_continuous(name = 'Systolic Blood Pressure (mmHg)',
limits = c(80, 200)) +
scale_x_discrete(name = NULL) +
theme_bw(base_size = 14) +
theme(legend.position = "none",
panel.grid.minor = element_blank()) +
facet_panels( vars(USUBJID, TRT01P), scales = 'same') ) %>%
as_panels_df()
trellis_df <- left_join(summary_df,
panels_df,
by = c("USUBJID", "TRT01P")) %>%
as_trelliscope_df(
name = "Explore Individual Subject Data",
path = './_trelliscope',
description = "Effectiveness of a long-term homocysteine-lowering treatment
with folic acid plus pyridoxine in reducing systolic blood")%>%
set_default_labels(c("USUBJID", "TRT01P")) %>%
set_default_layout(ncol = 3, page = 1) %>%
set_var_labels(
mean_AVAL = 'Mean SBP') %>%
set_default_sort(c("sd_AVAL"), dirs = "desc") %>%
set_tags(
stats = c("mean_AVAL","sd_AVAL","max_AVAL","mean_CHG","sd_CHG","max_CHG"),
info = c("USUBJID", "TRT01P")) %>%
view_trelliscope()
knitr::include_app('_trelliscope/index.html', height = "900px")
pacman::p_load(scatterPlotMatrix)
d <- dat_twisk_w[,-c(1:2)] %>%
relocate(TRT01P, .after = last_col()) %>%
na.omit()
scatterPlotMatrix(data = d,
zAxisDim = 'TRT01P',
categoricalCS = "Set1",
slidersPosition = list(
dimCount = 4,
xStartingDimIndex = 2,
yStartingDimIndex = 2 ),
columnLabels = c('W0', 'W12', 'W24','TRT'),
distribType = 1,
regressionType = 2,
plotProperties = list( noCatColor= "#43665e",
watermarkColor = "#ddd",
point = list( alpha = 0.5, radius = 3 ),
regression = list( strokeWidth = 1 ) ),
corrPlotType = "Text",
width = '1000px', height = '1000px')
\[Y_{t} = \beta_0 + \beta_1X + \beta_2Y_{t0}\]
$emmeans
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 127 1.10 119 125 129 115.409 <.0001
TRT 124 1.13 117 121 126 109.409 <.0001
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
$contrasts
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -3.69 1.59 118 -6.83 -0.548 -2.325 0.0218
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
AVAL | TRT01P | BASE | USUBJID | AVISIT | |
---|---|---|---|---|---|
1 | 129 | TRT | 124 | S-00002 | Week 12 |
2 | 126 | TRT | 124 | S-00002 | Week 24 |
3 | 132 | CNT | 119 | S-00003 | Week 12 |
4 | 114 | CNT | 119 | S-00003 | Week 24 |
5 | 115 | CNT | 113 | S-00004 | Week 12 |
6..248 | |||||
249 | 118 | TRT | 131 | S-00152 | Week 24 |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 49 | 6.91 | 7.02 | 35, 62 | <0.001 |
BASE | 0.61 | 0.052 | 11.7 | 0.51, 0.72 | <0.001 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -3.7 | 1.59 | -2.33 | -6.8, -0.55 | 0.022 |
AIC = 1,864; BIC = 1,873; Log-likelihood = -929; Deviance = 1,858 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID AVISIT (ref = first) TRT01P (ref = first);
model AVAL = TRT01P BASE / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P / diff cl;
run;
\[Y_{t} = \beta_0 + \beta_1X + \beta_2Y_{t0} + \beta_3time + \beta_4X\times time\]
$emmeans
AVISIT = Week 12:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 129 1.30 127 126 131 98.565 <.0001
TRT 124 1.34 127 121 127 92.235 <.0001
AVISIT = Week 24:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 126 1.35 115 123 128 93.294 <.0001
TRT 123 1.36 114 120 126 90.192 <.0001
Confidence level used: 0.95
$contrasts
AVISIT = Week 12:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -4.58 1.88 128 -8.30 -0.858 -2.434 0.0163
AVISIT = Week 24:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -2.68 1.92 115 -6.49 1.132 -1.393 0.1664
Confidence level used: 0.95
AVAL | TRT01P | BASE | AVISIT | USUBJID | |
---|---|---|---|---|---|
1 | 129 | TRT | 124 | Week 12 | S-00002 |
2 | 126 | TRT | 124 | Week 24 | S-00002 |
3 | 132 | CNT | 119 | Week 12 | S-00003 |
4 | 114 | CNT | 119 | Week 24 | S-00003 |
5 | 115 | CNT | 113 | Week 12 | S-00004 |
6..248 | |||||
249 | 118 | TRT | 131 | Week 24 | S-00152 |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 50 | 6.98 | 7.17 | 36, 64 | <0.001 |
AVISIT | |||||
Week 12 | — | — | — | — | |
Week 12 | — | — | — | — | |
Week 24 | -2.9 | 1.46 | -1.97 | -5.8, 0.02 | 0.052 |
BASE | 0.61 | 0.052 | 11.7 | 0.51, 0.72 | <0.001 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -4.6 | 1.88 | -2.43 | -8.3, -0.86 | 0.016 |
TRT01P * AVISIT | |||||
TRT * Week 24 | 1.9 | 2.08 | 0.914 | -2.2, 6.0 | 0.36 |
AIC = 1,855; BIC = 1,863; Log-likelihood = -924; Deviance = 1,849 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID AVISIT (ref = first) TRT01P (ref = first);
model AVAL = TRT01P BASE AVISIT AVISIT*TRT01P/ solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans AVISIT*TRT01P / slicediff=AVISIT diff cl;
run;
\[Y_{t} = \beta_0 + \beta_1X + \beta_2time + \beta_3time\times X\]
$emmeans
ABLFL = N:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 128 1.55 133 125 132 82.619 <.0001
TRT 122 1.59 132 119 126 77.020 <.0001
ABLFL = Y:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 130 1.80 137 127 134 72.239 <.0001
TRT 126 1.84 137 123 130 68.592 <.0001
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
$contrasts
ABLFL = N:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -6.07 2.22 133 -10.47 -1.68 -2.732 0.0072
ABLFL = Y:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -3.89 2.58 137 -8.99 1.21 -1.508 0.1338
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 128 | 1.55 | 82.6 | 125, 132 | <0.001 |
ABLFL | |||||
N | — | — | — | — | |
Y | 1.8 | 1.30 | 1.42 | -0.73, 4.4 | 0.16 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -6.1 | 2.22 | -2.73 | -10, -1.7 | 0.007 |
TRT01P * ABLFL | |||||
TRT * Y | 2.2 | 1.86 | 1.17 | -1.5, 5.9 | 0.24 |
AIC = 3,009; BIC = 3,027; Log-likelihood = -1,498; Deviance = 2,997 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
class USUBJID AVISIT (ref = first) ABLFL (ref = first) TRT01P (ref = first);
model AVAL = TRT01P ABLFL ABLFL*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P*ABLFL / slicediff=ABLFL diff cl;
run;
\[Y_{t} = \beta_0 + \beta_1X + \beta_2dummy\_time_1 + \beta_3dummy\_time_2 +\\ \beta_4dumm\_time_1\times X + \beta_5dummy\_time_2 \times X\]
$emmeans
AVISIT = Week 0:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 131 1.81 137 127 134 72.003 <.0001
TRT 127 1.85 137 123 130 68.229 <.0001
AVISIT = Week 12:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 130 1.76 134 127 134 74.083 <.0001
TRT 123 1.80 135 119 127 68.204 <.0001
AVISIT = Week 24:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 127 1.68 129 124 130 75.574 <.0001
TRT 122 1.71 127 119 125 71.349 <.0001
Confidence level used: 0.95
$contrasts
AVISIT = Week 0:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -4.15 2.59 137 -9.28 0.984 -1.598 0.1123
AVISIT = Week 12:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -7.09 2.52 134 -12.07 -2.117 -2.819 0.0055
AVISIT = Week 24:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -5.23 2.40 128 -9.97 -0.482 -2.180 0.0311
Confidence level used: 0.95
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 131 | 1.81 | 72.0 | 127, 134 | <0.001 |
AVISIT | |||||
Week 0 | — | — | — | — | |
Week 12 | -0.63 | 1.44 | -0.441 | -3.5, 2.2 | 0.66 |
Week 24 | -3.5 | 1.56 | -2.27 | -6.6, -0.45 | 0.025 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -4.1 | 2.59 | -1.60 | -9.3, 0.98 | 0.11 |
TRT01P * AVISIT | |||||
TRT * Week 12 | -2.9 | 2.06 | -1.43 | -7.0, 1.1 | 0.16 |
TRT * Week 24 | -1.1 | 2.22 | -0.486 | -5.5, 3.3 | 0.63 |
AIC = 2,999; BIC = 3,017; Log-likelihood = -1,494; Deviance = 2,987 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
class USUBJID AVISIT (ref = first) TRT01P (ref = first);
model AVAL = TRT01P AVISIT AVISIT*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P*AVISIT / slicediff=AVISIT diff cl;
run;
\[Y_{t} = \beta_0 + \beta_1time + \beta_2time\times X\]
$emmeans
ABLFL = 1:
TRT01P emmean SE df asymp.LCL asymp.UCL z.ratio p.value
CNT nonEst NA NA NA NA NA NA
TRT nonEst NA NA NA NA NA NA
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
$contrasts
ABLFL = 1:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
CNT - TRT -3.7 1.57 118 -6.81 -0.59 -2.356 0.0201
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
AVAL | ABLFL | USUBJID | AVISIT | TRT01P | |
---|---|---|---|---|---|
1 | 124 | 0 | S-00002 | Week 0 | TRT |
2 | 129 | -1 | S-00002 | Week 12 | TRT |
3 | 126 | -1 | S-00002 | Week 24 | TRT |
4 | 119 | 0 | S-00003 | Week 0 | CNT |
5 | 132 | -1 | S-00003 | Week 12 | CNT |
6..387 | |||||
388 | 118 | -1 | S-00152 | Week 24 | TRT |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 128 | 1.29 | 99.1 | 126, 131 | <0.001 |
ABLFL | 1.1 | 1.21 | 0.905 | -1.3, 3.5 | 0.37 |
ABLFL * TRT01P | |||||
ABLFL * TRT | 3.7 | 1.57 | 2.36 | 0.59, 6.8 | 0.020 |
AIC = 3,015; BIC = 3,033; Log-likelihood = -1,501; Deviance = 3,003 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
class USUBJID TRT01P (ref = first);
model AVAL = ABLFLN ABLFLN*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
run;
\[Y_{t} = \beta_0 + \beta_1dummytime_1 + \beta_2dummytime_2 + \\ \beta_3dummytime_1 \times X + \beta_4dummytime_2 \times X\]
m2d <- mmrm(AVAL ~ I(AVISIT == "Week 12") + I(AVISIT == "Week 24") +
I(AVISIT == "Week 12" & TRT01P == "TRT") + I(AVISIT == "Week 24" & TRT01P == "TRT") +
us(AVISIT|USUBJID),
data = dat_twisk_l )
summary(m2d)$coefficient %>%
as.data.frame() %>%
rownames_to_column() %>%
select(-df) %>%
gt(rowname_col = "rownames") %>%
fmt_number(columns = 2:4, decimals = 2) %>%
fmt(columns = 5,
fns = function(x) gtsummary::style_pvalue(x, digits = 2)) %>%
cols_align_decimal() %>%
cols_label(rowname = '')
Estimate | Std. Error | t value | Pr(>|t|) | |
---|---|---|---|---|
(Intercept) | 128.65 | 1.30 | 98.63 | <0.001 |
I(AVISIT == "Week 12")TRUE | 0.07 | 1.37 | 0.05 | 0.96 |
I(AVISIT == "Week 24")TRUE | −2.64 | 1.46 | −1.81 | 0.072 |
I(AVISIT == "Week 12" & TRT01P == "TRT")TRUE | −4.38 | 1.86 | −2.36 | 0.020 |
I(AVISIT == "Week 24" & TRT01P == "TRT")TRUE | −2.90 | 1.91 | −1.52 | 0.13 |
AVAL | I(AVISIT == "Week 12") | I(AVISIT == "Week 24") | I(AVISIT == "Week 12" & TRT01P == "TRT") | I(AVISIT == "Week 24" & TRT01P == "TRT") | USUBJID | AVISIT | |
---|---|---|---|---|---|---|---|
1 | 124 | FALSE | FALSE | FALSE | FALSE | S-00002 | Week 0 |
2 | 129 | TRUE | FALSE | TRUE | FALSE | S-00002 | Week 12 |
3 | 126 | FALSE | TRUE | FALSE | TRUE | S-00002 | Week 24 |
4 | 119 | FALSE | FALSE | FALSE | FALSE | S-00003 | Week 0 |
5 | 132 | TRUE | FALSE | FALSE | FALSE | S-00003 | Week 12 |
6..387 | |||||||
388 | 118 | FALSE | TRUE | FALSE | TRUE | S-00152 | Week 24 |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 129 | 1.30 | 98.6 | 126, 131 | <0.001 |
I(AVISIT == "Week 12" & TRT01P == "TRT") | |||||
FALSE | — | — | — | — | |
TRUE | -4.4 | 1.86 | -2.36 | -8.1, -0.71 | 0.020 |
I(AVISIT == "Week 12") | |||||
FALSE | — | — | — | — | |
TRUE | 0.07 | 1.37 | 0.049 | -2.6, 2.8 | 0.96 |
I(AVISIT == "Week 24" & TRT01P == "TRT") | |||||
FALSE | — | — | — | — | |
TRUE | -2.9 | 1.91 | -1.52 | -6.7, 0.87 | 0.13 |
I(AVISIT == "Week 24") | |||||
FALSE | — | — | — | — | |
TRUE | -2.6 | 1.46 | -1.81 | -5.5, 0.24 | 0.072 |
AIC = 3,006; BIC = 3,023; Log-likelihood = -1,497; Deviance = 2,994 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
class USUBJID TRT01P (ref = first);
model AVAL = Week_12 Week_24 Week_12*TRT01P Week_24*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
run;
\[Y_{t} - Y_{t0} = \beta_0 + \beta_1X\]
$emmeans
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT -1.85 1.31 120 -4.44 0.74 -1.415 0.1598
TRT -3.81 1.34 118 -6.47 -1.15 -2.835 0.0054
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
$contrasts
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -1.96 1.88 119 -5.67 1.75 -1.044 0.2984
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | -1.9 | 1.31 | -1.41 | -4.4, 0.74 | 0.16 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -2.0 | 1.88 | -1.04 | -5.7, 1.8 | 0.30 |
AIC = 1,906; BIC = 1,914; Log-likelihood = -950; Deviance = 1,900 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID TRT01P (ref = first);
model CHG = TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P / diff cl;
run;
\[Y_{t} - Y_{t0} = \beta_0 + \beta_1X + \beta_2 time + \beta_3X \times time\]
$emmeans
AVISIT = Week 12:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT -0.642 1.44 128 -3.50 2.216 -0.444 0.6575
TRT -3.381 1.49 128 -6.33 -0.434 -2.270 0.0249
AVISIT = Week 24:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT -3.563 1.57 112 -6.68 -0.447 -2.265 0.0254
TRT -4.379 1.60 110 -7.54 -1.216 -2.744 0.0071
Confidence level used: 0.95
$contrasts
AVISIT = Week 12:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -2.739 2.07 128 -6.84 1.37 -1.320 0.1891
AVISIT = Week 24:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -0.817 2.24 111 -5.26 3.62 -0.364 0.7162
Confidence level used: 0.95
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | -0.64 | 1.44 | -0.444 | -3.5, 2.2 | 0.66 |
AVISIT | |||||
Week 12 | — | — | — | — | |
Week 12 | — | — | — | — | |
Week 24 | -2.9 | 1.46 | -1.99 | -5.8, -0.02 | 0.048 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -2.7 | 2.07 | -1.32 | -6.8, 1.4 | 0.19 |
TRT01P * AVISIT | |||||
TRT * Week 24 | 1.9 | 2.08 | 0.923 | -2.2, 6.1 | 0.36 |
AIC = 1,896; BIC = 1,905; Log-likelihood = -945; Deviance = 1,890 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID AVISIT (ref = first) TRT01P (ref = first);
model CHG = TRT01P AVISIT AVISIT*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P*AVISIT / slicediff=AVISIT diff cl;
run;
\[Y_{t} - Y_{t0} = \beta_0 + \beta_1X +\beta_2Y_{t0}\]
$emmeans
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT -1.06 1.10 119 -3.24 1.12 -0.963 0.3373
TRT -4.75 1.13 117 -6.99 -2.52 -4.210 0.0001
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
$contrasts
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -3.69 1.59 118 -6.83 -0.548 -2.325 0.0218
Results are averaged over the levels of: AVISIT
Confidence level used: 0.95
CHG | TRT01P | BASE | USUBJID | AVISIT | |
---|---|---|---|---|---|
1 | 5 | TRT | 124 | S-00002 | Week 12 |
2 | 2 | TRT | 124 | S-00002 | Week 24 |
3 | 13 | CNT | 119 | S-00003 | Week 12 |
4 | -5 | CNT | 119 | S-00003 | Week 24 |
5 | 2 | CNT | 113 | S-00004 | Week 12 |
6..248 | |||||
249 | -13 | TRT | 131 | S-00152 | Week 24 |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 49 | 6.91 | 7.02 | 35, 62 | <0.001 |
BASE | -0.39 | 0.052 | -7.40 | -0.49, -0.28 | <0.001 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -3.7 | 1.59 | -2.33 | -6.8, -0.55 | 0.022 |
AIC = 1,864; BIC = 1,873; Log-likelihood = -929; Deviance = 1,858 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID TRT01P (ref = first);
model CHG = TRT01P BASE / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P / diff cl;
run;
\[Y_{t} - Y_{t0} = \beta_0 + \beta_1X + \beta_2 Y_{t0} + \beta_3X \times time\]
$emmeans
AVISIT = Week 12:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT 0.30 1.30 127 -2.28 2.8808 0.230 0.8187
TRT -4.28 1.34 127 -6.94 -1.6206 -3.184 0.0018
AVISIT = Week 24:
TRT01P emmean SE df lower.CL upper.CL t.ratio p.value
CNT -2.57 1.35 115 -5.24 0.0969 -1.909 0.0588
TRT -5.25 1.36 114 -7.95 -2.5502 -3.851 0.0002
Confidence level used: 0.95
$contrasts
AVISIT = Week 12:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -4.58 1.88 128 -8.30 -0.858 -2.434 0.0163
AVISIT = Week 24:
contrast estimate SE df lower.CL upper.CL t.ratio p.value
TRT - CNT -2.68 1.92 115 -6.49 1.132 -1.393 0.1664
Confidence level used: 0.95
CHG | TRT01P | BASE | AVISIT | USUBJID | |
---|---|---|---|---|---|
1 | 5 | TRT | 124 | Week 12 | S-00002 |
2 | 2 | TRT | 124 | Week 24 | S-00002 |
3 | 13 | CNT | 119 | Week 12 | S-00003 |
4 | -5 | CNT | 119 | Week 24 | S-00003 |
5 | 2 | CNT | 113 | Week 12 | S-00004 |
6..248 | |||||
249 | -13 | TRT | 131 | Week 24 | S-00152 |
Characteristic | Beta | SE1 | Statistic | 95% CI1 | p-value |
---|---|---|---|---|---|
(Intercept) | 50 | 6.98 | 7.17 | 36, 64 | <0.001 |
AVISIT | |||||
Week 12 | — | — | — | — | |
Week 12 | — | — | — | — | |
Week 24 | -2.9 | 1.46 | -1.97 | -5.8, 0.02 | 0.052 |
BASE | -0.39 | 0.052 | -7.39 | -0.49, -0.28 | <0.001 |
TRT01P | |||||
CNT | — | — | — | — | |
TRT | -4.6 | 1.88 | -2.43 | -8.3, -0.86 | 0.016 |
TRT01P * AVISIT | |||||
TRT * Week 24 | 1.9 | 2.08 | 0.914 | -2.2, 6.0 | 0.36 |
AIC = 1,855; BIC = 1,863; Log-likelihood = -924; Deviance = 1,849 | |||||
1 SE = Standard Error, CI = Confidence Interval |
proc glimmix data=dat_twisk_l noclprint = 10;
where AVISITN ^= 0;
class USUBJID AVISIT (ref = first) TRT01P (ref = first);
model CHG = TRT01P BASE AVISIT AVISIT*TRT01P / solution ddfm=satterthwaite;
random _residual_ / subject=USUBJID type=un;
lsmeans TRT01P*AVISIT / slicediff=AVISIT diff cl;
run;
Beta | SE | Statistic | 95% CI | p-value | model | |
---|---|---|---|---|---|---|
(1a) Longitudinal analysis of covariance | −3.7 | 1.59 | −2.33 | (−6.8, −0.5) | 0.022 | AVAL ~ TRT01P + BASE + us(AVISIT | USUBJID) |
(2a) Repeated measures analysis | −6.1 | 2.22 | −2.73 | (−10.5, −1.7) | 0.007 | AVAL ~ TRT01P + ABLFL + ABLFL:TRT01P + us(AVISIT | USUBJID) |
(2c) Repeated measures wo/ treatment | −3.7 | 1.57 | −2.36 | (−6.8, −0.6) | 0.020 | AVAL ~ ABLFL + ABLFL:TRT01P + us(AVISIT | USUBJID) |
(3a) Analysis of changes (not adjusted) | −2.0 | 1.88 | −1.04 | (−5.7, 1.8) | 0.30 | CHG ~ TRT01P + us(AVISIT | USUBJID) |
(3c) Analysis of changes (adjusted) | −3.7 | 1.59 | −2.33 | (−6.8, −0.5) | 0.022 | CHG ~ TRT01P + BASE + us(AVISIT | USUBJID) |
(1a) Longitudinal analysis of covariance |
(3c) Analysis of changes (adjusted) |
---|---|
\[Y_{t} = \beta_0 + \beta_1X + \beta_2Y_{t0}\] | \[Y_{t} - Y_{t0} = \beta_0 + \beta_1X +\beta_2Y_{t0}\] |
\[Y_{t} = \beta_0 + \beta_1X +\beta_2Y_{t0} + Y_{t0}\] | |
\[Y_{t} = \beta_0 + \beta_1X + (1 + \beta_2) Y_{t0}\] | |
\[Y_{t} = 49 - 3.7X + 0.6Y_{t0}\] | \[Y_{t} = 49 - 3.7X - 0.4Y_{t0}\] |
sim_1 <- import('mod/sim/sim_1.rds')
sim_w <- sim_1 %>%
select(-BASE, -CHG) %>%
pivot_wider(names_from = c(MISS, AVISIT),
values_from = c(AVAL) )
add_by_n <- function(data, variable, by, tbl, ...) {
data %>%
select(all_of(c(variable, by))) %>%
dplyr::group_by(.data[[by]]) %>%
dplyr::summarise_all(~sum(!is.na(.))) %>%
rlang::set_names(c("by", "variable")) %>%
dplyr::left_join(
tbl$df_by %>% select(by, by_col),
by = "by"
) %>%
mutate(
by_col = paste0("add_n_", by_col),
variable = style_number(variable)
) %>%
select(-by) %>%
tidyr::pivot_wider(names_from = by_col,
values_from = variable)
}
tbl_summary(data = sim_w %>% mutate(TRT01P = fct_rev(TRT01P)),
by = TRT01P ,
include = starts_with('COMP_'),
digit = list(everything() ~ c(1,1)),
statistic = list(all_continuous() ~ "{mean} ({sd})"),
missing = 'no',
label = list( `COMP_Week 0` ~ 'Week 0 (Randomization)',
`COMP_Week 12` ~ 'Week 12',
`COMP_Week 24` ~ 'Week 24')) %>%
add_stat( fns = everything() ~ add_by_n ) %>%
add_difference(test = list(all_continuous() ~ "t.test"),
pvalue_fun = ~style_pvalue(.x, digits = 2),
estimate_fun = list(all_continuous() ~ function(x) style_number(x, digits=1)) ) %>%
bold_labels() %>%
modify_column_merge(pattern = "{estimate} ({ci})") %>%
modify_header(
label = '',
estimate = '**DIFF (95% CI)**',
all_stat_cols(FALSE) ~ "**{level}**",
starts_with("add_n_stat") ~ "**n**") %>%
modify_table_body(
~ .x %>%
dplyr::relocate(add_n_stat_1, .before = stat_1) %>%
dplyr::relocate(add_n_stat_2, .before = stat_2)
) %>%
modify_spanning_header(
c(all_stat_cols(F), starts_with("add_n_stat")) ~ "**Treatment**") %>%
as_gt() %>%
gtsave('tbl/sim-tbl_comp.png', zoom = 3)
knitr::include_graphics('tbl/sim-tbl_comp.png')
tbl_summary(data = sim_w %>% mutate(TRT01P = fct_rev(TRT01P)),
by = TRT01P ,
include = starts_with('MISS_'),
digit = list(everything() ~ c(1,1)),
statistic = list(all_continuous() ~ "{mean} ({sd})"),
missing = 'no',
label = list( `MISS_Week 0` ~ 'Week 0 (Randomization)',
`MISS_Week 12` ~ 'Week 12',
`MISS_Week 24` ~ 'Week 24')) %>%
add_stat( fns = everything() ~ add_by_n ) %>%
add_difference(test = list(all_continuous() ~ "t.test"),
pvalue_fun = ~style_pvalue(.x, digits = 2),
estimate_fun = list(all_continuous() ~ function(x) style_number(x, digits=1)) ) %>%
bold_labels() %>%
modify_column_merge(pattern = "{estimate} ({ci})") %>%
modify_header(
label = '',
estimate = '**DIFF (95% CI)**',
all_stat_cols(FALSE) ~ "**{level}**",
starts_with("add_n_stat") ~ "**n**") %>%
modify_table_body(
~ .x %>%
dplyr::relocate(add_n_stat_1, .before = stat_1) %>%
dplyr::relocate(add_n_stat_2, .before = stat_2)
) %>%
modify_spanning_header(
c(all_stat_cols(F), starts_with("add_n_stat")) ~ "**Treatment**") %>%
as_gt() %>%
gtsave('tbl/sim-tbl_miss.png', zoom = 3)
knitr::include_graphics('tbl/sim-tbl_miss.png')
pacman::p_load(ggdist)
sim_l <- import('mod/sim/sim_l.rds')
sim_p <- sim_l %>%
group_by(model, MISS) %>%
summarise(power = sum(I(statistic< -1.98)) / n() )
sim_g <- sim_l %>%
group_by(model, MISS) %>%
summarise(statistic = mean(statistic) )
sim_l %>%
ggplot(aes(y = model, x = statistic )) +
geom_vline(xintercept = -2, color = 'gray50') +
stat_halfeye(adjust = 0.70, alpha = 0.50) +
facet_wrap(~ MISS,
labeller = as_labeller(c(COMP='Completed Data', MISS='MCAR (25%)')) ) +
scale_y_discrete(limits = rev,
name = '') +
geom_text(data = sim_p,
vjust = -1.0,
aes(x = 0.85, label = style_percent(power, digits = 0, symbol = TRUE) ) ) +
geom_text(data = sim_g,
vjust = -1.0,
aes(x = statistic, label = style_number(statistic, digits = 1) ) ) +
theme_bw(base_size = 18) +
theme(panel.grid.minor = element_blank(),
axis.text.y = element_text(face = 'bold',size = 10))