Skip to content

Commit 591247f

Browse files
author
Daniel Sabanes Bove
committed
code complete for emmeans contrasts helpers
1 parent 2d628fc commit 591247f

File tree

10 files changed

+268
-0
lines changed

10 files changed

+268
-0
lines changed

simulations/missing-data-benchmarks/R/eval/helpers.R

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# get_trt_visit_num_ests ----
2+
13
# extract ATE estimates at each visit from mmrm fit
24
get_mmrm_trt_visit_num_ests <- function(fit) {
35
marginal_means <- emmeans::emmeans(
@@ -55,6 +57,8 @@ get_trt_visit_num_ests <- function(method, fit, dt, converged) {
5557
}
5658
}
5759

60+
# get_convergence ----
61+
5862
# extract convergence status from mmrm fit
5963
get_mmrm_convergence <- function(converged) {
6064
converged
@@ -88,6 +92,7 @@ get_convergence <- function(method, fit, converged) {
8892
}
8993
}
9094

95+
# get_trt_visit_num_ses ----
9196

9297
# extract standard errors for ATE estimates at each visit from mmrm fit
9398
get_mmrm_trt_visit_num_ses <- function(fit) {
@@ -146,6 +151,8 @@ get_trt_visit_num_ses <- function(method, fit, dt, converged) {
146151
}
147152
}
148153

154+
# get_trt_visit_num_pvals ----
155+
149156
# extract p-values for 2-sided hypothesis tests about ATEs at each visit from
150157
# mmrm fit
151158
get_mmrm_trt_visit_num_pvals <- function(fit) {
@@ -207,3 +214,112 @@ get_trt_visit_num_pvals <- function(method, fit, dt, converged) {
207214
rep(NA, 10) # hard code 10, the number of visits in all simulations
208215
}
209216
}
217+
218+
# get_emmeans_output ----
219+
220+
# compute the 95% CI from emmeans output
221+
get_95_ci <- function(emmeans_df) {
222+
emmeans_df %>%
223+
mutate(
224+
lower = estimate - 1.96 * SE,
225+
upper = estimate + 1.96 * SE,
226+
)
227+
}
228+
229+
format_emmeans_df <- function(emmeans_df) {
230+
emmeans_df %>%
231+
transmute(
232+
visit_num = visit_num,
233+
estimate = estimate,
234+
stderr = SE,
235+
df = df,
236+
tvalue = t.ratio,
237+
pvalue = p.value,
238+
lower = lower,
239+
upper = upper
240+
)
241+
}
242+
243+
# extract model fits output at each visit from mmrm fit
244+
get_mmrm_emmeans_output <- function(fit) {
245+
# extract emmeans output
246+
marginal_means <- emmeans(
247+
fit,
248+
spec = trt.vs.ctrl ~ trt | visit_num,
249+
weights = "proportional"
250+
)
251+
emmeans_df <- as.data.frame(marginal_means$contrasts)
252+
253+
# compute lower and upper 95% CI
254+
emmeans_df <- get_95_ci(emmeans_df)
255+
256+
# format to resemble SAS output
257+
format_emmeans_df(emmeans_df)
258+
}
259+
260+
# extract model fit outputs at each visit from glmmTMB fit
261+
get_glmm_emmeans_output <- function(fit) {
262+
marginal_means <- emmeans(
263+
fit,
264+
spec = trt.vs.ctrl ~ trt | visit_num,
265+
weights = "proportional"
266+
)
267+
emmeans_df <- as.data.frame(marginal_means$contrasts)
268+
269+
# compute lower and upper 95% CI
270+
emmeans_df <- get_95_ci(emmeans_df)
271+
272+
# format to resemble SAS output
273+
format_emmeans_df(emmeans_df)
274+
}
275+
276+
# extract model fit outputs estimates at each visit from gls fit
277+
get_nlme_emmeans_output <- function(fit, data) {
278+
marginal_means <- emmeans(
279+
fit,
280+
spec = trt.vs.ctrl ~ trt | visit_num,
281+
weights = "proportional",
282+
data = data,
283+
mode = "df.error"
284+
)
285+
emmeans_df <- as.data.frame(marginal_means$contrasts)
286+
287+
# compute lower and upper 95% CI
288+
emmeans_df <- get_95_ci(emmeans_df)
289+
290+
# format to resemble SAS output
291+
format_emmeans_df(emmeans_df)
292+
}
293+
294+
# extract model fit outputs estimates at each visit from PROC MIXED fit
295+
get_mixed_emmeans_like_output <- function(fit) {
296+
fit %>% transmute(
297+
visit_num = str_extract(contrast, "^([^:])+"),
298+
estimate = Estimate,
299+
stderr = StdErr,
300+
df = DF,
301+
tvalue = tValue,
302+
pvalue = map2_dbl(tValue, DF, function(tvalue, df) {
303+
2 * min(c(pt(tvalue, df), pt(tvalue, df, lower.tail = FALSE)))
304+
}),
305+
lower = Lower,
306+
upper = Upper
307+
)
308+
}
309+
310+
# general function for emmeans like information
311+
get_emmeans_output <- function(method, fit, dt, converged) {
312+
if (get_convergence(method, fit, converged)) {
313+
if (str_detect(method, "mmrm")) {
314+
get_mmrm_emmeans_output(fit)
315+
} else if (str_detect(method, "glmmtmb")) {
316+
get_glmm_emmeans_output(fit)
317+
} else if (str_detect(method, "nlme")) {
318+
get_nlme_emmeans_output(fit, dt)
319+
} else if (str_detect(method, "proc_mixed")) {
320+
get_mixed_emmeans_like_output(fit)
321+
}
322+
} else {
323+
NA
324+
}
325+
}

simulations/missing-data-benchmarks/tests/testthat.R

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# load required libraries
22
library(simChef)
3+
library(checkmate)
34
library(testthat)
45
library(mmrm)
56
library(glmmTMB)
@@ -11,6 +12,9 @@ library(tidyr)
1112
library(stringr)
1213
library(emmeans)
1314

15+
# flag whether SAS tests should be run
16+
run_sas_tests <- FALSE
17+
1418
# source the R scripts
1519
sim_functions_files <- list.files(
1620
c("R/dgp", "R/method", "R/eval", "R/viz"),

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-bias.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ test_that("bias_fun computes DGP-specific bias", {
6666
bcva_change = eff_us$bcva_change,
6767
covar_type = "us"
6868
)
69+
70+
skip_if_not(run_sas_tests)
71+
6972
proc_mixed_no_eff <- proc_mixed_wrapper_fun(
7073
participant = no_eff_us$participant,
7174
trt = no_eff_us$trt,

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-convergence-rate.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ test_that("coconvergence_rate_rate_fun computes DGP-specific convergence rate",
6666
bcva_change = eff_us$bcva_change,
6767
covar_type = "us"
6868
)
69+
70+
skip_if_not(run_sas_tests)
71+
6972
proc_mixed_no_eff <- proc_mixed_wrapper_fun(
7073
participant = no_eff_us$participant,
7174
trt = no_eff_us$trt,

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-coverage.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ test_that("coverage_fun computes DGP-specific coverage", {
6666
bcva_change = eff_us$bcva_change,
6767
covar_type = "us"
6868
)
69+
70+
skip_if_not(run_sas_tests)
71+
6972
proc_mixed_no_eff <- proc_mixed_wrapper_fun(
7073
participant = no_eff_us$participant,
7174
trt = no_eff_us$trt,

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-helpers.R

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
# get_trt_visit_num_ests ----
2+
13
test_that("get_mmrm_trt_visit_num_ests extracts interaction estimates", {
24
# generate data
35
set.seed(51235)
@@ -74,6 +76,7 @@ test_that("get_nlme_trt_visit_num_ests extracts interaction estimates", {
7476
})
7577

7678
test_that("get_mixed_trt_visit_num_ests extracts interaction estimates", {
79+
skip_if_not(run_sas_tests)
7780
# generate data
7881
set.seed(51235)
7982
dgp_output <- rct_dgp_fun(
@@ -98,6 +101,8 @@ test_that("get_mixed_trt_visit_num_ests extracts interaction estimates", {
98101
expect_equal(ests, c(0.5, 1.0, 1.5), tolerance = 0.1)
99102
})
100103

104+
# get_convergence ----
105+
101106
test_that("get_mmrm_convergence extracts convergence status", {
102107
# generate data
103108
set.seed(51235)
@@ -149,6 +154,7 @@ test_that("get_glmm_convergence extracts convergence status", {
149154
})
150155

151156
test_that("get_mixed_convergence extracts convergence status", {
157+
skip_if_not(run_sas_tests)
152158
# generate data
153159
set.seed(51235)
154160
dgp_output <- rct_dgp_fun(
@@ -173,6 +179,8 @@ test_that("get_mixed_convergence extracts convergence status", {
173179
expect_equal(conv_status, TRUE)
174180
})
175181

182+
# get_trt_visit_num_ses ----
183+
176184
test_that("get_mmrm_trt_visit_num_ses extracts standard errors", {
177185
# generate data
178186
set.seed(51235)
@@ -252,6 +260,7 @@ test_that("get_nlme_trt_visit_num_ses extracts standard errors", {
252260
})
253261

254262
test_that("get_mixed_trt_visit_num_ses extracts standard errors", {
263+
skip_if_not(run_sas_tests)
255264
# generate data
256265
set.seed(51235)
257266
dgp_output <- rct_dgp_fun(
@@ -277,6 +286,8 @@ test_that("get_mixed_trt_visit_num_ses extracts standard errors", {
277286
expect_equal(length(ses), 3)
278287
})
279288

289+
# get_trt_visit_num_pvals ----
290+
280291
test_that("get_mmrm_trt_visit_num_pvals extracts nominal p-values", {
281292
# generate data
282293
set.seed(51235)
@@ -356,6 +367,7 @@ test_that("get_nlme_trt_visit_num_pvals extracts nominal p-values", {
356367
})
357368

358369
test_that("get_mixed_trt_visit_num_pvals extracts nominal p-values", {
370+
skip_if_not(run_sas_tests)
359371
# generate data
360372
set.seed(51235)
361373
dgp_output <- rct_dgp_fun(
@@ -380,3 +392,118 @@ test_that("get_mixed_trt_visit_num_pvals extracts nominal p-values", {
380392
expect_equal(is.numeric(pvals), TRUE)
381393
expect_equal(length(pvals), 3)
382394
})
395+
396+
# get_emmeans_output ----
397+
398+
test_that("get_mmrm_emmeans_output extracts emmeans contrasts", {
399+
# generate data
400+
set.seed(51235)
401+
dgp_output <- rct_dgp_fun(
402+
n_obs = 100,
403+
outcome_covar_mat = diag(3),
404+
trt_visit_coef = 0.5
405+
)
406+
407+
# fit the mmrm with mmrm
408+
mmrm_fit <- mmrm_wrapper_fun(
409+
participant = dgp_output$participant,
410+
visit_num = dgp_output$visit_num,
411+
base_bcva = dgp_output$base_bcva,
412+
strata = dgp_output$strata,
413+
trt = dgp_output$trt,
414+
bcva_change = dgp_output$bcva_change,
415+
covar_type = "us"
416+
)
417+
418+
# extract emmeans contrasts estimates
419+
result <- get_mmrm_emmeans_output(mmrm_fit$fit)
420+
expect_data_frame(result)
421+
expect_identical(nrow(result), 3L)
422+
expected_cols <- c("visit_num", "estimate", "stderr", "df", "tvalue", "pvalue", "lower", "upper")
423+
expect_named(result, expected_cols)
424+
})
425+
426+
test_that("get_glmm_emmeans_output extracts emmeans contrasts", {
427+
# generate data
428+
set.seed(51235)
429+
dgp_output <- rct_dgp_fun(
430+
n_obs = 100,
431+
outcome_covar_mat = diag(3),
432+
trt_visit_coef = 0.5
433+
)
434+
435+
# fit the mmrm with mmrm
436+
glmmtmb_fit <- glmmtmb_wrapper_fun(
437+
participant = dgp_output$participant,
438+
visit_num = dgp_output$visit_num,
439+
base_bcva = dgp_output$base_bcva,
440+
strata = dgp_output$strata,
441+
trt = dgp_output$trt,
442+
bcva_change = dgp_output$bcva_change,
443+
covar_type = "us"
444+
)
445+
446+
# extract estimates
447+
result <- get_glmm_emmeans_output(glmmtmb_fit$fit)
448+
expect_data_frame(result)
449+
expect_identical(nrow(result), 3L)
450+
expected_cols <- c("visit_num", "estimate", "stderr", "df", "tvalue", "pvalue", "lower", "upper")
451+
expect_named(result, expected_cols)
452+
})
453+
454+
test_that("get_nlme_emmeans_output extracts emmeans contrasts", {
455+
# generate data
456+
set.seed(51235)
457+
dgp_output <- rct_dgp_fun(
458+
n_obs = 100,
459+
outcome_covar_mat = diag(3),
460+
trt_visit_coef = 0.5
461+
)
462+
463+
# fit the mmrm with mmrm
464+
nlme_fit <- nlme_wrapper_fun(
465+
participant = dgp_output$participant,
466+
visit_num = dgp_output$visit_num,
467+
base_bcva = dgp_output$base_bcva,
468+
strata = dgp_output$strata,
469+
trt = dgp_output$trt,
470+
bcva_change = dgp_output$bcva_change,
471+
covar_type = "us"
472+
)
473+
474+
# extract estimates
475+
result <- get_nlme_emmeans_output(nlme_fit$fit, nlme_fit$data)
476+
expect_data_frame(result)
477+
expect_identical(nrow(result), 3L)
478+
expected_cols <- c("visit_num", "estimate", "stderr", "df", "tvalue", "pvalue", "lower", "upper")
479+
expect_named(result, expected_cols)
480+
})
481+
482+
test_that("get_mixed_emmeans_like_output extracts emmeans contrasts", {
483+
skip_if_not(run_sas_tests)
484+
# generate data
485+
set.seed(51235)
486+
dgp_output <- rct_dgp_fun(
487+
n_obs = 100,
488+
outcome_covar_mat = diag(3),
489+
trt_visit_coef = 0.5
490+
)
491+
492+
# fit the mmrm with mmrm
493+
proc_mixed_fit <- proc_mixed_wrapper_fun(
494+
participant = dgp_output$participant,
495+
visit_num = dgp_output$visit_num,
496+
base_bcva = dgp_output$base_bcva,
497+
strata = dgp_output$strata,
498+
trt = dgp_output$trt,
499+
bcva_change = dgp_output$bcva_change,
500+
covar_type = "us"
501+
)
502+
503+
# extract estimates
504+
result <- get_mixed_emmeans_like_output(proc_mixed_fit$fit)
505+
expect_data_frame(result)
506+
expect_identical(nrow(result), 3L)
507+
expected_cols <- c("visit_num", "estimate", "stderr", "df", "tvalue", "pvalue", "lower", "upper")
508+
expect_named(result, expected_cols)
509+
})

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-type-1-error.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ test_that("type_1_error_rate_fun computes DGP-specific type 1 error rates", {
6666
bcva_change = eff_us$bcva_change,
6767
covar_type = "us"
6868
)
69+
skip_if_not(run_sas_tests)
6970
proc_mixed_no_eff <- proc_mixed_wrapper_fun(
7071
participant = no_eff_us$participant,
7172
trt = no_eff_us$trt,

simulations/missing-data-benchmarks/tests/testthat/eval-tests/test-type-2-error.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ test_that("type_2_error_rate_fun computes DGP-specific type 2 error rates", {
6666
bcva_change = eff_us$bcva_change,
6767
covar_type = "us"
6868
)
69+
70+
skip_if_not(run_sas_tests)
71+
6972
proc_mixed_no_eff <- proc_mixed_wrapper_fun(
7073
participant = no_eff_us$participant,
7174
trt = no_eff_us$trt,

0 commit comments

Comments
 (0)