@@ -25,36 +25,26 @@ format_p_adjust <- function(method) {
2525 tukey = " Tukey" ,
2626 scheffe = " Scheffe" ,
2727 sidak = " Sidak" ,
28+ `sup-t` = " Simultaneous confidence bands" ,
2829 method
2930 )
3031}
3132
3233
34+ # p-value adjustment -----
35+
3336.p_adjust <- function (params , p_adjust , model = NULL , verbose = TRUE ) {
3437 # check if we have any adjustment at all, and a p-column
3538 if (! is.null(p_adjust ) && " p" %in% colnames(params ) && p_adjust != " none" ) {
3639 # # TODO add "mvt" method from emmeans
3740
3841 # prepare arguments
39- all_methods <- c(stats :: p.adjust.methods , " tukey" , " scheffe" , " sidak" )
42+ all_methods <- c(stats :: p.adjust.methods , " tukey" , " scheffe" , " sidak" , " sup-t " )
4043
4144 # for interaction terms, e.g. for "by" argument in emmeans
4245 # pairwise comparison, we have to adjust the rank resp. the
4346 # number of estimates in a comparison family
44- rank_adjust <- tryCatch(
45- {
46- correction <- 1
47- by_vars <- model @ misc $ by.vars
48- if (! is.null(by_vars ) && by_vars %in% colnames(params )) {
49- correction <- insight :: n_unique(params [[by_vars ]])
50- }
51- correction
52- },
53- error = function (e ) {
54- 1
55- }
56- )
57-
47+ rank_adjust <- .p_adjust_rank(model , params )
5848
5949 # only proceed if valid argument-value
6050 if (tolower(p_adjust ) %in% tolower(all_methods )) {
@@ -69,44 +59,18 @@ format_p_adjust <- function(method) {
6959 params $ p <- stats :: p.adjust(params $ p , method = p_adjust )
7060 } else if (tolower(p_adjust ) == " tukey" ) {
7161 # tukey adjustment
72- if (" df" %in% colnames(params ) && length(stat_column ) > 0 ) {
73- params $ p <- suppressWarnings(stats :: ptukey(
74- sqrt(2 ) * abs(params [[stat_column ]]),
75- nrow(params ) / rank_adjust ,
76- params $ df ,
77- lower.tail = FALSE
78- ))
79- # for specific contrasts, ptukey might fail, and the tukey-adjustement
80- # could just be simple p-value calculation
81- if (all(is.na(params $ p ))) {
82- params $ p <- 2 * stats :: pt(abs(params [[stat_column ]]), df = params $ df , lower.tail = FALSE )
83- verbose <- FALSE
84- }
85- }
62+ result <- .p_adjust_tukey(params , stat_column , rank_adjust , verbose )
63+ params <- result $ params
64+ verbose <- result $ verbose
8665 } else if (tolower(p_adjust ) == " scheffe" && ! is.null(model )) {
8766 # scheffe adjustment
88- if (" df" %in% colnames(params ) && length(stat_column ) > 0 ) {
89- # 1st try
90- scheffe_ranks <- try(qr(model @ linfct )$ rank , silent = TRUE )
91-
92- # 2nd try
93- if (inherits(scheffe_ranks , " try-error" ) || is.null(scheffe_ranks )) {
94- scheffe_ranks <- try(model $ qr $ rank , silent = TRUE )
95- }
96-
97- if (inherits(scheffe_ranks , " try-error" ) || is.null(scheffe_ranks )) {
98- scheffe_ranks <- nrow(params )
99- }
100- scheffe_ranks <- scheffe_ranks / rank_adjust
101- params $ p <- stats :: pf(params [[stat_column ]]^ 2 / scheffe_ranks ,
102- df1 = scheffe_ranks ,
103- df2 = params $ df ,
104- lower.tail = FALSE
105- )
106- }
67+ params <- .p_adjust_scheffe(model , params , stat_column , rank_adjust )
10768 } else if (tolower(p_adjust ) == " sidak" ) {
10869 # sidak adjustment
10970 params $ p <- 1 - (1 - params $ p )^ (nrow(params ) / rank_adjust )
71+ } else if (tolower(p_adjust ) == " sup-t" ) {
72+ # sup-t adjustment
73+ params <- .p_adjust_supt(model , params )
11074 }
11175
11276 if (isTRUE(all(old_p_vals == params $ p )) && ! identical(p_adjust , " none" ) && verbose ) {
@@ -118,3 +82,133 @@ format_p_adjust <- function(method) {
11882 }
11983 params
12084}
85+
86+
87+ # calculate rank adjustment -----
88+
89+ .p_adjust_rank <- function (model , params ) {
90+ tryCatch(
91+ {
92+ correction <- 1
93+ by_vars <- model @ misc $ by.vars
94+ if (! is.null(by_vars ) && by_vars %in% colnames(params )) {
95+ correction <- insight :: n_unique(params [[by_vars ]])
96+ }
97+ correction
98+ },
99+ error = function (e ) {
100+ 1
101+ }
102+ )
103+ }
104+
105+
106+ # tukey adjustment -----
107+
108+ .p_adjust_tukey <- function (params , stat_column , rank_adjust = 1 , verbose = TRUE ) {
109+ df_column <- colnames(params )[stats :: na.omit(match(c(" df" , " df_error" ), colnames(params )))][1 ]
110+ if (length(df_column ) && length(stat_column )) {
111+ params $ p <- suppressWarnings(stats :: ptukey(
112+ sqrt(2 ) * abs(params [[stat_column ]]),
113+ nmeans = nrow(params ) / rank_adjust ,
114+ df = params [[df_column ]],
115+ lower.tail = FALSE
116+ ))
117+ # for specific contrasts, ptukey might fail, and the tukey-adjustement
118+ # could just be simple p-value calculation
119+ if (all(is.na(params $ p ))) {
120+ params $ p <- 2 * stats :: pt(
121+ abs(params [[stat_column ]]),
122+ df = params [[df_column ]],
123+ lower.tail = FALSE
124+ )
125+ verbose <- FALSE
126+ }
127+ }
128+ list (params = params , verbose = verbose )
129+ }
130+
131+
132+ # scheffe adjustment -----
133+
134+ .p_adjust_scheffe <- function (model , params , stat_column , rank_adjust = 1 ) {
135+ df_column <- colnames(params )[stats :: na.omit(match(c(" df" , " df_error" ), colnames(params )))][1 ]
136+ if (length(df_column ) && length(stat_column )) {
137+ # 1st try
138+ scheffe_ranks <- try(qr(model @ linfct )$ rank , silent = TRUE )
139+
140+ # 2nd try
141+ if (inherits(scheffe_ranks , " try-error" ) || is.null(scheffe_ranks )) {
142+ scheffe_ranks <- try(model $ qr $ rank , silent = TRUE )
143+ }
144+
145+ if (inherits(scheffe_ranks , " try-error" ) || is.null(scheffe_ranks )) {
146+ scheffe_ranks <- nrow(params )
147+ }
148+ scheffe_ranks <- scheffe_ranks / rank_adjust
149+ params $ p <- stats :: pf(params [[stat_column ]]^ 2 / scheffe_ranks ,
150+ df1 = scheffe_ranks ,
151+ df2 = params [[df_column ]],
152+ lower.tail = FALSE
153+ )
154+ }
155+ params
156+ }
157+
158+
159+ # sup-t adjustment -----
160+
161+ .p_adjust_supt <- function (model , params ) {
162+ if (" Component" %in% colnames(params ) && insight :: n_unique(params $ Component ) > 1 ) {
163+ # if we have multiple components, we adjust each component separately
164+ for (component in unique(params $ Component )) {
165+ if (! is.na(component )) {
166+ params [which(params $ Component == component ), ] <- .supt_adjust(
167+ params [which(params $ Component == component ), ],
168+ model ,
169+ component = component
170+ )
171+ }
172+ }
173+ params
174+ } else {
175+ .supt_adjust(params , model )
176+ }
177+ }
178+
179+
180+ .supt_adjust <- function (params , model , component = NULL ) {
181+ insight :: check_if_installed(" mvtnorm" )
182+ # get correlation matrix, based on the covariance matrix
183+ vc <- .safe(stats :: cov2cor(insight :: get_varcov(model , component = component )))
184+ if (is.null(vc )) {
185+ insight :: format_warning(" Could not calculate covariance matrix for `sup-t` adjustment." )
186+ return (params )
187+ }
188+ # get confidence interval level, or set default
189+ ci_level <- .safe(params $ CI [1 ])
190+ if (is.null(ci_level )) {
191+ ci_level <- 0.95
192+ }
193+ # find degrees of freedom column, if available
194+ df_column <- colnames(params )[stats :: na.omit(match(c(" df" , " df_error" ), colnames(params )))][1 ]
195+ if (length(df_column ) == 0 ) {
196+ return (params )
197+ }
198+ # calculate updated confidence interval level, based on simultaenous
199+ # confidence intervals (https://onlinelibrary.wiley.com/doi/10.1002/jae.2656)
200+ crit <- mvtnorm :: qmvt(ci_level , df = params [[df_column ]][1 ], tail = " both.tails" , corr = vc )$ quantile
201+ # update confidence intervals
202+ params $ CI_low <- params $ Coefficient - crit * params $ SE
203+ params $ CI_high <- params $ Coefficient + crit * params $ SE
204+ # udpate p-values
205+ for (i in 1 : nrow(params )) {
206+ params $ p [i ] <- 1 - mvtnorm :: pmvt(
207+ lower = rep(- abs(stats :: qt(params $ p [i ] / 2 , df = params [[df_column ]][i ])), nrow(vc )),
208+ upper = rep(abs(stats :: qt(params $ p [i ] / 2 , df = params [[df_column ]][i ])), nrow(vc )),
209+ corr = vc ,
210+ df = params [[df_column ]][i ]
211+ )
212+ }
213+ params
214+ }
0 commit comments