11# ' @export
2- estimate_contrasts.estimate_predicted <- function (model ,
3- contrast = NULL ,
4- by = NULL ,
5- predict = " response" ,
6- ci = 0.95 ,
7- p_adjust = " none" ,
8- comparison = " pairwise" ,
9- verbose = TRUE ,
10- ... ) {
2+ estimate_contrasts.estimate_predicted <- function (
3+ model ,
4+ contrast = NULL ,
5+ by = NULL ,
6+ predict = " response" ,
7+ ci = 0.95 ,
8+ p_adjust = " none" ,
9+ comparison = " pairwise" ,
10+ verbose = TRUE ,
11+ ...
12+ ) {
1113 # sanity check
1214 if (inherits(comparison , " formula" )) {
1315 comparison <- all.vars(comparison )[1 ]
@@ -20,7 +22,7 @@ estimate_contrasts.estimate_predicted <- function(model,
2022 # the "model" object is an object of class "estimate_predicted", we want
2123 # to copy that into a separate object, for clearer names
2224 predictions <- object <- model
23- model <- attributes (object )$ model
25+ model <- insight :: get_model (object )
2426 datagrid <- attributes(object )$ datagrid
2527
2628 # sanity check - user-defined by-variables may not be in the data
@@ -80,12 +82,7 @@ estimate_contrasts.estimate_predicted <- function(model,
8082 se_from_predictions <- tryCatch(
8183 {
8284 # arguments for predict(), to get SE on response scale for non-Gaussian models
83- my_args <- list (
84- model ,
85- newdata = datagrid ,
86- type = predict ,
87- se.fit = TRUE
88- )
85+ my_args <- list (model , newdata = datagrid , type = predict , se.fit = TRUE )
8986 # for mixed models, need to set re.form to NULL or NA
9087 if (insight :: is_mixed_model(model )) {
9188 my_args $ re.form <- NULL
@@ -120,6 +117,7 @@ estimate_contrasts.estimate_predicted <- function(model,
120117 }
121118
122119 # compute contrasts or comparisons
120+ # fmt: skip
123121 out <- switch (comparison ,
124122 pairwise = .compute_comparisons(predictions , dof , vcov_matrix , datagrid , contrast , by , crit_factor ),
125123 interaction = .compute_interactions(predictions , dof , vcov_matrix , datagrid , contrast , by , crit_factor )
@@ -143,7 +141,9 @@ estimate_contrasts.estimate_predicted <- function(model,
143141 to_remove <- unique(out [[i ]])
144142 for (j in to_remove ) {
145143 if (all(c(" Level1" , " Level2" ) %in% colnames(out ))) {
144+ # fmt: skip
146145 levels(out $ Level1 ) <- insight :: trim_ws(gsub(j , " " , levels(out $ Level1 ), fixed = TRUE ))
146+ # fmt: skip
147147 levels(out $ Level2 ) <- insight :: trim_ws(gsub(j , " " , levels(out $ Level2 ), fixed = TRUE ))
148148 } else if (" Parameter" %in% colnames(out )) {
149149 out $ Parameter <- insight :: trim_ws(gsub(j , " " , out $ Parameter , fixed = TRUE ))
@@ -181,7 +181,15 @@ estimate_contrasts.estimate_predicted <- function(model,
181181
182182
183183# pairwise comparisons ----------------------------------------------------
184- .compute_comparisons <- function (predictions , dof , vcov_matrix , datagrid , contrast , by , crit_factor ) {
184+ .compute_comparisons <- function (
185+ predictions ,
186+ dof ,
187+ vcov_matrix ,
188+ datagrid ,
189+ contrast ,
190+ by ,
191+ crit_factor
192+ ) {
185193 # we need the focal terms and all unique values from the datagrid
186194 focal_terms <- unique(c(contrast , by ))
187195
@@ -254,54 +262,57 @@ estimate_contrasts.estimate_predicted <- function(model,
254262 }
255263
256264 # now we iterate over all pairs and try to find the corresponding predictions
257- out <- do.call(rbind , lapply(seq_len(nrow(pairs_data [[1 ]])), function (i ) {
258- pos1 <- predictions [[focal_terms [1 ]]] == pairs_data [[1 ]][i , 1 ]
259- pos2 <- predictions [[focal_terms [1 ]]] == pairs_data [[2 ]][i , 1 ]
260-
261- # for all focal terms, make sure we only keep the matching pairs
262- if (length(focal_terms ) > 1 ) {
263- for (j in 2 : length(focal_terms )) {
264- pos1 <- pos1 & predictions [[focal_terms [j ]]] == pairs_data [[1 ]][i , j ]
265- pos2 <- pos2 & predictions [[focal_terms [j ]]] == pairs_data [[2 ]][i , j ]
265+ out <- do.call(
266+ rbind ,
267+ lapply(seq_len(nrow(pairs_data [[1 ]])), function (i ) {
268+ pos1 <- predictions [[focal_terms [1 ]]] == pairs_data [[1 ]][i , 1 ]
269+ pos2 <- predictions [[focal_terms [1 ]]] == pairs_data [[2 ]][i , 1 ]
270+
271+ # for all focal terms, make sure we only keep the matching pairs
272+ if (length(focal_terms ) > 1 ) {
273+ for (j in 2 : length(focal_terms )) {
274+ pos1 <- pos1 & predictions [[focal_terms [j ]]] == pairs_data [[1 ]][i , j ]
275+ pos2 <- pos2 & predictions [[focal_terms [j ]]] == pairs_data [[2 ]][i , j ]
276+ }
266277 }
267- }
268- # once we have found the correct rows for the pairs, we can calculate
269- # the contrast. We need the predicted values first
270- predicted1 <- predictions $ Predicted [pos1 ]
271- predicted2 <- predictions $ Predicted [ pos2 ]
272-
273- # we then create labels for the pairs. "result" is a data frame with
274- # the labels (of the pairwise contrasts) as columns.
275- result <- data.frame (
276- Parameter = paste(
277- paste0(" (" , paste(pairs_data [[1 ]][i , contrast_pos ], collapse = " " ), " )" ),
278- paste0( " ( " , paste( pairs_data [[ 2 ]][ i , contrast_pos ], collapse = " " ), " ) " ),
279- sep = " - "
280- ),
281- stringsAsFactors = FALSE
282- )
283- # we then add the contrast and the standard error. for linear models, the
284- # SE is sqrt(se1^2 + se2^2).
285- result $ Difference <- predicted1 - predicted2
286- # sum of squared standard errors
287- sum_se_squared <- predictions $ SE [ pos1 ] ^ 2 + predictions $ SE [ pos2 ] ^ 2
288- # for non-Gaussian models, we subtract the covariance of the two predictions
289- # but only if the vcov_matrix is not NULL and has the correct dimensions
290- correct_row_dims <- nrow (vcov_matrix ) > 0 && all(nrow (vcov_matrix ) > = which(pos1 ))
291- correct_col_dims <- ncol( vcov_matrix ) > 0 && all(ncol( vcov_matrix ) > = which( pos2 ))
292- if (is.null( vcov_matrix ) || ! correct_row_dims || ! correct_col_dims ) {
293- vcov_sub <- 0
294- } else {
295- vcov_sub <- vcov_matrix [which( pos1 ), which( pos2 )] ^ 2
296- }
297- # Avoid negative values in sqrt()
298- if ( vcov_sub > = sum_se_squared ) {
299- result $ SE <- sqrt( sum_se_squared )
300- } else {
301- result $ SE <- sqrt( sum_se_squared - vcov_sub )
302- }
303- result
304- }) )
278+ # once we have found the correct rows for the pairs, we can calculate
279+ # the contrast. We need the predicted values first
280+ predicted1 <- predictions $ Predicted [ pos1 ]
281+ predicted2 <- predictions $ Predicted [pos2 ]
282+
283+ # we then create labels for the pairs. "result" is a data frame with
284+ # the labels (of the pairwise contrasts) as columns.
285+ result <- data.frame (
286+ Parameter = paste (
287+ paste0( " ( " , paste(pairs_data [[ 1 ]][ i , contrast_pos ], collapse = " " ), " ) " ),
288+ paste0(" (" , paste(pairs_data [[2 ]][i , contrast_pos ], collapse = " " ), " )" ),
289+ sep = " - "
290+ ),
291+ stringsAsFactors = FALSE
292+ )
293+ # we then add the contrast and the standard error. for linear models, the
294+ # SE is sqrt(se1^2 + se2^2).
295+ result $ Difference <- predicted1 - predicted2
296+ # sum of squared standard errors
297+ sum_se_squared <- predictions $ SE [ pos1 ] ^ 2 + predictions $ SE [ pos2 ] ^ 2
298+ # for non-Gaussian models, we subtract the covariance of the two predictions
299+ # but only if the vcov_matrix is not NULL and has the correct dimensions
300+ correct_row_dims <- nrow( vcov_matrix ) > 0 && all(nrow( vcov_matrix ) > = which( pos1 ))
301+ correct_col_dims <- ncol (vcov_matrix ) > 0 && all(ncol (vcov_matrix ) > = which(pos2 ))
302+ if (is.null( vcov_matrix ) || ! correct_row_dims || ! correct_col_dims ) {
303+ vcov_sub <- 0
304+ } else {
305+ vcov_sub <- vcov_matrix [which( pos1 ), which( pos2 )] ^ 2
306+ }
307+ # Avoid negative values in sqrt()
308+ if ( vcov_sub > = sum_se_squared ) {
309+ result $ SE <- sqrt( sum_se_squared )
310+ } else {
311+ result $ SE <- sqrt( sum_se_squared - vcov_sub )
312+ }
313+ result
314+ })
315+ )
305316 # add CI and p-values
306317 out $ CI_low <- out $ Difference - stats :: qt(crit_factor , df = dof ) * out $ SE
307318 out $ CI_high <- out $ Difference + stats :: qt(crit_factor , df = dof ) * out $ SE
@@ -313,10 +324,13 @@ estimate_contrasts.estimate_predicted <- function(model,
313324 idx <- rep_len(TRUE , nrow(out ))
314325 for (filter_by in by ) {
315326 # create index with "by" variables for each comparison pair
316- filter_data <- do.call(cbind , lapply(pairs_data , function (i ) {
317- colnames(i ) <- focal_terms
318- i [filter_by ]
319- }))
327+ filter_data <- do.call(
328+ cbind ,
329+ lapply(pairs_data , function (i ) {
330+ colnames(i ) <- focal_terms
331+ i [filter_by ]
332+ })
333+ )
320334 # check which pairs have identical values - these are the rows we want to keep
321335 idx <- idx & unname(apply(filter_data , 1 , function (r ) r [1 ] == r [2 ]))
322336 }
@@ -333,14 +347,24 @@ estimate_contrasts.estimate_predicted <- function(model,
333347
334348
335349# interaction contrasts ----------------------------------------------------
336- .compute_interactions <- function (predictions , dof , vcov_matrix , datagrid , contrast , by , crit_factor ) {
350+ .compute_interactions <- function (
351+ predictions ,
352+ dof ,
353+ vcov_matrix ,
354+ datagrid ,
355+ contrast ,
356+ by ,
357+ crit_factor
358+ ) {
337359 # we need the focal terms and all unique values from the datagrid
338360 focal_terms <- c(contrast , by )
339361 at_list <- lapply(datagrid [focal_terms ], unique )
340362
341363 # # TODO: interaction contrasts currently only work for two focal terms
342364 if (length(focal_terms ) != 2 ) {
343- insight :: format_error(" Interaction contrasts currently only work for two focal terms." )
365+ insight :: format_error(
366+ " Interaction contrasts currently only work for two focal terms."
367+ )
344368 }
345369
346370 # create pairwise combinations of first focal term
@@ -370,60 +394,72 @@ estimate_contrasts.estimate_predicted <- function(model,
370394 pairs_focal2 $ Freq <- NULL
371395
372396 # now we iterate over all pairs and try to find the corresponding predictions
373- out <- do.call(rbind , lapply(seq_len(nrow(pairs_focal1 )), function (i ) {
374- # differences between levels of first focal term
375- pos1 <- predictions [[focal_terms [1 ]]] == pairs_focal1 [i , 1 ]
376- pos2 <- predictions [[focal_terms [1 ]]] == pairs_focal1 [i , 2 ]
377-
378- do.call(rbind , lapply(seq_len(nrow(pairs_focal2 )), function (j ) {
379- # difference between levels of first focal term, *within* first
380- # level of second focal term
381- pos_1a <- pos1 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 1 ]
382- pos_1b <- pos2 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 1 ]
383- # difference between levels of first focal term, *within* second
384- # level of second focal term
385- pos_2a <- pos1 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 2 ]
386- pos_2b <- pos2 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 2 ]
387- # once we have found the correct rows for the pairs, we can calculate
388- # the contrast. We need the predicted values first
389- predicted1 <- predictions $ Predicted [pos_1a ] - predictions $ Predicted [pos_1b ]
390- predicted2 <- predictions $ Predicted [pos_2a ] - predictions $ Predicted [pos_2b ]
391- # we then create labels for the pairs. "result" is a data frame with
392- # the labels (of the pairwise contrasts) as columns.
393- result <- data.frame (
394- a = paste(pairs_focal1 [i , 1 ], pairs_focal1 [i , 2 ], sep = " -" ),
395- b = paste(pairs_focal2 [j , 1 ], pairs_focal2 [j , 2 ], sep = " and " ),
396- stringsAsFactors = FALSE
397+ out <- do.call(
398+ rbind ,
399+ lapply(seq_len(nrow(pairs_focal1 )), function (i ) {
400+ # differences between levels of first focal term
401+ pos1 <- predictions [[focal_terms [1 ]]] == pairs_focal1 [i , 1 ]
402+ pos2 <- predictions [[focal_terms [1 ]]] == pairs_focal1 [i , 2 ]
403+
404+ do.call(
405+ rbind ,
406+ lapply(seq_len(nrow(pairs_focal2 )), function (j ) {
407+ # difference between levels of first focal term, *within* first
408+ # level of second focal term
409+ pos_1a <- pos1 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 1 ]
410+ pos_1b <- pos2 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 1 ]
411+ # difference between levels of first focal term, *within* second
412+ # level of second focal term
413+ pos_2a <- pos1 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 2 ]
414+ pos_2b <- pos2 & predictions [[focal_terms [2 ]]] == pairs_focal2 [j , 2 ]
415+ # once we have found the correct rows for the pairs, we can calculate
416+ # the contrast. We need the predicted values first
417+ predicted1 <- predictions $ Predicted [pos_1a ] - predictions $ Predicted [pos_1b ]
418+ predicted2 <- predictions $ Predicted [pos_2a ] - predictions $ Predicted [pos_2b ]
419+ # we then create labels for the pairs. "result" is a data frame with
420+ # the labels (of the pairwise contrasts) as columns.
421+ result <- data.frame (
422+ a = paste(pairs_focal1 [i , 1 ], pairs_focal1 [i , 2 ], sep = " -" ),
423+ b = paste(pairs_focal2 [j , 1 ], pairs_focal2 [j , 2 ], sep = " and " ),
424+ stringsAsFactors = FALSE
425+ )
426+ colnames(result ) <- focal_terms
427+ # we then add the contrast and the standard error. for linear models, the
428+ # SE is sqrt(se1^2 + se2^2)
429+ result $ Difference <- predicted1 - predicted2
430+ sum_se_squared <- sum(
431+ predictions $ SE [pos_1a ]^ 2 ,
432+ predictions $ SE [pos_1b ]^ 2 ,
433+ predictions $ SE [pos_2a ]^ 2 ,
434+ predictions $ SE [pos_2b ]^ 2
435+ )
436+ # for non-Gaussian models, we subtract the covariance of the two predictions
437+ # but only if the vcov_matrix is not NULL and has the correct dimensions
438+ correct_row_dims <- nrow(vcov_matrix ) > 0 &&
439+ all(nrow(vcov_matrix ) > = which(pos_1a )) &&
440+ all(nrow(vcov_matrix ) > = which(pos_2a )) # nolint
441+ correct_col_dims <- ncol(vcov_matrix ) > 0 &&
442+ all(ncol(vcov_matrix ) > = which(pos_1b )) &&
443+ all(ncol(vcov_matrix ) > = which(pos_2b )) # nolint
444+ if (is.null(vcov_matrix ) || ! correct_row_dims || ! correct_col_dims ) {
445+ vcov_sub <- 0
446+ } else {
447+ vcov_sub <- sum(
448+ vcov_matrix [which(pos_1a ), which(pos_1b )]^ 2 ,
449+ vcov_matrix [which(pos_2a ), which(pos_2b )]^ 2
450+ )
451+ }
452+ # Avoid negative values in sqrt()
453+ if (vcov_sub > = sum_se_squared ) {
454+ result $ SE <- sqrt(sum_se_squared )
455+ } else {
456+ result $ SE <- sqrt(sum_se_squared - vcov_sub )
457+ }
458+ result
459+ })
397460 )
398- colnames(result ) <- focal_terms
399- # we then add the contrast and the standard error. for linear models, the
400- # SE is sqrt(se1^2 + se2^2)
401- result $ Difference <- predicted1 - predicted2
402- sum_se_squared <- sum(
403- predictions $ SE [pos_1a ]^ 2 , predictions $ SE [pos_1b ]^ 2 ,
404- predictions $ SE [pos_2a ]^ 2 , predictions $ SE [pos_2b ]^ 2
405- )
406- # for non-Gaussian models, we subtract the covariance of the two predictions
407- # but only if the vcov_matrix is not NULL and has the correct dimensions
408- correct_row_dims <- nrow(vcov_matrix ) > 0 && all(nrow(vcov_matrix ) > = which(pos_1a )) && all(nrow(vcov_matrix ) > = which(pos_2a )) # nolint
409- correct_col_dims <- ncol(vcov_matrix ) > 0 && all(ncol(vcov_matrix ) > = which(pos_1b )) && all(ncol(vcov_matrix ) > = which(pos_2b )) # nolint
410- if (is.null(vcov_matrix ) || ! correct_row_dims || ! correct_col_dims ) {
411- vcov_sub <- 0
412- } else {
413- vcov_sub <- sum(
414- vcov_matrix [which(pos_1a ), which(pos_1b )]^ 2 ,
415- vcov_matrix [which(pos_2a ), which(pos_2b )]^ 2
416- )
417- }
418- # Avoid negative values in sqrt()
419- if (vcov_sub > = sum_se_squared ) {
420- result $ SE <- sqrt(sum_se_squared )
421- } else {
422- result $ SE <- sqrt(sum_se_squared - vcov_sub )
423- }
424- result
425- }))
426- }))
461+ })
462+ )
427463 # add CI and p-values
428464 out $ CI_low <- out $ Difference - stats :: qt(crit_factor , df = dof ) * out $ SE
429465 out $ CI_high <- out $ Difference + stats :: qt(crit_factor , df = dof ) * out $ SE
0 commit comments