├── .Rbuildignore ├── .gitattributes ├── .github ├── .gitignore └── workflows │ ├── R-CMD-check.yaml │ └── test-coverage.yaml ├── .gitignore ├── DESCRIPTION ├── NAMESPACE ├── NEWS.md ├── R ├── anova.art.R ├── art.R ├── art.con.R ├── art.con.internal.R ├── artlm.R ├── artlm.con.R ├── data.R ├── flat.anova.R ├── global.variables.R ├── internal.R └── release.questions.R ├── README.Rmd ├── README.md ├── cran-comments.md ├── data ├── ElkinAB.RData ├── ElkinABC.RData ├── Higgins1990Table1.RData ├── Higgins1990Table1.art.RData ├── Higgins1990Table5.RData ├── Higgins1990Table5.art.RData ├── HigginsABC.RData ├── HigginsABC.art.RData └── InteractionTestData.RData ├── inst ├── CITATION └── WORDLIST ├── man ├── ElkinAB.Rd ├── ElkinABC.Rd ├── Higgins1990Table1.Rd ├── Higgins1990Table1.art.Rd ├── Higgins1990Table5.Rd ├── Higgins1990Table5.art.Rd ├── HigginsABC.Rd ├── HigginsABC.art.Rd ├── InteractionTestData.Rd ├── anova.art.Rd ├── art.Rd ├── art.con.Rd ├── artlm.Rd ├── artlm.con.Rd └── summary.art.Rd ├── tests ├── testthat.R └── testthat │ ├── test.anova.art.R │ ├── test.art.R │ ├── test.art.con.R │ ├── test.artlm.R │ ├── test.artlm.con.R │ ├── test.parse.art.formula.R │ └── test.summary.art.R └── vignettes ├── art-contrasts.Rmd └── art-effect-size.Rmd /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^CRAN-RELEASE$ 2 | ^.*\.Rproj$ 3 | ^\.Rproj\.user$ 4 | ^\.Rhistory$ 5 | ^\.RData$ 6 | ^[^/]*\.Rmd$ 7 | ^[^/]*\.html$ 8 | ^[^/]*\.txt$ 9 | ^.*~$ 10 | ^\.gitignore$ 11 | ^\.git$ 12 | ^\.gitattributes$ 13 | ^\.travis\.yml$ 14 | ^figure$ 15 | ^cache$ 16 | ^cran-comments.md$ 17 | ^inst/WORDLIST$ 18 | ^\.github$ 19 | ^doc$ 20 | ^Meta$ 21 | ^revdep$ 22 | ^CRAN-SUBMISSION$ 23 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | 9 | name: R-CMD-check.yaml 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | R-CMD-check: 15 | runs-on: ${{ matrix.config.os }} 16 | 17 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 18 | 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | config: 23 | - {os: macos-latest, r: 'release'} 24 | - {os: windows-latest, r: 'release'} 25 | - {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'} 26 | - {os: ubuntu-latest, r: 'release'} 27 | - {os: ubuntu-latest, r: 'oldrel-1'} 28 | 29 | env: 30 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 31 | R_KEEP_PKG_SOURCE: yes 32 | 33 | steps: 34 | - uses: actions/checkout@v4 35 | 36 | - uses: r-lib/actions/setup-pandoc@v2 37 | 38 | - uses: r-lib/actions/setup-r@v2 39 | with: 40 | r-version: ${{ matrix.config.r }} 41 | http-user-agent: ${{ matrix.config.http-user-agent }} 42 | use-public-rspm: true 43 | 44 | - uses: r-lib/actions/setup-r-dependencies@v2 45 | with: 46 | extra-packages: any::rcmdcheck 47 | needs: check 48 | 49 | - uses: r-lib/actions/check-r-package@v2 50 | with: 51 | upload-snapshots: true 52 | build_args: 'c("--no-manual","--compact-vignettes=gs+qpdf")' 53 | -------------------------------------------------------------------------------- /.github/workflows/test-coverage.yaml: -------------------------------------------------------------------------------- 1 | # Workflow derived from https://github.com/r-lib/actions/tree/v2/examples 2 | # Need help debugging build failures? Start at https://github.com/r-lib/actions#where-to-find-help 3 | on: 4 | push: 5 | branches: 6 | - '*' 7 | pull_request: 8 | 9 | name: test-coverage.yaml 10 | 11 | permissions: read-all 12 | 13 | jobs: 14 | test-coverage: 15 | runs-on: ubuntu-latest 16 | env: 17 | GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }} 18 | 19 | steps: 20 | - uses: actions/checkout@v4 21 | 22 | - uses: r-lib/actions/setup-r@v2 23 | with: 24 | use-public-rspm: true 25 | 26 | - uses: r-lib/actions/setup-r-dependencies@v2 27 | with: 28 | extra-packages: any::covr, any::xml2 29 | needs: coverage 30 | 31 | - name: Test coverage 32 | run: | 33 | cov <- covr::package_coverage( 34 | quiet = FALSE, 35 | clean = FALSE, 36 | install_path = file.path(normalizePath(Sys.getenv("RUNNER_TEMP"), winslash = "/"), "package") 37 | ) 38 | print(cov) 39 | covr::to_cobertura(cov) 40 | shell: Rscript {0} 41 | 42 | - uses: codecov/codecov-action@v5 43 | with: 44 | # Fail if error if not on PR, or if on PR and token is given 45 | fail_ci_if_error: ${{ github.event_name != 'pull_request' || secrets.CODECOV_TOKEN }} 46 | files: ./cobertura.xml 47 | plugins: noop 48 | disable_search: true 49 | token: ${{ secrets.CODECOV_TOKEN }} 50 | 51 | - name: Show testthat output 52 | if: always() 53 | run: | 54 | ## -------------------------------------------------------------------- 55 | find '${{ runner.temp }}/package' -name 'testthat.Rout*' -exec cat '{}' \; || true 56 | shell: bash 57 | 58 | - name: Upload test results 59 | if: failure() 60 | uses: actions/upload-artifact@v4 61 | with: 62 | name: coverage-test-failures 63 | path: ${{ runner.temp }}/package 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .project 3 | .settings 4 | *.Rproj* 5 | .Rhistory 6 | README.html 7 | art-contrasts.html 8 | art-effect-size.html 9 | cache 10 | .RData 11 | .Rproj.user 12 | doc 13 | Meta 14 | .DS_Store 15 | .Rhistory 16 | revdep/ 17 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: ARTool 2 | Title: Aligned Rank Transform 3 | Version: 0.11.2 4 | Date: 2025-04-09 5 | Authors@R: c(person("Matthew", "Kay", role = c("aut", "cre"), 6 | email = "mjskay@northwestern.edu"), 7 | person("Lisa A.", "Elkin", role = "aut", 8 | email = "lelkin@cs.washington.edu"), 9 | person("James J.", "Higgins", role = "aut", 10 | email = "jhiggins@ksu.edu"), 11 | person("Jacob O.", "Wobbrock", role = "aut", 12 | email = "wobbrock@uw.edu")) 13 | Maintainer: Matthew Kay 14 | Description: The aligned rank transform for nonparametric 15 | factorial ANOVAs as described by Wobbrock, 16 | Findlater, Gergle, and Higgins (2011) . 17 | Also supports aligned rank transform contrasts as described 18 | by Elkin, Kay, Higgins, and Wobbrock (2021) 19 | . 20 | Depends: R (>= 3.2) 21 | Imports: 22 | lme4, 23 | car (>= 2.0-24), 24 | plyr, 25 | magrittr, 26 | dplyr, 27 | emmeans 28 | Suggests: 29 | testthat (>= 0.10.0), 30 | knitr, 31 | rmarkdown, 32 | ggplot2, 33 | tidyr, 34 | pander, 35 | lmerTest, 36 | cluster, 37 | phia, 38 | survival, 39 | psych, 40 | stringi, 41 | DescTools, 42 | tibble, 43 | covr 44 | License: GPL (>= 2) 45 | Language: en-US 46 | BugReports: https://github.com/mjskay/ARTool/issues 47 | URL: https://github.com/mjskay/ARTool/ 48 | VignetteBuilder: knitr 49 | RoxygenNote: 7.3.2 50 | Encoding: UTF-8 51 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | S3method(anova,art) 4 | S3method(print,anova.art) 5 | S3method(print,art) 6 | S3method(print,summary.art) 7 | S3method(summary,art) 8 | export(art) 9 | export(art.con) 10 | export(artlm) 11 | export(artlm.con) 12 | import(dplyr) 13 | importFrom(car,Anova) 14 | importFrom(emmeans,contrast) 15 | importFrom(emmeans,emmeans) 16 | importFrom(lme4,lmer) 17 | importFrom(magrittr,"%<>%") 18 | importFrom(plyr,ddply) 19 | importFrom(plyr,laply) 20 | importFrom(plyr,ldply) 21 | importFrom(plyr,llply) 22 | importFrom(stats,anova) 23 | importFrom(stats,aov) 24 | importFrom(stats,as.formula) 25 | importFrom(stats,complete.cases) 26 | importFrom(stats,lm) 27 | importFrom(stats,model.frame) 28 | importFrom(stats,p.adjust) 29 | importFrom(stats,p.adjust.methods) 30 | importFrom(stats,symnum) 31 | importFrom(stats,terms) 32 | importFrom(stats,update) 33 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # ARTool 0.11.2 2 | 3 | Minor changes: 4 | 5 | * Retain unconcatenated columns in `art.con()` internal data so that contrasts 6 | can be computed for random slopes in mixed effects models (#37). 7 | 8 | Bug fixes: 9 | 10 | * Fix broken examples due to changes in `emmeans`. 11 | 12 | 13 | # ARTool 0.11.1 14 | 15 | Minor changes: 16 | 17 | * Updated DOIs and citations (#32). 18 | * Ensure `tibble`s can be used with `art.con()` (#27). 19 | 20 | 21 | # ARTool 0.11.0 22 | 23 | Major changes: 24 | 25 | * Added the `art.con()` and `artlm.con()` implementing the ART-C procedure 26 | for contrast testing, and updated `vignette("art-contrasts")` to 27 | demonstrate it. 28 | 29 | 30 | # ARTool 0.10.8 31 | 32 | Minor release to update maintainer email address. 33 | 34 | 35 | # ARTool 0.10.7 36 | 37 | Minor changes: 38 | 39 | * Forward-compatible fix for tests needed for when `stringsAsFactors` default 40 | becomes `FALSE` (in R 4). 41 | 42 | Bug fixes: 43 | 44 | * Fix for a bug in alignment that occurs with high-order interactions (4+) 45 | (thanks to Hidekazu Kaneko). 46 | 47 | 48 | # ARTool 0.10.6 49 | 50 | Minor changes: 51 | 52 | * Minor changes to ensure tests pass due to changes in output from `lme4::lmer` 53 | 54 | 55 | # ARTool 0.10.5 56 | 57 | Minor changes: 58 | 59 | * Replace `lsmeans` with `emmeans` in code and docs due to `lsmeans` being deprecated 60 | * Use `psych::d.ci` for Cohen's _d_ CIs in effect size vignette 61 | 62 | 63 | # ARTool 0.10.4 64 | 65 | Minor changes: 66 | 67 | * Reference `phia` vignette using function call instead of non-canonical URL 68 | * Cautionary note about standardized effect sizes in effect size vignette 69 | * Added `testInteractions()` example as alternative in contrasts vignette 70 | 71 | Bug fixes: 72 | 73 | * Dependency fixes for failed test at `testthat/test.artlm.R:35` (our use of `lsmeans()` in that 74 | test requires some additional packages only declared as "Suggests" in `lsmeans`, so now 75 | we "Suggest" them as well). 76 | 77 | 78 | # ARTool 0.10.1 79 | 80 | New features: 81 | 82 | * New vignette describing effect size estimates 83 | * Using `lsmeans()` interactions argument instead of `phia` for interaction contrasts vignette 84 | 85 | Bug fixes: 86 | 87 | * Require R >= 3.2 and `lsmeans` >= 2.22 to fix some bugs in earlier versions 88 | 89 | 90 | # ARTool 0.10.0 91 | 92 | New features: 93 | 94 | * Support for `Error()` terms in model formulas (resulting models are run using `aov()`) 95 | * Checks for numeric variables passed into formulas that may cause incorrect results (if the user intended data to be treated as categorical) 96 | * More detailed ANOVA tables 97 | * New vignette describing contrast tests, particularly for interactions (vignette("art-contrasts")) 98 | 99 | Bug fixes: 100 | 101 | * Formulas now correctly support arbitrary expressions as terms (rather than just column names). 102 | 103 | 104 | # ARTool 0.9.5 105 | 106 | Testing fix for changes in upcoming version of `lsmeans`: round `lsmeans` p value tests to 5 decimal places to accommodate changes to Tukey adjustment 107 | 108 | 109 | # ARTool 0.9.4 110 | 111 | Minor changes to testing based on updated version of `testthat` (0.10.0): 112 | 113 | * More closely follow recommended `testthat` usage 114 | * Skip tests requiring `lsmeans` if it is not installed 115 | -------------------------------------------------------------------------------- /R/anova.art.R: -------------------------------------------------------------------------------- 1 | # ANOVAs for art objects 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | 7 | #' Aligned Rank Transform Analysis of Variance 8 | #' 9 | #' Conduct analyses of variance on aligned rank transformed data. 10 | #' 11 | #' This function runs several ANOVAs: one for each fixed effect term in the 12 | #' model \code{object}. In each ANOVA, the independent variables are the same, 13 | #' but the response is aligned by a different fixed effect term (if response is 14 | #' "aligned") or aligned and ranked by that fixed effect term (if response is 15 | #' "art"). These models are generated using \code{\link{artlm}}. 16 | #' 17 | #' From each model, only the relevant output rows are kept (unless 18 | #' \code{all.rows} is \code{TRUE}, in which case all rows are kept). 19 | #' 20 | #' When \code{response} is \code{"art"} (the default), only one row is kept 21 | #' from each ANOVA: the row corresponding to fixed effect term the response was 22 | #' aligned and ranked by. These results represent nonparametric tests of 23 | #' significance for the effect of each term on the original response variable. 24 | #' 25 | #' When \code{response} is \code{"aligned"}, all rows \emph{except} the row 26 | #' corresponding to the fixed effect term the response was aligned by are kept. 27 | #' If the ART procedure is appropriate for this data, these tests should have 28 | #' all effects "stripped out", and have an F value of ~0. If that is not the 29 | #' case, another analysis should be considered. This diagnostic is tested by 30 | #' \code{\link{summary.art}} and a warning generated if the F values are not 31 | #' all approximately 0. 32 | #' 33 | #' @name anova.art 34 | #' @rdname anova.art 35 | #' @aliases anova.art print.anova.art 36 | #' @param object An object of class \code{\link{art}}. 37 | #' @param response Which response to run the ANOVA on: the aligned responses 38 | #' (\code{"aligned"}) or the aligned and ranked responses (\code{"art"}). This 39 | #' argument is passed to \code{\link{artlm}}. See 'Details'. 40 | #' @param type Type of ANOVAs to conduct. If \code{type} is \code{1} or 41 | #' \code{"I"}, then conducts Type I ANOVAs using \code{\link{anova}}. 42 | #' Otherwise, conducts Type II or Type III ANOVAs using \code{\link[car]{Anova}}. 43 | #' The default is Type III \emph{if} the underlying model supports it. Models 44 | #' fit with \code{Error} terms are fit using \code{\link{aov}}, which only 45 | #' supports Type I ANOVAs. 46 | #' @param factor.contrasts The name of the contrast-generating function to be 47 | #' applied by default to fixed effect factors. See the first element of 48 | #' \code{\link{options}("contrasts")}. The default is to use 49 | #' \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is appropriate for 50 | #' Type III ANOVAs (also the default). This argument is passed to 51 | #' \code{\link{artlm}}. 52 | #' @param test Test statistic to use. Default \code{"F"}. Note that some models 53 | #' and ANOVA types may not support \code{"Chisq"}. 54 | #' @param all.rows Show all rows of the resulting ANOVA tables? By default 55 | #' (\code{FALSE}), shows only the rows that are relevant depending on the type 56 | #' of \code{response}. 57 | #' @param x An object of class \code{\link{art}}. 58 | #' @param verbose When \code{TRUE}, sums of squares and residual sum of squares 59 | #' in addition to degrees of freedom are printed in some ANOVA types (e.g. 60 | #' repeated measures ANOVAs). Default \code{FALSE}, for brevity. 61 | #' @param digits Digits of output in printed table; see \code{\link{print}}. 62 | #' @param \dots Additional arguments passed to \code{\link[car]{Anova}} or 63 | #' \code{\link{anova}} by \code{anova.art} or to \code{\link{print}} by 64 | #' \code{print.anova.art}. 65 | #' @return An object of class \code{"anova"}, which usually is printed. 66 | #' @author Matthew Kay 67 | #' @seealso See \code{\link{art}} for an example. See also 68 | #' \code{\link{summary.art}}, \code{\link{artlm}}. 69 | #' @references Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 70 | #' (2011). The aligned rank transform for nonparametric factorial analyses 71 | #' using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 72 | #' Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 73 | #' (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 74 | #' @keywords nonparametric 75 | #' 76 | #' @export 77 | anova.art = function(object, 78 | response=c("art", "aligned"), 79 | type=c("III", "II", "I", 3, 2, 1), 80 | factor.contrasts="contr.sum", 81 | test=c("F", "Chisq"), 82 | all.rows=FALSE, 83 | ... 84 | ) { 85 | #sensible names for generic parameters 86 | m = object 87 | 88 | #match enum arguments 89 | response = match.arg(response) 90 | type = as.character(type) 91 | type = match.arg(type) 92 | test = match.arg(test) 93 | 94 | #get transformed responses based on response type requested 95 | responses = switch(response, 96 | aligned=m$aligned, 97 | art=m$aligned.ranks) 98 | 99 | #determine anova type to use 100 | type = if (type %in% c(1,"I")) "I" 101 | else if (type %in% c(2,"II")) "II" 102 | else if (type %in% c(3,"III")) "III" 103 | 104 | #are we going to need to show the term we aligned by 105 | #for each row of the output? 106 | show.aligned.by.term = response == "aligned" || all.rows 107 | 108 | #run linear models and anovas 109 | df = m$data 110 | anovas = NULL 111 | table.description = "" 112 | for (j in 1:ncol(responses)) { #can't use ldply here because it appears to drop row names when binding rows 113 | aligned.by.term = colnames(responses)[[j]] 114 | 115 | #get linear model 116 | m.l = artlm(m, aligned.by.term, response=response, factor.contrasts=factor.contrasts) 117 | 118 | #run anova and extract desired results 119 | anova.j = flat.anova(m.l, type=type, test=test, ...) 120 | if (j == 1) table.description = attr(anova.j, "description") 121 | 122 | #extract desired result rows from anova 123 | #for art, this is the one row correponding to the effect we aligned and ranked by 124 | #for aligned, this is every effect *except* the one we aligned and ranked by 125 | if (!all.rows) { 126 | include.row = anova.j$Term == aligned.by.term 127 | if (response == "aligned") include.row = !include.row 128 | anova.j = anova.j[include.row,] 129 | } 130 | 131 | #Add "Aligned By" column when needed to disambiguate 132 | if (nrow(anova.j) > 0) { 133 | #if only one fixed effect we can get no rows here, e.g. if response="aligned" 134 | #and all.rows=FALSE, so the above guard is necessary 135 | if (show.aligned.by.term) { 136 | anova.j = cbind(anova.j[,1,drop=FALSE], `Aligned By`=aligned.by.term, anova.j[,-1,drop=FALSE]) 137 | } 138 | } 139 | 140 | anovas = rbind(anovas, anova.j) 141 | } 142 | 143 | #fill in the rest of the anova table metadata and return 144 | class(anovas) = c("anova.art", "anova", "data.frame") 145 | attr(anovas, "model") = 146 | if (m$n.grouping.terms > 0) "lmer" 147 | else if (m$n.error.terms > 0) "aov" 148 | else "lm" 149 | attr(anovas, "table.description") = table.description 150 | attr(anovas, "response") = response 151 | attr(anovas, "response.term") = colnames(m$cell.means)[1] 152 | anovas 153 | } 154 | 155 | ### Generate p stars for a vector of p values 156 | #' @importFrom stats symnum 157 | p.stars = function(p.values) { 158 | unclass(symnum(p.values, corr = FALSE, na = FALSE, cutpoints = c(0, 159 | 0.001, 0.01, 0.05, 0.1, 1), symbols = c("***", "**", 160 | "*", ".", " "))) 161 | } 162 | 163 | #' @rdname anova.art 164 | #' @importFrom magrittr %<>% 165 | #' @export 166 | print.anova.art = function(x, verbose=FALSE, digits=5, ...) { 167 | #print heading and metadata 168 | cat("Analysis of Variance of Aligned Rank Transformed Data\n\n") 169 | cat("Table Type:", attr(x, "table.description"), "\n") 170 | cat("Model:", switch(attr(x, "model"), 171 | lm = "No Repeated Measures (lm)\n", 172 | aov = "Repeated Measures (aov)\n", 173 | lmer = "Mixed Effects (lmer)\n", 174 | )) 175 | cat(sep="", "Response: ", attr(x, "response"), "(", attr(x, "response.term"), ")\n\n") 176 | 177 | #format p values 178 | p.col = last(which(grepl("^(P|Pr)\\(", names(x)))) 179 | stars.legend = if (!is.na(p.col)) { 180 | #add column for p stars 181 | stars = p.stars(x[[p.col]]) 182 | x = cbind(x, ` ` = stars) 183 | #reformat p values for printing 184 | x[[p.col]] %<>% format.pval() 185 | #return stars legend 186 | attr(stars, "legend") 187 | } else NULL 188 | 189 | #generate row names from Terms 190 | rownames(x) = paste(1:nrow(x), x$Term) 191 | x$Term = NULL 192 | 193 | #abbreviate columns 194 | if (!is.null(x$Error)) x$Error %<>% abbreviate(5) 195 | if (!is.null(x$`Aligned By`)) x$`Aligned By` %<>% abbreviate(10) 196 | 197 | #drop "Sum Sq" (etc) columns when not doing verbose output 198 | if (!verbose) x %<>% select(everything(), -contains("Sum Sq"), -contains("Mean Sq")) 199 | 200 | #print table 201 | print.data.frame(x, digits=digits, ...) 202 | 203 | #print legend 204 | if (!is.null(stars.legend)) { 205 | cat("---\nSignif. codes: ", stars.legend, "\n") 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /R/art.R: -------------------------------------------------------------------------------- 1 | # art function and some basic generic function implementations for art objects, 2 | # such as print.art and summary.art 3 | # 4 | # Author: mjskay 5 | ############################################################################### 6 | 7 | 8 | #' Aligned Rank Transform 9 | #' 10 | #' Apply the aligned rank transform to a factorial model (with optional 11 | #' grouping terms). Usually done in preparation for a nonparametric analyses of 12 | #' variance on models with numeric or ordinal responses, which can be done by 13 | #' following up with \code{anova.art}. 14 | #' 15 | #' The aligned rank transform allows a nonparametric analysis of variance to be 16 | #' conducted on factorial models with fixed and random effects (or repeated 17 | #' measures) and numeric or ordinal responses. This is done by first aligning 18 | #' and ranking the fixed effects using this function, then conducting an 19 | #' analysis of variance on linear models built from the transformed data using 20 | #' \code{\link{anova.art}} (see 'Examples'). The model specified using this 21 | #' function \emph{must} include all interactions of fixed effects. 22 | #' 23 | #' The \code{formula} should contain a single response variable (left-hand 24 | #' side) that can be numeric, an ordered factor, or logical. The right-hand 25 | #' side of the formula should contain one or more fixed effect factors, zero or 26 | #' more grouping terms, and zero or more error terms. Error terms and grouping 27 | #' terms cannot be used simultaneously. All possible interactions of the fixed 28 | #' effect terms must be included. For example, \code{y ~ x} and \code{y ~ 29 | #' a*b*c} and \code{y ~ a + b + b:c} are legal, but \code{y ~ a + b} is not, as 30 | #' it omits the interaction \code{a:b}. Grouping terms are specified as in 31 | #' \code{\link[lme4]{lmer}}, e.g. \code{y ~ a*b*c + (1|d)} includes the random 32 | #' intercept term \code{(1|d)}. Error terms are specified as in 33 | #' \code{\link{aov}}, e.g. \code{y ~ a*b*c + Error(d)}. Grouping terms and 34 | #' error terms are not involved in the transformation, but are included in the 35 | #' model when ANOVAs are conducted, see \code{\link{anova.art}}. 36 | #' 37 | #' For details on the transformation itself, see Wobbrock \emph{et al.} (2011) 38 | #' or the ARTool website: \url{https://depts.washington.edu/acelab/proj/art/}. 39 | #' 40 | #' @param formula A factorial formula with optional grouping terms or error 41 | #' terms (but not both). Should be a formula with a single response variable 42 | #' (left-hand side) and one or more terms with all interactions on the 43 | #' right-hand side, e.g. \code{y ~ x} or \code{y ~ a*b*c} or \code{y ~ a + b + 44 | #' b:c}. If you want to run a mixed effects ANOVA on the transformed data using 45 | #' \code{\link[lme4]{lmer}}, you can include grouping terms, as in \code{y ~ a*b*c + 46 | #' (1|d)}. If you want to run a repeated measures ANOVA using 47 | #' \code{\link{aov}}, you can include error terms, as in \code{y ~ a*b*c + 48 | #' Error(d)}. See 'Details'. 49 | #' @param data An optional data frame containing the variables in the model. 50 | #' @param rank.comparison.digits The number of digits to round aligned 51 | #' responses to before ranking (to ensure ties are computed consistently). See 52 | #' the \code{digits} argument of \code{\link{round}}. The default value is 53 | #' based on the default \code{tolerance} used for fuzzy comparison in 54 | #' \code{all.equal}. 55 | #' @param check.errors.are.factors Should we check to ensure \code{Error()} 56 | #' terms are all factors? A common mistake involves coding a categorical variable 57 | #' as numeric and passing it to \code{Error()}, yielding incorrect results 58 | #' from \code{\link{aov}}. Disabling this check is not recommended unless you 59 | #' know what you are doing; the most common uses of \code{Error()} (e.g. 60 | #' in repeated measures designs) involve categorical variables (factors). 61 | #' @return An object of class \code{"art"}: 62 | #' 63 | #' \item{call}{ The call used to generate the transformed data. } 64 | #' \item{formula}{ The formula used to generate the transformed data. } 65 | #' \item{cell.means}{ A data frame of cell means for each fixed term and 66 | #' interaction on the right-hand side of formula. } \item{estimated.effects}{ A 67 | #' data frame of estimated effects for each fixed term and interaction on the 68 | #' right-hand side of formula. } \item{residuals}{ A vector of residuals 69 | #' (response - cell mean of highest-order interaction). } \item{aligned}{ A 70 | #' data frame of aligned responses for each fixed term and interaction on the 71 | #' right-hand side of formula. } \item{aligned.ranks}{ A data frame of aligned 72 | #' and ranked responses for each fixed term and interaction on the right-hand 73 | #' side of formula. } \item{data}{ The input data frame } 74 | #' \item{n.grouping.terms}{ The number of grouping variables in the input 75 | #' formula. } 76 | #' 77 | #' For a complete description of cell means, estimated effects, aligned ranks, 78 | #' etc., in the above output, see Wobbrock \emph{et al.} (2011). 79 | #' @author Matthew Kay 80 | #' @seealso \code{\link{summary.art}}, \code{\link{anova.art}}, 81 | #' \code{\link{artlm}}, \code{\link{artlm.con}}, \code{\link{art.con}}. 82 | #' @references Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 83 | #' \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 84 | #' 85 | #' Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 86 | #' (2011). The aligned rank transform for nonparametric factorial analyses 87 | #' using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 88 | #' Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 89 | #' (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 90 | #' @keywords nonparametric 91 | #' @examples 92 | #' \donttest{ 93 | #' data(Higgins1990Table5, package = "ARTool") 94 | #' 95 | #' ## perform aligned rank transform 96 | #' m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 97 | #' 98 | #' ## see summary data to ensure aligned rank transform is appropriate for this data 99 | #' summary(m) 100 | #' ## looks good (aligned effects sum to 0 and F values on aligned responses 101 | #' ## not of interest are all ~0) 102 | #' 103 | #' ## we can always look at the anova of aligned data if we want more detail 104 | #' ## to assess the appropriateness of ART. F values in this anova should all 105 | #' ## be approx 0. 106 | #' anova(m, response="aligned") 107 | #' 108 | #' ## then we can run an anova on the ART responses (equivalent to anova(m, response="art")) 109 | #' anova(m) 110 | #' 111 | #' 112 | #' ## if we want contrast tests, we can use art.con(): 113 | #' ## Ex 1: pairwise contrasts on Moisture: 114 | #' art.con(m, "Moisture") 115 | #' ## Ex 2: pairwise contrasts on Moisture:Fertilizer: 116 | #' art.con(m, "Moisture:Fertilizer") 117 | #' ## Ex 3: difference-of-difference tests on the Moisture:Fertilizer interaction: 118 | #' art.con(m, "Moisture:Fertilizer", interaction = TRUE) 119 | #' 120 | #' 121 | #' ## The above three examples with art.con() can be constructed manually as well. 122 | #' ## art.con() extracts the appropriate linear model and conducts contrasts 123 | #' ## using emmeans(). If we want to use a specific method for post-hoc tests 124 | #' ## other than emmeans(), artlm.con(m, term) returns the linear model for the 125 | #' ## specified term which we can then examine using our preferred method 126 | #' ## (emmeans, glht, etc). The equivalent calls for the above examples are: 127 | #' library(emmeans) 128 | #' 129 | #' ## Ex 1: pairwise contrasts on Moisture: 130 | #' contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method = "pairwise") 131 | #' 132 | #' ## Ex 2: pairwise contrasts on Moisture:Fertilizer: 133 | #' ## See artlm.con() documentation for more details on the syntax, specifically 134 | #' ## the formula passed to emmeans. 135 | #' contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method = "pairwise") 136 | #' 137 | #' ## Ex 3: difference-of-difference tests on the Moisture:Fertilizer interaction: 138 | #' ## Note the use of artlm() instead of artlm.con() 139 | #' contrast( 140 | #' emmeans(artlm(m, "Moisture:Fertilizer"), ~ Moisture:Fertilizer), 141 | #' method = "pairwise", interaction = TRUE 142 | #' ) 143 | #' 144 | #' 145 | #' ## For a more in-depth explanation and example of contrasts with art and 146 | #' ## differences between interaction types, see vignette("art-contrasts") 147 | #' 148 | #' } 149 | #' 150 | #' @importFrom stats complete.cases model.frame terms 151 | #' @importFrom plyr laply llply 152 | #' @export 153 | art = function(formula, data, 154 | #number of digits to round aligned responses to before ranking (to ensure ties are computed consistently) 155 | rank.comparison.digits = -floor(log10(.Machine$double.eps ^ 0.5)), 156 | check.errors.are.factors = TRUE 157 | ) { 158 | #parse and validate formula 159 | f = parse.art.formula(formula) 160 | 161 | #generate base model data frame (fixed effects only) 162 | if (missing(data)) { 163 | data = environment(formula) 164 | } 165 | #get data frame from input formula and data 166 | #first col will be response, followed by fixed effects 167 | df = model.frame(f$fixed.only, data, na.action=function(object) { 168 | #verify that all cases are complete (no NAs) 169 | #TODO: add na.rm or na.action support 170 | if (!all(complete.cases(object))) { 171 | stop("Aligned Rank Transform cannot be performed when fixed effects have missing data (NAs).") 172 | } 173 | object 174 | }) 175 | 176 | #verify that the reponse is numeric or ordinal and translate to ordinal 177 | if (!is.numeric(df[,1]) && !is.ordered(df[,1]) && !is.logical(df[,1])) { 178 | stop("Reponse term must be numeric, ordered factor, or logical (it was ", do.call(paste, as.list(class(df[,1]))), ")") 179 | } 180 | #coerce response to numeric for processing 181 | df[,1] = as.numeric(df[,1]) 182 | 183 | #verify that all fixed effects are factors #TODO: can these be ordered factors? 184 | non.factor.terms = Filter(function (col) !is.factor(df[,col]) && !is.logical(df[,col]), 2:ncol(df)) 185 | if (any(non.factor.terms)) { 186 | stop( 187 | "All fixed effect terms must be factors or logical (e.g. not numeric).\n", 188 | " The following terms are not factors or logical:\n ", 189 | paste0(names(df)[non.factor.terms], collapse = "\n "), 190 | "\n If these terms are intended to represent categorical data, you may\n ", 191 | "want to convert them into factors using factor()." 192 | ) 193 | } 194 | #coerce fixed effects to numeric for processing 195 | for (j in 2:ncol(df)) { 196 | df[,j] = as.numeric(df[,j]) 197 | } 198 | 199 | #for error terms, issue error if any terms aren't factors 200 | if (check.errors.are.factors && f$n.error.terms > 0) { 201 | error.term.df = model.frame(f$error.terms, data) 202 | non.factor.error.terms = Filter(function (col) !is.factor(error.term.df[,col]), 1:ncol(error.term.df)) 203 | if (any(non.factor.error.terms)) { 204 | stop( 205 | "The following Error terms are not factors:\n ", 206 | paste0(names(error.term.df)[non.factor.error.terms], collapse = "\n "), 207 | "\n If these terms are intended to represent categorical data, such as subjects in a \n", 208 | " repeated measures design, you should convert them into factors using factor().\n", 209 | " \n", 210 | " If you know what you are doing and still want Error terms that are not factors, use\n", 211 | " check.errors.are.factors = FALSE." 212 | ) 213 | } 214 | } 215 | 216 | #calculate cell means and estimated effects 217 | m = art.estimated.effects(terms(f$fixed.only), df) 218 | 219 | #calculate residuals (response - cell mean of highest-order interaction) 220 | m$residuals = df[,1] - m$cell.means[,ncol(m$cell.means)] 221 | 222 | #calculate aligned responses 223 | m$aligned = m$residuals + m$estimated.effects 224 | 225 | #compute aligned and ranked responses 226 | m$aligned.ranks = data.frame(llply(round(m$aligned, rank.comparison.digits), rank), check.names=FALSE) 227 | 228 | class(m) = "art" 229 | m$formula = formula 230 | m$call = match.call() 231 | m$data = data 232 | m$n.grouping.terms = f$n.grouping.terms 233 | m$n.error.terms = f$n.error.terms 234 | m 235 | } 236 | 237 | 238 | #' Aligned Rank Transform Summary 239 | #' 240 | #' Summary and diagnostics for aligned rank transformed data 241 | #' 242 | #' This function gives diagnostic output to help evaluate whether the ART 243 | #' procedure is appropriate for an analysis. It tests that column sums of 244 | #' aligned responses are ~0 and that F values of ANOVAs on aligned responses 245 | #' not of interest are ~0. For more details on these diagnostics see Wobbrock 246 | #' \emph{et al.} (2011). 247 | #' 248 | #' @param object An object of class \code{\link{art}}. 249 | #' @param \dots Potentially further arguments passed from other methods. 250 | #' @return An object of class \code{"summary.art"}, which usually is printed. 251 | #' @author Matthew Kay 252 | #' @seealso See \code{\link{art}} for an example. See also 253 | #' \code{\link{anova.art}}. 254 | #' @references Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 255 | #' (2011). The aligned rank transform for nonparametric factorial analyses 256 | #' using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 257 | #' Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 258 | #' (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 259 | #' @keywords nonparametric 260 | #' 261 | #' @importFrom stats anova 262 | #' @export 263 | summary.art = function(object, ...) { 264 | #sensible names for generic parameters 265 | m = object 266 | 267 | #verify that aligned responses sum to 0 (using fuzzy compare) 268 | m$aligned.col.sums = colSums(m$aligned) 269 | if (!isTRUE(all.equal(as.vector(m$aligned.col.sums), rep(0, length(m$aligned.col.sums))))) { 270 | stop("Aligned responses do not sum to ~0. ART may not be appropriate.") 271 | } 272 | 273 | #verify that F values of ANOVA are all ~0 (using fuzzy compare) 274 | m$aligned.anova = anova(m, response="aligned") 275 | if (!isTRUE(all.equal(m$aligned.anova$F, rep(0, length(m$aligned.anova$F))))) { 276 | warning("F values of ANOVAs on aligned responses not of interest are not all ~0. ART may not be appropriate.") 277 | } 278 | 279 | class(m) = c("summary.art", class(m)) 280 | m 281 | } 282 | 283 | #' @export 284 | print.art = function(x, ...) print(summary(x), ...) 285 | 286 | #' @export 287 | print.summary.art = function(x, 288 | #number of digits to display (based on tolerance used for fuzzy compare in all.equals) 289 | display.digits = -floor(log10(.Machine$double.eps ^ 0.5)), 290 | ... 291 | ) { 292 | #sensible names for generic parameters 293 | m = x 294 | 295 | cat("Aligned Rank Transform of Factorial Model\n\nCall:\n", paste(deparse(m$call), sep="\n", collapse="\n"), "\n\n", sep="") 296 | cat("Column sums of aligned responses (should all be ~0):\n") 297 | print(round(m$aligned.col.sums, display.digits), ...) 298 | cat("\nF values of ANOVAs on aligned responses not of interest (should all be ~0):\n") 299 | print(round(summary(m$aligned.anova$F), display.digits), ...) 300 | } 301 | -------------------------------------------------------------------------------- /R/art.con.R: -------------------------------------------------------------------------------- 1 | # art.con function for both "normal" contrasts and interaction contrasts 2 | # 3 | # Author: lelkin 4 | ############################################################################### 5 | 6 | #' Aligned Ranked Transform Contrasts 7 | #' 8 | #' Conduct contrast tests following an Aligned Ranked Transform (ART) ANOVA 9 | #' (\code{\link{anova.art}}). Conducts contrasts on \code{\link{art}} models 10 | #' using aligned-and-ranked linear models using the ART (Wobbrock et al. 2011) 11 | #' or ART-C (Elkin et al. 2021) alignment procedure, as appropriate to the requested contrast. 12 | #' 13 | #' An \code{\link{art}} model \code{m} stores the \code{formula} and \code{data} 14 | #' that were passed to \code{\link{art}} when \code{m} was created. Depending on the 15 | #' requested contrast type, this function either extracts the linear model from \code{m} 16 | #' needed to perform that contrast or creates a new linear model on data 17 | #' aligned-and-ranked using the ART-C 18 | #' procedure, then conducts the contrasts specified in parameter \code{formula}. 19 | #' 20 | #' Internally, this function uses \code{\link{artlm.con}} (when \code{interaction = FALSE}) 21 | #' or \code{\link{artlm}} (when \code{interaction = TRUE}) to get the linear 22 | #' model necessary for the requested contrast, computes estimated marginal 23 | #' means on the linear model using \code{\link[emmeans]{emmeans}}, and conducts contrasts 24 | #' using \code{\link[emmeans]{contrast}}. 25 | #' 26 | #' @param m An object of class \code{\link{art}}. 27 | #' @param formula Either a character vector or a formula specifying the fixed 28 | #' effects whose levels will be compared. See "Formula" section below. 29 | #' @param response Which response to use: the aligned response 30 | #' (\code{"aligned"}) or the aligned-and-ranked response 31 | #' (\code{"art"}). Default is "art". This argument is passed to \code{\link{artlm.con}} 32 | #' (when \code{interaction = FALSE}) or \code{\link{artlm}} (when \code{interaction = TRUE}). 33 | #' @param factor.contrasts The name of the contrast-generating function to be 34 | #' applied by default to fixed effect factors. Sets the the first element of 35 | #' \code{\link{options}("contrasts")} for the duration of this function. The 36 | #' default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 37 | #' appropriate for Type III ANOVAs (the default ANOVA type for 38 | #' \code{\link{anova.art}}). This argument is passed to \code{\link{artlm.con}} / 39 | #' \code{\link{artlm}}. 40 | #' @param method Contrast method argument passed to \code{\link[emmeans]{contrast}}. 41 | #' Note: the default is \code{"pairwise"} even though the default for the 42 | #' \code{\link[emmeans]{contrast}} function is \code{"eff"}. 43 | #' @param interaction Logical value. If \code{FALSE} (the default), conducts contrasts using 44 | #' the ART-C procedure and \code{\link{artlm.con}}. If \code{TRUE}, conducts 45 | #' difference-of-difference contrasts using a model returned by \code{\link{artlm}}. 46 | #' See the "Interaction Contrasts" section in \code{\link[emmeans]{contrast}}. 47 | #' @param adjust Character: adjustment method (e.g., "bonferroni") passed to 48 | #' \code{\link[emmeans]{contrast}}. If not provided, \code{\link[emmeans]{contrast}} will use 49 | #' its default ("tukey" at the time of publication). All available options are listed 50 | #' in \code{\link[emmeans]{summary.emmGrid}} in the "P-value adjustments" section. 51 | #' @param \dots Additional arguments passed to \code{\link{lm}} or 52 | #' \code{\link[lme4]{lmer}}. 53 | #' @return An object of class \code{emmGrid}. See \code{\link[emmeans]{contrast}} 54 | #' for details. 55 | #' @author Lisa A. Elkin, Matthew Kay, Jacob O. Wobbrock 56 | #' 57 | #' @section Formula: Contrasts compare combinations of levels from multiple 58 | #' factors. The \code{formula} parameter indicates which factors are involved. Two 59 | #' formats are accepted: (1) a character vector as used in 60 | #' \code{\link{artlm}} and \code{\link{artlm.con}}, with factors separated by \code{":"}; 61 | #' or (2) a formula as used in \code{\link[emmeans]{emmeans}}, with factors separated by \code{*}. 62 | #' For example, contrasts comparing 63 | #' combinations of levels of factors \emph{X1} and \emph{X2} can be expressed 64 | #' as \code{"X1:X2"} (character vector) or as \code{~ X1*X2} (formula). 65 | #' 66 | #' @references Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 67 | #' (2021). An aligned rank transform procedure for multifactor contrast tests. 68 | #' \emph{Proceedings of the ACM Symposium on User Interface Software and 69 | #' Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 70 | #' ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 71 | #' 72 | #' Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 73 | #' (2011). The aligned rank transform for nonparametric factorial analyses 74 | #' using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 75 | #' Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 76 | #' (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 77 | #' 78 | #' @export 79 | #' 80 | #' @examples 81 | #' \donttest{ 82 | #' data(Higgins1990Table5, package = "ARTool") 83 | #' 84 | #' library(dplyr) 85 | #' 86 | #' ## Perform aligned rank transform 87 | #' m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 88 | #' 89 | #' ## In a some workflows, contrast tests using ART-C would follow a 90 | #' ## significant omnibus effect found by running an anova on the ART responses 91 | #' ## (equivalent to anova(m, response="art")). 92 | #' ## If conducting planned contrasts, this step can be skipped. 93 | #' anova(m) 94 | #' 95 | #' ## We can conduct contrasts comparing levels of Moisture using the ART-C procedure. 96 | #' ## If conducting contrasts as a post hoc test, this would follow a significant effect 97 | #' ## of Moisture on DryMatter. 98 | #' 99 | #' ## Using a character vector 100 | #' art.con(m, "Moisture") 101 | #' ## Or using a formula 102 | #' art.con(m, ~ Moisture) 103 | #' 104 | #' ## Note: Since the ART-C procedure is mathematically equivalent to the ART procedure 105 | #' ## in the single-factor case, this is the same as 106 | #' ## emmeans(artlm(m, "Moisture"), pairwise ~ Moisture) 107 | #' 108 | #' ## art.con() returns an emmGrid object, which does not print asterisks 109 | #' ## beside "significant" tests (p < 0.05). If you wish to add stars beside 110 | #' ## tests of a particular significant level, you can always do that to the 111 | #' ## data frame returned by the summary() method of emmGrid. For example: 112 | #' art.con(m, ~ Moisture) %>% 113 | #' summary() %>% 114 | #' mutate(sig = ifelse(p.value < 0.05, "*", "")) 115 | #' 116 | #' ## Or a more complex example: 117 | #' art.con(m, ~ Moisture) %>% 118 | #' summary() %>% 119 | #' mutate(sig = symnum(p.value, corr = FALSE, na = FALSE, 120 | #' cutpoints = c(0, 0.001, 0.01, 0.05, 0.1, 1), 121 | #' symbols = c("***", "**", "*", ".", " ") 122 | #' )) 123 | #' 124 | #' ## We can conduct contrasts comparing combinations of levels 125 | #' ## of Moisture and Fertilizer using the ART-C procedure. 126 | #' ## If conducting contrasts as a post hoc test, this would follow 127 | #' ## a significant Moisture:Fertlizer interaction effect on Drymatter. 128 | #' 129 | #' ## Using a character vector for formula 130 | #' art.con(m, "Moisture:Fertilizer") 131 | #' ## Using a formula 132 | #' art.con(m, ~ Moisture*Fertilizer) 133 | #' 134 | #' ## We can also conduct interaction contrasts (comparing differences of differences) 135 | #' art.con(m, "Moisture:Fertilizer", interaction = TRUE) 136 | #' 137 | #' ## For more examples, see vignette("art-contrasts") 138 | #' 139 | #' } 140 | art.con = function( 141 | m, formula, response = "art", factor.contrasts="contr.sum", method = "pairwise", 142 | interaction = FALSE, adjust, ... 143 | ) { 144 | f.parsed = parse.art.con.formula(formula) 145 | 146 | # syntax handled differently for interaction contrasts. 147 | if (interaction) { 148 | art.interaction.contrast = .do.art.interaction.contrast(m, f.parsed, response, factor.contrasts, method, adjust, ...) 149 | art.interaction.contrast 150 | } 151 | else { 152 | artlm.con = artlm.con.internal(m, f.parsed, response, factor.contrasts, ...) 153 | art.contrast = .do.art.contrast(f.parsed, artlm.con, method, adjust) 154 | art.contrast 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /R/art.con.internal.R: -------------------------------------------------------------------------------- 1 | ### Gets variables from spec 2 | ### Makes sure op is the only operator in spec. Throws error if not. 3 | ### Returns vector containing all variables in spec 4 | ### spec is an expression (e.g., a:b:`var with spaces and : in it`) 5 | ### op is a string representation of the allowed operation (e.g., ":") 6 | ### Note: for our current purposes, we only have one validation operator for each situation 7 | ### we would use this function in. Obviously, it would have to be re-written to accomodate more. 8 | get.variables = function(spec, op) { 9 | if (is.call(spec) && spec[[1]] == op) { 10 | # recursive case: get variables from children 11 | c(get.variables(spec[[2]], op), get.variables(spec[[3]], op)) 12 | } else if (is.name(spec)) { 13 | # base case: this is a variable 14 | # if recursive case is never called, need to make sure we still return a vector 15 | # if recursive case is called, it's fine, c(c(1), c(2)) = c(1,2) 16 | c(spec) 17 | } else { 18 | stop("Contrast term can only contain variables and `", op, "`.") 19 | } 20 | } 21 | 22 | 23 | ### Parses and validates contrast formula of form "a:b:c" 24 | ### Raises exception if formula does not validate. 25 | ### returns list with: 26 | ### interaction.variables: list of fixed variables (of type name) in interaction (e.g., list(a, b, c)). 27 | ### interaction.term.labels: quoted interaction term label (e.g. "a:b:c") 28 | ### concat.interaction.variable: concatenation (of type name) of all variables in interaction.variables (e.g., abc) 29 | #' @importFrom stats terms 30 | parse.art.con.string.formula = function(f.orig) { 31 | 32 | # make sure f.orig is a single string (as opposed to a vector of multiple strings) 33 | # don't think this is actually needed. seems to get caught earlier by 34 | # "Contrast must either be formula of form ~ X1*X2*X3 or 35 | # term of form \"X1:X2:X3\")" error below 36 | if (!is.character(f.orig) || length(f.orig) != 1) { 37 | stop("Contrast must be string term of form \"X1:X2\")") 38 | } 39 | 40 | # parse spec into an expression 41 | f.orig.expr = parse(text = f.orig, keep.source = FALSE)[[1]] 42 | variables = get.variables(f.orig.expr, ":") 43 | 44 | term.labels = f.orig 45 | 46 | # ensure we have exactly one interaction and no other terms 47 | if (length(term.labels) != 1) { 48 | stop( 49 | "Model must have exactly one interaction and no other terms (" , 50 | length(term.labels), " terms given)" 51 | ) 52 | } 53 | 54 | # Setup return list 55 | # interaction.term.label: string name for interaction term label (e.g. "a:b:c") 56 | interaction.term.label = term.labels 57 | # interaction.variables: list of fixed variables (e.g., list(a, b, c)) 58 | interaction.variables = variables 59 | # concat.interaction.variable: string formula with all 60 | # interaction variables concatenated (e.g., abc) 61 | concat.interaction.variable = as.name(paste(interaction.variables, collapse="")) 62 | 63 | list( 64 | interaction.term.label = interaction.term.label, 65 | interaction.variables = interaction.variables, 66 | concat.interaction.variable = concat.interaction.variable 67 | ) 68 | } 69 | 70 | ### parses f.orig which is formula or term (f) passed to art.con or artlm.con 71 | ### f.orig can be of form ~ a*b*c or "a:b:c" 72 | ### if f.orig is of "formula" form (i.e., ~ a*b*c), 73 | ### converts it to "string" form (i.e., "a:b:c") 74 | ### returns: result of parse.art.con.string.formula when passed "string" version of f.orig 75 | ### list with 76 | ### interaction.variables: list of fixed variables (of type name) in interaction (e.g., list(a, b, c)). 77 | ### interaction.term.labels: quoted interaction term label (e.g. "a:b:c") 78 | ### concat.interaction.variable: concatenation (of type name) of all variables in interaction.variables (e.g., abc) 79 | #' @importFrom stats as.formula 80 | parse.art.con.formula = function(f.orig) { 81 | 82 | # looking for ~ a*b*c 83 | if (inherits(f.orig, "formula")) { 84 | # make sure only operator on rhs of original formula is "*" 85 | # e.g., f.orig = ~ a*b*c -> f.orig[[1]] = ~ and f.orig[[2]] = a*b*c 86 | if (f.orig[[1]] != "~") { 87 | stop("Left hand side of formula must be `~`.") 88 | } 89 | 90 | # if there is a dependent variable (i.e., variable on lhs of ~), then f.orig will have length 3 91 | # otherwise, f.orig will have length 2 92 | # e.g., f.orig = Y ~ a*b*c -> f.orig[[1]] = ~, f.orig[[2]] = Y, f.orig[[3]] = a*b*c 93 | # e.g., f.orig = ~ a*b*c -> f.orig[[1]] = ~, f.orig[[2]] = a*b*c 94 | if (length(f.orig) > 2) { 95 | stop( 96 | "Formula must not have any variables on LHS (got ", f.orig[[2]], "). ", 97 | "Did you mean ", gsub(pattern = toString(f.orig[[2]]), x = deparse(f.orig), replacement = ''), "?" 98 | ) 99 | } 100 | 101 | # get formula variables (i.e., individual variables on lhs), and ensure "*" is the only RHS operator. 102 | # e.g., f.orig = ~ a*b*c -> f.orig[[1]] = `~`, f.orig[[2]] = a*b*c 103 | f.orig.expr = f.orig[[2]] 104 | variables = get.variables(f.orig.expr, "*") 105 | # stitch variables back together with : operator between each variable. 106 | # e.g. variables = c(a,b,c) -> a:b:c 107 | stiched.variables = Reduce(function(x, y) call(":", x, y), variables) 108 | # add "~" to lhs and turn into formula 109 | # e.g., stiched.variables = a:b:c -> ~a:b:c 110 | f = as.formula(call("~", stiched.variables)) 111 | 112 | # extract terms from formula 113 | f.terms = terms(f) 114 | 115 | # char vector of names of rhs terms and their interactions 116 | # e.g. ~ a:b:c -> c("a:b:c")) 117 | term.labels = attr(f.terms, "term.labels") 118 | 119 | # don't think we need this. 120 | # I think it gets caught by 121 | # "Term or formula passed to art.con or artlm.con must contain a single 122 | # interaction term and no other terms, and the interaction term must be in art model formula." 123 | if (length(term.labels) != 1) { 124 | stop( 125 | "Model must have exactly one interaction and no other terms (" , 126 | length(term.labels), " terms given)" 127 | ) 128 | } 129 | 130 | return(parse.art.con.string.formula(term.labels[[1]])) 131 | } # end f is formula 132 | 133 | # looking for "a:b:c" 134 | else if (is.character(f.orig) & length(f.orig) == 1) { 135 | return(parse.art.con.string.formula(f.orig)) 136 | } 137 | 138 | # Error if f is not formula or string 139 | else { 140 | stop("Contrast must either be formula of form `~ X1*X2*X3` or term of form \"X1:X2:X3\")") 141 | } 142 | } 143 | 144 | ### Parses formula from supplied model 145 | ### Validates that interaction term from f is in model formula 146 | ### Does not validate model formula itself since model formula was validated when model was created 147 | ### Given a formula like response ~ a*b*c + (1|d) + Error(g) 148 | ### input: 149 | ### m.f is the formula for the original art model 150 | ### f.parsed is the already parsed contrast formula 151 | ### returns list with: 152 | ### response: response (type name) (e.g. response) 153 | ### fixed.variables: list of named fixed variables (of type name) (e.g. list(a, b, c)) 154 | ### grouping.variables: list of grouping variables (of type name) (e.g. list(1|d)) 155 | ### error.variables: list of error variables (of type name) (e.g. list(Error(g))) 156 | parse.art.model.formula = function(m.f, f.parsed) { 157 | 158 | # get model formula terms 159 | m.f.terms = terms(m.f) 160 | # char vector of names of rhs terms and their interactions 161 | # e.g. Response ~ a*b + (1|d) + Error(g) -> c("a", "b", "1 | d", "Error(g)", "a:b")) not necessarily in this order 162 | m.f.term.labels = attr(m.f.terms, "term.labels") 163 | 164 | # check if interaction term from f is in model formula 165 | f.interaction.term.label = f.parsed$interaction.term.label # f.parsed always has exactly one interaction 166 | is.interaction.term = grepl(f.interaction.term.label, m.f.term.labels) 167 | 168 | # if interaction term from f is not in model formula, error 169 | if (!any(is.interaction.term)) { 170 | stop( 171 | "Term or formula passed to art.con or artlm.con must contain a single interaction term\n", 172 | "and no other terms, and the interaction term must be in art model formula." 173 | ) 174 | } 175 | 176 | # response ~ a*b*c*d + (1|d) + Error(g) -> list(response, a, b, c, d, 1|d, Error(g)) 177 | m.f.variables.all = as.list(attr(m.f.terms, "variables"))[c(-1)] 178 | m.f.response = m.f.variables.all[[1]] 179 | m.f.variables = m.f.variables.all[c(-1)] 180 | 181 | #determine which variables on the rhs are grouping variables, error variables, or fixed variables 182 | is.grouping.variable = sapply(m.f.variables, function(term) as.list(term)[[1]] == quote(`|`)) 183 | is.error.variable = sapply(m.f.variables, function(term) as.list(term)[[1]] == quote(`Error`)) 184 | #all other variables that aren't grouping or error variables must be fixed variables 185 | is.fixed.variable = !(is.grouping.variable | is.error.variable) 186 | 187 | m.f.grouping = m.f.variables[is.grouping.variable] 188 | m.f.error = m.f.variables[is.error.variable] 189 | m.f.fixed = m.f.variables[is.fixed.variable] # remove response 190 | 191 | list( 192 | response = m.f.response, 193 | fixed.variables = m.f.fixed, 194 | grouping.variables = m.f.grouping, 195 | error.variables = m.f.error 196 | ) 197 | } 198 | 199 | ### creates new data frame which is a copy of df except 200 | ### adds a new column by concatenated columns of df whose names are the interaction variables in f 201 | ### removes columns whose names are the interaction variables in f 202 | ### m.formula is the original formula used to create the ART model 203 | ### df is the data frame used to creat the ART model 204 | ### formula is the contrast formula 205 | generate.art.concatenated.df = function(m.f.parsed, df, f.parsed) { 206 | 207 | # concatenate columns of data frame whose columns names are the variables in f.parsed.interaction.variables 208 | # e.g. abc 209 | f.concatenated.variable = f.parsed$concat.interaction.variable 210 | # e.g. list(a, b, c) 211 | f.interaction.variables = f.parsed$interaction.variables 212 | # indexing in art.con.df used below with f.concatenated.variable does not work on tibbles, 213 | # so convert to a data.frame in advance 214 | art.con.df = as.data.frame(df, optional = TRUE) 215 | # turn list of names to vector of strings. e.g, aa = as.name("a"), bb = as.name("b"), list(aa, bb) -> c("a","b") 216 | # unname throws error when only one interaction variable and we don't need to concatenate in that case 217 | # this is easier than debugging it 218 | if (length(f.interaction.variables) > 1) { 219 | f.interaction.variables.string.vec = sapply(f.interaction.variables,deparse) 220 | art.con.df[[f.concatenated.variable]] = do.call(paste, c(unname(art.con.df[,f.interaction.variables.string.vec]), sep = ",")) 221 | } 222 | # note: when m was created would have thrown error if a fixed var column in df was not a factor 223 | art.con.df[[f.concatenated.variable]] = factor(art.con.df[[f.concatenated.variable]]) 224 | art.con.df 225 | } 226 | 227 | ### removes variables from the model formula (m.f) that are in the contrast formula (f) 228 | ### replaces them with the new concatenated (the concatenation of all variables in the contrast formula) 229 | ### creates and returns art model on art.concatenated.df using the created formula 230 | ### note: this is not the same as using the full factorial model of all columns in df 231 | ### there can be columns in df that are not used in the model. 232 | generate.art.concatenated.model = function(m.f, m.f.parsed, art.concatenated.df, f.parsed) { 233 | 234 | # fixed variables in model formula 235 | m.f.fixed.variables = m.f.parsed$fixed.variables 236 | # interaction variables 237 | f.interaction.variables = f.parsed$interaction.variables 238 | # concatenated interaction variable 239 | f.concat.interaction.variable = f.parsed$concat.interaction.variable 240 | 241 | # indices of interaction variables in model formula fixed variable list 242 | # e.g. f.interaction.variables = (a, c) and m.f.fixed.variables = (a, b, c) -> c(1, 3) 243 | interaction.variable.index = match(f.interaction.variables, m.f.fixed.variables) 244 | # remove interaction variables from model formula 245 | # e.g. f.interaction.variables = (a, c) and m.f.fixed.variables = (a, b, c) -> list(b) 246 | m.f.fixed.variables.no.interaction.vars = m.f.fixed.variables[-interaction.variable.index] 247 | m.f.fixed.variables.with.concat = c(m.f.fixed.variables.no.interaction.vars, f.concat.interaction.variable) 248 | 249 | # create formula with response m.f.response, fixed vars m.f.fixed.variables.with.concat 250 | # grouping variables m.f.grouping.variables, error variables m.f.error.variables 251 | m.f.response = m.f.parsed$response 252 | m.f.grouping.variables = m.f.parsed$grouping.variables 253 | m.f.error.variables = m.f.parsed$error.variables 254 | 255 | # collapse fixed variable vector into string separated by * 256 | # e.g. list(ac,b) -> "ac*b" 257 | m.f.fixed.str = paste(m.f.fixed.variables.with.concat, collapse = "*") 258 | # add parentheses around each grouping variable and 259 | # collapse grouping variables vector into string separated by + 260 | # e.g list(1|d, 1|e) -> "(1|d) + (1|e)" 261 | # note: empty list coerced to empty vector i.e list() -> character(0) 262 | m.f.grouping.paren = if (length(m.f.grouping.variables) == 0) character() else paste("(", m.f.grouping.variables, ")", sep="") 263 | m.f.grouping.str = if (length(m.f.grouping.paren) == 0) character() else paste(m.f.grouping.paren, collapse = "+") 264 | # collaprse error variables vector into string separated by + 265 | # e.g. list(Error(g), Error(h)) -> "Error(g) + Error(h)" 266 | # note: empty list coerced to empty vector i.e list() -> character(0) 267 | m.f.error.str = if (length(m.f.error.variables) == 0) character() else paste(m.f.error.variables, collapse = "+") 268 | 269 | # assemble concatenated art formula 270 | # some terms may be missing (e.g., no grouping term). remove those from vector 271 | # because weird things happen when pasting multiple strings together and one is empty 272 | strings.to.join = c(m.f.fixed.str, m.f.grouping.str, m.f.error.str) 273 | nonempty.strings.to.join = strings.to.join[strings.to.join != ""] 274 | art.concatenated.formula.rhs.str = paste(nonempty.strings.to.join, collapse = "+") 275 | art.concatenated.formula = as.formula(paste(m.f.response, "~", art.concatenated.formula.rhs.str)) 276 | # make art concatenated model 277 | m = art(art.concatenated.formula, data = art.concatenated.df) 278 | m 279 | } 280 | 281 | ### called internally from artlm.con. 282 | ### aligns-and-ranks data in m with ART-C procedure 283 | ### creates linear model, linear mixed model, or aov model depending on grouping terms in m.f 284 | ### and returns resulting model 285 | ### m: art model passed into art.con 286 | ### f.parsed: parsed contrast formula 287 | ### response: "aligned" for compare aligned responses or "art" for compare aligned-and-ranked responses 288 | ### factor.contrasts: e.g. contr.sum passed to artlm. 289 | ### ...: extra parameter passed to artlm and subsequently lm or lmer 290 | ### returns: An object of class lm if m.f does not 291 | ### contain grouping or error terms, an object of class merMod 292 | ### (i.e. a model fit by lmer) if it contains grouping terms, or 293 | ### an object of class aovlist (i.e. a model fit by aov) if 294 | ### it contains error terms. 295 | ### Note: only allowed from artlm.con not art.con. 296 | artlm.con.internal = function(m, f.parsed, response, factor.contrasts, ...) { 297 | # make sure m is an art model 298 | if (!inherits(m, "art")) { 299 | stop("Model must be an art model; got ", deparse0(class(m)), ".") 300 | } 301 | # get model formula 302 | m.f = m$formula 303 | # m$data holds data used to create m, even if data frame var name was assigned to something else. 304 | df = m$data 305 | # parse model formula 306 | m.f.parsed = parse.art.model.formula(m.f, f.parsed) 307 | # concatenarte columns in df corresponding to interaction terms in f 308 | art.concatenated.df = generate.art.concatenated.df(m.f.parsed, df, f.parsed) 309 | # concatenate terms in m.f correcsponding to interaction terms in f and create new art model 310 | art.concatenated.model = generate.art.concatenated.model(m.f, m.f.parsed, art.concatenated.df, f.parsed) 311 | # artlm with new art model 312 | artlm.con.internal = artlm( 313 | art.concatenated.model, 314 | toString(f.parsed$concat.interaction.variable), 315 | response = response, 316 | factor.contrasts = factor.contrasts, 317 | ... 318 | ) 319 | artlm.con.internal 320 | } 321 | 322 | ### called internally from art.con iff interaction = TRUE 323 | ### m: art model passed into art.con 324 | ### f.parsed: parsed contrast formula 325 | ### response: "aligned" for compare aligned responses or "art" for compare aligned-and-ranked responses 326 | ### factor.contrasts: e.g. contr.sum passed to artlm. 327 | ### method: e.g. pairwise. passed to contrast 328 | ### adjust: e.g. tukey. passed to contrast 329 | ### ...: extra parameter passed to artlm and subsequently lm or lmer 330 | ### returns: result of conducting interaction contrasts on terms specified in f.parsed 331 | ### (object of class emmGrid) 332 | .do.art.interaction.contrast = function(m, f.parsed, response, factor.contrasts, method, adjust, ...) { 333 | # e.g. list("a", "b", "c") 334 | interaction.variables = f.parsed$interaction.variables 335 | # e.g. list("a", "b", "c") -> "a:b:c". will be passed to artlm 336 | interaction.string.term = paste(interaction.variables, collapse=":") 337 | # e.g. list("a", "b", "c") -> "~ a*b*c". will be passed to emmeans 338 | interaction.expression = Reduce(function(x, y) call("*", x, y), interaction.variables) 339 | interaction.formula = as.formula(call("~", interaction.expression)) 340 | 341 | contrast( 342 | emmeans( 343 | artlm(m, interaction.string.term, response = response, factor.contrasts = factor.contrasts, ...), 344 | interaction.formula 345 | ), 346 | method = method, 347 | adjust = adjust, 348 | interaction = TRUE 349 | ) 350 | } 351 | 352 | ### conducts contrasts given model returned by artlm.con 353 | ### f.parsed: parsed contrast formula 354 | ### artlm.con: model returned by artlm.con given the original inputs to art.con 355 | ### method: contrast method propogated to contrast 356 | ### adjust: adjustment method propogated to contrast 357 | ### returns: result of conducting contrasts on artlm.con model (object of class emmGrid) 358 | ### syntax: m = art(Y ~ X1*X2, data = df) 359 | ### art.con(m, "X1") or art.con(m, ~X1) 360 | ### art.con(m, "X1:X2") or art.con(m, ~ X1*X2) 361 | ### Note: called internally from art.con iff interaction = FALSE 362 | #' @importFrom stats p.adjust p.adjust.methods 363 | #' @importFrom emmeans emmeans contrast 364 | .do.art.contrast = function(f.parsed, artlm.con, method, adjust) { 365 | # e.g. f.parsed$concat.interaction.variable = X1X2 -> ~ X1X2 366 | emmeans.formula = as.formula(call("~", f.parsed$concat.interaction.variable)) 367 | art.con.emmeans = emmeans(artlm.con, emmeans.formula) 368 | art.con = contrast(art.con.emmeans, method, adjust=adjust) 369 | art.con 370 | } 371 | -------------------------------------------------------------------------------- /R/artlm.R: -------------------------------------------------------------------------------- 1 | # art function and some basic generic function implementations for art objects 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | 7 | #' Per-Term Linear Model from Aligned Rank Transformed Data 8 | #' 9 | #' Build a linear model for ART data with response aligned or aligned and 10 | #' ranked by the specified term from the model. 11 | #' 12 | #' This function is used primarily for post-hoc tests. To run an ANOVA, it does 13 | #' not need to be called directly; instead, use \code{\link{anova.art}}, which 14 | #' calls this function as needed. 15 | #' 16 | #' @param m An object of class \code{\link{art}}. 17 | #' @param term A character vector indicating the effect term 18 | #' in the transformed data in \code{m} to use as the aligned or art response. 19 | #' @param response Which response to use: the aligned response 20 | #' (\code{"aligned"}) or the aligned and ranked (\code{"art"}) response. 21 | #' @param factor.contrasts The name of the contrast-generating function to be 22 | #' applied by default to fixed effect factors. Sets the the first element of 23 | #' \code{\link{options}("contrasts")} for the duration of this function. The 24 | #' default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 25 | #' appropriate for Type III ANOVAs (the default ANOVA type for 26 | #' \code{\link{anova.art}}). 27 | #' @param \dots Additional arguments passed to \code{\link{lm}} or 28 | #' \code{\link[lme4]{lmer}}. 29 | #' @return An object of class \code{\link{lm}} if \code{formula(m)} does not 30 | #' contain grouping or error terms, an object of class \code{\link[lme4]{merMod}} 31 | #' (i.e. a model fit by \code{\link[lme4]{lmer}}) if it contains grouping terms, or 32 | #' an object of class \code{aovlist} (i.e. a model fit by \code{\link{aov}}) if 33 | #' it contains error terms. 34 | #' @author Matthew Kay 35 | #' @seealso See \code{\link{art}} for an example. See also 36 | #' \code{\link{anova.art}}, which makes use of this function. 37 | #' @keywords nonparametric 38 | #' 39 | #' @importFrom stats lm update aov 40 | #' @importFrom lme4 lmer 41 | #' @export 42 | artlm = function(m, term, 43 | response=c("art", "aligned"), 44 | factor.contrasts="contr.sum", 45 | ... 46 | ) { 47 | #match enum arguments 48 | response = match.arg(response) 49 | 50 | #for the duration of this function, switch to the supplied contrast types 51 | original.contrasts = getOption("contrasts") 52 | tryCatch({ 53 | options(contrasts=c(factor.contrasts, original.contrasts[-1])) 54 | 55 | #place the transformed (aligned or aligned and ranked) version of y 56 | #into the data frame as the dummy response ".y" 57 | df = m$data 58 | df$.y = switch(response, 59 | aligned=m$aligned[[term]], 60 | art=m$aligned.ranks[[term]]) 61 | 62 | #modify formula to use dummy response name ".y" 63 | f = update(m$formula, .y ~ .) 64 | 65 | #reassign the environment of the model formula to this frame so that the data can be correctly recreated 66 | #by other functions (e.g. emmeans:::recover.data) that use the function call and environment 67 | environment(f) = sys.frame(sys.nframe()) 68 | 69 | #run linear model 70 | if (m$n.grouping.terms > 0) { #grouping terms => REML 71 | m = lmer(f, data=df, ...) 72 | } else if (m$n.error.terms > 0) { #error terms => repeated measures ANOVA 73 | m = aov(f, data=df, ...) 74 | } else { #no grouping or error terms => OLS 75 | m = lm(f, data=df, ...) 76 | } 77 | 78 | attr(m, "term") = term 79 | attr(m, "response") = response 80 | m 81 | }, finally = { 82 | options(contrasts=original.contrasts) 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /R/artlm.con.R: -------------------------------------------------------------------------------- 1 | # artlm function to align-and-rank with ART-C and then create linear model 2 | # 3 | # Author: lelkin 4 | ############################################################################### 5 | 6 | #' Per-Term Linear Model on Data Aligned-and-Ranked with ART-C 7 | #' 8 | #' Given an \code{\link{art}} model, build a linear model from data aligned or 9 | #' aligned-and-ranked with ART-C alignment procedure by the specified term in 10 | #' the model. 11 | #' 12 | #' This function is used internally by \code{\link{art.con}} to construct 13 | #' linear models for contrasts using the ART-C procedure (Elkin et al. 2021). 14 | #' It is typically not necessary to use this function directly to conduct contrasts using 15 | #' the ART-C procedure, you can use \code{\link{art.con}} instead, which will 16 | #' ensure that the correct model and contrast test is run. However, should you 17 | #' wish to use the ART-C procedure with a different contrast test 18 | #' than provided by \code{\link{art.con}}, you may with to use this function. 19 | #' 20 | #' @param m An object of class \code{\link{art}}. 21 | #' @param term A character vector indicating the effect term in 22 | #' the transformed data in \code{m} to use as the aligned or art response. 23 | #' @param response Which response to use: the aligned (with ART-C) response 24 | #' (\code{"aligned"}) or the aligned and ranked (with ART-C) response 25 | #' (\code{"art"}). 26 | #' @param factor.contrasts The name of the contrast-generating function to be 27 | #' applied by default to fixed effect factors. Sets the the first element of 28 | #' \code{\link{options}("contrasts")} for the duration of this function. The 29 | #' default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 30 | #' appropriate for Type III ANOVAs (the default ANOVA type for 31 | #' \code{\link{anova.art}}). 32 | #' @param \dots Additional arguments passed to \code{\link{lm}} or 33 | #' \code{\link[lme4]{lmer}}. 34 | #' @return An object of class \code{\link{lm}} if \code{formula(m)} does not 35 | #' contain grouping or error terms, an object of class \code{\link[lme4]{merMod}} 36 | #' (i.e. a model fit by \code{\link[lme4]{lmer}}) if it does contain grouping terms, or 37 | #' an object of class \code{aovlist} (i.e. a model fit by \code{\link{aov}}) 38 | #' if it contains error terms. 39 | #' @details Internally, the ART-C procedure concatenates the variables specified 40 | #' in \code{term}, and then removes the originals. When specifying the effect 41 | #' terms on which to conduct contrasts, use the concatenation of the effects 42 | #' specified in \code{term} instead of the original variables. This is demonstrated 43 | #' in the example below. 44 | #' @seealso See also \code{\link{art.con}}, which makes use of this function. 45 | #' 46 | #' @author Lisa A. Elkin 47 | #' @references Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 48 | #' (2021). An aligned rank transform procedure for multifactor contrast tests. 49 | #' \emph{Proceedings of the ACM Symposium on User Interface Software and 50 | #' Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 51 | #' ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 52 | #' @export 53 | #' 54 | #' @examples 55 | #' \donttest{ 56 | #' data(Higgins1990Table5, package = "ARTool") 57 | #' 58 | #' ## create an art model 59 | #' m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 60 | #' 61 | #' ## use emmeans to conduct pairwise contrasts on "Moisture" 62 | #' library(emmeans) 63 | #' contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method = "pairwise") 64 | #' 65 | #' ## use emmeans to conduct pairwise contrasts on "Moisture:Fertilizer" 66 | #' ## N.B. internally, artlm.con concatenates the factors Moisture and Fertilizer 67 | #' ## to create MoistureFertilizer. If you try to use any of Moisture, Fertilizer, 68 | #' ## Moisture:Fertilizer, or Moisture*Fertilizer in the RHS of the formula 69 | #' ## passed to emmeans, you will get an error because the factors Moisture and Fertilizer 70 | #' ## do not exist in the model returned by artlm.con. 71 | #' contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method = "pairwise") 72 | #' 73 | #' ## Note: art.con uses emmeans internally, and the above examples are equivalent to 74 | #' ## the following calls to art.con, which is the recommended approach as it will 75 | #' ## ensure the model selected and the contrasts extracted from emmeans match. 76 | #' art.con(m, "Moisture") 77 | #' art.con(m, "Moisture:Fertilizer") 78 | #' 79 | #' } 80 | #' 81 | artlm.con = function(m, term, response = "art", factor.contrasts = "contr.sum", ...) { 82 | f.parsed = parse.art.con.string.formula(term) 83 | 84 | artlm.con = artlm.con.internal(m, f.parsed, response, factor.contrasts, ...) 85 | artlm.con 86 | } 87 | 88 | -------------------------------------------------------------------------------- /R/data.R: -------------------------------------------------------------------------------- 1 | # documentation of datasets 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | 7 | #' Aligned Rank Transformed Version of Higgins1990Table1 8 | #' 9 | #' The ART version of \code{\link{Higgins1990Table1}} as produced by the 10 | #' original ARTool, used to test the correctness of \code{\link{art}} output. 11 | #' 12 | #' 13 | #' @name Higgins1990Table1.art 14 | #' @docType data 15 | #' @format A data frame with 36 observations on the following 10 variables. 16 | #' \describe{ 17 | #' \item{Subject}{a factor with levels \code{"s1"} .. \code{"s36"}} 18 | #' \item{Row}{a factor with levels \code{"r1"} .. \code{"r3"}} 19 | #' \item{Column}{a factor with levels \code{"c1"} .. \code{"c3"}} 20 | #' \item{Response}{a numeric vector} 21 | #' \item{aligned.Response..for.Row}{a numeric vector} 22 | #' \item{aligned.Response..for.Column}{a numeric vector} 23 | #' \item{aligned.Response..for.Row.Column}{a numeric vector} 24 | #' \item{ART.Response..for.Row}{a numeric vector} 25 | #' \item{ART.Response..for.Column}{a numeric vector} 26 | #' \item{ART.Response..for.Row.Column}{a numeric vector} 27 | #' } 28 | #' @seealso \code{\link{Higgins1990Table1}}, \code{\link{art}}. 29 | #' @source Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 30 | #' \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 31 | #' @keywords datasets internal 32 | NULL 33 | 34 | 35 | #' Synthetic 3x3 Factorial Randomized Experiment 36 | #' 37 | #' Synthetic data from a balanced 3x3 factorial experiment with main effects, 38 | #' no interaction, and independent and identically distributed (i.i.d.) Normal 39 | #' errors. 40 | #' 41 | #' 42 | #' @name Higgins1990Table1 43 | #' @docType data 44 | #' @format A data frame with 36 observations on the following 4 variables. 45 | #' \describe{ 46 | #' \item{Subject}{a factor with levels \code{"s1"} .. \code{"s36"}} 47 | #' \item{Row}{a factor with levels \code{"r1"} .. \code{"r3"}} 48 | #' \item{Column}{a factor with levels \code{"c1"} .. \code{"c3"}} 49 | #' \item{Response}{a numeric vector} 50 | #' } 51 | #' @seealso \code{\link{art}}, \code{\link{anova.art}}. 52 | #' @source Higgins, J. J., Blair, R. C. and Tashtoush, S. (1990). The aligned 53 | #' rank transform procedure. \emph{Proceedings of the Conference on Applied 54 | #' Statistics in Agriculture}. Manhattan, Kansas: Kansas State University, pp. 55 | #' 185-195. 56 | #' @keywords datasets 57 | #' @examples 58 | #' 59 | #' data(Higgins1990Table1, package = "ARTool") 60 | #' 61 | #' ## run aligned-rank transform and ANOVA on the data 62 | #' ## Note: because there is only one observation per Subject 63 | #' ## in this dataset, we do not need to include Subject as 64 | #' ## a grouping term in this formula. Indeed, if we did, 65 | #' ## lmer would complain when we attempt the ANOVA. 66 | #' m <- art(Response ~ Row*Column, data=Higgins1990Table1) 67 | #' anova(m) 68 | #' 69 | NULL 70 | 71 | 72 | #' Aligned Rank Transformed Version of Higgins1990Table5 73 | #' 74 | #' The ART version of \code{\link{Higgins1990Table5}} as produced by the 75 | #' original ARTool, used to test the correctness of \code{\link{art}} output. 76 | #' 77 | #' 78 | #' @name Higgins1990Table5.art 79 | #' @docType data 80 | #' @format A data frame with 48 observations on the following 10 variables. 81 | #' \describe{ 82 | #' \item{Tray}{a factor with levels \code{"t1"} .. \code{"t12"}} 83 | #' \item{Moisture}{a factor with levels \code{"m1"} .. \code{"m4"}} 84 | #' \item{Fertilizer}{a factor with levels \code{"f1"} .. \code{"f4"}} 85 | #' \item{DryMatter}{a numeric vector} 86 | #' \item{aligned.DryMatter..for.Moisture}{a numeric vector} 87 | #' \item{aligned.DryMatter..for.Fertilizer}{a numeric vector} 88 | #' \item{aligned.DryMatter..for.Moisture.Fertilizer}{a numeric vector} 89 | #' \item{ART.DryMatter..for.Moisture}{a numeric vector} 90 | #' \item{ART.DryMatter..for.Fertilizer}{a numeric vector} 91 | #' \item{ART.DryMatter..for.Moisture.Fertilizer}{a numeric vector} 92 | #' } 93 | #' @seealso \code{\link{Higgins1990Table5}}, \code{\link{art}}. 94 | #' @source Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 95 | #' \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 96 | #' @keywords datasets internal 97 | NULL 98 | 99 | 100 | #' Split-plot Experiment Examining Effect of Moisture and Fertilizer on Dry 101 | #' Matter in Peat Pots 102 | #' 103 | #' This dataset comes from a split-plot experiment examining \code{Tray}s of 4 104 | #' peat pots each. \code{Moisture} was varied between \code{Tray}s (i.e. it was 105 | #' the whole-plot treatment) and \code{Fertilizer} was varied within 106 | #' \code{Tray}s (i.e. it was the sub-plot treatment). The outcome measure was 107 | #' \code{DryMatter}. 108 | #' 109 | #' This dataset, originally from Milliken & Johnson (1984), is reproduced here 110 | #' from Higgins \emph{et al.} (1990). 111 | #' 112 | #' 113 | #' @name Higgins1990Table5 114 | #' @docType data 115 | #' @format A data frame with 48 observations on the following 4 variables. 116 | #' \describe{ 117 | #' \item{Tray}{a factor with levels \code{"t1"} .. \code{"t12"}} 118 | #' \item{Moisture}{a factor with levels \code{"m1"} .. \code{"m4"}} 119 | #' \item{Fertilizer}{a factor with levels \code{"f1"} .. \code{"f4"}} 120 | #' \item{DryMatter}{a numeric vector} 121 | #' } 122 | #' @seealso See \code{\link{art}} for a more complete example. See also 123 | #' \code{\link{anova.art}}. 124 | #' @references Higgins, J. J., Blair, R. C. and Tashtoush, S. (1990). The 125 | #' aligned rank transform procedure. \emph{Proceedings of the Conference on 126 | #' Applied Statistics in Agriculture}. Manhattan, Kansas: Kansas State 127 | #' University, pp. 185-195. 128 | #' @source Milliken, G.A., Johnson, D.E. (1984). \emph{Analysis of Messy Data 129 | #' Vol I: Designed Experiments}. Van Nostrand Reinhold Company, New York. 130 | #' @keywords datasets 131 | #' @examples 132 | #' 133 | #' data(Higgins1990Table5, package = "ARTool") 134 | #' 135 | #' ## run aligned-rank transform and ANOVA on the data 136 | #' m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 137 | #' anova(m) 138 | #' 139 | NULL 140 | 141 | 142 | #' Aligned Rank Transformed Version of HigginsABC 143 | #' 144 | #' The ART version of \code{\link{HigginsABC}} as produced by the original 145 | #' ARTool, used to test the correctness of \code{\link{art}} output. 146 | #' 147 | #' 148 | #' @name HigginsABC.art 149 | #' @docType data 150 | #' @format A data frame with 16 observations on the following 19 variables. 151 | #' \describe{ 152 | #' \item{Subject}{a factor with levels \code{"s1"} .. \code{"s8"}} 153 | #' \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 154 | #' \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 155 | #' \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 156 | #' \item{Y}{a numeric vector} 157 | #' \item{aligned.Y..for.A}{a numeric vector} 158 | #' \item{aligned.Y..for.B}{a numeric vector} 159 | #' \item{aligned.Y..for.A.B}{a numeric vector} 160 | #' \item{aligned.Y..for.C}{a numeric vector} 161 | #' \item{aligned.Y..for.A.C}{a numeric vector} 162 | #' \item{aligned.Y..for.B.C}{a numeric vector} 163 | #' \item{aligned.Y..for.A.B.C}{a numeric vector} 164 | #' \item{ART.Y..for.A}{a numeric vector} 165 | #' \item{ART.Y..for.B}{a numeric vector} 166 | #' \item{ART.Y..for.A.B}{a numeric vector} 167 | #' \item{ART.Y..for.C}{a numeric vector} 168 | #' \item{ART.Y..for.A.C}{a numeric vector} 169 | #' \item{ART.Y..for.B.C}{a numeric vector} 170 | #' \item{ART.Y..for.A.B.C}{a numeric vector} 171 | #' } 172 | #' @seealso \code{\link{HigginsABC}}, \code{\link{art}}. 173 | #' @source Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 174 | #' \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 175 | #' @keywords datasets internal 176 | NULL 177 | 178 | 179 | #' Synthetic 2x2x2 Mixed Design Experiment 180 | #' 181 | #' Synthetic data from an experiment with two between-\code{Subject}s factors 182 | #' (\code{A} and \code{B}) having two levels each and one 183 | #' within-\code{Subject}s factor (\code{C}) with two levels. 184 | #' 185 | #' 186 | #' @name HigginsABC 187 | #' @docType data 188 | #' @format A data frame with 16 observations on the following 5 variables. 189 | #' \describe{ 190 | #' \item{Subject}{a factor with levels \code{"s1"} .. \code{"s8"}} 191 | #' \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 192 | #' \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 193 | #' \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 194 | #' \item{Y}{a numeric vector} 195 | #' } 196 | #' @seealso \code{\link{art}}, \code{\link{anova.art}}. 197 | #' @source Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 198 | #' \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 199 | #' @keywords datasets 200 | #' @examples 201 | #' \donttest{ 202 | #' data(HigginsABC, HigginsABC.art, package = "ARTool") 203 | #' 204 | #' ## run aligned-rank transform and ANOVA on the data 205 | #' m <- art(Y ~ A*B*C + Error(Subject), data = HigginsABC) 206 | #' anova(m) 207 | #' } 208 | NULL 209 | 210 | #' Synthetic 2x2 Within-Subjects Experiment 211 | #' 212 | #' Synthetic data from an experiment with two within-subjects factors 213 | #' (\code{A} and \code{B}) having two levels each. 214 | #' 215 | #' @name ElkinAB 216 | #' @docType data 217 | #' @format A data frame with 32 observations on the following 4 variables. 218 | #' \describe{ 219 | #' \item{S}{a factor representing subjects with levels \code{"s1"} .. \code{"s8"}} 220 | #' \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 221 | #' \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 222 | #' \item{Y}{a numeric vector} 223 | #' } 224 | #' @keywords datasets 225 | #' @source Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 226 | #' (2021). An aligned rank transform procedure for multifactor contrast tests. 227 | #' \emph{Proceedings of the ACM Symposium on User Interface Software and 228 | #' Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 229 | #' ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 230 | #' @examples 231 | #' \donttest{ 232 | #' data(ElkinAB, package = "ARTool") 233 | #' 234 | #' ## run contrast using the ART-C procedure on the data. 235 | #' m <- art(Y ~ A*B + (1|S), data = ElkinAB) 236 | #' art.con(m, "A:B") 237 | #' } 238 | NULL 239 | 240 | #' Synthetic 2x2x2 Within-Subjects Experiment 241 | #' 242 | #' Synthetic data from an experiment with three within-subjects factors 243 | #' (\code{A}, \code{B}, and \code{C}) having two levels each. 244 | #' 245 | #' @name ElkinABC 246 | #' @docType data 247 | #' @format A data frame with 64 observations on the following 5 variables. 248 | #' \describe{ 249 | #' \item{S}{a factor representing subjects with levels \code{"s1"} .. \code{"s8"}} 250 | #' \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 251 | #' \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 252 | #' \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 253 | #' \item{Y}{a numeric vector} 254 | #' } 255 | #' @keywords datasets 256 | #' @source Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 257 | #' (2021). An aligned rank transform procedure for multifactor contrast tests. 258 | #' \emph{Proceedings of the ACM Symposium on User Interface Software and 259 | #' Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 260 | #' ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 261 | #' @examples 262 | #' \donttest{ 263 | #' data(ElkinABC, package = "ARTool") 264 | #' 265 | #' ## run contrast using the ART-C procedure on the data. 266 | #' m <- art(Y ~ A*B*C + (1|S), data = ElkinABC) 267 | #' art.con(m, "A:B:C") 268 | #' } 269 | NULL 270 | 271 | 272 | #' Synthetic Data Used in the Contrast Test Vignette 273 | #' 274 | #' See \code{vignette("art-contrasts")} for a description of this data. 275 | #' 276 | #' 277 | #' @name InteractionTestData 278 | #' @docType data 279 | #' @seealso \code{\link{art}}, \code{\link{anova.art}}. 280 | #' @keywords datasets 281 | #' @examples 282 | #' ## see vignette("art-contrasts") 283 | NULL 284 | -------------------------------------------------------------------------------- /R/flat.anova.R: -------------------------------------------------------------------------------- 1 | # Internal function: Standardizsed ANOVA tables for lm, lmer, and aov objects 2 | # with descriptions of the methods used and a single table for all results 3 | # (instead of multiple tables as returned by aovlist objects --- hence "flat") 4 | # 5 | # Author: mjskay 6 | ############################################################################### 7 | 8 | # Names that should be suppressed from global variable check by codetools 9 | # Names used broadly should be put in global.variables.R 10 | globalVariables(c("Df", "Df.res", "Sum Sq", "Sum Sq.res", "Term", "Error")) 11 | 12 | 13 | flat.anova = function(m, ...) { 14 | UseMethod("flat.anova", m) 15 | } 16 | 17 | #' @importFrom stats anova 18 | #' @importFrom car Anova 19 | #' @importFrom magrittr %<>% 20 | flat.anova.default = function(m, type="III", test="F", ...) { 21 | #get ANOVA table 22 | a = switch(type, 23 | I = anova(m, test=test, ...), 24 | II = Anova(m, type="II", test=test, ...), 25 | III = Anova(m, type="III", test=test, ...)[-1,] #first row is intercept => ignore 26 | ) 27 | 28 | #get the anova description from the heading 29 | description = strsplit(attr(a, "heading"),"\n")[[1]] 30 | if (type == "I") description %<>% paste("(Type I)") #Type I ANOVAs don't include the label themselves 31 | 32 | #add a column to keep track of the term name (rather than as row names because 33 | #in some instances this will not be unique, and row names must be unique) 34 | a = cbind(Term = rownames(a), a) 35 | 36 | attr(a, "description") = description 37 | a 38 | } 39 | 40 | #' @importFrom magrittr %<>% 41 | flat.anova.lm = function(m, type="III", test="F", ...) { 42 | a = flat.anova.default(m, type, test, ...) 43 | description = attr(a, "description") 44 | 45 | #for lm (no grouping variables), the anova table will have an extra row with residual df; 46 | #add the residual df from that row as a column and drop the unneeded row 47 | a %<>% columnify.anova.residuals() 48 | 49 | attr(a, "description") = description 50 | a 51 | } 52 | 53 | #' @importFrom magrittr %<>% 54 | #' @import dplyr 55 | columnify.anova.residuals = function (a.table) { 56 | #given a flat anova table with "Term", "Df", and "Sum Sq" columns 57 | #and the last row containing residual Df and Sum Sq, move the 58 | #residual Df and Sum Sq into columns in the other rows 59 | 60 | k = nrow(a.table) 61 | df.res = a.table[k,"Df"] 62 | sumsq.res = a.table[k,"Sum Sq"] 63 | a.table %<>% 64 | .[-k,,drop=FALSE] %>% #drop last row (residuals) 65 | mutate( 66 | Df.res = df.res, 67 | `Sum Sq.res` = sumsq.res 68 | ) 69 | 70 | #reorder columns 71 | cbind( 72 | select(a.table, Term, Df, Df.res, `Sum Sq`, `Sum Sq.res`), 73 | select(a.table, -Term, -Df, -Df.res, -`Sum Sq`, -`Sum Sq.res`) 74 | ) 75 | } 76 | 77 | ### Flat version of an anova from an aov model 78 | #' @importFrom plyr ldply 79 | #' @import dplyr 80 | flat.anova.aovlist = function(m, 81 | type="I", test="F", #type and test are ignored: they are always "I" and "F" for aov objects 82 | ... 83 | ) { 84 | 85 | #construct flat anova table 86 | a = ldply(seq_along(m), function(i) { 87 | error = names(m)[[i]] 88 | ldply(summary(m[[i]]), function(anova.j) { 89 | if (nrow(anova.j) > 1) { 90 | #last row just has residual df and sum of squares, 91 | #extract residual df to move into its own column 92 | anova.j %>% 93 | mutate( 94 | Term = gsub("\\s+$", "", rownames(.)), 95 | Error = error 96 | ) %>% 97 | columnify.anova.residuals() 98 | } 99 | }) 100 | }) 101 | 102 | #reorder columns 103 | a = cbind( 104 | select(a, Term, Error), 105 | select(a, -Term, -Error) 106 | ) 107 | 108 | attr(a, "description") = "Repeated Measures Analysis of Variance Table (Type I)" 109 | a 110 | } 111 | 112 | -------------------------------------------------------------------------------- /R/global.variables.R: -------------------------------------------------------------------------------- 1 | # Names that should be suppressed from global variable check by codetools 2 | # Names used broadly should be put here; Names used in specific files should 3 | # be put at the top of the corresponding file. 4 | # 5 | # Author: mjskay 6 | ############################################################################### 7 | 8 | # names used in dlpyr functions 9 | globalVariables(c(".", "everything", "contains")) 10 | -------------------------------------------------------------------------------- /R/internal.R: -------------------------------------------------------------------------------- 1 | # Internal helper functions for the art function 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | 7 | ### Parses and validates model formula for art. 8 | ### Raises exception if formula does not validate (e.g. not factorial). 9 | ### Given a formula like y ~ a*b*c + (1|d) + Error(g * h) + Error(i), 10 | ### returns list with: 11 | ### fixed.only: formula with only fixed components (y ~ a+b+c) 12 | ### fixed.terms: additive formula with only variables from fixed terms and no response 13 | ### (for use with ddply) (e.g., ~ a + b + c) 14 | ### fixed.term.labels: character vector of term labels (e.g., c("a", "b", "c", "a:b", "a:c", "b:c", "a:b:c")) 15 | ### n.grouping.terms: number of grouping terms like (1|d) (e.g. 1) 16 | ### n.error.terms: number of error terms like Error(g) (e.g. 2) 17 | ### error.terms: formula with error terms extracted from within Error() (e.g. ~ g * h + i) 18 | #' @importFrom stats terms 19 | #' @importFrom plyr laply 20 | parse.art.formula = function(formula) { 21 | #extract terms from the formula 22 | f.terms = terms(formula) 23 | 24 | #ensure we have an independent variable and an intercept 25 | if (attr(f.terms, "response") != 1) { 26 | stop("Model must have exactly one dependent variable (got ", attr(f.terms, "response"), ")") 27 | } 28 | if (attr(f.terms, "intercept") == 0) { 29 | stop("Model must have an intercept (got ", attr(f.terms, "intercept"), ")") 30 | } 31 | 32 | #unique variables in the rhs of the formula as list of quoted variables 33 | #e.g. y ~ a*b*c + (1|d) + Error(g) -> list(quote(a), quote(b), quote(c), quote((1|d)), quote(Error(g)))) 34 | variables = as.list(attr(f.terms, "variables"))[c(-1,-2)] 35 | #char vector of names of rhs terms and their interactions 36 | #e.g. y ~ a*b*c + (1|d) + Error(g) -> c("a","b","c","1 | d","Error(g)","a:b","b:c","a:b:c")) 37 | term.labels = attr(f.terms, "term.labels") 38 | #vector of length(f.term.labels); value is the order of the interaction of the corresponding entry in term.labels 39 | #e.g. y ~ a*b*c + (1|d) + Error(g) -> c(1,1,1,1,1,2,2,3) 40 | term.order = attr(f.terms, "order") 41 | 42 | #determine which variables on the rhs are grouping variables, error variables, or fixed variables 43 | is.grouping.variable = laply(variables, function(term) as.list(term)[[1]] == quote(`|`)) 44 | is.error.variable = laply(variables, function(term) is.call(term) & as.list(term)[[1]] == quote(`Error`)) 45 | #all other variables that aren't grouping or error variables must be fixed variables 46 | is.fixed.variable = !(is.grouping.variable | is.error.variable) 47 | 48 | #ensure we have at least one fixed effect and are using either grouping terms or error terms but not both 49 | if (sum(is.fixed.variable) == 0) { 50 | stop("Model must have at least one fixed effect (0 given)") 51 | } 52 | if (any(is.grouping.variable) & any(is.error.variable)) { 53 | stop("Model cannot contain both grouping terms, like (1|d), and error terms, like Error(d). Use one or the other.") 54 | } 55 | 56 | #get table with rows == rhs variables and cols == term labels, each cell == 1 if variable in term 57 | variables.by.terms = attr(f.terms, "factors")[-1,,drop=FALSE] #prevent reducing to vector if only one cell 58 | 59 | #make a version of the formula terms with only fixed effects 60 | n.rhs.variables = length(variables) 61 | n.rhs.terms = length(term.labels) 62 | n.interaction.terms = n.rhs.terms - n.rhs.variables 63 | if (n.interaction.terms < 0) { 64 | #only happens when not factorial 65 | stop("Model must include all combinations of interactions of fixed effects.") 66 | } 67 | is.fixed.term = c(is.fixed.variable, rep(TRUE, n.interaction.terms)) 68 | fixed.variables.by.terms = variables.by.terms[is.fixed.variable, is.fixed.term, drop=FALSE] 69 | fixed.term.labels = term.labels[is.fixed.term] 70 | fixed.term.order = term.order[is.fixed.term] 71 | 72 | #ensure design of fixed effects portion of model has all interactions 73 | #first, pull out the response and the main (i.e. order-1) fixed effect terms 74 | response = formula[[2]] 75 | #build a factorial model of all fixed effects 76 | #e.g. y ~ a*b*c + (1|d) + Error(g) -> y ~ a*b*c 77 | factorial.formula = eval(bquote(.(response) ~ .(Reduce(function(x,y) bquote(.(x) * .(y)), variables[is.fixed.variable])))) 78 | environment(factorial.formula) = environment(formula) 79 | #verify the factorial model is the same as the fixed effects in the supplied model 80 | factorial.factors = attr(terms(factorial.formula), "factors")[-1,,drop=FALSE] 81 | if (!all(dim(factorial.factors) == dim(fixed.variables.by.terms)) || !all(factorial.factors == fixed.variables.by.terms)) { 82 | stop("Model must include all combinations of interactions of fixed effects.") 83 | } 84 | 85 | #build a formula with only fixed variables on the right-hand-side (added to each other) 86 | #e.g. y ~ a*b*c + (1|d) + Error(g) -> ~ a + b + c 87 | fixed.terms = eval(bquote(~ .(Reduce(function(x,y) bquote(.(x) + .(y)), variables[is.fixed.variable])))) 88 | environment(fixed.terms) = environment(formula) 89 | 90 | #build a formula with all Error terms extracted from Error() on the right-hand side (added to each other) 91 | #e.g. y ~ a*b*c + (1|d) + Error(g * h) + Error(i) -> ~ g * h + i 92 | error.terms = eval(bquote(~ .(Reduce(function(x,y) bquote(.(x) + .(y)), Map(function (v) v[[2]], variables[is.error.variable]))))) 93 | environment(error.terms) = environment(formula) 94 | 95 | #return validated formulas 96 | list( 97 | fixed.only = factorial.formula, 98 | fixed.terms = fixed.terms, 99 | fixed.term.labels = fixed.term.labels, 100 | n.grouping.terms = sum(is.grouping.variable), 101 | n.error.terms = sum(is.error.variable), 102 | error.terms = error.terms 103 | ) 104 | } 105 | 106 | 107 | ### Given a factorial, fixed-effects-only formula, 108 | ### calculate the cell means and estimated effects 109 | ### for all responses. Returns a list of three 110 | ### data frames all indexed in parallel: data 111 | ### (the original data as determined by 112 | ### model.frame(formula, data)), 113 | ### cell.means, and estimated.effects 114 | ### 115 | ### Given some formula f and an input data frame df, 116 | ### parameters to art.estimated effects are: 117 | ### formula.terms = terms(f) 118 | ### data = model.frame(f, df) 119 | #' @importFrom plyr ddply 120 | art.estimated.effects = function(formula.terms, data) { 121 | #N.B. in this method "interaction" refers to 122 | #all 0 - n order interactions (i.e., grand mean, 123 | #first-order/"main" effects, and 2+-order interactions) 124 | 125 | #matrix with interactions as columns 126 | #and the response + all first-order factors as rows, 127 | #with each cell indicating if the first-order factor (row) 128 | #contributes to the nth-order interaction (column) 129 | interaction.matrix = cbind(data.frame(.grand=FALSE), attr(formula.terms, "factors") == 1) 130 | interaction.names = colnames(interaction.matrix) 131 | term.names = row.names(interaction.matrix) 132 | #interaction order of each column in interaction.matrix 133 | #(order of grand mean (first column) is 0, main effects 134 | #are 1, n-way interactions are n) 135 | interaction.order = c(0, attr(formula.terms, "order")) 136 | 137 | #calculate cell means for each interaction 138 | cell.means = data.frame(y=rep(mean(data[,1]), nrow(data))) 139 | colnames(cell.means) = term.names[1] 140 | data$.row = 1:nrow(data) #original row indices so we can keep rows in order when we split/combine 141 | for (j in 2:ncol(interaction.matrix)) { 142 | term.index = interaction.matrix[,j] 143 | #calculate cell means 144 | #must dervie term.formula as below (instead of just passing term.names[term.index] to ddply) 145 | #because otherwise expressions like "factor(a)" would be converted to ~ factor(a) (instead of ~ `factor(a)`, which 146 | #is what we want here because the expression has already been evaluated previously) 147 | #A nicer way to do all this would be good to come up with eventually 148 | term.formula = eval(bquote(~ .(Reduce(function(x,y) bquote(.(x) + .(y)), Map(as.name, term.names[term.index]))))) 149 | cell.mean.df = ddply(data, term.formula, function (df) { 150 | df$.cell.mean = mean(df[,1]) #mean of response for this interaction 151 | df 152 | }) 153 | #put results into cell.means in the order of the original rows (so that they match up) 154 | cell.means[[interaction.names[j]]] = cell.mean.df[order(cell.mean.df$.row), ".cell.mean"] 155 | } 156 | 157 | #calculate estimated effects for each interaction 158 | estimated.effects = data.frame(y=cell.means[,1]) #estimated effect for grand mean == grand mean 159 | colnames(estimated.effects) = term.names[1] 160 | for (j in 2:ncol(interaction.matrix)) { 161 | #index of which cell means (columns of cell.means) 162 | #contribute to the estimated effect of this interaction. 163 | #This will select any columns involving a subset of the same factors; e.g. for A:B:C it would select 164 | #the grand mean, A, B, C, A:B, A:C, B:C, A:B:C; but not (say) A:D (since D is not in A:B:C). 165 | cell.means.cols = colSums(interaction.matrix[interaction.matrix[,j],]) == interaction.order 166 | #cell means contribute positively if they have the same 167 | #order (mod 2) as this interaction and negatively otherwise 168 | cell.means.multiplier = ifelse((interaction.order - interaction.order[j]) %% 2, -1, 1) 169 | 170 | #calculate estimated effect 171 | estimated.effects[[interaction.names[j]]] = 172 | rowSums(t(t(cell.means[,cell.means.cols]) * cell.means.multiplier[cell.means.cols])) 173 | } 174 | estimated.effects[1] = NULL #drop first column (grand mean), not needed 175 | 176 | 177 | list( 178 | cell.means=cell.means, 179 | estimated.effects=estimated.effects 180 | ) 181 | } 182 | 183 | ## deparse1 backport 184 | deparse0 = function(expr, collapse = " ", width.cutoff = 500L, ...) { 185 | paste(deparse(expr, width.cutoff, ...), collapse = collapse) 186 | } 187 | -------------------------------------------------------------------------------- /R/release.questions.R: -------------------------------------------------------------------------------- 1 | # Release questions for devtools::release() 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | 7 | release_questions <- function() { 8 | c( 9 | "Is README.md up to date?" 10 | ) 11 | } -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | # ARTool: R Package for the Aligned Rank Transform for Nonparametric Factorial ANOVAs 6 | 7 | [![R build status](https://github.com/mjskay/ARTool/workflows/R-CMD-check/badge.svg)](https://github.com/mjskay/ARTool/actions) 8 | [![Coverage status](https://codecov.io/gh/mjskay/ARTool/branch/master/graph/badge.svg)](https://app.codecov.io/github/mjskay/ARTool?branch=master) 9 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/ARTool)](https://CRAN.R-project.org/package=ARTool) 10 | [![GPL >= 2](https://img.shields.io/badge/GPL-%E2%89%A52-brightgreen.svg)](https://cran.r-project.org/web/licenses/GPL-3) 11 | [![DOI](https://zenodo.org/badge/19809/mjskay/ARTool.svg)](https://zenodo.org/badge/latestdoi/19809/mjskay/ARTool) 12 | [![DOI](https://img.shields.io/badge/DOI-10.1145%2F1978942.1978963-blue.svg)](https://dx.doi.org/10.1145/1978942.1978963) 13 | 14 | _Matthew Kay, Northwestern University _
15 | _Lisa A. Elkin, University of Washington, _
16 | _James J. Higgins, Kansas State University, _
17 | _Jacob O. Wobbrock, University of Washington _ 18 | 19 | ARTool is an R package implementing the Aligned Rank Transform for conducting 20 | nonparametric analyses of variance on factorial models. This implementation is 21 | based on the ART procedure as used in the original implementation of 22 | [ARTool](https://depts.washington.edu/acelab/proj/art/) by Wobbrock et al. 23 | 24 | The package automates the Aligning-and-Ranking process using the `art` function. 25 | It also automates the process of running a series of ANOVAs on the transformed 26 | data and extracting the results of interest. It supports traditional ANOVA 27 | models (fit using `lm`), repeated measures ANOVAs (fit using `aov`), and 28 | mixed effects models (fit using `lmer`); the model used is determined by the 29 | formula passed to `art`. 30 | 31 | __Note__: The documentation of this package assumes some level of familiarity 32 | with when and why you may want to use the aligned rank transform; the 33 | [ARTool page](https://depts.washington.edu/acelab/proj/art/) provides a more in-depth (and 34 | highly approachable) introduction to the aligned rank transform and the 35 | motivation for its use. 36 | 37 | ## Installation 38 | 39 | You can install the latest released version from CRAN with this R command: 40 | 41 | ```{r, eval=FALSE} 42 | install.packages("ARTool") 43 | ``` 44 | 45 | __Or__, you can install the latest development version from GitHub with these R 46 | commands: 47 | 48 | ```{r, eval=FALSE} 49 | install.packages("devtools") 50 | devtools::install_github("mjskay/ARTool") 51 | ``` 52 | 53 | ## Example 54 | 55 | The general approach to using ART is to transform your data using `art` , verify 56 | the ART procedure is appropriate to the dataset using `summary` , and then run an 57 | ANOVA on the transformed data using `anova` . 58 | 59 | First, let us load some example data: 60 | 61 | ```{r, message=FALSE} 62 | library(ARTool) 63 | data(Higgins1990Table5, package = "ARTool") 64 | ``` 65 | 66 | `Higgins1990Table5` is a data frame from an experiment in which the effects of `Moisture` 67 | and `Fertilizer` on `DryMatter` in peat pots was tested. Four pots were placed on 68 | each `Tray` , with `Moisture` varied between `Tray` s and `Fertilizer` varied 69 | within `Tray` s. We can see the basic structure of the data: 70 | 71 | ```{r} 72 | str(Higgins1990Table5) 73 | head(Higgins1990Table5, n=8) 74 | ``` 75 | 76 | ### Step 1: Transform the data 77 | 78 | To analyze this data using the aligned rank transform, we first transform the 79 | data using `art` . We specify the response variable (`DryMatter` ), the fixed 80 | effects and all of their interactions (`Moisture*Fertilizer`, or equivalently 81 | `Moisture + Fertilizer + Moisture:Fertilizer`), and any grouping terms if 82 | present (here, `(1|Tray)` ). 83 | 84 | While `(1|Tray)` has no effect on the results of 85 | the aligned rank transformation, it will be used by `anova` to determine the 86 | type of model to run: when grouping terms are present, mixed effects models 87 | are run using `lmer`. If you wish to use a repeated measures ANOVA instead of 88 | a mixed effects model, you can use an `Error` term instead (see below for an 89 | example of this). If you do not having repeated measures, do not include 90 | any grouping terms or error terms. 91 | 92 | ```{r} 93 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 94 | ``` 95 | 96 | ### Step 2: Verify appropriateness of ART 97 | 98 | To verify that the ART procedure was correctly applied and is appropriate for 99 | this dataset, we can look at the output of `summary` : 100 | 101 | ```{r} 102 | summary(m) 103 | ``` 104 | 105 | We see that the columns sums of aligned responses and the F values of ANOVAs on 106 | aligned responses not of interest are all ~0, indicating that the alignment 107 | correctly "stripped out" effects not of interest. Thus, we can apply the ANOVA 108 | on the transformed data. 109 | 110 | ### Step 3: Run the ANOVA 111 | 112 | ARTool automatically selects the model to be used 113 | for the ANOVA. Because we have included a grouping term, `(1|Tray)`, ARTool 114 | will fit mixed effects models using `lmer` and run the ANOVAs on them: 115 | 116 | ```{r} 117 | anova(m) 118 | ``` 119 | 120 | ### Alternative model: Repeated Measures ANOVA 121 | 122 | This particular study could also be analyzed using a repeated measures ANOVA, 123 | yielding the same results (note that repeated measures ANOVAs and mixed 124 | effects models will not always yield the same results). To instead run 125 | a repeated measures ANOVA, add an `Error` term to the model as you 126 | might for a call to `aov`: 127 | 128 | ```{r} 129 | m <- art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data=Higgins1990Table5) 130 | anova(m) 131 | ``` 132 | 133 | ## Contrast tests 134 | 135 | For an example of how to run contrast tests on an `art` model, see `vignette("art-contrasts")`. 136 | 137 | ## Problems 138 | 139 | Should you encounter any bugs in this package, please file it 140 | [here](https://github.com/mjskay/ARTool/issues/new) with minimal code to 141 | reproduce the issue. 142 | 143 | ## Citations 144 | 145 | Kay, M., Elkin, L. A., Higgins, J. J., and Wobbrock, J. O. (`r format(Sys.Date(), "%Y")`). 146 | _ARTool: Aligned Rank Transform for Nonparametric Factorial ANOVAs_. 147 | R package version `r getNamespaceVersion("ARTool")`, . 148 | DOI: [10.5281/zenodo.594511](https://dx.doi.org/10.5281/zenodo.594511). 149 | 150 | For the *ART* procedure used by `art()` and `anova.art()`, cite: 151 | 152 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. (2011). The Aligned 153 | Rank Transform for Nonparametric Factorial Analyses Using Only ANOVA 154 | Procedures. _Proceedings of the ACM Conference on Human Factors in 155 | Computing Systems (CHI 2011)_. Vancouver, British Columbia (May 7-12, 2011). 156 | New York: ACM Press, pp. 143-146. . 157 | DOI: [10.1145/1978942.1978963](https://dx.doi.org/10.1145/1978942.1978963). 158 | 159 | For the *ART-C* contrast testing procedure used by `art.con()` and `artlm.con()`, cite: 160 | 161 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. (2021). An Aligned 162 | Rank Transform Procedure for Multifactor Contrast Tests. _Proceedings of the ACM 163 | Symposium on User Interface Software and Technology (UIST 2021)_. Virtual Event 164 | (October 10-14, 2021). New York: ACM Press, pp. 754-768. DOI: 165 | [10.1145/3472749.3474784](https://dx.doi.org/10.1145/3472749.3474784) 166 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # ARTool: R Package for the Aligned Rank Transform for Nonparametric Factorial ANOVAs 3 | 4 | [![R build 5 | status](https://github.com/mjskay/ARTool/workflows/R-CMD-check/badge.svg)](https://github.com/mjskay/ARTool/actions) 6 | [![Coverage 7 | status](https://codecov.io/gh/mjskay/ARTool/branch/master/graph/badge.svg)](https://app.codecov.io/github/mjskay/ARTool?branch=master) 8 | [![CRAN_Status_Badge](https://www.r-pkg.org/badges/version/ARTool)](https://CRAN.R-project.org/package=ARTool) 9 | [![GPL \>= 10 | 2](https://img.shields.io/badge/GPL-%E2%89%A52-brightgreen.svg)](https://cran.r-project.org/web/licenses/GPL-3) 11 | [![DOI](https://zenodo.org/badge/19809/mjskay/ARTool.svg)](https://zenodo.org/badge/latestdoi/19809/mjskay/ARTool) 12 | [![DOI](https://img.shields.io/badge/DOI-10.1145%2F1978942.1978963-blue.svg)](https://dx.doi.org/10.1145/1978942.1978963) 13 | 14 | *Matthew Kay, Northwestern University *
15 | *Lisa A. Elkin, University of Washington, 16 | *
*James J. Higgins, Kansas State 17 | University, *
*Jacob O. Wobbrock, University of 18 | Washington * 19 | 20 | ARTool is an R package implementing the Aligned Rank Transform for 21 | conducting nonparametric analyses of variance on factorial models. This 22 | implementation is based on the ART procedure as used in the original 23 | implementation of 24 | [ARTool](https://depts.washington.edu/acelab/proj/art/) by Wobbrock et 25 | al. 26 | 27 | The package automates the Aligning-and-Ranking process using the `art` 28 | function. It also automates the process of running a series of ANOVAs on 29 | the transformed data and extracting the results of interest. It supports 30 | traditional ANOVA models (fit using `lm`), repeated measures ANOVAs (fit 31 | using `aov`), and mixed effects models (fit using `lmer`); the model 32 | used is determined by the formula passed to `art`. 33 | 34 | **Note**: The documentation of this package assumes some level of 35 | familiarity with when and why you may want to use the aligned rank 36 | transform; the [ARTool 37 | page](https://depts.washington.edu/acelab/proj/art/) provides a more 38 | in-depth (and highly approachable) introduction to the aligned rank 39 | transform and the motivation for its use. 40 | 41 | ## Installation 42 | 43 | You can install the latest released version from CRAN with this R 44 | command: 45 | 46 | ``` r 47 | install.packages("ARTool") 48 | ``` 49 | 50 | **Or**, you can install the latest development version from GitHub with 51 | these R commands: 52 | 53 | ``` r 54 | install.packages("devtools") 55 | devtools::install_github("mjskay/ARTool") 56 | ``` 57 | 58 | ## Example 59 | 60 | The general approach to using ART is to transform your data using `art` 61 | , verify the ART procedure is appropriate to the dataset using `summary` 62 | , and then run an ANOVA on the transformed data using `anova` . 63 | 64 | First, let us load some example data: 65 | 66 | ``` r 67 | library(ARTool) 68 | data(Higgins1990Table5, package = "ARTool") 69 | ``` 70 | 71 | `Higgins1990Table5` is a data frame from an experiment in which the 72 | effects of `Moisture` and `Fertilizer` on `DryMatter` in peat pots was 73 | tested. Four pots were placed on each `Tray` , with `Moisture` varied 74 | between `Tray` s and `Fertilizer` varied within `Tray` s. We can see the 75 | basic structure of the data: 76 | 77 | ``` r 78 | str(Higgins1990Table5) 79 | ``` 80 | 81 | ## 'data.frame': 48 obs. of 4 variables: 82 | ## $ Tray : Factor w/ 12 levels "t1","t2","t3",..: 1 1 1 1 2 2 2 2 3 3 ... 83 | ## $ Moisture : Factor w/ 4 levels "m1","m2","m3",..: 1 1 1 1 1 1 1 1 1 1 ... 84 | ## $ Fertilizer: Factor w/ 4 levels "f1","f2","f3",..: 1 2 3 4 1 2 3 4 1 2 ... 85 | ## $ DryMatter : num 3.3 4.3 4.5 5.8 4 4.1 6.5 7.3 1.9 3.8 ... 86 | 87 | ``` r 88 | head(Higgins1990Table5, n=8) 89 | ``` 90 | 91 | ## Tray Moisture Fertilizer DryMatter 92 | ## 1 t1 m1 f1 3.3 93 | ## 2 t1 m1 f2 4.3 94 | ## 3 t1 m1 f3 4.5 95 | ## 4 t1 m1 f4 5.8 96 | ## 5 t2 m1 f1 4.0 97 | ## 6 t2 m1 f2 4.1 98 | ## 7 t2 m1 f3 6.5 99 | ## 8 t2 m1 f4 7.3 100 | 101 | ### Step 1: Transform the data 102 | 103 | To analyze this data using the aligned rank transform, we first 104 | transform the data using `art` . We specify the response variable 105 | (`DryMatter` ), the fixed effects and all of their interactions 106 | (`Moisture*Fertilizer`, or equivalently 107 | `Moisture + Fertilizer + Moisture:Fertilizer`), and any grouping terms 108 | if present (here, `(1|Tray)` ). 109 | 110 | While `(1|Tray)` has no effect on the results of the aligned rank 111 | transformation, it will be used by `anova` to determine the type of 112 | model to run: when grouping terms are present, mixed effects models are 113 | run using `lmer`. If you wish to use a repeated measures ANOVA instead 114 | of a mixed effects model, you can use an `Error` term instead (see below 115 | for an example of this). If you do not having repeated measures, do not 116 | include any grouping terms or error terms. 117 | 118 | ``` r 119 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 120 | ``` 121 | 122 | ### Step 2: Verify appropriateness of ART 123 | 124 | To verify that the ART procedure was correctly applied and is 125 | appropriate for this dataset, we can look at the output of `summary` : 126 | 127 | ``` r 128 | summary(m) 129 | ``` 130 | 131 | ## Aligned Rank Transform of Factorial Model 132 | ## 133 | ## Call: 134 | ## art(formula = DryMatter ~ Moisture * Fertilizer + (1 | Tray), 135 | ## data = Higgins1990Table5) 136 | ## 137 | ## Column sums of aligned responses (should all be ~0): 138 | ## Moisture Fertilizer Moisture:Fertilizer 139 | ## 0 0 0 140 | ## 141 | ## F values of ANOVAs on aligned responses not of interest (should all be ~0): 142 | ## Min. 1st Qu. Median Mean 3rd Qu. Max. 143 | ## 0 0 0 0 0 0 144 | 145 | We see that the columns sums of aligned responses and the F values of 146 | ANOVAs on aligned responses not of interest are all ~0, indicating that 147 | the alignment correctly “stripped out” effects not of interest. Thus, we 148 | can apply the ANOVA on the transformed data. 149 | 150 | ### Step 3: Run the ANOVA 151 | 152 | ARTool automatically selects the model to be used for the ANOVA. Because 153 | we have included a grouping term, `(1|Tray)`, ARTool will fit mixed 154 | effects models using `lmer` and run the ANOVAs on them: 155 | 156 | ``` r 157 | anova(m) 158 | ``` 159 | 160 | ## Analysis of Variance of Aligned Rank Transformed Data 161 | ## 162 | ## Table Type: Analysis of Deviance Table (Type III Wald F tests with Kenward-Roger df) 163 | ## Model: Mixed Effects (lmer) 164 | ## Response: art(DryMatter) 165 | ## 166 | ## F Df Df.res Pr(>F) 167 | ## 1 Moisture 23.833 3 8 0.00024199 *** 168 | ## 2 Fertilizer 122.402 3 24 1.1124e-14 *** 169 | ## 3 Moisture:Fertilizer 5.118 9 24 0.00064665 *** 170 | ## --- 171 | ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 172 | 173 | ### Alternative model: Repeated Measures ANOVA 174 | 175 | This particular study could also be analyzed using a repeated measures 176 | ANOVA, yielding the same results (note that repeated measures ANOVAs and 177 | mixed effects models will not always yield the same results). To instead 178 | run a repeated measures ANOVA, add an `Error` term to the model as you 179 | might for a call to `aov`: 180 | 181 | ``` r 182 | m <- art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data=Higgins1990Table5) 183 | anova(m) 184 | ``` 185 | 186 | ## Analysis of Variance of Aligned Rank Transformed Data 187 | ## 188 | ## Table Type: Repeated Measures Analysis of Variance Table (Type I) 189 | ## Model: Repeated Measures (aov) 190 | ## Response: art(DryMatter) 191 | ## 192 | ## Error Df Df.res F value Pr(>F) 193 | ## 1 Moisture Tray 3 8 23.833 0.00024199 *** 194 | ## 2 Fertilizer Withn 3 24 122.402 1.1124e-14 *** 195 | ## 3 Moisture:Fertilizer Withn 9 24 5.118 0.00064665 *** 196 | ## --- 197 | ## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 198 | 199 | ## Contrast tests 200 | 201 | For an example of how to run contrast tests on an `art` model, see 202 | `vignette("art-contrasts")`. 203 | 204 | ## Problems 205 | 206 | Should you encounter any bugs in this package, please file it 207 | [here](https://github.com/mjskay/ARTool/issues/new) with minimal code to 208 | reproduce the issue. 209 | 210 | ## Citations 211 | 212 | Kay, M., Elkin, L. A., Higgins, J. J., and Wobbrock, J. O. (2025). 213 | *ARTool: Aligned Rank Transform for Nonparametric Factorial ANOVAs*. R 214 | package version 0.11.2, . DOI: 215 | [10.5281/zenodo.594511](https://dx.doi.org/10.5281/zenodo.594511). 216 | 217 | For the *ART* procedure used by `art()` and `anova.art()`, cite: 218 | 219 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. (2011). 220 | The Aligned Rank Transform for Nonparametric Factorial Analyses Using 221 | Only ANOVA Procedures. *Proceedings of the ACM Conference on Human 222 | Factors in Computing Systems (CHI 2011)*. Vancouver, British Columbia 223 | (May 7-12, 2011). New York: ACM Press, pp. 143-146. 224 | . DOI: 225 | [10.1145/1978942.1978963](https://dx.doi.org/10.1145/1978942.1978963). 226 | 227 | For the *ART-C* contrast testing procedure used by `art.con()` and 228 | `artlm.con()`, cite: 229 | 230 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. (2021). An 231 | Aligned Rank Transform Procedure for Multifactor Contrast Tests. 232 | *Proceedings of the ACM Symposium on User Interface Software and 233 | Technology (UIST 2021)*. Virtual Event (October 10-14, 2021). New York: 234 | ACM Press, pp. 754-768. DOI: 235 | [10.1145/3472749.3474784](https://dx.doi.org/10.1145/3472749.3474784) 236 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Submission comments 2 | This submission fixes a bug in an example introduced due to changes in emmeans. 3 | 4 | It appears as a new submission as the package was archived on CRAN: 5 | 6 | > X-CRAN-Comment: Archived on 2025-04-04 as issues were not corrected 7 | > in time 8 | 9 | Apologies, I missed the CRAN email about check failures during the spring break. 10 | 11 | > Possibly misspelled words in DESCRIPTION: 12 | > ANOVAs (15:15) 13 | > Elkin (18:8) 14 | > Findlater (16:5) 15 | > Gergle (16:16) 16 | > Wobbrock (15:38, 18:33) 17 | 18 | These words are all spelled correctly. 19 | 20 | 21 | ## Test environments 22 | * local Windows install, R-release 23 | * MacOS (Github), R-release 24 | * Linux (Github), R-devel 25 | * Linux (Github), R-release 26 | * Linux (Github), R-oldrel 27 | * win-builder Windows install, R-release 28 | * win-builder Windows install, R-devel 29 | 30 | 31 | ## R CMD check results 32 | 0 errors | 0 warnings | 1 note 33 | 34 | * checking CRAN incoming feasibility ... NOTE 35 | Maintainer: 'Matthew Kay ' 36 | 37 | New submission 38 | 39 | Package was archived on CRAN 40 | 41 | Possibly misspelled words in DESCRIPTION: 42 | ANOVAs (15:15) 43 | Elkin (18:8) 44 | Findlater (16:5) 45 | Gergle (16:16) 46 | Wobbrock (15:38, 18:33) 47 | 48 | CRAN repository db overrides: 49 | X-CRAN-Comment: Archived on 2025-04-04 as issues were not corrected 50 | in time 51 | -------------------------------------------------------------------------------- /data/ElkinAB.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/ElkinAB.RData -------------------------------------------------------------------------------- /data/ElkinABC.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/ElkinABC.RData -------------------------------------------------------------------------------- /data/Higgins1990Table1.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/Higgins1990Table1.RData -------------------------------------------------------------------------------- /data/Higgins1990Table1.art.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/Higgins1990Table1.art.RData -------------------------------------------------------------------------------- /data/Higgins1990Table5.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/Higgins1990Table5.RData -------------------------------------------------------------------------------- /data/Higgins1990Table5.art.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/Higgins1990Table5.art.RData -------------------------------------------------------------------------------- /data/HigginsABC.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/HigginsABC.RData -------------------------------------------------------------------------------- /data/HigginsABC.art.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/HigginsABC.art.RData -------------------------------------------------------------------------------- /data/InteractionTestData.RData: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mjskay/ARTool/4dbe6e368371fd98808fff9501049573159a5e6b/data/InteractionTestData.RData -------------------------------------------------------------------------------- /inst/CITATION: -------------------------------------------------------------------------------- 1 | year <- sub("-.*", "", meta$Date) 2 | note <- sprintf("R package version %s", meta$Version) 3 | 4 | bibentry( 5 | bibtype = "Manual", 6 | title = "{ARTool}: Aligned Rank Transform for Nonparametric Factorial ANOVAs", 7 | author = c( 8 | person("Matthew", "Kay"), 9 | person("Lisa A.", "Elkin"), 10 | person("James J.", "Higgins"), 11 | person("Jacob O.", "Wobbrock") 12 | ), 13 | year = year, 14 | note = note, 15 | url = "https://github.com/mjskay/ARTool", 16 | doi = "10.5281/zenodo.594511" 17 | ) 18 | 19 | bibentry( 20 | bibtype = "InProceedings", 21 | title = "The Aligned Rank Transform for Nonparametric Factorial Analyses Using Only ANOVA Procedures", 22 | author = c( 23 | person("Jacob O.", "Wobbrock"), 24 | person("Leah", "Findlater"), 25 | person("Darren", "Gergle"), 26 | person("James J.", "Higgins") 27 | ), 28 | year = 2011, 29 | booktitle = "Proceedings of the ACM Conference on Human Factors in Computing Systems (CHI '11)", 30 | address = "New York", 31 | publisher = "ACM Press", 32 | pages = "143--146", 33 | url = "https://depts.washington.edu/acelab/proj/art/", 34 | doi = "10.1145/1978942.1978963" 35 | ) 36 | 37 | bibentry( 38 | header = "For the ART-C contrast testing procedure used in art.con() and artlm.con(), cite:", 39 | bibtype = "InProceedings", 40 | title = "An Aligned Rank Transform Procedure for Multifactor Contrast Tests", 41 | author = c( 42 | person("Lisa A.", "Elkin"), 43 | person("Matthew", "Kay"), 44 | person("James J.", "Higgins"), 45 | person("Jacob O.", "Wobbrock") 46 | ), 47 | year = 2021, 48 | booktitle = "Proceedings of the ACM Symposium on User Interface Software and Technology (UIST '21)", 49 | address = "New York", 50 | publisher = "ACM Press", 51 | pages = "754--768", 52 | doi = "10.1145/3472749.3474784" 53 | ) 54 | -------------------------------------------------------------------------------- /inst/WORDLIST: -------------------------------------------------------------------------------- 1 | ACM 2 | acelab 3 | aimgroup 4 | arxiv 5 | arXiv 6 | bonferroni 7 | depts 8 | doi 9 | DOI 10 | edu 11 | eprint 12 | et al 13 | Elkin 14 | Findlater 15 | Gergle 16 | github 17 | Hidekazu 18 | HigginsABC 19 | homoscedasticity 20 | http 21 | https 22 | interpretable 23 | ish 24 | Kaneko 25 | Lelkin 26 | Milliken 27 | mjskay 28 | multifactor 29 | Multifactor 30 | Nostrand 31 | pre 32 | proj 33 | Tashtoush 34 | UIST 35 | unconcatenated 36 | tukey 37 | washington 38 | zenodo 39 | -------------------------------------------------------------------------------- /man/ElkinAB.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{ElkinAB} 5 | \alias{ElkinAB} 6 | \title{Synthetic 2x2 Within-Subjects Experiment} 7 | \format{ 8 | A data frame with 32 observations on the following 4 variables. 9 | \describe{ 10 | \item{S}{a factor representing subjects with levels \code{"s1"} .. \code{"s8"}} 11 | \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 12 | \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 13 | \item{Y}{a numeric vector} 14 | } 15 | } 16 | \source{ 17 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 18 | (2021). An aligned rank transform procedure for multifactor contrast tests. 19 | \emph{Proceedings of the ACM Symposium on User Interface Software and 20 | Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 21 | ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 22 | } 23 | \description{ 24 | Synthetic data from an experiment with two within-subjects factors 25 | (\code{A} and \code{B}) having two levels each. 26 | } 27 | \examples{ 28 | \donttest{ 29 | data(ElkinAB, package = "ARTool") 30 | 31 | ## run contrast using the ART-C procedure on the data. 32 | m <- art(Y ~ A*B + (1|S), data = ElkinAB) 33 | art.con(m, "A:B") 34 | } 35 | } 36 | \keyword{datasets} 37 | -------------------------------------------------------------------------------- /man/ElkinABC.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{ElkinABC} 5 | \alias{ElkinABC} 6 | \title{Synthetic 2x2x2 Within-Subjects Experiment} 7 | \format{ 8 | A data frame with 64 observations on the following 5 variables. 9 | \describe{ 10 | \item{S}{a factor representing subjects with levels \code{"s1"} .. \code{"s8"}} 11 | \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 12 | \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 13 | \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 14 | \item{Y}{a numeric vector} 15 | } 16 | } 17 | \source{ 18 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 19 | (2021). An aligned rank transform procedure for multifactor contrast tests. 20 | \emph{Proceedings of the ACM Symposium on User Interface Software and 21 | Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 22 | ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 23 | } 24 | \description{ 25 | Synthetic data from an experiment with three within-subjects factors 26 | (\code{A}, \code{B}, and \code{C}) having two levels each. 27 | } 28 | \examples{ 29 | \donttest{ 30 | data(ElkinABC, package = "ARTool") 31 | 32 | ## run contrast using the ART-C procedure on the data. 33 | m <- art(Y ~ A*B*C + (1|S), data = ElkinABC) 34 | art.con(m, "A:B:C") 35 | } 36 | } 37 | \keyword{datasets} 38 | -------------------------------------------------------------------------------- /man/Higgins1990Table1.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{Higgins1990Table1} 5 | \alias{Higgins1990Table1} 6 | \title{Synthetic 3x3 Factorial Randomized Experiment} 7 | \format{ 8 | A data frame with 36 observations on the following 4 variables. 9 | \describe{ 10 | \item{Subject}{a factor with levels \code{"s1"} .. \code{"s36"}} 11 | \item{Row}{a factor with levels \code{"r1"} .. \code{"r3"}} 12 | \item{Column}{a factor with levels \code{"c1"} .. \code{"c3"}} 13 | \item{Response}{a numeric vector} 14 | } 15 | } 16 | \source{ 17 | Higgins, J. J., Blair, R. C. and Tashtoush, S. (1990). The aligned 18 | rank transform procedure. \emph{Proceedings of the Conference on Applied 19 | Statistics in Agriculture}. Manhattan, Kansas: Kansas State University, pp. 20 | 185-195. 21 | } 22 | \description{ 23 | Synthetic data from a balanced 3x3 factorial experiment with main effects, 24 | no interaction, and independent and identically distributed (i.i.d.) Normal 25 | errors. 26 | } 27 | \examples{ 28 | 29 | data(Higgins1990Table1, package = "ARTool") 30 | 31 | ## run aligned-rank transform and ANOVA on the data 32 | ## Note: because there is only one observation per Subject 33 | ## in this dataset, we do not need to include Subject as 34 | ## a grouping term in this formula. Indeed, if we did, 35 | ## lmer would complain when we attempt the ANOVA. 36 | m <- art(Response ~ Row*Column, data=Higgins1990Table1) 37 | anova(m) 38 | 39 | } 40 | \seealso{ 41 | \code{\link{art}}, \code{\link{anova.art}}. 42 | } 43 | \keyword{datasets} 44 | -------------------------------------------------------------------------------- /man/Higgins1990Table1.art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{Higgins1990Table1.art} 5 | \alias{Higgins1990Table1.art} 6 | \title{Aligned Rank Transformed Version of Higgins1990Table1} 7 | \format{ 8 | A data frame with 36 observations on the following 10 variables. 9 | \describe{ 10 | \item{Subject}{a factor with levels \code{"s1"} .. \code{"s36"}} 11 | \item{Row}{a factor with levels \code{"r1"} .. \code{"r3"}} 12 | \item{Column}{a factor with levels \code{"c1"} .. \code{"c3"}} 13 | \item{Response}{a numeric vector} 14 | \item{aligned.Response..for.Row}{a numeric vector} 15 | \item{aligned.Response..for.Column}{a numeric vector} 16 | \item{aligned.Response..for.Row.Column}{a numeric vector} 17 | \item{ART.Response..for.Row}{a numeric vector} 18 | \item{ART.Response..for.Column}{a numeric vector} 19 | \item{ART.Response..for.Row.Column}{a numeric vector} 20 | } 21 | } 22 | \source{ 23 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 24 | \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 25 | } 26 | \description{ 27 | The ART version of \code{\link{Higgins1990Table1}} as produced by the 28 | original ARTool, used to test the correctness of \code{\link{art}} output. 29 | } 30 | \seealso{ 31 | \code{\link{Higgins1990Table1}}, \code{\link{art}}. 32 | } 33 | \keyword{datasets} 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/Higgins1990Table5.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{Higgins1990Table5} 5 | \alias{Higgins1990Table5} 6 | \title{Split-plot Experiment Examining Effect of Moisture and Fertilizer on Dry 7 | Matter in Peat Pots} 8 | \format{ 9 | A data frame with 48 observations on the following 4 variables. 10 | \describe{ 11 | \item{Tray}{a factor with levels \code{"t1"} .. \code{"t12"}} 12 | \item{Moisture}{a factor with levels \code{"m1"} .. \code{"m4"}} 13 | \item{Fertilizer}{a factor with levels \code{"f1"} .. \code{"f4"}} 14 | \item{DryMatter}{a numeric vector} 15 | } 16 | } 17 | \source{ 18 | Milliken, G.A., Johnson, D.E. (1984). \emph{Analysis of Messy Data 19 | Vol I: Designed Experiments}. Van Nostrand Reinhold Company, New York. 20 | } 21 | \description{ 22 | This dataset comes from a split-plot experiment examining \code{Tray}s of 4 23 | peat pots each. \code{Moisture} was varied between \code{Tray}s (i.e. it was 24 | the whole-plot treatment) and \code{Fertilizer} was varied within 25 | \code{Tray}s (i.e. it was the sub-plot treatment). The outcome measure was 26 | \code{DryMatter}. 27 | } 28 | \details{ 29 | This dataset, originally from Milliken & Johnson (1984), is reproduced here 30 | from Higgins \emph{et al.} (1990). 31 | } 32 | \examples{ 33 | 34 | data(Higgins1990Table5, package = "ARTool") 35 | 36 | ## run aligned-rank transform and ANOVA on the data 37 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 38 | anova(m) 39 | 40 | } 41 | \references{ 42 | Higgins, J. J., Blair, R. C. and Tashtoush, S. (1990). The 43 | aligned rank transform procedure. \emph{Proceedings of the Conference on 44 | Applied Statistics in Agriculture}. Manhattan, Kansas: Kansas State 45 | University, pp. 185-195. 46 | } 47 | \seealso{ 48 | See \code{\link{art}} for a more complete example. See also 49 | \code{\link{anova.art}}. 50 | } 51 | \keyword{datasets} 52 | -------------------------------------------------------------------------------- /man/Higgins1990Table5.art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{Higgins1990Table5.art} 5 | \alias{Higgins1990Table5.art} 6 | \title{Aligned Rank Transformed Version of Higgins1990Table5} 7 | \format{ 8 | A data frame with 48 observations on the following 10 variables. 9 | \describe{ 10 | \item{Tray}{a factor with levels \code{"t1"} .. \code{"t12"}} 11 | \item{Moisture}{a factor with levels \code{"m1"} .. \code{"m4"}} 12 | \item{Fertilizer}{a factor with levels \code{"f1"} .. \code{"f4"}} 13 | \item{DryMatter}{a numeric vector} 14 | \item{aligned.DryMatter..for.Moisture}{a numeric vector} 15 | \item{aligned.DryMatter..for.Fertilizer}{a numeric vector} 16 | \item{aligned.DryMatter..for.Moisture.Fertilizer}{a numeric vector} 17 | \item{ART.DryMatter..for.Moisture}{a numeric vector} 18 | \item{ART.DryMatter..for.Fertilizer}{a numeric vector} 19 | \item{ART.DryMatter..for.Moisture.Fertilizer}{a numeric vector} 20 | } 21 | } 22 | \source{ 23 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 24 | \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 25 | } 26 | \description{ 27 | The ART version of \code{\link{Higgins1990Table5}} as produced by the 28 | original ARTool, used to test the correctness of \code{\link{art}} output. 29 | } 30 | \seealso{ 31 | \code{\link{Higgins1990Table5}}, \code{\link{art}}. 32 | } 33 | \keyword{datasets} 34 | \keyword{internal} 35 | -------------------------------------------------------------------------------- /man/HigginsABC.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{HigginsABC} 5 | \alias{HigginsABC} 6 | \title{Synthetic 2x2x2 Mixed Design Experiment} 7 | \format{ 8 | A data frame with 16 observations on the following 5 variables. 9 | \describe{ 10 | \item{Subject}{a factor with levels \code{"s1"} .. \code{"s8"}} 11 | \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 12 | \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 13 | \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 14 | \item{Y}{a numeric vector} 15 | } 16 | } 17 | \source{ 18 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 19 | \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 20 | } 21 | \description{ 22 | Synthetic data from an experiment with two between-\code{Subject}s factors 23 | (\code{A} and \code{B}) having two levels each and one 24 | within-\code{Subject}s factor (\code{C}) with two levels. 25 | } 26 | \examples{ 27 | \donttest{ 28 | data(HigginsABC, HigginsABC.art, package = "ARTool") 29 | 30 | ## run aligned-rank transform and ANOVA on the data 31 | m <- art(Y ~ A*B*C + Error(Subject), data = HigginsABC) 32 | anova(m) 33 | } 34 | } 35 | \seealso{ 36 | \code{\link{art}}, \code{\link{anova.art}}. 37 | } 38 | \keyword{datasets} 39 | -------------------------------------------------------------------------------- /man/HigginsABC.art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{HigginsABC.art} 5 | \alias{HigginsABC.art} 6 | \title{Aligned Rank Transformed Version of HigginsABC} 7 | \format{ 8 | A data frame with 16 observations on the following 19 variables. 9 | \describe{ 10 | \item{Subject}{a factor with levels \code{"s1"} .. \code{"s8"}} 11 | \item{A}{a factor with levels \code{"a1"} \code{"a2"}} 12 | \item{B}{a factor with levels \code{"b1"} \code{"b2"}} 13 | \item{C}{a factor with levels \code{"c1"} \code{"c2"}} 14 | \item{Y}{a numeric vector} 15 | \item{aligned.Y..for.A}{a numeric vector} 16 | \item{aligned.Y..for.B}{a numeric vector} 17 | \item{aligned.Y..for.A.B}{a numeric vector} 18 | \item{aligned.Y..for.C}{a numeric vector} 19 | \item{aligned.Y..for.A.C}{a numeric vector} 20 | \item{aligned.Y..for.B.C}{a numeric vector} 21 | \item{aligned.Y..for.A.B.C}{a numeric vector} 22 | \item{ART.Y..for.A}{a numeric vector} 23 | \item{ART.Y..for.B}{a numeric vector} 24 | \item{ART.Y..for.A.B}{a numeric vector} 25 | \item{ART.Y..for.C}{a numeric vector} 26 | \item{ART.Y..for.A.C}{a numeric vector} 27 | \item{ART.Y..for.B.C}{a numeric vector} 28 | \item{ART.Y..for.A.B.C}{a numeric vector} 29 | } 30 | } 31 | \source{ 32 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 33 | \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 34 | } 35 | \description{ 36 | The ART version of \code{\link{HigginsABC}} as produced by the original 37 | ARTool, used to test the correctness of \code{\link{art}} output. 38 | } 39 | \seealso{ 40 | \code{\link{HigginsABC}}, \code{\link{art}}. 41 | } 42 | \keyword{datasets} 43 | \keyword{internal} 44 | -------------------------------------------------------------------------------- /man/InteractionTestData.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/data.R 3 | \docType{data} 4 | \name{InteractionTestData} 5 | \alias{InteractionTestData} 6 | \title{Synthetic Data Used in the Contrast Test Vignette} 7 | \description{ 8 | See \code{vignette("art-contrasts")} for a description of this data. 9 | } 10 | \examples{ 11 | ## see vignette("art-contrasts") 12 | } 13 | \seealso{ 14 | \code{\link{art}}, \code{\link{anova.art}}. 15 | } 16 | \keyword{datasets} 17 | -------------------------------------------------------------------------------- /man/anova.art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/anova.art.R 3 | \name{anova.art} 4 | \alias{anova.art} 5 | \alias{print.anova.art} 6 | \title{Aligned Rank Transform Analysis of Variance} 7 | \usage{ 8 | \method{anova}{art}( 9 | object, 10 | response = c("art", "aligned"), 11 | type = c("III", "II", "I", 3, 2, 1), 12 | factor.contrasts = "contr.sum", 13 | test = c("F", "Chisq"), 14 | all.rows = FALSE, 15 | ... 16 | ) 17 | 18 | \method{print}{anova.art}(x, verbose = FALSE, digits = 5, ...) 19 | } 20 | \arguments{ 21 | \item{object}{An object of class \code{\link{art}}.} 22 | 23 | \item{response}{Which response to run the ANOVA on: the aligned responses 24 | (\code{"aligned"}) or the aligned and ranked responses (\code{"art"}). This 25 | argument is passed to \code{\link{artlm}}. See 'Details'.} 26 | 27 | \item{type}{Type of ANOVAs to conduct. If \code{type} is \code{1} or 28 | \code{"I"}, then conducts Type I ANOVAs using \code{\link{anova}}. 29 | Otherwise, conducts Type II or Type III ANOVAs using \code{\link[car]{Anova}}. 30 | The default is Type III \emph{if} the underlying model supports it. Models 31 | fit with \code{Error} terms are fit using \code{\link{aov}}, which only 32 | supports Type I ANOVAs.} 33 | 34 | \item{factor.contrasts}{The name of the contrast-generating function to be 35 | applied by default to fixed effect factors. See the first element of 36 | \code{\link{options}("contrasts")}. The default is to use 37 | \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is appropriate for 38 | Type III ANOVAs (also the default). This argument is passed to 39 | \code{\link{artlm}}.} 40 | 41 | \item{test}{Test statistic to use. Default \code{"F"}. Note that some models 42 | and ANOVA types may not support \code{"Chisq"}.} 43 | 44 | \item{all.rows}{Show all rows of the resulting ANOVA tables? By default 45 | (\code{FALSE}), shows only the rows that are relevant depending on the type 46 | of \code{response}.} 47 | 48 | \item{\dots}{Additional arguments passed to \code{\link[car]{Anova}} or 49 | \code{\link{anova}} by \code{anova.art} or to \code{\link{print}} by 50 | \code{print.anova.art}.} 51 | 52 | \item{x}{An object of class \code{\link{art}}.} 53 | 54 | \item{verbose}{When \code{TRUE}, sums of squares and residual sum of squares 55 | in addition to degrees of freedom are printed in some ANOVA types (e.g. 56 | repeated measures ANOVAs). Default \code{FALSE}, for brevity.} 57 | 58 | \item{digits}{Digits of output in printed table; see \code{\link{print}}.} 59 | } 60 | \value{ 61 | An object of class \code{"anova"}, which usually is printed. 62 | } 63 | \description{ 64 | Conduct analyses of variance on aligned rank transformed data. 65 | } 66 | \details{ 67 | This function runs several ANOVAs: one for each fixed effect term in the 68 | model \code{object}. In each ANOVA, the independent variables are the same, 69 | but the response is aligned by a different fixed effect term (if response is 70 | "aligned") or aligned and ranked by that fixed effect term (if response is 71 | "art"). These models are generated using \code{\link{artlm}}. 72 | 73 | From each model, only the relevant output rows are kept (unless 74 | \code{all.rows} is \code{TRUE}, in which case all rows are kept). 75 | 76 | When \code{response} is \code{"art"} (the default), only one row is kept 77 | from each ANOVA: the row corresponding to fixed effect term the response was 78 | aligned and ranked by. These results represent nonparametric tests of 79 | significance for the effect of each term on the original response variable. 80 | 81 | When \code{response} is \code{"aligned"}, all rows \emph{except} the row 82 | corresponding to the fixed effect term the response was aligned by are kept. 83 | If the ART procedure is appropriate for this data, these tests should have 84 | all effects "stripped out", and have an F value of ~0. If that is not the 85 | case, another analysis should be considered. This diagnostic is tested by 86 | \code{\link{summary.art}} and a warning generated if the F values are not 87 | all approximately 0. 88 | } 89 | \references{ 90 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 91 | (2011). The aligned rank transform for nonparametric factorial analyses 92 | using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 93 | Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 94 | (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 95 | } 96 | \seealso{ 97 | See \code{\link{art}} for an example. See also 98 | \code{\link{summary.art}}, \code{\link{artlm}}. 99 | } 100 | \author{ 101 | Matthew Kay 102 | } 103 | \keyword{nonparametric} 104 | -------------------------------------------------------------------------------- /man/art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/art.R 3 | \name{art} 4 | \alias{art} 5 | \title{Aligned Rank Transform} 6 | \usage{ 7 | art( 8 | formula, 9 | data, 10 | rank.comparison.digits = -floor(log10(.Machine$double.eps^0.5)), 11 | check.errors.are.factors = TRUE 12 | ) 13 | } 14 | \arguments{ 15 | \item{formula}{A factorial formula with optional grouping terms or error 16 | terms (but not both). Should be a formula with a single response variable 17 | (left-hand side) and one or more terms with all interactions on the 18 | right-hand side, e.g. \code{y ~ x} or \code{y ~ a*b*c} or \code{y ~ a + b + 19 | b:c}. If you want to run a mixed effects ANOVA on the transformed data using 20 | \code{\link[lme4]{lmer}}, you can include grouping terms, as in \code{y ~ a*b*c + 21 | (1|d)}. If you want to run a repeated measures ANOVA using 22 | \code{\link{aov}}, you can include error terms, as in \code{y ~ a*b*c + 23 | Error(d)}. See 'Details'.} 24 | 25 | \item{data}{An optional data frame containing the variables in the model.} 26 | 27 | \item{rank.comparison.digits}{The number of digits to round aligned 28 | responses to before ranking (to ensure ties are computed consistently). See 29 | the \code{digits} argument of \code{\link{round}}. The default value is 30 | based on the default \code{tolerance} used for fuzzy comparison in 31 | \code{all.equal}.} 32 | 33 | \item{check.errors.are.factors}{Should we check to ensure \code{Error()} 34 | terms are all factors? A common mistake involves coding a categorical variable 35 | as numeric and passing it to \code{Error()}, yielding incorrect results 36 | from \code{\link{aov}}. Disabling this check is not recommended unless you 37 | know what you are doing; the most common uses of \code{Error()} (e.g. 38 | in repeated measures designs) involve categorical variables (factors).} 39 | } 40 | \value{ 41 | An object of class \code{"art"}: 42 | 43 | \item{call}{ The call used to generate the transformed data. } 44 | \item{formula}{ The formula used to generate the transformed data. } 45 | \item{cell.means}{ A data frame of cell means for each fixed term and 46 | interaction on the right-hand side of formula. } \item{estimated.effects}{ A 47 | data frame of estimated effects for each fixed term and interaction on the 48 | right-hand side of formula. } \item{residuals}{ A vector of residuals 49 | (response - cell mean of highest-order interaction). } \item{aligned}{ A 50 | data frame of aligned responses for each fixed term and interaction on the 51 | right-hand side of formula. } \item{aligned.ranks}{ A data frame of aligned 52 | and ranked responses for each fixed term and interaction on the right-hand 53 | side of formula. } \item{data}{ The input data frame } 54 | \item{n.grouping.terms}{ The number of grouping variables in the input 55 | formula. } 56 | 57 | For a complete description of cell means, estimated effects, aligned ranks, 58 | etc., in the above output, see Wobbrock \emph{et al.} (2011). 59 | } 60 | \description{ 61 | Apply the aligned rank transform to a factorial model (with optional 62 | grouping terms). Usually done in preparation for a nonparametric analyses of 63 | variance on models with numeric or ordinal responses, which can be done by 64 | following up with \code{anova.art}. 65 | } 66 | \details{ 67 | The aligned rank transform allows a nonparametric analysis of variance to be 68 | conducted on factorial models with fixed and random effects (or repeated 69 | measures) and numeric or ordinal responses. This is done by first aligning 70 | and ranking the fixed effects using this function, then conducting an 71 | analysis of variance on linear models built from the transformed data using 72 | \code{\link{anova.art}} (see 'Examples'). The model specified using this 73 | function \emph{must} include all interactions of fixed effects. 74 | 75 | The \code{formula} should contain a single response variable (left-hand 76 | side) that can be numeric, an ordered factor, or logical. The right-hand 77 | side of the formula should contain one or more fixed effect factors, zero or 78 | more grouping terms, and zero or more error terms. Error terms and grouping 79 | terms cannot be used simultaneously. All possible interactions of the fixed 80 | effect terms must be included. For example, \code{y ~ x} and \code{y ~ 81 | a*b*c} and \code{y ~ a + b + b:c} are legal, but \code{y ~ a + b} is not, as 82 | it omits the interaction \code{a:b}. Grouping terms are specified as in 83 | \code{\link[lme4]{lmer}}, e.g. \code{y ~ a*b*c + (1|d)} includes the random 84 | intercept term \code{(1|d)}. Error terms are specified as in 85 | \code{\link{aov}}, e.g. \code{y ~ a*b*c + Error(d)}. Grouping terms and 86 | error terms are not involved in the transformation, but are included in the 87 | model when ANOVAs are conducted, see \code{\link{anova.art}}. 88 | 89 | For details on the transformation itself, see Wobbrock \emph{et al.} (2011) 90 | or the ARTool website: \url{https://depts.washington.edu/acelab/proj/art/}. 91 | } 92 | \examples{ 93 | \donttest{ 94 | data(Higgins1990Table5, package = "ARTool") 95 | 96 | ## perform aligned rank transform 97 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 98 | 99 | ## see summary data to ensure aligned rank transform is appropriate for this data 100 | summary(m) 101 | ## looks good (aligned effects sum to 0 and F values on aligned responses 102 | ## not of interest are all ~0) 103 | 104 | ## we can always look at the anova of aligned data if we want more detail 105 | ## to assess the appropriateness of ART. F values in this anova should all 106 | ## be approx 0. 107 | anova(m, response="aligned") 108 | 109 | ## then we can run an anova on the ART responses (equivalent to anova(m, response="art")) 110 | anova(m) 111 | 112 | 113 | ## if we want contrast tests, we can use art.con(): 114 | ## Ex 1: pairwise contrasts on Moisture: 115 | art.con(m, "Moisture") 116 | ## Ex 2: pairwise contrasts on Moisture:Fertilizer: 117 | art.con(m, "Moisture:Fertilizer") 118 | ## Ex 3: difference-of-difference tests on the Moisture:Fertilizer interaction: 119 | art.con(m, "Moisture:Fertilizer", interaction = TRUE) 120 | 121 | 122 | ## The above three examples with art.con() can be constructed manually as well. 123 | ## art.con() extracts the appropriate linear model and conducts contrasts 124 | ## using emmeans(). If we want to use a specific method for post-hoc tests 125 | ## other than emmeans(), artlm.con(m, term) returns the linear model for the 126 | ## specified term which we can then examine using our preferred method 127 | ## (emmeans, glht, etc). The equivalent calls for the above examples are: 128 | library(emmeans) 129 | 130 | ## Ex 1: pairwise contrasts on Moisture: 131 | contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method = "pairwise") 132 | 133 | ## Ex 2: pairwise contrasts on Moisture:Fertilizer: 134 | ## See artlm.con() documentation for more details on the syntax, specifically 135 | ## the formula passed to emmeans. 136 | contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method = "pairwise") 137 | 138 | ## Ex 3: difference-of-difference tests on the Moisture:Fertilizer interaction: 139 | ## Note the use of artlm() instead of artlm.con() 140 | contrast( 141 | emmeans(artlm(m, "Moisture:Fertilizer"), ~ Moisture:Fertilizer), 142 | method = "pairwise", interaction = TRUE 143 | ) 144 | 145 | 146 | ## For a more in-depth explanation and example of contrasts with art and 147 | ## differences between interaction types, see vignette("art-contrasts") 148 | 149 | } 150 | 151 | } 152 | \references{ 153 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 154 | \emph{ARTool}. \url{https://depts.washington.edu/acelab/proj/art/}. 155 | 156 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 157 | (2011). The aligned rank transform for nonparametric factorial analyses 158 | using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 159 | Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 160 | (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 161 | } 162 | \seealso{ 163 | \code{\link{summary.art}}, \code{\link{anova.art}}, 164 | \code{\link{artlm}}, \code{\link{artlm.con}}, \code{\link{art.con}}. 165 | } 166 | \author{ 167 | Matthew Kay 168 | } 169 | \keyword{nonparametric} 170 | -------------------------------------------------------------------------------- /man/art.con.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/art.con.R 3 | \name{art.con} 4 | \alias{art.con} 5 | \title{Aligned Ranked Transform Contrasts} 6 | \usage{ 7 | art.con( 8 | m, 9 | formula, 10 | response = "art", 11 | factor.contrasts = "contr.sum", 12 | method = "pairwise", 13 | interaction = FALSE, 14 | adjust, 15 | ... 16 | ) 17 | } 18 | \arguments{ 19 | \item{m}{An object of class \code{\link{art}}.} 20 | 21 | \item{formula}{Either a character vector or a formula specifying the fixed 22 | effects whose levels will be compared. See "Formula" section below.} 23 | 24 | \item{response}{Which response to use: the aligned response 25 | (\code{"aligned"}) or the aligned-and-ranked response 26 | (\code{"art"}). Default is "art". This argument is passed to \code{\link{artlm.con}} 27 | (when \code{interaction = FALSE}) or \code{\link{artlm}} (when \code{interaction = TRUE}).} 28 | 29 | \item{factor.contrasts}{The name of the contrast-generating function to be 30 | applied by default to fixed effect factors. Sets the the first element of 31 | \code{\link{options}("contrasts")} for the duration of this function. The 32 | default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 33 | appropriate for Type III ANOVAs (the default ANOVA type for 34 | \code{\link{anova.art}}). This argument is passed to \code{\link{artlm.con}} / 35 | \code{\link{artlm}}.} 36 | 37 | \item{method}{Contrast method argument passed to \code{\link[emmeans]{contrast}}. 38 | Note: the default is \code{"pairwise"} even though the default for the 39 | \code{\link[emmeans]{contrast}} function is \code{"eff"}.} 40 | 41 | \item{interaction}{Logical value. If \code{FALSE} (the default), conducts contrasts using 42 | the ART-C procedure and \code{\link{artlm.con}}. If \code{TRUE}, conducts 43 | difference-of-difference contrasts using a model returned by \code{\link{artlm}}. 44 | See the "Interaction Contrasts" section in \code{\link[emmeans]{contrast}}.} 45 | 46 | \item{adjust}{Character: adjustment method (e.g., "bonferroni") passed to 47 | \code{\link[emmeans]{contrast}}. If not provided, \code{\link[emmeans]{contrast}} will use 48 | its default ("tukey" at the time of publication). All available options are listed 49 | in \code{\link[emmeans]{summary.emmGrid}} in the "P-value adjustments" section.} 50 | 51 | \item{\dots}{Additional arguments passed to \code{\link{lm}} or 52 | \code{\link[lme4]{lmer}}.} 53 | } 54 | \value{ 55 | An object of class \code{emmGrid}. See \code{\link[emmeans]{contrast}} 56 | for details. 57 | } 58 | \description{ 59 | Conduct contrast tests following an Aligned Ranked Transform (ART) ANOVA 60 | (\code{\link{anova.art}}). Conducts contrasts on \code{\link{art}} models 61 | using aligned-and-ranked linear models using the ART (Wobbrock et al. 2011) 62 | or ART-C (Elkin et al. 2021) alignment procedure, as appropriate to the requested contrast. 63 | } 64 | \details{ 65 | An \code{\link{art}} model \code{m} stores the \code{formula} and \code{data} 66 | that were passed to \code{\link{art}} when \code{m} was created. Depending on the 67 | requested contrast type, this function either extracts the linear model from \code{m} 68 | needed to perform that contrast or creates a new linear model on data 69 | aligned-and-ranked using the ART-C 70 | procedure, then conducts the contrasts specified in parameter \code{formula}. 71 | 72 | Internally, this function uses \code{\link{artlm.con}} (when \code{interaction = FALSE}) 73 | or \code{\link{artlm}} (when \code{interaction = TRUE}) to get the linear 74 | model necessary for the requested contrast, computes estimated marginal 75 | means on the linear model using \code{\link[emmeans]{emmeans}}, and conducts contrasts 76 | using \code{\link[emmeans]{contrast}}. 77 | } 78 | \section{Formula}{ 79 | Contrasts compare combinations of levels from multiple 80 | factors. The \code{formula} parameter indicates which factors are involved. Two 81 | formats are accepted: (1) a character vector as used in 82 | \code{\link{artlm}} and \code{\link{artlm.con}}, with factors separated by \code{":"}; 83 | or (2) a formula as used in \code{\link[emmeans]{emmeans}}, with factors separated by \code{*}. 84 | For example, contrasts comparing 85 | combinations of levels of factors \emph{X1} and \emph{X2} can be expressed 86 | as \code{"X1:X2"} (character vector) or as \code{~ X1*X2} (formula). 87 | } 88 | 89 | \examples{ 90 | \donttest{ 91 | data(Higgins1990Table5, package = "ARTool") 92 | 93 | library(dplyr) 94 | 95 | ## Perform aligned rank transform 96 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 97 | 98 | ## In a some workflows, contrast tests using ART-C would follow a 99 | ## significant omnibus effect found by running an anova on the ART responses 100 | ## (equivalent to anova(m, response="art")). 101 | ## If conducting planned contrasts, this step can be skipped. 102 | anova(m) 103 | 104 | ## We can conduct contrasts comparing levels of Moisture using the ART-C procedure. 105 | ## If conducting contrasts as a post hoc test, this would follow a significant effect 106 | ## of Moisture on DryMatter. 107 | 108 | ## Using a character vector 109 | art.con(m, "Moisture") 110 | ## Or using a formula 111 | art.con(m, ~ Moisture) 112 | 113 | ## Note: Since the ART-C procedure is mathematically equivalent to the ART procedure 114 | ## in the single-factor case, this is the same as 115 | ## emmeans(artlm(m, "Moisture"), pairwise ~ Moisture) 116 | 117 | ## art.con() returns an emmGrid object, which does not print asterisks 118 | ## beside "significant" tests (p < 0.05). If you wish to add stars beside 119 | ## tests of a particular significant level, you can always do that to the 120 | ## data frame returned by the summary() method of emmGrid. For example: 121 | art.con(m, ~ Moisture) \%>\% 122 | summary() \%>\% 123 | mutate(sig = ifelse(p.value < 0.05, "*", "")) 124 | 125 | ## Or a more complex example: 126 | art.con(m, ~ Moisture) \%>\% 127 | summary() \%>\% 128 | mutate(sig = symnum(p.value, corr = FALSE, na = FALSE, 129 | cutpoints = c(0, 0.001, 0.01, 0.05, 0.1, 1), 130 | symbols = c("***", "**", "*", ".", " ") 131 | )) 132 | 133 | ## We can conduct contrasts comparing combinations of levels 134 | ## of Moisture and Fertilizer using the ART-C procedure. 135 | ## If conducting contrasts as a post hoc test, this would follow 136 | ## a significant Moisture:Fertlizer interaction effect on Drymatter. 137 | 138 | ## Using a character vector for formula 139 | art.con(m, "Moisture:Fertilizer") 140 | ## Using a formula 141 | art.con(m, ~ Moisture*Fertilizer) 142 | 143 | ## We can also conduct interaction contrasts (comparing differences of differences) 144 | art.con(m, "Moisture:Fertilizer", interaction = TRUE) 145 | 146 | ## For more examples, see vignette("art-contrasts") 147 | 148 | } 149 | } 150 | \references{ 151 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 152 | (2021). An aligned rank transform procedure for multifactor contrast tests. 153 | \emph{Proceedings of the ACM Symposium on User Interface Software and 154 | Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 155 | ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 156 | 157 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 158 | (2011). The aligned rank transform for nonparametric factorial analyses 159 | using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 160 | Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 161 | (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 162 | } 163 | \author{ 164 | Lisa A. Elkin, Matthew Kay, Jacob O. Wobbrock 165 | } 166 | -------------------------------------------------------------------------------- /man/artlm.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/artlm.R 3 | \name{artlm} 4 | \alias{artlm} 5 | \title{Per-Term Linear Model from Aligned Rank Transformed Data} 6 | \usage{ 7 | artlm( 8 | m, 9 | term, 10 | response = c("art", "aligned"), 11 | factor.contrasts = "contr.sum", 12 | ... 13 | ) 14 | } 15 | \arguments{ 16 | \item{m}{An object of class \code{\link{art}}.} 17 | 18 | \item{term}{A character vector indicating the effect term 19 | in the transformed data in \code{m} to use as the aligned or art response.} 20 | 21 | \item{response}{Which response to use: the aligned response 22 | (\code{"aligned"}) or the aligned and ranked (\code{"art"}) response.} 23 | 24 | \item{factor.contrasts}{The name of the contrast-generating function to be 25 | applied by default to fixed effect factors. Sets the the first element of 26 | \code{\link{options}("contrasts")} for the duration of this function. The 27 | default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 28 | appropriate for Type III ANOVAs (the default ANOVA type for 29 | \code{\link{anova.art}}).} 30 | 31 | \item{\dots}{Additional arguments passed to \code{\link{lm}} or 32 | \code{\link[lme4]{lmer}}.} 33 | } 34 | \value{ 35 | An object of class \code{\link{lm}} if \code{formula(m)} does not 36 | contain grouping or error terms, an object of class \code{\link[lme4]{merMod}} 37 | (i.e. a model fit by \code{\link[lme4]{lmer}}) if it contains grouping terms, or 38 | an object of class \code{aovlist} (i.e. a model fit by \code{\link{aov}}) if 39 | it contains error terms. 40 | } 41 | \description{ 42 | Build a linear model for ART data with response aligned or aligned and 43 | ranked by the specified term from the model. 44 | } 45 | \details{ 46 | This function is used primarily for post-hoc tests. To run an ANOVA, it does 47 | not need to be called directly; instead, use \code{\link{anova.art}}, which 48 | calls this function as needed. 49 | } 50 | \seealso{ 51 | See \code{\link{art}} for an example. See also 52 | \code{\link{anova.art}}, which makes use of this function. 53 | } 54 | \author{ 55 | Matthew Kay 56 | } 57 | \keyword{nonparametric} 58 | -------------------------------------------------------------------------------- /man/artlm.con.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/artlm.con.R 3 | \name{artlm.con} 4 | \alias{artlm.con} 5 | \title{Per-Term Linear Model on Data Aligned-and-Ranked with ART-C} 6 | \usage{ 7 | artlm.con(m, term, response = "art", factor.contrasts = "contr.sum", ...) 8 | } 9 | \arguments{ 10 | \item{m}{An object of class \code{\link{art}}.} 11 | 12 | \item{term}{A character vector indicating the effect term in 13 | the transformed data in \code{m} to use as the aligned or art response.} 14 | 15 | \item{response}{Which response to use: the aligned (with ART-C) response 16 | (\code{"aligned"}) or the aligned and ranked (with ART-C) response 17 | (\code{"art"}).} 18 | 19 | \item{factor.contrasts}{The name of the contrast-generating function to be 20 | applied by default to fixed effect factors. Sets the the first element of 21 | \code{\link{options}("contrasts")} for the duration of this function. The 22 | default is to use \code{"contr.sum"}, i.e. sum-to-zero contrasts, which is 23 | appropriate for Type III ANOVAs (the default ANOVA type for 24 | \code{\link{anova.art}}).} 25 | 26 | \item{\dots}{Additional arguments passed to \code{\link{lm}} or 27 | \code{\link[lme4]{lmer}}.} 28 | } 29 | \value{ 30 | An object of class \code{\link{lm}} if \code{formula(m)} does not 31 | contain grouping or error terms, an object of class \code{\link[lme4]{merMod}} 32 | (i.e. a model fit by \code{\link[lme4]{lmer}}) if it does contain grouping terms, or 33 | an object of class \code{aovlist} (i.e. a model fit by \code{\link{aov}}) 34 | if it contains error terms. 35 | } 36 | \description{ 37 | Given an \code{\link{art}} model, build a linear model from data aligned or 38 | aligned-and-ranked with ART-C alignment procedure by the specified term in 39 | the model. 40 | } 41 | \details{ 42 | This function is used internally by \code{\link{art.con}} to construct 43 | linear models for contrasts using the ART-C procedure (Elkin et al. 2021). 44 | It is typically not necessary to use this function directly to conduct contrasts using 45 | the ART-C procedure, you can use \code{\link{art.con}} instead, which will 46 | ensure that the correct model and contrast test is run. However, should you 47 | wish to use the ART-C procedure with a different contrast test 48 | than provided by \code{\link{art.con}}, you may with to use this function. 49 | 50 | Internally, the ART-C procedure concatenates the variables specified 51 | in \code{term}, and then removes the originals. When specifying the effect 52 | terms on which to conduct contrasts, use the concatenation of the effects 53 | specified in \code{term} instead of the original variables. This is demonstrated 54 | in the example below. 55 | } 56 | \examples{ 57 | \donttest{ 58 | data(Higgins1990Table5, package = "ARTool") 59 | 60 | ## create an art model 61 | m <- art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 62 | 63 | ## use emmeans to conduct pairwise contrasts on "Moisture" 64 | library(emmeans) 65 | contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method = "pairwise") 66 | 67 | ## use emmeans to conduct pairwise contrasts on "Moisture:Fertilizer" 68 | ## N.B. internally, artlm.con concatenates the factors Moisture and Fertilizer 69 | ## to create MoistureFertilizer. If you try to use any of Moisture, Fertilizer, 70 | ## Moisture:Fertilizer, or Moisture*Fertilizer in the RHS of the formula 71 | ## passed to emmeans, you will get an error because the factors Moisture and Fertilizer 72 | ## do not exist in the model returned by artlm.con. 73 | contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method = "pairwise") 74 | 75 | ## Note: art.con uses emmeans internally, and the above examples are equivalent to 76 | ## the following calls to art.con, which is the recommended approach as it will 77 | ## ensure the model selected and the contrasts extracted from emmeans match. 78 | art.con(m, "Moisture") 79 | art.con(m, "Moisture:Fertilizer") 80 | 81 | } 82 | 83 | } 84 | \references{ 85 | Elkin, L. A., Kay, M, Higgins, J. J., and Wobbrock, J. O. 86 | (2021). An aligned rank transform procedure for multifactor contrast tests. 87 | \emph{Proceedings of the ACM Symposium on User Interface Software and 88 | Technology (UIST '21)}. Virtual Event (October 10--14, 2021). New York: 89 | ACM Press, pp. 754--768. \doi{10.1145/3472749.3474784} 90 | } 91 | \seealso{ 92 | See also \code{\link{art.con}}, which makes use of this function. 93 | } 94 | \author{ 95 | Lisa A. Elkin 96 | } 97 | -------------------------------------------------------------------------------- /man/summary.art.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/art.R 3 | \name{summary.art} 4 | \alias{summary.art} 5 | \title{Aligned Rank Transform Summary} 6 | \usage{ 7 | \method{summary}{art}(object, ...) 8 | } 9 | \arguments{ 10 | \item{object}{An object of class \code{\link{art}}.} 11 | 12 | \item{\dots}{Potentially further arguments passed from other methods.} 13 | } 14 | \value{ 15 | An object of class \code{"summary.art"}, which usually is printed. 16 | } 17 | \description{ 18 | Summary and diagnostics for aligned rank transformed data 19 | } 20 | \details{ 21 | This function gives diagnostic output to help evaluate whether the ART 22 | procedure is appropriate for an analysis. It tests that column sums of 23 | aligned responses are ~0 and that F values of ANOVAs on aligned responses 24 | not of interest are ~0. For more details on these diagnostics see Wobbrock 25 | \emph{et al.} (2011). 26 | } 27 | \references{ 28 | Wobbrock, J. O., Findlater, L., Gergle, D., and Higgins, J. J. 29 | (2011). The aligned rank transform for nonparametric factorial analyses 30 | using only ANOVA procedures. \emph{Proceedings of the ACM Conference on 31 | Human Factors in Computing Systems (CHI '11)}. Vancouver, British Columbia 32 | (May 7--12, 2011). New York: ACM Press, pp. 143--146. \doi{10.1145/1978942.1978963} 33 | } 34 | \seealso{ 35 | See \code{\link{art}} for an example. See also 36 | \code{\link{anova.art}}. 37 | } 38 | \author{ 39 | Matthew Kay 40 | } 41 | \keyword{nonparametric} 42 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(ARTool) 3 | 4 | test_check("ARTool") 5 | -------------------------------------------------------------------------------- /tests/testthat/test.anova.art.R: -------------------------------------------------------------------------------- 1 | # Tests for anova.art 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | library(testthat) 7 | library(ARTool) 8 | 9 | context("anova.art") 10 | 11 | 12 | comparable.anova = function(m, include.error=FALSE) { 13 | #get an anova from a model in a form that is easy to compare 14 | #(known column subset and rounded numeric results) 15 | a = suppressWarnings(as.data.frame(anova(m))) 16 | #extract character cols and convert to factors for consistency in comparison 17 | character_cols = lapply(a[,c("Term", if (include.error) "Error"), drop=FALSE], factor) 18 | #some anovas use "F value" instead of just "F", standardize 19 | if (!is.null(a$`F value`)) a$F = a$`F value` 20 | #extract numeric cols and round for consistency 21 | numeric_cols = round(a[,c("Df","Df.res","F","Pr(>F)")], digits=4) 22 | #stick it all back together and standardize the rownames 23 | a = cbind(character_cols, numeric_cols) 24 | rownames(a) = 1:nrow(a) 25 | a 26 | } 27 | 28 | test_that("anova.art of Higgins1990Table5 matches results of the original ARTool", { 29 | data(Higgins1990Table5, package = "ARTool") 30 | 31 | #reference result 32 | ref = data.frame( 33 | Term = factor(c("Moisture", "Fertilizer", "Moisture:Fertilizer")), 34 | Error = factor(c("Tray", "Within", "Within")), 35 | Df = c(3, 3, 9), 36 | Df.res = c(8, 24, 24), 37 | "F" = c(23.8326, 122.4017, 5.1180), 38 | "Pr(>F)" = c(0.0002, 0, 0.0006), 39 | check.names = FALSE 40 | ) 41 | 42 | #run art using repeated measures anova 43 | m = art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data=Higgins1990Table5) 44 | a = comparable.anova(m, include.error=TRUE) 45 | expect_equal(a, ref, tolerance = 0.0001) 46 | 47 | #run art using mixed effects model 48 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 49 | a = comparable.anova(m) 50 | expect_equal(a, ref[-2], tolerance = 0.0001) #no Error col 51 | }) 52 | 53 | test_that("anova.art of Higgins1990Table1 matches results of the original ARTool", { 54 | data(Higgins1990Table1, package = "ARTool") 55 | 56 | #reference result 57 | ref = data.frame( 58 | Term = factor(c("Row", "Column", "Row:Column")), 59 | Df = c(2, 2, 4), 60 | Df.res = c(27, 27, 27), 61 | "F" = c(29.9925, 77.8668, 0.6417), 62 | "Pr(>F)" = c(0, 0, 0.6374), 63 | check.names = FALSE 64 | ) 65 | 66 | #run art using linear model 67 | m = art(Response ~ Row*Column, data=Higgins1990Table1) 68 | a = comparable.anova(m) 69 | expect_equal(a, ref) 70 | }) 71 | 72 | test_that("anova.art of HigginsABC matches results of the original ARTool", { 73 | data(HigginsABC, package = "ARTool") 74 | 75 | #reference result 76 | ref = data.frame( 77 | Term = factor(c("A", "B", "C", "A:B", "A:C", "B:C", "A:B:C")), 78 | Error = factor(c("Subject", "Subject", "Within", "Subject", "Within", "Within", "Within")), 79 | Df = c(1, 1, 1, 1, 1, 1, 1), 80 | Df.res = c(4, 4, 4, 4, 4, 4, 4), 81 | F = c(120.4706, 120.4706, 14.3217, 81.92, 0.1259, 0.2319, 0.9715), 82 | "Pr(>F)" = c(4e-04, 4e-04, 0.0194, 8e-04, 0.7407, 0.6553, 0.3801), 83 | check.names = FALSE 84 | ) 85 | 86 | #run art using linear model 87 | m = art(Y ~ A*B*C + Error(Subject), data=HigginsABC) 88 | a = comparable.anova(m, include.error=TRUE) 89 | expect_equal(a, ref) 90 | }) 91 | -------------------------------------------------------------------------------- /tests/testthat/test.art.con.R: -------------------------------------------------------------------------------- 1 | # Tests for art.con 2 | # 3 | # Author: lelkin 4 | ############################################################################### 5 | 6 | library(testthat) 7 | library(ARTool) 8 | 9 | context("art.con") 10 | 11 | # artlm.con tests if artlm.con and art.con match and if artlm.con and artlm are the 12 | # same in the single-factor case (which of course by transitivity tests if art.con and artlm 13 | # are the same in the single-factor case) 14 | 15 | # Note: adjust method doesn't matter since t ratios don't get adjusted 16 | 17 | test_that("art.con returns same result as ARTool.exe + JMP: no grouping term, two-factor model", { 18 | data(Higgins1990Table5, package = "ARTool") 19 | 20 | # run art without grouping term to use lm 21 | m = art(DryMatter ~ Moisture*Fertilizer, data = Higgins1990Table5) 22 | 23 | # single-factor contrasts 24 | expect_equal( 25 | summary(art.con(m, "Moisture"))$t, 26 | c(-7.400493839,-10.82021662,-4.862418335,-3.419722785,2.5380755044,5.9577982894), 27 | tolerance = 0.0001 28 | ) 29 | 30 | # two-factor contrasts 31 | # compare "m1,f1 - m1,f2", "m1,f1 - m2,f1", and "m1,f1, m2,f2" to results from JMP. 32 | expect_equal( 33 | (summary(art.con(m, "Moisture:Fertilizer")) %>% filter(contrast %in% c("m1,f1 - m1,f2", "m1,f1 - m2,f1", "m1,f1 - m2,f2")) %>% select("t.ratio"))[[1]], 34 | c(-0.7886463535, -3.0362884611, -5.0079043449), 35 | tolerance = 0.0001 36 | ) 37 | }) 38 | 39 | test_that("art.con returns same result as ARTool.exe + JMP: no grouping term, three-factor model", { 40 | data(ElkinABC, package = "ARTool") 41 | 42 | m = art(Y ~ A*B*C, data = ElkinABC) 43 | 44 | # single-factor contrasts 45 | expect_equal( 46 | summary(art.con(m, "A"))$t, 47 | c(-5.772), 48 | tolerance = 0.0001 49 | ) 50 | 51 | # two-factor contrasts 52 | # compare "A1,B1 - A1,B2", "A1,B1 - A2,B1", and "A1,B1, A2,B2" to results from JMP. 53 | expect_equal( 54 | (summary(art.con(m, "A:B")) %>% filter(contrast %in% c("A1,B1 - A1,B2", "A1,B1 - A2,B1", "A1,B1 - A2,B2")) %>% select("t.ratio"))[[1]], 55 | c(-0.022614705, -4.6247071, -3.177366003), 56 | tolerance = 0.0001 57 | ) 58 | 59 | # three-factor contrasts 60 | # compare "A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", and "A1,B1,C1 - A2,B2,C2" to results from JMP. 61 | expect_equal( 62 | (summary(art.con(m, "A:B:C")) %>% filter(contrast %in% c("A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", "A1,B1,C1 - A2,B2,C2")) %>% select("t.ratio"))[[1]], 63 | c(-2.060574615, -2.060574615, -2.007739368), 64 | tolerance = 0.0001 65 | ) 66 | }) 67 | 68 | test_that("art.con returns same result as ARTool.exe + JMP: with grouping term, two-factor model", { 69 | data(Higgins1990Table5, package = "ARTool") 70 | 71 | #run with grouping term to force lmer 72 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 73 | 74 | # single-factor contrasts 75 | expect_equal( 76 | summary(art.con(m, "Moisture"))$t, 77 | c(-5.606839446, -8.197725544, -3.68391617, -2.590886098, 1.9229232756, 4.5138093733), 78 | tolerance = 0.0001 79 | ) 80 | 81 | # two-factor contrasts 82 | # compare "m1,f1 - m1,f2", "m1,f1 - m2,f1", and "m1,f1, m2,f2" to results from JMP. 83 | expect_equal( 84 | (summary(art.con(m, "Moisture:Fertilizer")) %>% filter(contrast %in% c("m1,f1 - m1,f2", "m1,f1 - m2,f1", "m1,f1 - m2,f2")) %>% select("t.ratio"))[[1]], 85 | c(-1.107980814, -3.036288461, -5.007904345), 86 | tolerance = 0.0001 87 | ) 88 | }) 89 | 90 | test_that("art.con returns same result as ARTool.exe + JMP: with grouping term, three-factor model", { 91 | data(ElkinABC, package = "ARTool") 92 | 93 | m = art(Y ~ A*B*C + (1|S), data = ElkinABC) 94 | 95 | # single-factor contrasts 96 | expect_equal( 97 | summary(art.con(m, "A"))$t, 98 | c(-16.98), 99 | tolerance = 0.001 100 | ) 101 | 102 | # two-factor contrasts 103 | # compare "A1,B1 - A1,B2", "A1,B1 - A2,B1", and "A1,B1, A2,B2" to results from JMP. 104 | expect_equal( 105 | (summary(art.con(m, "A:B")) %>% filter(contrast %in% c("A1,B1 - A1,B2", "A1,B1 - A2,B1", "A1,B1 - A2,B2")) %>% select("t.ratio"))[[1]], 106 | c(-0.072115146, -14.74754746, -10.13217808), 107 | tolerance = 0.0001 108 | ) 109 | 110 | # three-factor contrasts 111 | # compare "A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", and "A1,B1,C1 - A2,B2,C2" to results from JMP. 112 | expect_equal( 113 | (summary(art.con(m, "A:B:C")) %>% filter(contrast %in% c("A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", "A1,B1,C1 - A2,B2,C2")) %>% select("t.ratio"))[[1]], 114 | c(-8.041788515, -8.041788515, -7.835588809), 115 | tolerance = 0.0001 116 | ) 117 | }) 118 | 119 | test_that("art.con returns same result as ARTool.exe + JMP: with error term, two-factor model", { 120 | data(Higgins1990Table5, package = "ARTool") 121 | 122 | #run art with Error term to force aov 123 | m = art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data = Higgins1990Table5) 124 | 125 | # single-factor contrasts 126 | expect_equal( 127 | summary(art.con(m, "Moisture"))$t, 128 | c(-5.606839446, -8.197725544, -3.68391617, -2.590886098, 1.9229232756, 4.5138093733), 129 | tolerance = 0.0001 130 | ) 131 | 132 | # two-factor contrasts 133 | # can't use Higgins Table 5 because it's unbalanced with respect to Moisture:Fertilizer 134 | data(ElkinAB, package = "ARTool") 135 | m = art(Y ~ A*B + Error(S), data = ElkinAB) 136 | 137 | # compare "A1,B1 - A1,B2", "A1,B1 - A2,B1", and "A1,B1, A2,B2" to results from JMP. 138 | expect_equal( 139 | (summary(art.con(m, "A:B")) %>% filter(contrast %in% c("A1,B1 - A1,B2", "A1,B1 - A2,B1", "A1,B1 - A2,B2")) %>% select("t.ratio"))[[1]], 140 | c(-0.487275267, -16.40493399, -20.7904114), 141 | tolerance = 0.0001 142 | ) 143 | 144 | }) 145 | 146 | test_that("art.con returns same result as ARTool.exe + JMP: with error term, two-factor model", { 147 | data(ElkinABC, package = "ARTool") 148 | 149 | m = art(Y ~ A*B*C + Error(S), data = ElkinABC) 150 | 151 | # single-factor contrasts 152 | expect_equal( 153 | summary(art.con(m, "A"))$t, 154 | c(-16.98), 155 | tolerance = 0.001 156 | ) 157 | 158 | # two-factor contrasts 159 | # compare "A1,B1 - A1,B2", "A1,B1 - A2,B1", and "A1,B1, A2,B2" to results from JMP. 160 | expect_equal( 161 | (summary(art.con(m, "A:B")) %>% filter(contrast %in% c("A1,B1 - A1,B2", "A1,B1 - A2,B1", "A1,B1 - A2,B2")) %>% select("t.ratio"))[[1]], 162 | c(-0.072115146, -14.74754746, -10.13217808), 163 | tolerance = 0.0001 164 | ) 165 | 166 | # three-factor contrasts 167 | # compare "A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", and "A1,B1,C1 - A2,B2,C2" to results from JMP. 168 | expect_equal( 169 | (summary(art.con(m, "A:B:C")) %>% filter(contrast %in% c("A1,B1,C1 - A1,B1,C2", "A1,B1,C1 - A1,B2,C2", "A1,B1,C1 - A2,B2,C2")) %>% select("t.ratio"))[[1]], 170 | c(-8.041788515, -8.041788515, -7.835588809), 171 | tolerance = 0.0001 172 | ) 173 | }) 174 | 175 | test_that("art.con character format and formula format are equivalent", { 176 | data(Higgins1990Table5, package = "ARTool") 177 | 178 | #run with grouping term to force lmer 179 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 180 | 181 | # single-factor contrasts 182 | expect_equal( 183 | summary(art.con(m, "Moisture")), 184 | summary(art.con(m, ~Moisture)) 185 | ) 186 | 187 | # two-factor contrasts 188 | # compare "m1,f1 - m1,f2", "m1,f1 - m2,f1", and "m1,f1, m2,f2" to results from JMP. 189 | expect_equal( 190 | summary(art.con(m, "Moisture:Fertilizer")), 191 | summary(art.con(m, ~Moisture*Fertilizer)) 192 | ) 193 | }) 194 | 195 | test_that("art.con interaction contrasts equivalent to artlm interaction contrasts",{ 196 | data(Higgins1990Table5, package = "ARTool") 197 | 198 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 199 | 200 | expect_equal( 201 | summary(art.con(m, "Moisture:Fertilizer", interaction=TRUE)), 202 | summary(contrast(emmeans(artlm(m, "Moisture:Fertilizer"), ~ Moisture:Fertilizer), method="pairwise", interaction=TRUE)) 203 | ) 204 | 205 | data(ElkinABC, package = "ARTool") 206 | 207 | m = art(Y ~ A*B*C, data = ElkinABC) 208 | expect_equal( 209 | summary(art.con(m, "A:B:C", interaction=TRUE)), 210 | summary(contrast(emmeans(artlm(m, "A:B:C"), ~ A:B:C), method="pairwise", interaction=TRUE)) 211 | ) 212 | }) 213 | 214 | test_that("throws error if vector of strings used",{ 215 | data(Higgins1990Table5, package = "ARTool") 216 | 217 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 218 | expect_error(art.con(m, c("Moisture:Fertilizer", "Moisture"))) 219 | }) 220 | 221 | test_that("throws error if contrast string term has a space in it",{ 222 | data(Higgins1990Table5, package = "ARTool") 223 | 224 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 225 | expect_error(art.con(m, "Moisture: Fertilizer")) 226 | expect_error(art.con(m, "Moisture :Fertilizer")) 227 | expect_error(art.con(m, " Moisture:Fertilizer")) 228 | }) 229 | 230 | test_that("throws error if there is a grouping or error term in string contrast formula",{ 231 | data(Higgins1990Table5, package = "ARTool") 232 | 233 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 234 | expect_error(art.con(m, "Moisture:Fertilizer+(1|Tray)")) 235 | expect_error(art.con(m, ~ Moisture*Fertilizer + (1|Tray))) 236 | 237 | m = art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data = Higgins1990Table5) 238 | expect_error(art.con(m, "Moisture:Fertilizer+Error(Tray)")) 239 | }) 240 | 241 | test_that("throws error if try to use : in formula version (i.e., not with string version)",{ 242 | data(Higgins1990Table5, package = "ARTool") 243 | 244 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 245 | expect_error(art.con(m, ~ Moisture:Fertilizer)) 246 | }) 247 | 248 | test_that("throws error if dependent variables in contrast formula",{ 249 | data(Higgins1990Table5, package = "ARTool") 250 | 251 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = Higgins1990Table5) 252 | expect_error(art.con(m, ~ DryMatter)) 253 | expect_error(art.con(m, DryMatter ~Moisture*Fertilizer)) 254 | }) 255 | 256 | test_that("throws error if model is not an ART model",{ 257 | data(Higgins1990Table5, package = "ARTool") 258 | 259 | m = aov(DryMatter ~ Moisture*Fertilizer, data = Higgins1990Table5) 260 | expect_error(art.con(m, ~ Moisture*Fertilizer)) 261 | }) 262 | -------------------------------------------------------------------------------- /tests/testthat/test.artlm.R: -------------------------------------------------------------------------------- 1 | # Tests for artlm 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | context("artlm") 7 | 8 | test_that("artlm returns models whose data can be recovered by emmeans", { 9 | skip_if_not_installed("emmeans") 10 | 11 | data(Higgins1990Table5, package = "ARTool") 12 | 13 | 14 | #run art without grouping term to use lm 15 | m = art(DryMatter ~ Moisture*Fertilizer, data=Higgins1990Table5) 16 | 17 | #should be able to run emmeans on the linear model 18 | #this will only work if emmeans can recover the data using 19 | #emmeans:::recover.data, which will only work if artlm 20 | #correctly sets the environment of the returned model 21 | expect_equal( 22 | summary(pairs(emmeans::emmeans(artlm(m, "Moisture"), "Moisture")))$t, 23 | c(-7.40049, -10.82022, -4.86242, -3.41972, 2.53808, 5.95780), 24 | tolerance = 0.0001 25 | ) 26 | 27 | 28 | #run art with grouping term to force lmer 29 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 30 | 31 | #will only work if emmeans can recover data (see comment above) 32 | expect_equal( 33 | summary(pairs(emmeans::emmeans(artlm(m, "Moisture"), "Moisture")))$t, 34 | c(-5.60684, -8.19773, -3.68392, -2.59089, 1.92292, 4.51381), 35 | tolerance = 0.0001 36 | ) 37 | 38 | 39 | #run art with Error term to force aov 40 | m = art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data=Higgins1990Table5) 41 | 42 | #will only work if emmeans can recover data (see comment above) 43 | expect_equal( 44 | summary(pairs(emmeans::emmeans(artlm(m, "Moisture"), "Moisture")))$t, 45 | c(-5.60684, -8.19773, -3.68392, -2.59089, 1.92292, 4.51381), 46 | tolerance = 0.0001 47 | ) 48 | }) 49 | -------------------------------------------------------------------------------- /tests/testthat/test.artlm.con.R: -------------------------------------------------------------------------------- 1 | # Tests for artlm.con 2 | # 3 | # Author: lelkin 4 | ############################################################################### 5 | 6 | library(testthat) 7 | library(ARTool) 8 | 9 | context("artlm.con") 10 | 11 | test_that("artlm.con matches with art.con",{ 12 | data(Higgins1990Table5, package = "ARTool") 13 | 14 | # run art without grouping term to use lm 15 | m = art(DryMatter ~ Moisture*Fertilizer, data=Higgins1990Table5) 16 | 17 | expect_equal( 18 | summary(art.con(m, "Moisture")), 19 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")) 20 | ) 21 | 22 | expect_equal( 23 | summary(art.con(m, "Moisture:Fertilizer")), 24 | summary(contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method="pairwise")) 25 | ) 26 | 27 | # grouping term to force lmer 28 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 29 | 30 | expect_equal( 31 | summary(art.con(m, "Moisture")), 32 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")) 33 | ) 34 | 35 | expect_equal( 36 | summary(art.con(m, "Moisture:Fertilizer")), 37 | summary(contrast(emmeans(artlm.con(m, "Moisture:Fertilizer"), ~ MoistureFertilizer), method="pairwise")) 38 | ) 39 | 40 | # should still work even if input data frame is a tibble 41 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data = tibble::as_tibble(Higgins1990Table5)) 42 | 43 | expect_equal( 44 | summary(art.con(m, "Moisture")), 45 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")) 46 | ) 47 | 48 | # error term 49 | # can't use Higgins Table 5 because it's unbalanced with respect to Moisture:Fertilizer 50 | data(ElkinAB, package = "ARTool") 51 | m = art(Y ~ A*B + Error(S), data=ElkinAB) 52 | 53 | expect_equal( 54 | summary(art.con(m, "A")), 55 | summary(contrast(emmeans(artlm.con(m, "A"), ~ A), method="pairwise")) 56 | ) 57 | 58 | expect_equal( 59 | summary(art.con(m, "A:B")), 60 | summary(contrast(emmeans(artlm.con(m, "A:B"), ~ AB), method="pairwise")) 61 | ) 62 | }) 63 | 64 | test_that("artlm.con matches with artlm in single-factor case",{ 65 | 66 | # run art without grouping term to use lm 67 | m = art(DryMatter ~ Moisture*Fertilizer, data=Higgins1990Table5) 68 | 69 | expect_equal( 70 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")), 71 | summary(contrast(emmeans(artlm(m, "Moisture"), ~ Moisture), method="pairwise")) 72 | ) 73 | 74 | # run art with grouping term to use lmer 75 | m = art(DryMatter ~ Moisture*Fertilizer + (1|Tray), data=Higgins1990Table5) 76 | 77 | expect_equal( 78 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")), 79 | summary(contrast(emmeans(artlm(m, "Moisture"), ~ Moisture), method="pairwise")) 80 | ) 81 | 82 | # run art with error term to force aov 83 | m = art(DryMatter ~ Moisture*Fertilizer + Error(Tray), data=Higgins1990Table5) 84 | 85 | expect_equal( 86 | summary(contrast(emmeans(artlm.con(m, "Moisture"), ~ Moisture), method="pairwise")), 87 | summary(contrast(emmeans(artlm(m, "Moisture"), ~ Moisture), method="pairwise")) 88 | ) 89 | 90 | }) 91 | -------------------------------------------------------------------------------- /tests/testthat/test.parse.art.formula.R: -------------------------------------------------------------------------------- 1 | # Tests for parse.art.formula 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | library(testthat) 7 | library(ARTool) 8 | 9 | context("art formula parsing") 10 | 11 | test_that("art formula must have 1 response variable", { 12 | expect_error(ARTool:::parse.art.formula(~ a*b*c), 'Model must have exactly one dependent variable \\(got 0\\)') 13 | expect_error(ARTool:::parse.art.formula(~ 0), 'Model must have exactly one dependent variable \\(got 0\\)') 14 | expect_error(ARTool:::parse.art.formula(~ (1|c)), 'Model must have exactly one dependent variable \\(got 0\\)') 15 | expect_error(ARTool:::parse.art.formula(~ Error(c)), 'Model must have exactly one dependent variable \\(got 0\\)') 16 | }) 17 | 18 | test_that("art formula must have an intercept", { 19 | expect_error(ARTool:::parse.art.formula(y ~ a*b*c + 0), 'Model must have an intercept \\(got 0\\)') 20 | expect_error(ARTool:::parse.art.formula(y ~ a*b*c - 1), 'Model must have an intercept \\(got 0\\)') 21 | expect_error(ARTool:::parse.art.formula(y ~ 0), 'Model must have an intercept \\(got 0\\)') 22 | expect_error(ARTool:::parse.art.formula(y ~ 0 + (1|c)), 'Model must have an intercept \\(got 0\\)') 23 | expect_error(ARTool:::parse.art.formula(y ~ 0 + Error(c)), 'Model must have an intercept \\(got 0\\)') 24 | }) 25 | 26 | test_that("art formula must have at least 1 fixed effect", { 27 | expect_error(ARTool:::parse.art.formula(y ~ 1), 'Model must have at least one fixed effect \\(0 given\\)') 28 | expect_error(ARTool:::parse.art.formula(y ~ 1 + (1|c)), 'Model must have at least one fixed effect \\(0 given\\)') 29 | expect_error(ARTool:::parse.art.formula(y ~ 1 + Error(c)), 'Model must have at least one fixed effect \\(0 given\\)') 30 | }) 31 | 32 | test_that("art formula must have all interactions of fixed effects", { 33 | expect_error(ARTool:::parse.art.formula(y ~ a + b), 'Model must include all combinations of interactions of fixed effects.') 34 | expect_error(ARTool:::parse.art.formula(y ~ a + b), 'Model must include all combinations of interactions of fixed effects.') 35 | expect_error(ARTool:::parse.art.formula(y ~ a + b + c:d), 'Model must include all combinations of interactions of fixed effects.') 36 | expect_error(ARTool:::parse.art.formula(y ~ a * b + c:d), 'Model must include all combinations of interactions of fixed effects.') 37 | expect_error(ARTool:::parse.art.formula(y ~ a * b + c), 'Model must include all combinations of interactions of fixed effects.') 38 | expect_error(ARTool:::parse.art.formula(y ~ a + b + (1|c)), 'Model must include all combinations of interactions of fixed effects.') 39 | expect_error(ARTool:::parse.art.formula(y ~ a + b + Error(c)), 'Model must include all combinations of interactions of fixed effects.') 40 | }) 41 | 42 | test_that("different variations on factorial model specifications are accepted by art", { 43 | expect_equal(ARTool:::parse.art.formula(y ~ a)$fixed.only, y ~ a) 44 | expect_equal(ARTool:::parse.art.formula(y ~ a)$fixed.terms, ~ a) 45 | expect_equal(ARTool:::parse.art.formula(y ~ a + (1|c))$fixed.only, y ~ a) 46 | expect_equal(ARTool:::parse.art.formula(y ~ a + (1|c))$fixed.terms, ~ a) 47 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(c))$fixed.only, y ~ a) 48 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(c))$fixed.terms, ~ a) 49 | expect_equal(ARTool:::parse.art.formula(y ~ a * b)$fixed.only, y ~ a * b) 50 | expect_equal(ARTool:::parse.art.formula(y ~ a * b)$fixed.terms, ~ a + b) 51 | expect_equal(ARTool:::parse.art.formula(y ~ a * b + (1|c))$fixed.only, y ~ a * b) 52 | expect_equal(ARTool:::parse.art.formula(y ~ a * b + (1|c))$fixed.terms, ~ a + b) 53 | expect_equal(ARTool:::parse.art.formula(y ~ a * b + Error(c))$fixed.only, y ~ a * b) 54 | expect_equal(ARTool:::parse.art.formula(y ~ a * b + Error(c))$fixed.terms, ~ a + b) 55 | expect_equal(ARTool:::parse.art.formula(y ~ a:b + a + b)$fixed.only, y ~ a*b) 56 | expect_equal(ARTool:::parse.art.formula(y ~ a:b + a + b)$fixed.terms, ~ a + b) 57 | expect_equal(ARTool:::parse.art.formula(y ~ a:b + a + b + (1|c))$fixed.only, y ~ a*b) 58 | expect_equal(ARTool:::parse.art.formula(y ~ a:b + a + b + (1|c))$fixed.terms, ~ a + b) 59 | expect_equal(ARTool:::parse.art.formula(y ~ a*b*c)$fixed.terms, ~ a + b + c) 60 | expect_equal(ARTool:::parse.art.formula(y ~ a*b*c*d)$fixed.terms, ~ a + b + c + d) 61 | }) 62 | 63 | test_that("grouping terms and error terms are counted correctly", { 64 | expect_equal(ARTool:::parse.art.formula(y ~ a + (1|d))$n.grouping.terms, 1) 65 | expect_equal(ARTool:::parse.art.formula(y ~ a + (1|d) + (1|g))$n.grouping.terms, 2) 66 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(d))$n.error.terms, 1) 67 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(d) + Error(g))$n.error.terms, 2) 68 | }) 69 | 70 | test_that("grouping terms and error terms are counted correctly", { 71 | expect_error(ARTool:::parse.art.formula(y ~ a + (1|d) + Error(g)), "Model cannot contain both grouping terms, like (1|d), and error terms, like Error(d). Use one or the other.", fixed=TRUE) 72 | }) 73 | 74 | test_that("formulas with expressions for terms are parsed correctly", { 75 | expect_equal(ARTool:::parse.art.formula(y ~ factor(a) + b + Error(g) + factor(a):b)$fixed.only, y ~ factor(a) * b) 76 | expect_equal(ARTool:::parse.art.formula(y ~ factor(a) + b + Error(g) + factor(a):b)$fixed.terms, ~ factor(a) + b) 77 | expect_equal(ARTool:::parse.art.formula(y ~ factor(a) + b + Error(g) + factor(a):b)$fixed.term.labels, c("factor(a)", "b", "factor(a):b")) 78 | }) 79 | 80 | test_that("formulas with expressions for responses are parsed correctly", { 81 | expect_equal(ARTool:::parse.art.formula(as.numeric(y) ~ a)$fixed.only, as.numeric(y) ~ a) 82 | }) 83 | 84 | test_that("formulas with error terms are parsed correctly", { 85 | expect_equal(ARTool:::parse.art.formula(y ~ a)$error.terms, ~ NULL) 86 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(g))$error.terms, ~ g) 87 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(g*h))$error.terms, ~ g*h) 88 | expect_equal(ARTool:::parse.art.formula(y ~ a + Error(g*h) + Error(i))$error.terms, ~ g * h + i) 89 | }) 90 | 91 | test_that("formulas generated from parsing have their environments set to that of the original formula", { 92 | f = y ~ a 93 | expect_equal(environment(ARTool:::parse.art.formula(f)$fixed.only), environment(f)) 94 | expect_equal(environment(ARTool:::parse.art.formula(f)$fixed.terms), environment(f)) 95 | expect_equal(environment(ARTool:::parse.art.formula(f)$error.terms), environment(f)) 96 | }) 97 | -------------------------------------------------------------------------------- /tests/testthat/test.summary.art.R: -------------------------------------------------------------------------------- 1 | # Tests for summary.art 2 | # 3 | # Author: mjskay 4 | ############################################################################### 5 | 6 | library(testthat) 7 | library(ARTool) 8 | 9 | context("summary.art") 10 | 11 | test_that("summary(art) gives a warning on models with approx nonzero F on aligned responses not of interest", { 12 | df = data.frame(y=1:20, a=factor(rep(c(1,2),10))) 13 | df$c = df$a 14 | df$c[[2]] = "1" 15 | df$c[[3]] = "2" 16 | m = art(y ~ a*c, data=df) 17 | 18 | expect_warning(summary(m), "F values of ANOVAs on aligned responses not of interest are not all ~0. ART may not be appropriate.") 19 | }) 20 | -------------------------------------------------------------------------------- /vignettes/art-contrasts.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Contrast tests with ART" 3 | author: "Matthew Kay, Lisa A. Elkin, Jacob O. Wobbrock" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Interaction Contrasts with ART} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ## Introduction 13 | 14 | The aligned-rank transform (ART) allows for non-parametric analyses of variance ([Wobbrock et al. 2011](https://doi.org/10.1145/1978942.1978963)). 15 | But how should we do contrast tests with ART? 16 | 17 | Contrasts involving levels of single factors, combinations of factors, or differences of 18 | differences across two factors can be performed by conducting those contrasts on a 19 | linear model aligned-and-ranked on the factors involved in the contrasts. 20 | This linear model may be one of the models used in the ART procedure, or it may require 21 | concatenating factors and constructing a new model, a procedure called *ART contrasts* 22 | or *ART-C* ([Elkin et al. 2021](https://dx.doi.org/10.1145/3472749.3474784)). 23 | 24 | The `art.con()` function selects the appropriate model given a desired set of contrasts 25 | and then performs the requested contrasts. This page explains when and why a separate 26 | aligning-and-ranking procedure is needed to conduct contrasts and demonstrates how to 27 | conduct those contrasts using the `art.con()` function within the ART paradigm. 28 | 29 | If you are not sure when/how to select the appropriate aligned-and-ranked linear 30 | model for a given contrast (i.e. when to use ART versus ART-C), the `art.con()` function 31 | demonstrated in this vignette will select the appropriate method given a contrast specification. 32 | 33 | ## Contents 34 | 35 | 1. [Test Dataset](#test-dataset): Description of the test data we will use to compare a linear model against ART 36 | 2. [Contrast tests of main effects](#contrast-tests-of-main-effects): Demo of conducting contrasts within a single factor (no interaction) 37 | 3. [Tests of differences in pairwise combinations of levels between factors in interactions](#tests-of-differences-in-pairwise-combinations-of-levels-between-factors-in-interactions): 38 | This type of interaction contrast must be conducted with ART-C 39 | 4. [Tests of differences of differences in interactions](#tests-of-differences-of-differences-in-interactions): This type of interaction contrast can be conducted with ART 40 | 41 | ## Libraries needed for this vignette 42 | 43 | ```{r setup, include=FALSE} 44 | knitr::opts_chunk$set( #default code chunk options 45 | fig.width = 6, 46 | fig.height = 4 47 | ) 48 | pander::panderOptions("table.split.table", Inf) #don't split wide tables in output 49 | pander::panderOptions("table.style", "rmarkdown") #table style that's supported by github 50 | ``` 51 | 52 | ```{r message=FALSE, warning=FALSE} 53 | library(dplyr) #data_frame, %>%, filter, summarise, group_by 54 | library(emmeans) #emmeans, contrast 55 | library(phia) #testInteractions 56 | library(tidyr) #spread 57 | library(ARTool) #art, artlm, artlm.con, art.con 58 | library(ggplot2) #ggplot, stat_..., geom_..., etc 59 | ``` 60 | 61 | 62 | ## Test dataset 63 | 64 | Let's generate some test data where we actually know what the effects are. Specifically, 65 | 66 | 67 | ```{r} 68 | n_per_group = 150 69 | df = tibble( 70 | X1 = factor(c(rep("A", n_per_group), rep("B", n_per_group))), 71 | X2 = factor(rep(c("C","D","E"), n_per_group * 2/3)), 72 | Y = rnorm( 73 | n_per_group * 2, 74 | (X1 == "B") 75 | + 2* (X2 == "D") 76 | + 2 * (X1 == "B" & X2 == "D") 77 | - 2 * (X1 == "A" & X2 == "D") 78 | + 2 * (X2 == "E") 79 | ) 80 | ) 81 | ``` 82 | 83 | This is normally-distributed error with the same variance at all levels, so we can compare the results of ART and ART-C to a linear model, 84 | which will correctly estimate the effects. 85 | 86 | I pre-ran the above code and saved it as `InteractionTestData` so that the text here is consistent: 87 | 88 | ```{r} 89 | data(InteractionTestData, package = "ARTool") 90 | df = InteractionTestData #save some typing 91 | ``` 92 | 93 | The "true" means from the model look like this: 94 | 95 | | X1 | X2 | Mean | 96 | |:--:|:--------:|:----:| 97 | | A | C or D | 0 | 98 | | A | E | 2 | 99 | | B | C | 1 | 100 | | B | D | 5 | 101 | | B | E | 3 | 102 | 103 | Which we can see pretty well: 104 | 105 | ```{r interaction_plot, fig.cap=""} 106 | # variant of the Dark2 colorbrewer scale with specific name mappings (so 107 | # we can keep color -> name mapping consistent throughout this document) 108 | palette = c("#1b9e77", "#d95f02", "#7570b3") 109 | names(palette) = c("C", "D", "E") 110 | 111 | df %>% 112 | ggplot(aes(x = X1, y = Y, color = X2)) + 113 | geom_violin(trim = FALSE, adjust = 1.5) + 114 | geom_point(pch = "-", size = 4) + 115 | stat_summary(fun = mean, geom = "point", size = 4) + 116 | stat_summary(aes(group = X2), fun = mean, geom = "line", size = 1) + 117 | stat_summary(aes(x = 1.5, group = NA), fun = mean, geom = "point", size = 9, pch = "+") + 118 | scale_y_continuous(breaks = seq(-6, 10, by = 2), minor_breaks = -6:10) + 119 | scale_color_manual(guide = "none", values = palette) + 120 | coord_cartesian(ylim = c(-6, 10)) + 121 | facet_grid(. ~ X2) 122 | ``` 123 | 124 | And "true" means for each level (averaging over the levels of the other factor): 125 | 126 | | Level | Mean | 127 | |:--------:|:--------:| 128 | | X1 == A | 0.66666 | 129 | | X1 == B | 3 | 130 | | X2 == C | 0.5 | 131 | | X2 == D | 2.5 | 132 | | X2 == E | 2.5 | 133 | 134 | Let's fit a linear model: 135 | 136 | ```{r} 137 | m.linear = lm(Y ~ X1*X2, data = df) 138 | anova(m.linear) 139 | ``` 140 | 141 | Now with ART: 142 | 143 | ```{r} 144 | m.art = art(Y ~ X1*X2, data = df) 145 | anova(m.art) 146 | ``` 147 | 148 | Both have significance at all levels (expected given the number of samples and the "true" effects) and similar enough F values. 149 | The real question is whether/what kind of contrast tests make sense. 150 | 151 | ### Contrast tests of main effects 152 | 153 | For the main effects, let's look at contrast tests for the linear model: 154 | 155 | ```{r, message=FALSE} 156 | contrast(emmeans(m.linear, ~ X1), method = "pairwise") 157 | contrast(emmeans(m.linear, ~ X2), method = "pairwise") 158 | ``` 159 | 160 | These are about right: The "true" effect for `A - B` is `-2.3333`, for `C - D` and `C - E` is `-2`, and for `D - E` is `0` (see table above). For ART, `artlm()` will return the appropriate linear model for single-factor contrasts, which 161 | we can then use with a library that does contrasts (such as `emmeans()`): 162 | 163 | ```{r, message=FALSE} 164 | # this works for single factors, though it is better (more general) to use 165 | # artlm.con() or art.con() (see below) 166 | contrast(emmeans(artlm(m.art, "X1"), ~ X1), method = "pairwise") 167 | contrast(emmeans(artlm(m.art, "X2"), ~ X2), method = "pairwise") 168 | ``` 169 | 170 | This is about right (effects in the same direction, the estimates aren't 171 | the same because they are on the scale of ranks and not the data, but the t values are similar to the linear model, as we should hope). However, we recommend using `artlm.con()` instead of `artlm()`, as it 172 | will also return the correct model in this case but not in the general case, as 173 | we will see below. Using `artlm.con()`, we get the same result as before: 174 | 175 | ```{r, message=FALSE} 176 | contrast(emmeans(artlm.con(m.art, "X1"), ~ X1), method = "pairwise") 177 | contrast(emmeans(artlm.con(m.art, "X2"), ~ X2), method = "pairwise") 178 | ``` 179 | 180 | We can also use the shortcut function `art.con()`, which will perform the 181 | appropriate call to both `artlm.con()` and `emmeans()` for the desired contrast: 182 | 183 | ```{r, message=FALSE} 184 | art.con(m.art, "X1") 185 | art.con(m.art, "X2") 186 | ``` 187 | 188 | **Within a single factor** ART (i.e., `artlm()`) and ART-C (`artlm.con()`) are mathematically equivalent, 189 | so the contrast tests for ART and ART-C have the same results. 190 | 191 | ### Tests of differences in pairwise combinations of levels between factors in interactions 192 | 193 | Now let's look at tests of differences in combinations of levels between factors: 194 | 195 | ```{r} 196 | contrast(emmeans(m.linear, ~ X1:X2), method = "pairwise") 197 | ``` 198 | 199 | If we naively apply the ART procedure (using `artlm()`), we will get incorrect results: 200 | 201 | ```{r} 202 | #DO NOT DO THIS! 203 | contrast(emmeans(artlm(m.art, "X1:X2"), ~ X1:X2), method = "pairwise") 204 | ``` 205 | 206 | Compare these to the linear model: very different results! 207 | 208 | The linear model tests are easy to interpret: 209 | they tell us the expected mean difference between combinations of levels. 210 | 211 | The ART results are more difficult to interpret. Take `A,C - A,D`, which looks like this: 212 | 213 | ```{r interaction_plot_AC_AD, fig.cap="", fig.width=3} 214 | df %>% 215 | filter(X1 == "A", X2 %in% c("C", "D")) %>% 216 | ggplot(aes(x = X1:X2, y = Y, color = X2)) + 217 | geom_violin(trim = FALSE, adjust = 1.5) + 218 | geom_point(pch = "-", size = 4) + 219 | stat_summary(fun = mean, geom = "point", size = 4) + 220 | scale_y_continuous(breaks = seq(-6, 10, by = 2), minor_breaks = -6:10) + 221 | scale_color_manual(guide = "none", values = palette) + 222 | coord_cartesian(ylim=c(-6,10)) 223 | ``` 224 | 225 | The linear model correctly estimates this difference as approximately `0`, which is both the true effect and what we should expect from a visual 226 | inspection of the data. Unlike the linear model, the ART model gives us a statistically significant difference between `A,C` and `A,D`, 227 | which if we interpret in the same way as the linear model is obviously incorrect. 228 | 229 | The key here is to understand that ART is reporting differences with the main effects subtracted out. 230 | That is, the `A,C - A,D` effect is something like the difference between this combination of levels if we first 231 | subtracted out the effect of `C - D`. We can see this if we take the ART estimate for `C - D` in the `emmeans` output for `X2` above (`-123.13`) and the 232 | ART estimate for `A,C - A,D` (`125.12`) here, we can get an approximate estimate of the difference (`-123.13 + 125.12 == 1.99`) 233 | that is consistent with the expected 0 (given the SE here). 234 | 235 | The ART-C procedure was developed to align and rank data specifically for contrasts involving levels from any number of factors, and is available through `art.con()`: 236 | 237 | ```{r message = FALSE} 238 | art.con(m.art, "X1:X2") 239 | ``` 240 | 241 | Like the linear model, `art.con()` correctly estimates the difference between `A,C - A,D` as approximately `0`. In fact, its results agree with the linear model for all contrasts conducted. (Note that the `art.con()` and linear model results appear in a different order). 242 | 243 | The syntax used above is consistent with *term* syntax used by `artlm()`. `art.con()` also accepts the *formula* syntax accepted by `emmeans::emmeans()`. We can conduct the same contrasts as above using the following syntax: 244 | 245 | ```{r message = FALSE} 246 | art.con(m.art, ~ X1*X2) 247 | ``` 248 | 249 | We can also manually conduct the contrasts with `emmeans::emmeans()` (or another library for running contrasts) 250 | by first extracting the linear model with `artlm.con()`. Note that the contrasts must be performed on the 251 | variable constructed by `artlm.con()` with the names of the factors involved concatenated together (`X1X2`): 252 | 253 | ```{r message = FALSE} 254 | contrast(emmeans(artlm.con(m.art, "X1:X2"), ~ X1X2), method = "pairwise") 255 | ``` 256 | 257 | 258 | ### Tests of _differences of differences_ in interactions 259 | 260 | You may also wish to test _differences of differences_; e.g., for the interaction `X1:X2`, we might ask, is the difference `A - B` different when `X2 = C` compared to when `X2 = D`? We can test this using the `interaction` argument to `art.con()`. 261 | When the `interaction` argument is supplied to art.con, differences of differences are tested on data that has been aligned-and-ranked using the **original** ART method (i.e., the data is **not** aligned-and-ranked using the ART-C method, 262 | as it is not necessary for these contrasts). 263 | 264 | Before we test, let's try to visualize what's going on in just this interaction: 265 | 266 | ```{r, interaction_plot_C_D, fig.cap=""} 267 | plot_interaction_for_X2_levels = function(...) { 268 | x2_levels = c(...) 269 | df. = filter(df, X2 %in% x2_levels) 270 | 271 | X1_in_X2 = df. %>% 272 | group_by(X1, X2) %>% 273 | summarise(Y = mean(Y), .groups = "drop") %>% 274 | spread(X1, Y) 275 | 276 | print( 277 | ggplot(df., aes(x = X1, y = Y, color = X2)) + 278 | geom_violin(trim = FALSE, adjust = 1.5) + 279 | geom_point(pch = "-", size = 4) + 280 | stat_summary(fun = mean, geom = "point", size = 4) + 281 | stat_summary(aes(group = X2), fun = mean, geom = "line", size = 1, linetype = "dashed") + 282 | geom_errorbar( 283 | aes(x = 2.2, ymin = A, ymax = B, y = NULL), 284 | data = X1_in_X2, width = .19, size = 0.8, color = "black" 285 | ) + 286 | geom_text( 287 | aes(x = 2.35, y = (A + B)/2, label = paste("A - B |", X2)), 288 | data = X1_in_X2, hjust = 0, size = 5, color = "black" 289 | ) + 290 | scale_y_continuous(breaks = seq(-6, 10, by = 2), minor_breaks = -6:10) + 291 | scale_color_manual(guide = "none", values = palette[x2_levels]) + 292 | coord_cartesian(xlim = c(0, 3.5), ylim = c(-6,10)) + 293 | facet_grid(. ~ X2) 294 | ) 295 | } 296 | plot_interaction_for_X2_levels("C", "D") 297 | ``` 298 | 299 | The true effect for `A - B | C` is -1, for `A - B | D` is -5, and for `(A - B | C) - (A - B | D)` is `(-1) - (-5) = 4`. 300 | Visually, we're asking if the two dashed lines in the above plot are parallel. Equivalently, we're asking if the vertical distance from the mean of 301 | A to the mean of B in the left panel (when X2 == C) is the same as the vertical distance between A and B in the right panel (when X2 == D). 302 | The true difference between these vertical distances (the "difference of a difference") is 4, which is also about what we would estimate 303 | it to be by looking at the above plot. 304 | 305 | We can get the estimate of this "difference of a difference" from the linear model by adding `interaction = TRUE` to the same call to `contrast` we made previously: 306 | 307 | ```{r} 308 | contrast(emmeans(m.linear, ~ X1:X2), method = "pairwise", interaction = TRUE) 309 | ``` 310 | 311 | Here we can interpret the row `A - B C - D` as the difference between (`A - B | C`) and (`A - B | D`), which 312 | is estimated as `3.82` (close to the true effect of 4, see the plot above). 313 | 314 | We can look at a similar plot for the row `A - B C - E`: 315 | 316 | ```{r interaction_plot_C_E, fig.cap=""} 317 | plot_interaction_for_X2_levels("C", "E") 318 | ``` 319 | 320 | Here the true effect for `A - B | C` is -1, `A - B | E` is also -1, and `(A - B | C) - (A - B | E)` is `0`. 321 | Visually, this sample looks close to the true effects (the height of `A - B | C` is about the same as `A - B | E`). 322 | From the the row `A-B : C-E` above we can see that the 323 | estimate from the linear model is ~0, as we should hope. 324 | 325 | A similar visual analysis finds the estimate for row `A - B D - E` (~ -4.2) also to be correct (true effect is -4): 326 | 327 | ```{r, interaction_plot_D_E, fig.cap=""} 328 | plot_interaction_for_X2_levels("D", "E") 329 | ``` 330 | 331 | Now we look at these differences of differences in ART, using `art.con()`: 332 | 333 | ```{r} 334 | art.con(m.art, "X1:X2", interaction = TRUE) 335 | ``` 336 | 337 | This is equivalent to: 338 | 339 | ```{r} 340 | contrast(emmeans(artlm(m.art, "X1:X2"), ~ X1:X2), method = "pairwise", interaction = TRUE) 341 | ``` 342 | 343 | And we see *t* values consistent with the linear model, and consistent estimates (given the standard error). 344 | These types of comparisons work under ART because they do not involve coefficients of main 345 | effects (see the description of these tests in `vignette("phia")`), 346 | thus are consistent even when ART has stripped out the main effects. 347 | 348 | If you prefer the `phia` package, the code to run the equivalent tests using the `testInteractions` 349 | function in `phia` instead of using `emmeans` is: 350 | 351 | ```{r} 352 | testInteractions(artlm(m.art, "X1:X2"), pairwise = c("X1","X2")) 353 | ``` 354 | 355 | While `emmeans()` uses _t_ tests in this case, `testInteractions()` gives the result of equivalent 356 | _F_ tests with one numerator degree of freedom (an _F_ test with $F(1,\nu) = f$ is equivalent to a two-sided 357 | _t_ test with $t(\nu) = \sqrt{f}$). We prefer the _t_ test in this case because the _t_ value preserves 358 | the direction of the effect (its sign) and is more amenable to calculating interpretable (ish) 359 | effect sizes like Cohen's _d_. For an example of the latter, see 360 | [vignette("art-effect-size")](art-effect-size.html). 361 | -------------------------------------------------------------------------------- /vignettes/art-effect-size.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Effect Sizes with ART" 3 | author: "Matthew Kay" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Effect Sizes with ART} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | 12 | ## Introduction 13 | 14 | The aligned-rank transform (ART) allows for non-parametric analyses of variance. 15 | But how do we derive effect sizes from ART results? 16 | 17 | __NOTE:__ Before embarking down the path of calculating standardized effect sizes, it is 18 | always worth asking if that is what you really want. Carefully consider, for example, the arguments 19 | of [Cummings (2011)](https://dx.doi.org/10.1001/archpediatrics.2011.97) against the use of 20 | standardized effect sizes and in favor of simple (unstandardized) effect sizes. If you decide you would rather 21 | use simple effect sizes, you may need to consider a different procedure than ART, as the ranking 22 | procedure destroys the information necessary to calculate simple effect sizes. 23 | 24 | ## Contents 25 | 26 | 1. [Test Dataset](#test-dataset): The test data we will use to compare a linear model against ART 27 | 1. [Partial _eta_-squared](#partial-eta-squared): Calculation of partial _eta_-squared (effect size for _F_ tests) 28 | 1. [Cohen's _d_](#cohens-d): Calculation of standardized mean differences (Cohen's _d_; effect size for _t_ tests), including confidence intervals 29 | 30 | ## Libraries needed for this 31 | 32 | ```{r setup, include=FALSE} 33 | knitr::opts_chunk$set( #default code chunk options 34 | fig.width = 6, 35 | fig.height = 4 36 | ) 37 | pander::panderOptions("table.split.table", Inf) #don't split wide tables in output 38 | pander::panderOptions("table.style", "rmarkdown") #table style that's supported by github 39 | ``` 40 | 41 | ```{r message=FALSE} 42 | library(dplyr) #%>% 43 | library(emmeans) #emmeans 44 | library(DescTools) #EtaSq 45 | library(car) #sigmaHat 46 | library(ARTool) #art, artlm 47 | library(ggplot2) #ggplot, stat_..., geom_..., etc 48 | ``` 49 | 50 | 51 | ## Test dataset 52 | 53 | Let's load the test dataset from [vignette("art-contrasts")](art-contrasts.html): 54 | 55 | ```{r} 56 | data(InteractionTestData, package = "ARTool") 57 | df = InteractionTestData #save some typing 58 | ``` 59 | 60 | Let's fit a linear model: 61 | 62 | ```{r} 63 | #we'll be doing type 3 tests, so we want sum-to-zero contrasts 64 | options(contrasts = c("contr.sum", "contr.poly")) 65 | m.linear = lm(Y ~ X1*X2, data=df) 66 | ``` 67 | 68 | Now with ART: 69 | 70 | ```{r} 71 | m.art = art(Y ~ X1*X2, data=df) 72 | ``` 73 | 74 | 75 | ## Partial _eta_-squared 76 | 77 | Note that for Fixed-effects-only models and repeated measures models 78 | (those with `Error()` terms) ARTool also collects the sums of squares, but 79 | does not print them by default. We can pass `verbose = TRUE` to `print()` 80 | to print them: 81 | 82 | ```{r} 83 | m.art.anova = anova(m.art) 84 | print(m.art.anova, verbose=TRUE) 85 | ``` 86 | 87 | We can use the sums of squares to calculate partial _eta_-squared: 88 | 89 | ```{r} 90 | m.art.anova$eta.sq.part = with(m.art.anova, `Sum Sq`/(`Sum Sq` + `Sum Sq.res`)) 91 | m.art.anova 92 | ``` 93 | 94 | We can compare the above results to partial _eta_-squared calculated on the 95 | linear model (the second column below): 96 | 97 | ```{r} 98 | EtaSq(m.linear, type=3) 99 | ``` 100 | 101 | The results are comparable. 102 | 103 | 104 | ## Cohen's _d_ 105 | 106 | We can derive Cohen's _d_ (the standardized mean difference) by dividing estimated differences by the 107 | residual standard deviation of the model. Note that this relies somewhat on the assumption of 108 | constant variance across levels (aka homoscedasticity). 109 | 110 | ### in the linear model (for comparison) 111 | 112 | As a comparison, let's first derive pairwise contrasts for 113 | all levels of X2 in the linear model: 114 | 115 | ```{r} 116 | x2.contrasts = summary(pairs(emmeans(m.linear, ~ X2))) 117 | ``` 118 | 119 | Then divide these estimates by the residual standard deviation to get an estimate of _d_: 120 | 121 | ```{r} 122 | x2.contrasts$d = x2.contrasts$estimate / sigmaHat(m.linear) 123 | x2.contrasts 124 | ``` 125 | 126 | Note that this is essentially the same as the unstandardized estimate for this model; 127 | that is because this test dataset was generated with a residual standard deviation of 1. 128 | 129 | ### in ART 130 | 131 | We can follow the same procedure on the ART model for factor X2: 132 | 133 | ```{r} 134 | m.art.x2 = artlm(m.art, "X2") 135 | x2.contrasts.art = summary(pairs(emmeans(m.art.x2, ~ X2))) 136 | x2.contrasts.art$d = x2.contrasts.art$estimate / sigmaHat(m.art.x2) 137 | x2.contrasts.art 138 | ``` 139 | 140 | Note how standardization is helping us now: The standardized mean differences (_d_) are 141 | quite similar to the estimates of _d_ from the linear model above. 142 | 143 | ## Confidence intervals 144 | 145 | We can also derive confidence intervals on these effect sizes. To do that, we'll use the `d.ci` function from the `psych` package, which also requires us to indicate how many observations were in each group for each contrast. That is easy in this case, as each group has 100 observations. Thus: 146 | 147 | ```{r} 148 | x2.contrasts.ci = confint(pairs(emmeans(m.linear, ~ X2))) %>% 149 | mutate(d = estimate / sigmaHat(m.linear)) %>% 150 | cbind(d = plyr::ldply(.$d, psych::d.ci, n1 = 100, n2 = 100)) 151 | 152 | x2.contrasts.ci 153 | ``` 154 | 155 | And from the ART model: 156 | 157 | ```{r} 158 | x2.contrasts.art.ci = confint(pairs(emmeans(m.art.x2, ~ X2))) %>% 159 | mutate(d = estimate / sigmaHat(m.art.x2)) %>% 160 | cbind(d = plyr::ldply(.$d, psych::d.ci, n1 = 100, n2 = 100)) 161 | 162 | x2.contrasts.art.ci 163 | ``` 164 | 165 | And plotting both, to compare (red dashed line is the true effect): 166 | 167 | ```{r cohens-d-comparison} 168 | rbind( 169 | cbind(x2.contrasts.ci, model="linear"), 170 | cbind(x2.contrasts.art.ci, model="ART") 171 | ) %>% 172 | ggplot(aes(x=model, y=d, ymin=d.lower, ymax=d.upper)) + 173 | geom_pointrange() + 174 | geom_hline(aes(yintercept = true_effect), 175 | data = data.frame(true_effect = c(-2, -2, 0), contrast = c("C - D", "C - E", "D - E")), 176 | linetype = "dashed", color = "red") + 177 | facet_grid(contrast ~ .) + 178 | coord_flip() 179 | ``` 180 | --------------------------------------------------------------------------------