Skip to content

Commit e0acb1a

Browse files
authored
Merge branch 'main' into rc_0_27_0
2 parents bda338f + f3eaf80 commit e0acb1a

17 files changed

+295
-101
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ Suggests:
175175
mmrm,
176176
multcomp,
177177
MuMIn,
178+
mvtnorm,
178179
NbClust,
179180
nFactors,
180181
nestedLogit,

NEWS.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@
2626
* Improved efficiency in `model_parameters()` for models from packages *brms*
2727
and *rstanarm*.
2828

29+
* `p_adjust` for `model_parameters()` gets a new options, `"sup-t"`, to calculate
30+
simultaneous confidence intervals.
31+
2932
# parameters 0.26.0
3033

3134
## Changes

R/1_model_parameters.R

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,10 @@
364364
#' Indices of Effect Existence and Significance in the Bayesian Framework.
365365
#' Frontiers in Psychology, 10, 2767. \doi{10.3389/fpsyg.2019.02767}
366366
#'
367+
#' - Montiel Olea, J. L., and Plagborg-Møller, M. (2019). Simultaneous
368+
#' confidence bands: Theory, implementation, and an application to SVARs.
369+
#' Journal of Applied Econometrics, 34(1), 1–17. \doi{10.1002/jae.2656}
370+
#'
367371
#' - Neter, J., Wasserman, W., and Kutner, M. H. (1989). Applied linear
368372
#' regression models.
369373
#'
@@ -463,11 +467,12 @@ parameters <- model_parameters
463467
#' coefficient. The transformed confidence interval more clearly captures this
464468
#' uncertainty. For `compare_parameters()`, `exponentiate = "nongaussian"`
465469
#' will only exponentiate coefficients from non-Gaussian families.
466-
#' @param p_adjust Character vector, if not `NULL`, indicates the method to
467-
#' adjust p-values. See [`stats::p.adjust()`] for details. Further
468-
#' possible adjustment methods are `"tukey"`, `"scheffe"`,
469-
#' `"sidak"` and `"none"` to explicitly disable adjustment for
470-
#' `emmGrid` objects (from **emmeans**).
470+
#' @param p_adjust String value, if not `NULL`, indicates the method to adjust
471+
#' p-values. See [`stats::p.adjust()`] for details. Further possible
472+
#' adjustment methods are `"tukey"`, `"scheffe"`, `"sidak"`, `"sup-t"`, and
473+
#' `"none"` to explicitly disable adjustment for `emmGrid` objects (from
474+
#' **emmeans**). `"sup-t"` computes simultaneous confidence bands, also called
475+
#' sup-t confidence band (Montiel Olea & Plagborg-Møller, 2019).
471476
#' @param ci_method Method for computing degrees of freedom for
472477
#' confidence intervals (CI) and the related p-values. Allowed are following
473478
#' options (which vary depending on the model class): `"residual"`,

R/format_p_adjust.R

Lines changed: 142 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
}

inst/WORDLIST

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,8 @@ McNemar
117117
Merkle
118118
Metaclustering
119119
Monti
120+
Møller
121+
Montiel
120122
Mundlak
121123
NHST
122124
NL
@@ -127,6 +129,7 @@ Nievergelt
127129
Nondegenerate
128130
Nonresponse
129131
ORCID
132+
Olea
130133
Olkin
131134
PCoA
132135
PHQ
@@ -135,6 +138,7 @@ PMCMRplus
135138
PeerJ
136139
Pernet
137140
Pettersson
141+
Plagborg
138142
PloS
139143
Psychometrika
140144
REWB
@@ -174,6 +178,7 @@ Stata
174178
Stigum
175179
Struyf
176180
Synthese
181+
SVARs
177182
TOST
178183
Tabachnick
179184
Thiyagarajan

man/compare_parameters.Rd

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/model_parameters.Rd

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/model_parameters.aov.Rd

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/model_parameters.cgam.Rd

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/model_parameters.default.Rd

Lines changed: 6 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)