Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use multivariate as default for follow-up tests RM ANOVA #319

Merged
merged 14 commits into from
Mar 12, 2024
83 changes: 9 additions & 74 deletions R/anovarepeatedmeasures.R
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
anovaContainer <- createJaspContainer()
# we set the dependencies on the container, this means that all items inside the container automatically have these dependencies
anovaContainer$dependOn(c("withinModelTerms", "betweenModelTerms", "repeatedMeasuresCells", "betweenSubjectFactors",
"repeatedMeasuresFactors", "covariates", "sumOfSquares", "multivariateModelFollowup"))
"repeatedMeasuresFactors", "covariates", "sumOfSquares", "poolErrorTermFollowup"))
jaspResults[["rmAnovaContainer"]] <- anovaContainer
}

Expand Down Expand Up @@ -255,7 +255,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
rmAnovaResult <- .rmAnovaComputeResults(longData, options)

if (rmAnovaResult[["tryResult"]] == "try-error" && grepl(as.character(rmAnovaResult[["tryMessage"]]), pattern = "allocate vector")) {
rmAnovaContainer$setError(gettext('Data set too big for univariate follow-up test. Try selecting "Use multivariate model for follow-up tests" in the Model tab.'))
rmAnovaContainer$setError(gettext('Data set too big for univariate follow-up test. Try unselecting "Pool error term for follow-up tests" in the Model tab.'))
return()
} else if (rmAnovaResult[["tryResult"]] == "try-error") {
rmAnovaContainer$setError(gettext("Some parameters are not estimable, most likely due to empty cells of the design."))
Expand All @@ -274,7 +274,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)

# set these options once for all afex::aov_car calls,
# this ensures for instance that afex::aov_car always returns objects of class afex_aov.
if (options$multivariateModelFollowup) followupModelType <- "multivariate" else followupModelType <- "univariate"
if (options$poolErrorTermFollowup) followupModelType <- "univariate" else followupModelType <- "multivariate"
afex::afex_options(
check_contrasts = TRUE, correction_aov = "GG",
emmeans_model = followupModelType, es_aov = "ges", factorize = TRUE,
Expand All @@ -290,7 +290,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
summaryResultOne <- summary(result, expand.split = FALSE)

result <- afex::aov_car(model.formula, data=dataset, type= 3, factorize = FALSE,
include_aov = isFALSE(options[["multivariateModelFollowup"]]))
include_aov = isTRUE(options[["poolErrorTermFollowup"]]))
summaryResult <- summary(result)

# Reformat the results to make it consistent with types 2 and 3
Expand All @@ -313,7 +313,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)

tryResult <- try({
result <- afex::aov_car(model.formula, data=dataset, type= 2, factorize = FALSE,
include_aov = isFALSE(options[["multivariateModelFollowup"]]))
include_aov = isTRUE(options[["poolErrorTermFollowup"]]))
summaryResult <- summary(result)
model <- as.data.frame(unclass(summaryResult$univariate.tests))
})
Expand All @@ -322,7 +322,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)

tryResult <- try({
result <- afex::aov_car(model.formula, data=dataset, type= 3, factorize = FALSE,
include_aov = isFALSE(options[["multivariateModelFollowup"]]))
include_aov = isTRUE(options[["poolErrorTermFollowup"]]))
summaryResult <- summary(result)
model <- as.data.frame(unclass(summaryResult$univariate.tests))
})
Expand Down Expand Up @@ -733,7 +733,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
return()

rmAnovaLevenesTable$setExpectedSize(length(options$repeatedMeasuresCells))
if (length(options$betweenModelTerms) == 0) {
if (length(options[["betweenSubjectFactors"]]) == 0) {
rmAnovaLevenesTable$setError(gettext("Cannot perform homogeneity tests because there are no between subjects factors specified."))
return()
}
Expand Down Expand Up @@ -871,7 +871,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
postHocContainer$dependOn(c("postHocTerms", "postHocEffectSize", "postHocCorrectionBonferroni",
"postHocCorrectionHolm", "postHocCorrectionScheffe", "postHocCorrectionTukey",
"postHocSignificanceFlag", "postHocCi",
"postHocCiLevel", "postHocPooledError"))
"postHocCiLevel"))

rmAnovaContainer[["postHocStandardContainer"]] <- postHocContainer

Expand All @@ -894,8 +894,6 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
fullModel <- rmAnovaContainer[["anovaResult"]]$object$fullModel
allNames <- unlist(lapply(options$repeatedMeasuresFactors, function(x) x$name)) # Factornames

balancedDesign <- all(sapply(unlist(options$betweenModelTerms), function(x) length(unique(table(dataset[[.v(x)]]))) == 1))

for (var in variables) {

resultPostHoc <- summary(pairs(referenceGrid[[var]], adjust="bonferroni"),
Expand Down Expand Up @@ -929,36 +927,6 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)

if (any(var == .v(allNames))) { ## If the variable is a repeated measures factor

if (!options$postHocPooledError && balancedDesign) {

# Loop over all the levels within factor and do pairwise t.tests on them
for (compIndex in seq_along(comparisons)) {

levelANoDots <- gsub(.unv(comparisons[[compIndex]][1]), pattern = "\\.", replacement = " ")
levelBNoDots <- gsub(.unv(comparisons[[compIndex]][2]), pattern = "\\.", replacement = " ")
facLevelNoDots <- gsub(longData[[var]], pattern = "\\.", replacement = " ")

# gsubs necessary to deal with X and "." introduced to level names by emmeans
x <- subset(longData, gsub("X", "", facLevelNoDots) == gsub("X", "", levelANoDots))
x <- tapply(x[[.BANOVAdependentName]], x[[.BANOVAsubjectName]], mean)
y <- subset(longData, gsub("X", "", facLevelNoDots) == gsub("X", "", levelBNoDots))
y <- tapply(y[[.BANOVAdependentName]], y[[.BANOVAsubjectName]], mean)

tResult <- t.test(x, y, paired = TRUE, var.equal = FALSE, conf.level = bonfAdjustCIlevel)
tResult <- unname(unlist(tResult[c("estimate", "statistic", "p.value", "conf.int")]))
resultPostHoc[compIndex, c("estimate", "t.ratio", "p.value", "lower.CL", "upper.CL")] <- tResult

}

resultPostHoc[["SE"]] <- resultPostHoc[["estimate"]] / resultPostHoc[["t.ratio"]]
resultPostHoc[["bonferroni"]] <- p.adjust(resultPostHoc[["p.value"]], method = "bonferroni")
resultPostHoc[["holm"]] <- p.adjust(resultPostHoc[["p.value"]], method = "holm")

} else if (!options$postHocPooledError) {
postHocContainer$setError(gettext("Unpooled error term only allowed in balanced designs."))
return()
}

resultPostHoc[["scheffe"]] <- "."
resultPostHoc[["tukey"]] <- "."
if (options$postHocCorrectionScheffe || options$postHocCorrectionTukey) {
Expand Down Expand Up @@ -1057,9 +1025,6 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
if (isTRUE(options$postHocTypeStandardEffectSize) || isTRUE(options$postHocEffectSize)) {
postHocTable$addColumnInfo(name="cohenD", title=gettext("Cohen's d"), type="number")

if (isFALSE(options$postHocPooledError))
postHocTable$addFootnote(gettext("Computation of Cohen's d based on pooled error."))

if (options$postHocCi) {
thisOverTitleCohenD <- gettextf("%s%% CI for Cohen's d", options$postHocCiLevel * 100)
postHocTable$addColumnInfo(name="cohenD_LowerCI", type = "number", title = gettext("Lower"), overtitle = thisOverTitleCohenD)
Expand Down Expand Up @@ -1103,7 +1068,7 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
return()

contrastContainer <- createJaspContainer(title = gettext("Contrast Tables"))
contrastContainer$dependOn(c("contrasts", "contrastEqualVariance", "contrastCiLevel",
contrastContainer$dependOn(c("contrasts", "contrastCiLevel",
"contrastCi", "customContrasts"))

for (contrast in options$contrasts) {
Expand Down Expand Up @@ -1194,36 +1159,6 @@ AnovaRepeatedMeasuresInternal <- function(jaspResults, dataset = NULL, options)
contrastResult <- cbind(contrastResult, confint(contrastResult, level = options$contrastCiLevel)[,5:6])
contrastResult[["Comparison"]] <- .unv(contrastResult[["contrast"]])

if (options$contrastEqualVariance == FALSE && contrast$variable %in% unlist(options$withinModelTerms) &&
length(contrast$variable) == 1 && contrast$decoded != "custom") {

newDF <- do.call(data.frame, tapply(longData[[.BANOVAdependentName]], longData[[.v(contrast$variable)]], cbind))
ssNr <- tapply(longData[[.BANOVAsubjectName]], longData[[.v(contrast$variable)]], cbind)

for (i in 1:ncol(newDF)) {
newDF[[i]] <- tapply(newDF[[i]], ssNr[[i]], mean)
}
newDF <- newDF[1:length(unique(ssNr[[1]])), ]

allTestResults <- list()

for (coefIndex in 1:length(contrCoef)) {
allTestResults[[coefIndex]] <- t.test(as.matrix(newDF) %*% contrCoef[[coefIndex]])
}

contrastResult[["estimate"]]<- sapply(allTestResults, function(x) x[["estimate"]])
contrastResult[["t.ratio"]] <- sapply(allTestResults, function(x) x[["statistic"]])
contrastResult[["df"]] <- sapply(allTestResults, function(x) x[["parameter"]])
contrastResult[["SE"]] <- sapply(allTestResults, function(x) x[["estimate"]] / x[["statistic"]])
contrastResult[["p.value"]] <- sapply(allTestResults, function(x) x[["p.value"]])

} else if (options$contrastEqualVariance == FALSE) {

contrastContainer[[contrastContainerName]]$setError(gettext("Unequal variances only available for main effects of within subjects factors"))
return()

}

if (contrast$decoded == "custom" | length(contrast$variable) > 1) {
contrastResult$Comparison <- 1:nrow(contrastResult)
weightType <- if (all(apply(contrastMatrix, 2, function(x) x %% 1 == 0))) "integer" else "number"
Expand Down
9 changes: 5 additions & 4 deletions inst/help/AnovaRepeatedMeasures.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ The repeated Measures ANOVA allows the user to analyze the differences between m
- Type I: Sequential sum of squares. It is the reduction of error when each factor of the model is added to the factors already included, preserving the order of factors in the model. The results depend on the order in which the factors are added to the model. This is important to consider when the model contains more than one factor.
- Type II: Hierarchical/partially sequential sum of squares. It is the reduction of error when each factor is added to the model that includes all the other factors, except the factors where the added factor is a part of, such as interactions containing that factor. Langsrud (2003) advises to apply this type for an ANOVA with unbalanced data.
- Type III: Partial sum of squares. It is the reduction of error when each factor is added to the model that includes all the other factors, including interactions with this factor. This type is often selected, because it takes interactions into account (Langsrud, 2003). This type is selected by default.
- Use multivariate model for follow-up tests: By selecting this option, the multivariate linear model, rather than the aov model, will be used for follow-up tests (contrasts, post-hoc tests, marginal means). Multivariate tests likely provide a better correction for violations of sphericity.
- Pool error term for follow-up tests: By selecting this option, the univariate linear model, rather than the multivariate model, will be used for follow-up tests (contrasts, post-hoc tests, marginal means). Caution: multivariate models (i.e., unpooled error terms) handle departures from sphericity better, since these models allow the standard errors to differ for each level of the repeated measure(s) factor(s).

### Assumption checks
- Sphericity tests: Sphericity entails that the variances of the differences of the repeated measures conditions all have the same variance.
Expand All @@ -59,15 +59,15 @@ For each repeated measures factor, a specific contrast can be selected by clicki
- repeated: By selecting this contrast, the mean of each level is compared to the mean of the following level.
- polynomial: This contrast tests polynomial trends in the data. The specific polynomial that will be used for the analysis depends on the number of levels of the repeated measures factor. The degree of the trend used for the analysis is the number of levels minus 1. Therefore, if the repeated measures factor consist of 2 levels, a linear trend is analysed. If the repeated measures factor consists of three levels, a quadratic trend is analysed in addition to the linear trend.
- custom: Here, the contrast weights can be specified manually. Some weights need to be non-zero.
- Assume equal variances: This option can be selected when the variances of the levels of the independent variable are assumed to be equal.
- Confidence interval: Confidence interval for the location parameter. By default, the confidence interval is set to 95%. This can be changed into the desired percentage.
- Pool error term for follow-up tests: By selecting this option, the univariate linear model, rather than the multivariate model, will be used for follow-up tests (contrasts, post-hoc tests, marginal means). Caution: multivariate models (i.e., unpooled error terms) handle departures from sphericity better, since these models allow the standard errors to differ for each level of the repeated measure(s) factor(s).
- Confidence interval: Confidence interval for the location parameter. By default, the confidence interval is set to 95%. This can be changed into the desired percentage.


### Post Hoc Tests
- Confidence intervals: When this option is selected, the confidence interval for the mean difference is calculated. This is done for every post hoc method except for Dunn. By default this is set to 95% but this can be adjusted into the desired percentage.
To perform a post hoc test, drag one or more factor names to the right column. Several options are available:
- Effect size: By selecting this option, the effect size (i.e., the magnitude of the observed effect) will be displayed. The used measure for the effect size is Cohen's d. The effect size will only be displayed for the post hoc type `Standard`.
- Pool error term for RM factors: A pooled error term assumes that the variances of the contrast scores are approximately equal (i.e., sphericity assumption) See Morey (2008) for more details.
- Pool error term for follow-up tests: By selecting this option, the univariate linear model, rather than the multivariate model, will be used for follow-up tests (contrasts, post-hoc tests, marginal means). Caution: multivariate models (i.e., unpooled error terms) handle departures from sphericity better, since these models allow the standard errors to differ for each level of the repeated measure(s) factor(s).
- Correction: To correct for multiple comparison testing and avoid Type I errors, different methods for correcting the p-value are available:
- Tukey: Compare all possible pairs of group means. This correction can be used when the groups of the repeated measures have an equal sample size and variance. This method is commonly used and is selected by default.
- Scheffe: Adjusting significance levels in a linear regression, to account for multiple comparisons. This method is considered to be quite conservative.
Expand Down Expand Up @@ -98,6 +98,7 @@ To create a descriptive plot, select the repeated measures factor to be placed o
- None: When this option is selected, no adjustment will be applied.
- Bonferroni: Bonferroni correction of the confidence intervals.
- Sidak: Sidak correction of the confidence intervals.
- Pool error term for follow-up tests: By selecting this option, the univariate linear model, rather than the multivariate model, will be used for follow-up tests (contrasts, post-hoc tests, marginal means). Caution: multivariate models (i.e., unpooled error terms) handle departures from sphericity better, since these models allow the standard errors to differ for each level of the repeated measure(s) factor(s).

### Simple Main Effects
The simple main effects represent the effect of one repeated measure factor for each level of the other repeated measures factor, by conducting an ANOVA for each subset of the data as specified by the moderator variables.
Expand Down
16 changes: 14 additions & 2 deletions inst/qml/AnovaRepeatedMeasures.qml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,13 @@ Form

Classical.SumOfSquares{}

CheckBox { name: "multivariateModelFollowup"; label: qsTr("Use multivariate model for follow-up tests"); checked: false }
CheckBox
{
id: poolErrorTermFollowup
name: "poolErrorTermFollowup"
label: qsTr("Pool error term for follow-up tests")
checked: false
}
}

Section
Expand Down Expand Up @@ -115,7 +121,13 @@ Form
{
columns: 2
CheckBox { name: "postHocEffectSize"; label: qsTr("Effect size") }
CheckBox { name: "postHocPooledError"; label: qsTr("Pool error term for RM factors"); checked: true }
CheckBox
{
isBound: false
label: qsTr("Pool error term for follow-up tests")
checked: poolErrorTermFollowup.checked
onCheckedChanged: poolErrorTermFollowup.checked = checked
}
}

Group
Expand Down
8 changes: 7 additions & 1 deletion inst/qml/common/classical/Contrasts.qml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ Section
Component
{
id: equalVarianceAssumption
CheckBox { name: "contrastEqualVariance"; label: qsTr("Assume equal variances"); checked: true }
CheckBox
{
isBound: false
label: qsTr("Pool error term for follow-up tests")
checked: poolErrorTermFollowup.checked
onCheckedChanged: poolErrorTermFollowup.checked = checked
}
}
sourceComponent: analysis === Common.Type.Analysis.RMANOVA ? equalVarianceAssumption : undefined
}
Expand Down
9 changes: 9 additions & 0 deletions inst/qml/common/classical/MarginalMeans.qml
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,14 @@ Section
]
}
}

CheckBox
{
isBound: false
visible: analysis === Common.Type.Analysis.RMANOVA
label: qsTr("Pool error term for follow-up tests")
checked: poolErrorTermFollowup.checked
onCheckedChanged: poolErrorTermFollowup.checked = checked
}

}
2 changes: 1 addition & 1 deletion tests/testthat/_snaps/ancova/puppylove1.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/testthat/_snaps/ancova/puppylove2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion tests/testthat/_snaps/ancova/puppylove3.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading