├── .gitattributes ├── .gitignore ├── 01 ANOVA made easy ├── ANOVA made easy.R ├── Phobia.rds └── mindful_work_stress.rds ├── 02 ANCOVA ├── ANCOVA made easy.R ├── ANCOVA.Rproj ├── Alcohol_data.rds ├── Centering-and-ANOVA.Rmd └── Centering-and-ANOVA.html ├── 03 Main and simple effects analysis ├── Main and simple effects.R ├── Main and simple effects.Rproj └── Phobia.rds ├── 04 Interaction analysis ├── 01 Interaction contrasts.R ├── 02 removable interactions.R ├── Interaction analysis.Rproj ├── coffee.csv ├── coffee_plot.png └── dyslexia_stroop.rds ├── 05 Effect sizes and multiple comparisons ├── Effect sizes and multiple comparisons.Rproj ├── Effect sizes.R ├── Multiple comparisons.R └── Phobia.rds ├── 06 assumption check and non-parametric tests ├── 01 assumption_tests.R ├── 02 permutation analyses.R ├── 03 Contrast Bootstrap.R ├── assumption check and non-parametric tests.Rproj └── obk_between.rds ├── 07 Accepting nulls ├── 01 Testing null effects.R ├── 02 Testing null contrasts.R ├── Accepting nulls.Rproj └── Alcohol_data.rds ├── 08 ANOVA and (G)LMMs ├── 01 LMM vs ANOVA.R ├── 02 GLMM - analysis of accuracy.R ├── ANOVA and (G)LMMs.Rproj └── stroop_e1.rds ├── LICENSE ├── README.Rmd ├── README.md └── logo ├── Hex.R └── Hex.png /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | * linguist-vendored 4 | *.R linguist-vendored=false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # History files 2 | .Rhistory 3 | .Rapp.history 4 | 5 | # Session Data files 6 | .RData 7 | 8 | # User-specific files 9 | .Ruserdata 10 | 11 | # Example code in package build process 12 | *-Ex.R 13 | 14 | # Output files from R CMD build 15 | /*.tar.gz 16 | 17 | # Output files from R CMD check 18 | /*.Rcheck/ 19 | 20 | # RStudio files 21 | .Rproj.user/ 22 | 23 | # produced vignettes 24 | vignettes/*.html 25 | vignettes/*.pdf 26 | 27 | # OAuth2 token, see https://github.com/hadley/httr/releases/tag/v0.3 28 | .httr-oauth 29 | 30 | # knitr and R markdown default cache directories 31 | *_cache/ 32 | /cache/ 33 | 34 | # Temporary files created by R markdown 35 | *.utf8.md 36 | *.knit.md 37 | -------------------------------------------------------------------------------- /01 ANOVA made easy/ANOVA made easy.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | # We've already seen how to deal with categorical predictors, and categorical 4 | # moderators in a regression model. , our regression model is 5 | # equivalent to an ANOVA. 6 | # 7 | # Although ANOVA is just a type of regression model (where all of our predictors 8 | # are categorical and we model all of the possible interactions), researchers 9 | # working with factorial data often present their models as a single ANOVA with 10 | # all the interactions (instead of building a series of models and comparing 11 | # them hierarchically). 12 | # 13 | # Although R has a built-in function for conducting ANOVAs - `aov()` - you 14 | # should NOT USE IT as it will not give you the results you want! 15 | # 16 | # 17 | # 18 | # 19 | # 20 | # To be clear: 21 | # 22 | # .-"""-. 23 | # / _ _ \ 24 | # ](_' `_)[ 25 | # `-. N ,-' 26 | # 8==| |==8 27 | # `---' 28 | # 29 | # >>>> DO NOT USE `aov()`! <<<< 30 | # 31 | # 32 | # To obtain proper ANOVA tables, we need: 33 | # 1. Effects coding of our factors. 34 | # 2. Type 3 sums-of-squares. 35 | # 36 | # (Go read about these requirments and why they are important here: 37 | # https://shouldbewriting.netlify.app/posts/2021-05-25-everything-about-anova) 38 | # 39 | # Though it is possible to do this in base-R, we will be using the `afex` 40 | # package, which takes care of all of that behind the scenes and gives the 41 | # desired and expected ANOVA results. 42 | 43 | 44 | 45 | library(afex) # for ANOVA 46 | library(emmeans) # for follow up analysis 47 | library(effectsize) # for effect sizes 48 | library(ggeffects) # for plotting 49 | 50 | 51 | 52 | 53 | # A Between Subjects Design ----------------------------------------------- 54 | 55 | 56 | Phobia <- readRDS("Phobia.rds") 57 | head(Phobia) 58 | 59 | 60 | 61 | ## 1. Build a model ---- 62 | m_aov <- aov_ez(id = "ID", dv = "BehavioralAvoidance", 63 | between = c("Condition", "Gender"), 64 | data = Phobia, 65 | anova_table = list(es = "pes")) # pes = partial eta squared 66 | 67 | # We get all effects, their sig and effect size (partial eta square) 68 | m_aov 69 | 70 | 71 | 72 | 73 | # We can use functions from `effectsize` to get confidence intervals for various 74 | # effect sizes: 75 | eta_squared(m_aov, partial = TRUE) 76 | ?eta_squared # see more types 77 | 78 | 79 | 80 | ## 2. Explore the model ---- 81 | ggemmeans(m_aov, c("Condition", "Gender")) |> 82 | plot(add.data = TRUE, connect.lines = TRUE) 83 | # see also: 84 | # afex_plot(m_aov, ~ Condition, ~ Gender) 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | # Repeated Measures Design ------------------------------------------------ 95 | 96 | 97 | ## Wide vs long data 98 | # For repeated measures ANOVAs we need to prepare our data in two ways: 99 | # 1. If we have many observations per subject / condition, we must aggregate 100 | # the data to a single value per subject / condition. This can be done with: 101 | # - `dplyr`'s `summarise()` 102 | # - `prepdat`'s `prep()` 103 | # - etc... 104 | # 2. The data must be in the LONG format. 105 | 106 | 107 | mindful_work_stress <- readRDS("mindful_work_stress.rds") 108 | 109 | 110 | 111 | # WIDE DATA has: 112 | # 1. A row for each subject, 113 | # 2. Between-subject variables have a column 114 | # 3. Repeated measures are stored across columns, and the within-subject are 115 | # stored in column names 116 | head(mindful_work_stress) 117 | 118 | 119 | 120 | 121 | # LONG DATA (also known as 'tidy data'), has: 122 | # 1. One row per each OBSERVATION, 123 | # 2. A column for each variable (including the subject ID!) 124 | # 3. Repeated measures are stored across rows. 125 | mindful_work_stress_long <- mindful_work_stress |> 126 | tidyr::pivot_longer(cols = c(T1,T2), 127 | names_to = "Time", 128 | values_to = "work_stress") 129 | 130 | head(mindful_work_stress_long) 131 | 132 | 133 | 134 | 135 | ## 1. Build a model ---- 136 | fit_mfs <- aov_ez("id", "work_stress", 137 | between = "Family_status", 138 | within = "Time", 139 | data = mindful_work_stress_long, 140 | anova_table = list(es = "pes")) 141 | fit_mfs 142 | 143 | eta_squared(fit_mfs, partial = TRUE) 144 | 145 | # Repeated measures are really just one way of saying that there are multiple 146 | # levels in our data. Although rm-ANOVA can deal with simple cases like the ones 147 | # presented here, for more complex data structures (more nesting, more than one 148 | # random effects factor, modeling of a continuous predictor, etc.) HLM/LMM are 149 | # required (which you can learn next semester). 150 | # see `vignette("afex_mixed_example", package = "afex")` for an example of how 151 | # to run HLM/LMM ANOVAs. 152 | 153 | 154 | 155 | 156 | 157 | ## 2. Explore the model ---- 158 | ggemmeans(fit_mfs, c("Time", "Family_status")) |> 159 | plot(add.data = TRUE, connect.lines = TRUE) 160 | 161 | 162 | 163 | # Contrast analysis... 164 | 165 | -------------------------------------------------------------------------------- /01 ANOVA made easy/Phobia.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/01 ANOVA made easy/Phobia.rds -------------------------------------------------------------------------------- /01 ANOVA made easy/mindful_work_stress.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/01 ANOVA made easy/mindful_work_stress.rds -------------------------------------------------------------------------------- /02 ANCOVA/ANCOVA made easy.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | 4 | afex_options(es_aov = 'pes', 5 | correction_aov = 'GG', 6 | emmeans_model = 'univariate') 7 | 8 | # Load Data --------------------------------------------------------------- 9 | 10 | # Load data (is RDS file - these are R files the contain objects, in our 11 | # case, a tidy data-frame) 12 | Alcohol_data <- readRDS("Alcohol_data.rds") 13 | head(Alcohol_data) 14 | 15 | 16 | 17 | # Fit ANOVA model --------------------------------------------------------- 18 | 19 | ersp_anova <- aov_ez('Subject','ersp',Alcohol_data, 20 | within = c('Frequency','Correctness'), 21 | between = c('Alcohol')) 22 | ersp_anova 23 | 24 | # But mothers education level is related to the outcome.. 25 | # We probably would want to control for it - reduce the MSE.. 26 | 27 | 28 | 29 | 30 | # Fit ANCOVA model -------------------------------------------------------- 31 | 32 | # Keep in mind that some have argued that the use (or misuse) of ANCOVA 33 | # should be avoided. See: http://doi.org/10.1037//0021-843X.110.1.40 34 | 35 | ersp_ancova <- aov_ez('Subject', 'ersp', Alcohol_data, 36 | within = c('Frequency','Correctness'), 37 | between = c('Alcohol'), 38 | # The new bits: 39 | covariate = 'mograde', 40 | factorize = FALSE) # MUST set `factorize = FALSE`! 41 | # Note the warning! 42 | 43 | ersp_anova 44 | ersp_ancova 45 | 46 | 47 | 48 | 49 | # Center the covariable and re-fit the model ------------------------------- 50 | 51 | # Why center the covariable? 52 | # See `Centering-and-ANOVA.html` for an 53 | # extremely detailed explanation. 54 | 55 | Alcohol_data$mograde_c <- scale(Alcohol_data$mograde, 56 | center = TRUE, scale = FALSE) 57 | 58 | # Re-Fit model 59 | ersp_ancova2 <- aov_ez('Subject','ersp',Alcohol_data, 60 | within = c('Frequency','Correctness'), 61 | between = c('Alcohol'), 62 | # The new bits 63 | covariate = 'mograde_c', factorize = FALSE) 64 | ersp_anova 65 | ersp_ancova 66 | ersp_ancova2 # Huge difference! 67 | 68 | 69 | # Follow up analysis ------------------------------------------------------ 70 | 71 | # use emmeans as usual (: 72 | -------------------------------------------------------------------------------- /02 ANCOVA/ANCOVA.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /02 ANCOVA/Alcohol_data.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/02 ANCOVA/Alcohol_data.rds -------------------------------------------------------------------------------- /02 ANCOVA/Centering-and-ANOVA.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Centering Variables and ANOVA Tables" 3 | subtitle: "Why and how to center variables when generating ANOVA tables" 4 | output: html_document 5 | --- 6 | 7 | ```{r setup, include=FALSE} 8 | knitr::opts_chunk$set(echo = TRUE) 9 | ``` 10 | 11 | # What Are ANOVA Tables? 12 | 13 | ANOVA tables, like regression tables, produce significance tests (and sometimes estimates of effect sizes). Unlike regression tables, where a test if given for each coefficient, in ANOVA tables a test is given by some grouping scheme: by model (of model comparison), of by factor where all coefficients that represent a categorical variable are tested in a joint test. It is the latter table, used usually in analysis of factorial data, that is discussed here. 14 | 15 | > Thesis: centering predictors changes the results given by ANOVA tables. 16 | 17 | Generally, the results given by ANOVA tables with centered variables are the ones we are interested in. 18 | 19 | # Why Center? 20 | 21 | In moderation models / models with interaction terms, centering of variables affects the estimates (and thus the joint test and significance) of lower order terms ([Dalal & Zickar, 2011](https://doi.org/10.1177%2F1094428111430540)). It is only after centering variables, do these tests for lower order terms represent what we expect them to - *main effects*, averaged across the levels of all other terms on average ([AFNI](https://afni.nimh.nih.gov/pub/dist/doc/htmldoc/STATISTICS/center.html#centering-with-one-group-of-subjects)). 22 | 23 | # How to Center Variables? 24 | 25 | *Centering* is mathematical process that gives the 0 of variable $X$ some non-arbitrary meaning. This can be any value, but usually we are interested in the mean of $X$. 26 | 27 | ## Continuous Variables 28 | 29 | Centering of continuous variables is pretty straightforward - we subtract the mean of $X$ from each value of $X$: 30 | 31 | $$ 32 | X_{centered} = X-\bar{X} 33 | $$ 34 | Note that we don't have to subtract the mean; for example, if $X$ is IQ, 0 is meaningless - in fact, it's not even on the scale (with a lower bound of ~50)! I can subtract the mean of my sample, but I can also subtract 100 instead, which is the "population average". Similarly, if $X$ is "age", 0 is a day-old baby, a value that is not usually particularly meaningful. 35 | 36 | ## Categorical Variables 37 | 38 | It would seem like an impossible task - how can you subtract any numeric value from a categorical variable? This is true, but the idea of a "*meaningful 0*" is that in our model giving a value of 0 to this $X$ will represent the average across all level of $X$. When modeling categorical variables, this means setting all dummy variables to 0. 39 | 40 | Usually, dummy variables are generated using a treatment coding scheme, such that setting all dummy variables to 0 represents some "baseline" group. But there are other coding schemes, some of which give 0 the meaning we're looking for - for example, [effects coding](https://stats.idre.ucla.edu/other/mult-pkg/faq/general/faqwhat-is-effect-coding/) ([Aiken, et al., 1991, pp. 127](https://books.google.co.il/books?hl=iw&lr=&id=LcWLUyXcmnkC); [Singmann & Kellen, 2017, pp. 24](http://singmann.org/download/publications/singmann_kellen-introduction-mixed-models.pdf)).^[Unfortunately, this makes the interpretation of the actual coefficient not as straightforward as when using treatment coding...] ^[When conducting ANOVAs with [`afex`](https://cran.r-project.org/package=afex), you don't need to worry about setting effects coding, as `afex` [takes care of this for you](https://github.com/singmann/afex/issues/63). However when conducting ANCOVAs with `afex`, continuous variables [**are not centered**](https://github.com/singmann/afex/issues/59) (but a warning is given), and you have to do that yourself. This is also true for JASP (that has `afex` under the hood) and even for SPSS (but if you're still using SPSS, this might be the least of your problems...).] 41 | 42 | In `R` this can be done by setting 43 | 44 | ```{r, eval=FALSE} 45 | options(contrasts=c('contr.sum', 'contr.poly')) 46 | ``` 47 | 48 | Or for a single factor: 49 | 50 | ```{r, eval=FALSE} 51 | contrasts(iris$Species) <- contr.sum 52 | ``` 53 | 54 | 55 | # Addendum 56 | 57 | When generating ANOVA tables, the $SS$ of factor $A$ are computed with all other coefficients held constant at 0 (similar to how coefficients are interpreted as simple slopes in moderation analysis). If factor $B$ has a treatment coding scheme, then when the coefficients of factor $B$ are 0, there actually represent the baseline group, as so the effect for $A$ is actually the simple effect of $A$ and not the main effect! 58 | 59 | 60 | "But wait!", I hear you shout, "When we learned ANOVA is Intro to stats, we weren't taught to center any variables!". 61 | 62 | This is true - you didn't explicitly learn this, but taking a closer look at the equations of the various $SS$s will reveal that you've been doing just that all along. 63 | 64 | For $SS_A$: 65 | $$ 66 | SS_A = n\sum (\bar{X}_{i.}-\bar{X}_{..})^2 67 | $$ 68 | When $\bar{X}_{i.}$ is itself the mean of group $i$ of factor $A$ *averaged across* the levels of factor $B$ (as denoted by the $.$)! 69 | 70 | We can even re-write the equation for $SS_A$ to show this explicitly: 71 | 72 | $$ 73 | SS_A = n\sum (\bar{X}_{i.}-\bar{X}_{..})^2 = 74 | n\sum\sum (\bar{X}_{ij}-(\bar{X}_{.j}-\bar{X}_{..})-(\bar{X}_{ij}-\bar{X}_{.j}-\bar{X}_{ij}+\bar{X}_{..})-\bar{X}_{..})^2 75 | $$ 76 | 77 | Where $(\bar{X}_{.j}-\bar{X}_{..})$ is the centering of factor $B$ (subtracting from *all* group means, the total mean). 78 | 79 | This is also why centering a variable only affects the low-order effects of *other* variables. 80 | -------------------------------------------------------------------------------- /03 Main and simple effects analysis/Main and simple effects.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(emmeans) 4 | library(ggeffects) 5 | 6 | 7 | Phobia <- readRDS("Phobia.rds") 8 | head(Phobia) 9 | 10 | 11 | # 1. Fit the model -------------------------------------------------------- 12 | 13 | 14 | m_aov <- aov_ez(id = "ID", dv = "BehavioralAvoidance", 15 | between = c("Condition", "Gender"), 16 | data = Phobia, 17 | anova_table = list(es = "pes")) # pes = partial eta squared 18 | 19 | # We get all effects, their sig and effect size (partial eta square) 20 | m_aov 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | # 2. Model Exploration ---------------------------------------------------- 30 | 31 | 32 | 33 | # Effect sizes with CIs... 34 | # Make a plot... 35 | # (see 01 ANOVA made easy.R) 36 | 37 | 38 | 39 | # === NOTE === 40 | # The model we used here is an ANOVA - but what follows is applicable to any 41 | # type of multiple regression with categorical predictors. 42 | 43 | 44 | 45 | 46 | 47 | ## A. Main Effect Analysis (Condition) ======== 48 | # We are looking at a main effect in a 2-way design, so this means we are 49 | # averaging over the levels Gender. 50 | 51 | 52 | # Only the main effect for Condition was significant. Let's conduct a contrast on 53 | # the main effect. 54 | ggemmeans(m_aov, "Condition") |> 55 | plot(add.data = TRUE, connect.lines = TRUE) 56 | 57 | 58 | 59 | 60 | 61 | ### Step 1. Get estimated means ---- 62 | (em_Condition <- emmeans(m_aov, ~ Condition)) 63 | # Note the footnote! 64 | 65 | 66 | 67 | 68 | 69 | 70 | ### Step 2. Estimate the contrasts ---- 71 | contrast(em_Condition, method = "pairwise") 72 | 73 | 74 | ?`emmc-functions` # see for different types of built-in contrast weights. 75 | 76 | 77 | # But we can also build custom contrast weights! 78 | w <- data.frame( 79 | "Implosion vs Other2" = c(-1, 2, -1) / 2, # make sure each "side" sums to 1! 80 | "Implosion vs Other" = c(-1, 2, -1), 81 | "Desens vs CBT" = c(-1, 0, 1) 82 | ) 83 | w 84 | 85 | contrast(em_Condition, method = w) 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | ## B. Simple Effect Analysis (Condition by Gender) ======== 100 | 101 | 102 | # A "simple" effect, is the effect of some variable, conditional on some other 103 | # variable. 104 | 105 | ggemmeans(m_aov, c("Condition", "Gender")) |> 106 | plot(add.data = TRUE, connect.lines = TRUE, facet = TRUE) 107 | 108 | 109 | 110 | 111 | # We can "split" our model *by* some variable to see the effects conditional on 112 | # its levels. For example, we can look at each Condition to see test if there 113 | # are differences between the levels of Gender within each condition: 114 | joint_tests(m_aov, by = "Gender") 115 | 116 | 117 | 118 | 119 | # We can then conduct a contrast analysis for each simple effect... 120 | 121 | 122 | ### Step 1. Get estimated means ---- 123 | (em_Condition_by_Gender <- emmeans(m_aov, ~ Condition + Gender)) 124 | 125 | 126 | 127 | 128 | ### Step 2. Estimate the contrasts (conditionally) ---- 129 | 130 | (c_simpeff <- contrast(em_Condition_by_Gender, method = "pairwise", by = "Gender")) 131 | 132 | # Note that we have an mvt correction for each of the 2 contrasts. 133 | # We can have any other type of correction: 134 | update(c_simpeff, adjust = "bonf") 135 | update(c_simpeff, adjust = "fdr") 136 | # Or even have the corrections done on all 6 contrasts: 137 | update(c_simpeff, adjust = "bonf", by = NULL) # by = NULL removes partitioning 138 | 139 | 140 | # Same, but with custom contrasts: 141 | contrast(em_Condition_by_Gender, method = w, by = "Gender") 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | # Exercise ---------------------------------------------------------------- 152 | 153 | # A. Add `Phobia` as a predictor in the Condition*Gender ANOVA (making it a 154 | # 3-way between-subjects ANOVA) 155 | # B. What is the effect size of the Gender:Phobia interaction? 156 | # C. Explore the 2-way interaction between Condition:Phobia: 157 | # - Plots 158 | # - Simple effects 159 | # - Simple effect contrasts (use custom contrasts) 160 | # Interpret your results along the way... 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /03 Main and simple effects analysis/Main and simple effects.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /03 Main and simple effects analysis/Phobia.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/03 Main and simple effects analysis/Phobia.rds -------------------------------------------------------------------------------- /04 Interaction analysis/01 Interaction contrasts.R: -------------------------------------------------------------------------------- 1 | 2 | 3 | library(afex) # for ANOVA 4 | library(emmeans) # for follow up analysis 5 | library(ggeffects) # for plotting 6 | 7 | 8 | 9 | 10 | 11 | 12 | # let's look at the coffee_plot.png and get a feel for our data. 13 | # 14 | # What marginal (main) effects and interactions does it look like we have here? 15 | # What conditional (simple) effects does it look like we have here? 16 | 17 | coffee_data <- read.csv('coffee.csv') 18 | coffee_data$time <- factor(coffee_data$time, levels = c('morning', 'noon', 'afternoon')) 19 | head(coffee_data) 20 | 21 | 22 | 23 | 24 | coffee_fit <- aov_ez('ID', 'alertness', coffee_data, 25 | within = c('time', 'coffee'), 26 | between = 'sex', 27 | anova_table = list(es = "pes")) 28 | 29 | coffee_fit 30 | # what's up with the 3-way interaction?? 31 | 32 | 33 | ggemmeans(coffee_fit, c("time", "coffee", "sex")) |> 34 | plot(connect.lines = TRUE) 35 | 36 | 37 | 38 | 39 | # We will be looking at the Coffee-by-Time interaction. 40 | 41 | 42 | 43 | # === NOTE === 44 | # The model we used here is an ANOVA - but what follows is applicable to any 45 | # type of multiple regression with categorical predictors. 46 | 47 | 48 | 49 | 50 | # Explore the **simple effect** for `time` by `coffee` -------------------- 51 | # We are looking at a 2-way interaction in a 3-way design, so this means we are 52 | # averaging over the levels sex. 53 | 54 | 55 | ggemmeans(coffee_fit, c("time", "coffee")) |> 56 | plot(connect.lines = TRUE, facet = TRUE) 57 | 58 | 59 | 60 | ## Test simple effects ======== 61 | # We can break down an interaction into simple effects: 62 | # ("by" can be a vector for simple-simple effects, etc...) 63 | joint_tests(coffee_fit, by = "coffee") 64 | # Which rows are we looking at? 65 | 66 | 67 | 68 | 69 | 70 | ## Contrast Analysis ======== 71 | 72 | ### Step 1. Get the means -------- 73 | em_time.coffee <- emmeans(coffee_fit, ~ time + coffee) 74 | em_time.coffee 75 | 76 | 77 | 78 | 79 | 80 | ### Step 2. Compare them (conditionally) -------- 81 | # We want to look at the simple effects for time, conditionally on values of 82 | # coffee. So we must use "by"! 83 | 84 | # Here too we can use both types of methods: 85 | contrast(em_time.coffee, method = "consec", by = "coffee") # note p-value correction 86 | contrast(em_time.coffee, method = "poly", by = "coffee") # note p-value correction 87 | 88 | 89 | 90 | w.time <- data.frame( 91 | "wakeup vs later" = c(-2, 1, 1) / 2, # make sure each "side" sums to (+/-)1! 92 | "start vs end of day" = c(-1, 0, 1) 93 | ) 94 | w.time # Are these orthogonal contrasts? 95 | cor(w.time) 96 | 97 | contrast(em_time.coffee, method = w.time, by = "coffee") 98 | 99 | 100 | 101 | 102 | 103 | 104 | # Follow-Up: Interaction Contrasts ---------------------------------------- 105 | 106 | # After seeing the conditional contrasts - the contrasts for the effect of time 107 | # within the levels of coffee, we can now ask: do these contrasts DIFFER BETWEEN 108 | # the levels of coffee? 109 | 110 | 111 | contrast(em_time.coffee, interaction = list(time = "consec", coffee = "pairwise")) 112 | 113 | # Here too we can use custom contrasts: 114 | contrast(em_time.coffee, interaction = list(time = w.time, coffee = "pairwise")) 115 | 116 | 117 | # How do we interpret these? 118 | 119 | 120 | 121 | 122 | 123 | 124 | # These steps can be used for higher-order interactions as well. For example, 125 | # for a 3-way interaction we can: 126 | # - Look at the *simple* 2-way interactions. 127 | # - Look at the *simple simple* effect. 128 | # - Conduct a contrast analysis for the *simple simple* effect. 129 | # - Conduct an interaction contrast for the *simple* 2-way interactions. 130 | # - Conduct an interaction contrast for the 3-way interactions. 131 | 132 | # Same for 4-way interactions... etc. 133 | 134 | 135 | 136 | # Exercise ---------------------------------------------------------------- 137 | 138 | 139 | # Explore the sex-by-time interaction using all the steps from above. 140 | # Answer these questions: 141 | # A. Which sex is the most alert in the morning? 142 | # B. What is the difference between noon and the afternoon for males? 143 | # C. Is this difference larger than the same difference for females? 144 | # Interpret your results along the way... 145 | # 146 | # 147 | # *. Confirm (w/ contrasts, simple effects...) that there really is no 3-way 148 | # interaction in the coffee data. 149 | 150 | 151 | -------------------------------------------------------------------------------- /04 Interaction analysis/02 removable interactions.R: -------------------------------------------------------------------------------- 1 | library(afex) 2 | library(emmeans) 3 | 4 | stroop_data <- readRDS("dyslexia_stroop.rds") 5 | head(stroop_data) 6 | # This is (fake) data from an experiment where participants with dyslexia and 7 | # control participants performed a stroop task. 8 | 9 | 10 | fit <- aov_ez("id", "mRT", stroop_data, 11 | within = "condition", 12 | between = "Group") 13 | fit 14 | # We have an interaction! Let's take a look... 15 | 16 | afex_plot(fit, ~ condition, ~ Group) 17 | # Looks like the Ss with dyslexia show larger stoop effects (compared to 18 | # controls). But there is an alternative explanation, as this is a "removable 19 | # interaction" - also known as an ordinal interaction. 20 | # 21 | # Read more: https://doi.org/10.3758/s13421-011-0158-0 22 | 23 | 24 | 25 | 26 | 27 | # Difference of differences ----------------------------------------------- 28 | 29 | 30 | emmip(fit, Group ~ condition, CIs = TRUE) 31 | # Looks like an interaction to me... 32 | 33 | 34 | ## 1. Get conditional means 35 | em_ <- emmeans(fit, ~ condition + Group) 36 | 37 | 38 | ## 2. Contrasts 39 | # 2a. Pairwise differences between the conditions, by group: 40 | c_cond_by_group <- contrast(em_, "pairwise", by = "Group") 41 | c_cond_by_group 42 | 43 | # 2b. Pairwise differences between the groups by pairwise contrasts: 44 | c_diff_of_diff <- contrast(c_cond_by_group, "pairwise", by = "contrast") 45 | c_diff_of_diff 46 | 47 | 48 | 49 | # # Note that we could have just done: 50 | # contrast(em_, interaction = list(Group = "pairwise", 51 | # condition = "pairwise")) 52 | # # Which gives the same results. 53 | 54 | 55 | 56 | 57 | # Difference of ratios ---------------------------------------------------- 58 | 59 | # But we can also compare RATIOS! 60 | # The is, instead of asking if {RT1 - RT2} is different than 0, 61 | # We ask if {RT1 / RT2} is different than 1. 62 | # 63 | # We do this by looking at the differences between the the log(emmeans), since: 64 | # exp(log(x) - log(y)) == x / y 65 | 66 | 67 | # Will this matter? 68 | emmip(fit, Group ~ condition, CIs = TRUE, trans = "log") 69 | # Where did the interaction go?? 70 | 71 | 72 | ## 1. Get conditional means 73 | # (Same as above.) 74 | 75 | 76 | ## 2. Contrasts 77 | # 2a1. Transform to log scale: 78 | em_log <- regrid(em_, trans = "log", predict.type = "response") 79 | # 2a2. Pairwise differences between the log of conditions, by group: 80 | c_cond_by_group_log <- contrast(em_log, "pairwise", by = "Group") 81 | c_cond_by_group_log 82 | 83 | # 2b1. Transform back to response scale: 84 | c_cond_by_group_ratio <- regrid(c_cond_by_group_log, 85 | trans = "response") 86 | # 2b2. Pairwise differences between the groups by pairwise ratio: 87 | c_diff_of_ratio <- contrast(c_cond_by_group_ratio, "pairwise", 88 | by = "contrast") 89 | c_diff_of_ratio 90 | 91 | 92 | # The difference of ratios is not significant!! 93 | # This result might suggest that the increased effect in the dyslexia group 94 | # is due to the slower overall RTs... 95 | 96 | 97 | 98 | 99 | # Ratio of ratios --------------------------------------------------------- 100 | 101 | # We can also compare the pairwise ratios by THEIR ratio. 102 | 103 | 104 | ## 1. Get conditional means 105 | # (Same as above) 106 | 107 | ## 2. Contrasts 108 | # 2a. Pairwise differences between the log of conditions, by group: 109 | # (Same as above) 110 | 111 | # 2b1. Pairwise ratio between the groups by pairwise ratio: 112 | c_diff_of_diff_log <- contrast(c_cond_by_group_log, "pairwise", 113 | by = "contrast") 114 | c_diff_of_diff_log 115 | 116 | 117 | 118 | 119 | 120 | # Summary ----------------------------------------------------------------- 121 | 122 | # 1. In this example, difference of ratios and ratio of ratios give similar 123 | # results, but this is never guaranteed! 124 | # 2. Even if the results were still significant on the log scale, it is still 125 | # possible that the interaction is removable! All this does is alleviate the 126 | # concern... somewhat... 127 | # 3. Note that the opposite can also happen - where a non-significant 128 | # interaction can be significant on the log scale. For example, when analyzing 129 | # mean accuracy (which you should be doing with glm / glmms). 130 | # See examples: 131 | # https://shouldbewriting.netlify.app/posts/2020-04-13-estimating-and-testing-glms-with-emmeans/ 132 | 133 | -------------------------------------------------------------------------------- /04 Interaction analysis/Interaction analysis.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /04 Interaction analysis/coffee.csv: -------------------------------------------------------------------------------- 1 | "ID","sex","time","coffee","alertness" 2 | 1,"male","morning","coffee",9.04926279664074 3 | 2,"male","morning","coffee",0 4 | 3,"male","morning","coffee",14.3880043515267 5 | 4,"male","morning","coffee",7.25876043148855 6 | 5,"male","morning","coffee",6.64294870399811 7 | 6,"male","morning","coffee",8.18514821598769 8 | 7,"male","morning","coffee",5.5976940009979 9 | 8,"male","morning","coffee",8.06752073260462 10 | 9,"male","morning","coffee",11.3353799990634 11 | 10,"male","morning","coffee",4.71270218999507 12 | 1,"male","morning","control",11.9565376209964 13 | 2,"male","morning","control",10.7008017034285 14 | 3,"male","morning","control",11.0797598620783 15 | 4,"male","morning","control",8.99620548700694 16 | 5,"male","morning","control",3.24171718197392 17 | 6,"male","morning","control",0.905772542265033 18 | 7,"male","morning","control",3.68595168274938 19 | 8,"male","morning","control",9.77951053434944 20 | 9,"male","morning","control",9.34978342375563 21 | 10,"male","morning","control",5.54138138369911 22 | 1,"male","noon","coffee",7.0377926251922 23 | 2,"male","noon","coffee",12.5766652545643 24 | 3,"male","noon","coffee",15.102374838249 25 | 4,"male","noon","coffee",15.8296030655653 26 | 5,"male","noon","coffee",10.3897758769037 27 | 6,"male","noon","coffee",12.870738019443 28 | 7,"male","noon","coffee",14.9728816882846 29 | 8,"male","noon","coffee",4.22400430959299 30 | 9,"male","noon","coffee",8.09525478702127 31 | 10,"male","noon","coffee",12.6915208402736 32 | 1,"male","noon","control",22.6533946978498 33 | 2,"male","noon","control",22.141254411415 34 | 3,"male","noon","control",14.4542363661699 35 | 4,"male","noon","control",15.6693475797786 36 | 5,"male","noon","control",16.9220764711679 37 | 6,"male","noon","control",25.9128941230761 38 | 7,"male","noon","control",21.0438690310327 39 | 8,"male","noon","control",16.6689613383204 40 | 9,"male","noon","control",15.0827203605676 41 | 10,"male","noon","control",20.3482366912867 42 | 1,"male","afternoon","coffee",18.047181784716 43 | 2,"male","afternoon","coffee",21.7120538360993 44 | 3,"male","afternoon","coffee",20.4110134325031 45 | 4,"male","afternoon","coffee",20.1321878494987 46 | 5,"male","afternoon","coffee",13.3182787765747 47 | 6,"male","afternoon","coffee",16.3518304778304 48 | 7,"male","afternoon","coffee",21.4123054904542 49 | 8,"male","afternoon","coffee",26.6215954627769 50 | 9,"male","afternoon","coffee",14.7045897218575 51 | 10,"male","afternoon","coffee",18.1859542383539 52 | 1,"male","afternoon","control",24.3045523521716 53 | 2,"male","afternoon","control",15.7458514951095 54 | 3,"male","afternoon","control",20.2067523059979 55 | 4,"male","afternoon","control",21.3551255391299 56 | 5,"male","afternoon","control",25.0865799368015 57 | 6,"male","afternoon","control",22.5346699894076 58 | 7,"male","afternoon","control",20.2901589329708 59 | 8,"male","afternoon","control",26.5982747145844 60 | 9,"male","afternoon","control",23.7267043467589 61 | 10,"male","afternoon","control",29.6015113405198 62 | 11,"female","morning","coffee",28.3341689125977 63 | 12,"female","morning","coffee",27.4026077615175 64 | 13,"female","morning","coffee",25.3538179538347 65 | 14,"female","morning","coffee",17.0544814057436 66 | 15,"female","morning","coffee",29.9728050186503 67 | 16,"female","morning","coffee",28.2227776608634 68 | 17,"female","morning","coffee",30 69 | 18,"female","morning","coffee",27.2283164600337 70 | 19,"female","morning","coffee",24.8586574893967 71 | 20,"female","morning","coffee",29.5757381736017 72 | 11,"female","morning","control",19.7622980046991 73 | 12,"female","morning","control",9.24193918349914 74 | 13,"female","morning","control",17.4601201033472 75 | 14,"female","morning","control",11.2201472099593 76 | 15,"female","morning","control",16.9600779779369 77 | 16,"female","morning","control",19.2392540889677 78 | 17,"female","morning","control",18.7485985915765 79 | 18,"female","morning","control",15.4497886781566 80 | 19,"female","morning","control",13.8062358989231 81 | 20,"female","morning","control",10.4553414508119 82 | 11,"female","noon","coffee",14.1682609438773 83 | 12,"female","noon","coffee",19.4713738309308 84 | 13,"female","noon","coffee",18.9248010536558 85 | 14,"female","noon","coffee",20.7686675711773 86 | 15,"female","noon","coffee",15.9669038143905 87 | 16,"female","noon","coffee",21.5640067519636 88 | 17,"female","noon","coffee",24.343937785037 89 | 18,"female","noon","coffee",24.9939059614897 90 | 19,"female","noon","coffee",15.6724999093305 91 | 20,"female","noon","coffee",15.0226334488122 92 | 11,"female","noon","control",7.66117918830564 93 | 12,"female","noon","control",17.1341031145166 94 | 13,"female","noon","control",19.0647345856461 95 | 14,"female","noon","control",13.6442857021166 96 | 15,"female","noon","control",16.8637938782559 97 | 16,"female","noon","control",20.0727754208264 98 | 17,"female","noon","control",16.1508621574358 99 | 18,"female","noon","control",16.8729654255025 100 | 19,"female","noon","control",14.7195645080243 101 | 20,"female","noon","control",10.1595372072475 102 | 11,"female","afternoon","coffee",12.2583768430558 103 | 12,"female","afternoon","coffee",15.594740867745 104 | 13,"female","afternoon","coffee",22.9552724960911 105 | 14,"female","afternoon","coffee",18.2665065232265 106 | 15,"female","afternoon","coffee",19.9088742859835 107 | 16,"female","afternoon","coffee",22.0085614378659 108 | 17,"female","afternoon","coffee",15.2214885864171 109 | 18,"female","afternoon","coffee",18.3941697258347 110 | 19,"female","afternoon","coffee",24.0796252303055 111 | 20,"female","afternoon","coffee",22.2093750741395 112 | 11,"female","afternoon","control",14.4521660393042 113 | 12,"female","afternoon","control",7.27999061276945 114 | 13,"female","afternoon","control",18.6962100512082 115 | 14,"female","afternoon","control",10.2433241654427 116 | 15,"female","afternoon","control",11.797250147972 117 | 16,"female","afternoon","control",12.6190775302071 118 | 17,"female","afternoon","control",13.2220729785113 119 | 18,"female","afternoon","control",9.16167052354407 120 | 19,"female","afternoon","control",4.8476059491702 121 | 20,"female","afternoon","control",11.4712433069609 122 | -------------------------------------------------------------------------------- /04 Interaction analysis/coffee_plot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/04 Interaction analysis/coffee_plot.png -------------------------------------------------------------------------------- /04 Interaction analysis/dyslexia_stroop.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/04 Interaction analysis/dyslexia_stroop.rds -------------------------------------------------------------------------------- /05 Effect sizes and multiple comparisons/Effect sizes and multiple comparisons.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /05 Effect sizes and multiple comparisons/Effect sizes.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(emmeans) 4 | library(effectsize) 5 | 6 | afex_options(es_aov = 'pes', 7 | correction_aov = 'GG', 8 | emmeans_model = 'multivariate') 9 | 10 | 11 | 12 | # Load Data --------------------------------------------------------------- 13 | 14 | 15 | data(obk.long) 16 | 17 | ?obk.long 18 | 19 | head(obk.long) 20 | 21 | 22 | 23 | # Fit ANOVA model --------------------------------------------------------- 24 | 25 | # for this example we will test the effects for treatment * phase (time): 26 | treatment_aov <- aov_ez("id", "value", obk.long, 27 | between = "treatment", 28 | within = "phase") 29 | treatment_aov 30 | 31 | afex_plot(treatment_aov, ~ phase, ~ treatment) 32 | 33 | 34 | 35 | 36 | 37 | # 1. Effect size for ANOVA table ------------------------------------------ 38 | 39 | # We can use the various functions from `effectsize`, which also return 40 | # confidence intervals, such as the various Eta-squares: 41 | eta_squared(treatment_aov) 42 | eta_squared(treatment_aov, partial = FALSE) 43 | eta_squared(treatment_aov, generalized = TRUE) 44 | 45 | # But also the Omega and Epsilon Squared: 46 | omega_squared(treatment_aov) 47 | epsilon_squared(treatment_aov) 48 | # Note that these CAN BE negative; even though this doesn't make any practical 49 | # sense, it is recommended to report the negative value and not a 0. 50 | 51 | 52 | 53 | # Read more about these here: 54 | # https://easystats.github.io/effectsize/articles/anovaES.html 55 | 56 | 57 | 58 | 59 | # 2. Effect size for simple effects --------------------------------------- 60 | 61 | # The effect sizes above use the effect's sums-of-squares (SSs). But these are 62 | # not always readily available. In such cases we can use shortcuts, based on 63 | # tests statistics. 64 | 65 | 66 | ## For simple effects 67 | (jt_treatment <- joint_tests(treatment_aov, by = "treatment")) 68 | F_to_eta2(jt_treatment$F.ratio, jt_treatment$df1, jt_treatment$df2) 69 | 70 | 71 | # We can put it all together with `dplyr`: 72 | joint_tests(treatment_aov, by = "treatment") |> 73 | mutate(F_to_eta2(F.ratio, df1, df2)) 74 | 75 | 76 | # Here too we can use 77 | # F_to_epsilon2() 78 | # F_to_omega2() 79 | # etc... 80 | 81 | 82 | # But note that these shortcuts only apply to the *partial* effect sizes. 83 | 84 | 85 | 86 | 87 | 88 | # 3. For contrasts -------------------------------------------------------- 89 | 90 | 91 | ### Eta and friends: 92 | em_phase <- emmeans(treatment_aov, ~ phase) 93 | c_phase <- contrast(em_phase, method = "pairwise") 94 | 95 | c_phase 96 | # Here we have the raw differences. 97 | # But sometimes we want (why?) standardized differences. 98 | 99 | # For that we need the standardizing factor - sigma (this is akin to the 100 | # pooled-sd in Cohen's d), and it's df. 101 | # We can get both from out anova table! 102 | 103 | # Sigma is the sqrt(MSE) of the relevant effect: 104 | sig <- sqrt(treatment_aov$anova_table["phase", "MSE"]) 105 | sig.df <- treatment_aov$anova_table["phase", "den Df"] 106 | 107 | # We can then use the eff_size() function to convert our contrasts to 108 | # standardized differences: 109 | eff_size(c_phase, method = "identity", 110 | sigma = sig, edf = sig.df) 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /05 Effect sizes and multiple comparisons/Multiple comparisons.R: -------------------------------------------------------------------------------- 1 | library(afex) 2 | library(emmeans) 3 | 4 | afex_options(es_aov = 'pes', 5 | correction_aov = 'GG', 6 | emmeans_model = 'univariate') 7 | 8 | 9 | 10 | # Load data --------------------------------------------------------------- 11 | 12 | Phobia_data <- readRDS("Phobia.rds") 13 | 14 | head(Phobia_data) 15 | 16 | 17 | 18 | 19 | # Fit model --------------------------------------------------------------- 20 | 21 | 22 | fit <- aov_ez("ID", "BehavioralAvoidance", Phobia_data, 23 | between = c("Gender", "Phobia", "Condition")) 24 | fit 25 | 26 | 27 | 28 | afex_plot(fit, ~ Condition, ~ Phobia, ~ Gender) 29 | 30 | # Update contrasts ---------------------------------------------------------- 31 | 32 | em_all.means <- emmeans(fit, ~ Condition + Phobia) 33 | c_cond.by.pho <- contrast(em_all.means, method = "pairwise", by = "Phobia") 34 | 35 | c_cond.by.pho 36 | # Note that we automatically get p-value correction for 3 tests (within each 37 | # level of Phobia). By default we get Tukey (different contrast methods have 38 | # different default correction methods), but we can use other types: 39 | 40 | update(c_cond.by.pho, adjust = "none") 41 | update(c_cond.by.pho, adjust = "holm") 42 | update(c_cond.by.pho, adjust = "fdr") 43 | 44 | ?p.adjust # more? 45 | 46 | # We can also have the correction applied to all contrasts (not just in groups 47 | # of 3): 48 | update(c_cond.by.pho, adjust = "holm", by = NULL) 49 | 50 | # Or split in some other way: 51 | update(c_cond.by.pho, adjust = "fdr", by = "contrast") 52 | 53 | 54 | 55 | 56 | 57 | # Combine contrasts ------------------------------------------------------- 58 | 59 | # Let's explore! 60 | 61 | em_Gender <- emmeans(fit, ~ Gender) 62 | em_Phobia <- emmeans(fit, ~ Phobia) 63 | em_Condition <- emmeans(fit, ~ Condition) 64 | 65 | 66 | c_Gender <- contrast(em_Gender, "pairwise") 67 | c_Gender 68 | 69 | c_Phobia <- contrast(em_Phobia, "consec") 70 | c_Phobia 71 | 72 | c_Condition <- contrast(em_Condition, "pairwise") 73 | c_Condition 74 | 75 | 76 | # Combine tests and adjust p vlaues for ALL OF THEM: 77 | rbind(c_Condition, c_Gender, c_Phobia) # default to bonferroni 78 | rbind(c_Condition, c_Gender, c_Phobia, adjust = "none") 79 | rbind(c_Condition, c_Gender, c_Phobia, adjust = "fdr") 80 | # How are these affected? 81 | # What about {Implosion - CBT}? 82 | 83 | 84 | 85 | # Adjust p-values not from `emmeans` -------------------------------------- 86 | 87 | # Under the hood, `emmeans` uses the `p.adjust()` function, that can be used for 88 | # adjusting any vector of p-values, using several methods: 89 | 90 | ps <- c(0.3327, 0.0184, 0.1283, 0.0004, 91 | 0.2869, 0.1815, 0.1593, 0.0938, 0.0111) 92 | 93 | p.adjust(ps, method = "bonferroni") 94 | p.adjust(ps, method = "fdr") 95 | 96 | 97 | # HW ---------------------------------------------------------------------- 98 | 99 | # 1. Compute the following contrasts for the Phobia-by-Condition 100 | # interaction: Polynomial contrasts between all levels of Phobia within each 101 | # Condition. 102 | # 2. Use 2 adjusment methods (none / bonferroni / tukey / fdr). 103 | -------------------------------------------------------------------------------- /05 Effect sizes and multiple comparisons/Phobia.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/05 Effect sizes and multiple comparisons/Phobia.rds -------------------------------------------------------------------------------- /06 assumption check and non-parametric tests/01 assumption_tests.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(ggeffects) # for partial residual plots 4 | library(performance) # for check_* 5 | 6 | # Fit the ANOVA model 7 | data(obk.long, package = "afex") 8 | 9 | fit <- aov_ez('id', 'value', obk.long, 10 | between = c('treatment', 'gender'), 11 | within = c('phase', 'hour'), 12 | covariate = "age", factorize = FALSE) # Fitting an ANCOVA 13 | 14 | # As ANOVAs are a special case of linear regression (*), it has the same 15 | # assumptions as linear regression. These assumptions can generally be split 16 | # into two: 17 | # - Assumptions of the Model 18 | # - Assumptions of the Significant tests 19 | 20 | 21 | 22 | # Assumptions of the Model ------------------------------------------------ 23 | 24 | # These assumptions are related to the *fit* of your model. But before these, 25 | # there is one assumption that cannot be checked - that you are fitting the 26 | # right KIND of model! 27 | # Are you fitting a linear model to binary data? Are you fitting an ordinal 28 | # regression to a scale outcome? This will necessarily be a bad fit... If the 29 | # answer to any of these is yes, you should concider moving on to GLMMs. 30 | 31 | 32 | ## 1. "Linearity" ------------------------------- 33 | 34 | # Linear regression has this assumption, but for ANOVA this usually isn't 35 | # needed. Why? Because all variables are categorical - they are "points" not 36 | # part of some "line". 37 | # However, if we have a continuous covariate (in an ANCOVA), we should check the 38 | # linearity of the covariate. 39 | 40 | 41 | ggemmeans(fit, c("age", "phase", "hour")) |> 42 | plot(residuals = TRUE, residuals.line = TRUE) 43 | 44 | 45 | 46 | 47 | 48 | ## 2. No Collinearity --------------------------- 49 | 50 | # You may have heard that while regression can tolerate low collinearity, ANOVA cannot 51 | # tolerate ANY collinearity. Strictly speaking, this is not true - the ANOVA model will fit just fine, it will produce 52 | # correct estimates, etc. 53 | # What will be a problem is OUR interpretation of the effects. Instead of being 54 | # the "effect of A on Y", we will need to interpret our effects as we would in 55 | # a regression model: "the UNIQUE effect of A on Y". Bummer. 56 | 57 | check_collinearity(fit) 58 | 59 | # Not looking good... 60 | # Seems like the "age" covariable is causing some trouble. Do we really need it? 61 | 62 | 63 | 64 | 65 | # Assumptions of the Significance tests ----------------------------------- 66 | 67 | 68 | # Generally speaking, these assumptions are what allows us to convert Z and t 69 | # values into probabilities (p-values). So any violation of these assumptions 70 | # reduces the validity of our sig-tests. 71 | # 72 | # One assumption that all models have in common it that the prediction errors / 73 | # residuals are independent of one another. When this assumption is violated it 74 | # is sometimes called "autocorrelation". This assumption is hard to test, and it 75 | # is usually easier to use knowledge about the data - for example, if we have a 76 | # repeated measures design, or a nested design, then there is some dependency 77 | # between the observations and we would therefor want to account for this by 78 | # using a within/mixed ANOVA. 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | ## 1. Homogeneity of Variance ------------------- 88 | # AKA Homoscedasticity 89 | 90 | 91 | # (Note that this assumption is only relevant if we have between-subject groups 92 | # in our design.) 93 | check_homogeneity(fit) 94 | 95 | # A more general version of this assumption is that of heteroskedasticity: 96 | check_heteroskedasticity(fit) 97 | 98 | # >>> What to do if violated? <<< 99 | # Switch to non-parametric tests! 100 | 101 | 102 | 103 | 104 | 105 | 106 | ## 1b. Sphericity ------------------------------- 107 | 108 | # For within-subject conditions, we have an additional assumption, that of 109 | # sphericity. 110 | check_sphericity(fit) 111 | 112 | 113 | 114 | # >>> What to do if violated? <<< 115 | # - Use the Greenhouse-Geisser correction in the ANOVA table. 116 | # - For contrasts, use the multivariate option. 117 | # It's that easy! 118 | 119 | 120 | 121 | 122 | 123 | ## 1. Normality (of residuals) ------------------ 124 | 125 | # The least important assumption. Mild violations can be tolerated (but not if 126 | # they suggest that the wrong kind of model was fitted!) 127 | 128 | 129 | # Shapiro-Wilk test for the normality (of THE RESIDUALS!!!) 130 | normtest <- check_normality(fit) 131 | 132 | # But you should really LOOK at the residuals: 133 | plot(normtest, type = "qq", detrend = FALSE) 134 | 135 | parameters::describe_distribution(residuals(fit)) # Skewness & Kurtosis 136 | 137 | 138 | 139 | 140 | 141 | # >>> What to do if violated? <<< 142 | # This means that we shouldn't have used a Gaussian likelihood function (the 143 | # normal distribution) in our model - so we can: 144 | # 1. Try using a better one (using GLMMs)... A skewed or heavy tailed likelihood 145 | # function, or a completely different model family. Or... 146 | # 2. Switch to non-parametric tests! 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /06 assumption check and non-parametric tests/02 permutation analyses.R: -------------------------------------------------------------------------------- 1 | library(permuco) # for permutations 2 | citation("permuco") 3 | 4 | 5 | # MUST do this! 6 | options(contrasts = c('contr.sum', 'contr.poly')) 7 | 8 | data(obk.long, package = "afex") 9 | 10 | # Between-Subject Models -------------------------------------------------- 11 | 12 | 13 | fit_between_p <- aovperm(value ~ treatment * gender, 14 | data = obk.long) 15 | fit_between_p 16 | 17 | 18 | 19 | # Within-Subject Models --------------------------------------------------- 20 | 21 | # load data 22 | 23 | 24 | 25 | fit_within_p <- aovperm(value ~ phase * hour + Error(id / (phase * hour)), 26 | data = obk.long) 27 | fit_within_p 28 | 29 | 30 | # Mixed ------------------------------------------------------------------- 31 | 32 | 33 | fit_mixed_p <-aovperm(value ~ treatment * gender * phase * hour + 34 | Error(id / (phase * hour)), 35 | data = obk.long) 36 | fit_mixed_p 37 | 38 | 39 | # Read more --------------------------------------------------------------- 40 | 41 | # https://davegiles.blogspot.com/2019/04/what-is-permutation-test.html 42 | 43 | -------------------------------------------------------------------------------- /06 assumption check and non-parametric tests/03 Contrast Bootstrap.R: -------------------------------------------------------------------------------- 1 | library(emmeans) 2 | library(car) # for Boot 3 | 4 | # Bootstrapping is hard. Like, really really hard. 5 | # 6 | # Here I tried to write some code that you could adjust and re-use, if ever you 7 | # find yourself in the need. 8 | # 9 | # This code computes the bootstrap estimates and confidence intervals using the 10 | # The Percentile Bootstrap Method, the most common method (though not the only 11 | # one). You can read and cite more in: Wilcox, R. R. (2011). Introduction to 12 | # robust estimation and hypothesis testing (pp. 115-118). Academic press. 13 | 14 | 15 | data(obk.long, package = "afex") 16 | 17 | # Between Subject Models -------------------------------------------------- 18 | 19 | ## 1. Fit regular anova with `aov` 20 | fit_between <- aov(value ~ treatment * gender, 21 | data = obk.long, 22 | # MUST do this! 23 | contrasts = list(treatment = "contr.sum", 24 | gender = "contr.sum")) 25 | 26 | 27 | 28 | ## 2. Make function for contrast 29 | gender_treatment_boot <- function(.) { 30 | em_ <- emmeans(., ~ gender * treatment) 31 | c_ <- contrast(em_, 'pairwise', by = 'treatment') 32 | 33 | t_ <- summary(c_)$estimate 34 | # t_ <- summary(c_)$t.ratio 35 | 36 | return(t_) 37 | } 38 | 39 | gender_treatment_boot(fit_between) # test that it works 40 | 41 | 42 | 43 | ## 3. run bootstrap 44 | gender_treatment_boot_result <- 45 | Boot(fit_between, gender_treatment_boot, 46 | # For real analyses, use `R = 599` at least for alpha of 5%!! 47 | # (See Wilcox, 2011, p. 119) 48 | R = 10) 49 | 50 | 51 | 52 | 53 | ## 4. confidence intervals 54 | # original vs. bootstrapped estimate (bootMed) NOTE R < 50! Why? 55 | summary(gender_treatment_boot_result) 56 | confint(gender_treatment_boot_result, type = "bca") # does include zero? 57 | 58 | 59 | 60 | 61 | # Within-Subject/Mixed Models --------------------------------------------- 62 | 63 | # Although it is possible to do this with ANOVA, is it MUCH easier to do 64 | # this with LMM. See: 65 | # https://shouldbewriting.netlify.com/posts/2019-08-14-bootstrapping-rm-contrasts2/ 66 | 67 | 68 | ## 1. Fit regular anova with `mixed` 69 | library(afex) 70 | fit_lmm <- mixed(value ~ phase * hour + (phase * hour||id), 71 | expand_re = TRUE, 72 | data = obk.long) 73 | anova(fit_lmm) 74 | 75 | 76 | 77 | ## 2. Make function for contrast 78 | phase_hour_boot <- function(.) { 79 | em_ <- emmeans(., ~ phase * hour) 80 | c_ <- contrast(em_, 'poly', by = 'phase', max.degree = 2) 81 | 82 | t_ <- summary(c_)$estimate 83 | # t_ <- summary(c_)$t.ratio 84 | 85 | return(t_) 86 | } 87 | 88 | phase_hour_boot(fit_lmm) # test that it works 89 | 90 | 91 | 92 | ## 3. run bootstrap 93 | phase_hour_boot_result <- 94 | bootMer(fit_lmm$full_model, phase_hour_boot, 95 | # For real analyses, use `nsim = 599` at least for alpha of 5%!! 96 | # (See Wilcox, 2011, p. 119) 97 | nsim = 10, 98 | .progress = "txt") 99 | 100 | 101 | 102 | 103 | ## 4. confidence intervals 104 | # original vs. bootstrapped estimate (bootMed) NOTE R < 50! Why? 105 | summary(phase_hour_boot_result) 106 | confint(phase_hour_boot_result, type = "perc") # does include zero? 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /06 assumption check and non-parametric tests/assumption check and non-parametric tests.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /06 assumption check and non-parametric tests/obk_between.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/06 assumption check and non-parametric tests/obk_between.rds -------------------------------------------------------------------------------- /07 Accepting nulls/01 Testing null effects.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(effectsize) 4 | library(lme4) 5 | library(bayestestR) 6 | 7 | # Load data --------------------------------------------------------------- 8 | 9 | 10 | Alcohol_data <- readRDS("Alcohol_data.rds") |> 11 | # Looking only at the Frequency of interest 12 | subset(Frequency == '4to7Hz') 13 | 14 | head(Alcohol_data) 15 | 16 | 17 | 18 | 19 | # Regular ANOVA ----------------------------------------------------------- 20 | 21 | afex_options(es_aov = 'pes', 22 | correction_aov = 'GG', 23 | emmeans_model = 'univariate') 24 | 25 | fit_alcohol_theta <- aov_ez('Subject','ersp',Alcohol_data, 26 | within = c('Correctness'), 27 | between = c('Alcohol')) 28 | fit_alcohol_theta 29 | 30 | afex_plot(fit_alcohol_theta, ~ Alcohol, ~ Correctness) 31 | # Looks like no interaction. But we can't infer that based on a non-significant 32 | # p-value alone! 33 | 34 | 35 | 36 | 37 | 38 | 39 | # Method 1: equivalence testing -------------------------------------------- 40 | 41 | # We can use the effectsize package to obtain CIs for our effect sizes. 42 | # Using these CIs we can reject and non-inferiority hypothesis; i.e., that our 43 | # effect is significantly smaller than some small effect size. 44 | 45 | # We will be using the TOST approach: Two One Sided Tests (or: a single two 46 | # sided 90% CI): 47 | eta_squared(fit_alcohol_theta, alternative = "two.sided", ci = 0.90) 48 | 49 | # We can see that the upper bound for the interaction is 0.13, which is *not* 50 | # small. Thus, we cannot reject the hypothesis that the effect is non-inferior = 51 | # we cannot rule out the option that there is some non-null effect. 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | # Method 2: BIC comparisons ----------------------------------------------- 63 | 64 | # We can use the BIC (relative measure of fit) to see of removing the 65 | # interaction from our model provides with an equally good but more parsimonious 66 | # model. 67 | 68 | # Unfortunately, we cannot use an ANOVA for this - we must switch to a 69 | # regression (or in our case a mixed regression model). 70 | 71 | m_full <- lmer(ersp ~ Correctness * Alcohol + (1 | Subject), 72 | REML = FALSE, 73 | data = Alcohol_data) 74 | 75 | m_no.interaction <- lmer(ersp ~ Correctness + Alcohol + (1 | Subject), 76 | REML = FALSE, 77 | data = Alcohol_data) 78 | 79 | 80 | bayesfactor_models(m_no.interaction, denominator = m_full) 81 | 82 | # It seems like that no-interaction model is over 3000 times more supported by 83 | # the data compared to the full model, giving strong support for a lack of an 84 | # interaction! 85 | 86 | 87 | 88 | # The down side to this method is that it can only be easily applied to the 89 | # highest level effects (in out example, only to the 2-way interaction). 90 | 91 | 92 | 93 | # Method 3. GO FULL BAYES --------------------------------------------------- 94 | 95 | 96 | # There is A LOT more to be learned about Bayesian testing / estimation. 97 | # A good place to start: 98 | # - Look up `brms` 99 | # - Read here https://easystats.github.io/bayestestR/ (I might be biased) 100 | -------------------------------------------------------------------------------- /07 Accepting nulls/02 Testing null contrasts.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(emmeans) 4 | 5 | # For null contrasts we will use equivalence testing - test if an observed 6 | # effect is significantly smaller then some small effect - an effect so small we 7 | # would consider it "not interesting". 8 | # 9 | # See some of Daniel Lakens's work: 10 | # https://doi.org/10.1177/2515245918770963 11 | 12 | 13 | 14 | # Load data --------------------------------------------------------------- 15 | 16 | Alcohol_data <- readRDS("Alcohol_data.rds") |> 17 | # Looking only at the Frequency of interest 18 | dplyr::filter(Frequency == '4to7Hz') 19 | head(Alcohol_data) 20 | 21 | 22 | 23 | 24 | # Fit ANOVA --------------------------------------------------------------- 25 | 26 | afex_options(es_aov = 'pes', 27 | correction_aov = 'GG', 28 | emmeans_model = 'univariate') 29 | 30 | fit_alcohol_theta <- aov_ez('Subject','ersp',Alcohol_data, 31 | within = c('Correctness'), 32 | between = c('Alcohol')) 33 | fit_alcohol_theta 34 | 35 | afex_plot(fit_alcohol_theta, ~ Alcohol, ~ Correctness) 36 | # Looks like no interaction. But we can't infer that based on 37 | # a lack of significance. 38 | 39 | 40 | 41 | 42 | # Equivalence testing for contrasts --------------------------------------- 43 | 44 | 45 | # Q: Is the effect for {L1} vs {L5} differ between {Control + ND} vs {PFAS vs 46 | # FAS}? 47 | 48 | # Let's define those contrasts: 49 | contr.corr <- data.frame(L_effect = c(0,-1,1)) 50 | contr.alc <- data.frame("P/FAS effect" = c(1,1,-1,-1)/2) 51 | 52 | 53 | 54 | 55 | # Get conditional means: 56 | em_int <- emmeans(fit_alcohol_theta, ~ Correctness + Alcohol) 57 | 58 | 59 | # Conduct interaction-contrast analysis 60 | c_int <- contrast(em_int, interaction = list(Alcohol = contr.alc, 61 | Correctness = contr.corr)) 62 | c_int 63 | # From there results we can see that in our sample, {PFAS vs FAS} show a larger 64 | # {L1} vs {L5} effect compared to {Control + ND}. But these results are not 65 | # significantly different then 0. But are they significantly different from some 66 | # SESOI? 67 | 68 | 69 | 70 | 71 | 72 | ## 1. Define your SESOI (smallest effect size of interest) 73 | # Many ways to do this... Here I'll use a 1/10 of the dependent variables 74 | # standard deviation: 75 | (SESOI <- sd(Alcohol_data$ersp)/10) 76 | # Is this a good benchmark? Maybe... 77 | # I encorage you to this what a tiny difference would be, and not use sd/10. 78 | 79 | 80 | 81 | 82 | 83 | 84 | ## 2. Test 85 | # Use emmeans::test to test the contrasts we've built. the `delta` argument 86 | # tells `test()` that we want an equivalence test compared to this value. 87 | test(c_int, delta = SESOI) 88 | # Looks like even though our the difference is not significantly larger than 0, 89 | # it is also not significantly smaller than our SESOI. 90 | # 91 | # Note that this was a two-tailed test. See "side" argument: 92 | ?test.emmGrid 93 | 94 | 95 | 96 | 97 | # Using standardized differences ------------------------------------------ 98 | 99 | # TODO: explain these choices: 100 | sigma <- sqrt(fit_alcohol_theta$anova_table[3, "MSE"]) 101 | edf <- fit_alcohol_theta$anova_table[3, "den Df"] 102 | 103 | c_intz <- eff_size(c_int, method = "identity", 104 | sigma = sigma, 105 | edf = edf) 106 | test(c_intz, delta = 0.1) 107 | -------------------------------------------------------------------------------- /07 Accepting nulls/Accepting nulls.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: iso-8859-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | AutoAppendNewline: Yes 16 | StripTrailingWhitespace: Yes 17 | -------------------------------------------------------------------------------- /07 Accepting nulls/Alcohol_data.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/07 Accepting nulls/Alcohol_data.rds -------------------------------------------------------------------------------- /08 ANOVA and (G)LMMs/01 LMM vs ANOVA.R: -------------------------------------------------------------------------------- 1 | library(afex) 2 | library(patchwork) 3 | 4 | # In this lesson we will learn to fit LMMs, and compare them to "regular" 5 | # ANOVAs. 6 | # 7 | # The steps for fitting a model: 8 | # 1. Identify desired fixed-effects structure. 9 | # 2. Identify random factors. 10 | # 3. Identify (maximal) random-effects structure. 11 | # 4. Choose method for calculating p-values and fit maximal model. 12 | 13 | # 14 | # These are used to specify the model-formula, which has the following 15 | # structure: 16 | # DV ~ fixed_effects + (nested_random_effects | random_factor) 17 | 18 | 19 | # Regular ANOVA ----------------------------------------------------------- 20 | 21 | # Let's first first fit a regular anova to compare to: 22 | 23 | data(obk.long, package = "afex") 24 | obk.long$phase <- factor(obk.long$phase, levels = c("pre", "post", "fup")) 25 | 26 | str(obk.long) 27 | 28 | fit_aov <- aov_ez("id", "value", obk.long, 29 | within = c("phase", "hour"), 30 | between = c("gender", "treatment")) 31 | 32 | 33 | # Fit LMM ----------------------------------------------------------------- 34 | 35 | ## The steps: 36 | # 1. Identify desired fixed-effects structure 37 | # The effects are treatment, gender, phase, hour, and their interactions. 38 | # 39 | # 2. Identify random factors 40 | # The random factor is "id". 41 | # 42 | # 3. Identify (maximal) random-effects structure 43 | # phase, hour and their interaction are nested in id, so these will also get 44 | # random effects. 45 | 46 | value ~ treatment * gender * phase * hour + (phase * hour | id) 47 | 48 | 49 | # 4. Choose method for calculating p-values and fit maximal model 50 | # We will use the Satterthwaite approximation 51 | 52 | 53 | 54 | 55 | 56 | 57 | ## Fit the model with all that in mind: 58 | fit_lmm <- mixed(value ~ treatment * gender * phase * hour + (phase * hour | id), 59 | data = obk.long, 60 | method = "S") # p-value method 61 | 62 | # Why do we get an error? We do not have enough data points to also estimate the 63 | # correlation between the random effects. 64 | # 65 | # So we must ask `mixed()` not to estimate these, by 66 | # 1. Adding || instead of | in the random effects term 67 | # 2. Setting `expand_re = TRUE` 68 | 69 | fit_lmm <- mixed(value ~ treatment * gender * phase * hour + (phase * hour || id), 70 | data = obk.long, 71 | method = "S", # p-value method 72 | expand_re = TRUE) 73 | # Note that LMMs take longer to fit. 74 | 75 | 76 | 77 | 78 | 79 | # Compare ANOVA and LMM --------------------------------------------------- 80 | 81 | fit_aov 82 | fit_lmm 83 | 84 | # Note that F values, and sigs are very similar! 85 | 86 | p1 <- afex_plot(fit_aov, ~ treatment, ~ gender) 87 | p2 <- afex_plot(fit_lmm, ~ treatment, ~ gender) 88 | 89 | p1 + p2 + plot_layout(guides = "collect") 90 | 91 | 92 | 93 | 94 | # Follow-up analyses ------------------------------------------------------ 95 | 96 | # Same as with afex! 97 | 98 | library(emmeans) 99 | 100 | emm_options(lmer.df = "satterthwaite") 101 | 102 | # note we pull out the full model from the object 103 | joint_tests(fit_lmm$full_model, by = "gender") 104 | 105 | em_treat <- emmeans(fit_lmm$full_model, ~ treatment) 106 | em_treat 107 | 108 | contrast(em_treat, method = "pairwise") 109 | 110 | # Etc.... 111 | 112 | 113 | -------------------------------------------------------------------------------- /08 ANOVA and (G)LMMs/02 GLMM - analysis of accuracy.R: -------------------------------------------------------------------------------- 1 | 2 | library(afex) 3 | library(emmeans) 4 | 5 | # In this lesson, we will be examining different ways of analyzing measures that 6 | # are categorical by nature. 7 | # In psychology, one such measure is accuracy - when measured on a single trial, 8 | # it can only have two values: success or failure. These are usually coded as a 9 | # 1 or a 0 (but they could just as easily be coded as -0.9 and +34), and then 10 | # aggregated for each subject and condition by averaging all the 0's and 1's. 11 | # The result is a number ranging between 0-1, representing the mean accuracy for 12 | # that subject/condition. 13 | # As this is a number, it seems only natural to analyze this dependent variable 14 | # using ANOVAs. So let's see what that looks like. 15 | 16 | 17 | # This lesson is based on the following documentation: 18 | # http://singmann.github.io/afex/doc/afex_analysing_accuracy_data.html 19 | # It goes into further details and is worth a read. 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | # Here we have data from a 2*2 within-subject design: 29 | stroop_e1 <- readRDS("stroop_e1.rds") 30 | 31 | head(stroop_e1) 32 | # condition - (ego) deplete vs. control 33 | # congruency - of the stroop task 34 | # acc - mean accuracy per participant (id) and condition(s) 35 | # n - number of trials per participant (id) and condition(s) 36 | # (we will use these later) 37 | # (This is real data from: https://doi.org/10.1177/0956797620904990) 38 | 39 | 40 | # The question: does ego depletion moderate the classic stroop effect on 41 | # accuracy? In other words: is there a condition X congruency interaction? 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | # Analyzing with repeated measures anova ---------------------------------- 51 | # (reminder: a linear model) 52 | 53 | 54 | afex_options(correction_aov = 'GG', 55 | emmeans_model = 'multivariate', 56 | es_aov = 'pes') 57 | 58 | 59 | fit_anova <- aov_ez("id", "acc", stroop_e1, 60 | within = c("congruency", "condition")) 61 | fit_anova 62 | #> Anova Table (Type 3 tests) 63 | #> 64 | #> Response: acc 65 | #> Effect df MSE F pes p.value 66 | #> 1 congruency 1, 252 0.01 242.95 *** .491 <.001 67 | #> 2 condition 1, 252 0.00 5.43 * .021 .021 68 | #> 3 congruency:condition 1, 252 0.00 0.10 <.001 .757 69 | #> --- 70 | #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1 71 | 72 | 73 | # Hmmm... looks like there is no interaction. 74 | # We can also see this visually: 75 | afex_plot(fit_anova, ~ congruency, ~ condition) 76 | 77 | 78 | emmeans(fit_anova, ~ condition | congruency) 79 | 80 | 81 | 82 | 83 | 84 | # However... ANOVA is a type of liner model. But are accuracies linear? 85 | # It can be argued they are not! 86 | # For example: is a change from 50% to 51% the same as a change from 98% to 99%? 87 | # 88 | # We might even remember that we learned at some point that binary variables 89 | # have a binomial sampling distribution. We can see this in the plots - as the 90 | # mean accuracy is higher, the variance around it is smaller! 91 | # 92 | # Perhaps then, what we need is some type of logistic regression? A "repeated 93 | # measures" logistic regression? 94 | # We can do just that with generalized linear mixed models (GLMMs)! 95 | 96 | 97 | # Suggested reading 98 | # http://doi.org/10.1016/j.jml.2007.11.007 99 | # https://doi.org/10.1890/10-0340.1 100 | 101 | 102 | 103 | 104 | 105 | 106 | # Analyzing within GLMM --------------------------------------------------- 107 | 108 | 109 | # The syntax of a linear mixed model looks like this: 110 | # Y ~ Fixed_effects + (Random_effects | random_variable) 111 | # - The fixed effects are all of the effect of interest. 112 | # - You can think of the random effects as the "within subject" ones. 113 | # - You can think of the random variable as the unit in which the random effects 114 | # are nested - in our case, they are nested in the subjects. 115 | # (This is an oversimplification, better read up on this some more: 116 | # http://doi.org/10.4324/9780429318405-2) 117 | 118 | 119 | # In our case, the formula looks like this: 120 | acc ~ congruency * condition + (congruency * condition | id) 121 | 122 | 123 | # There are many functions for modeling (G)LMMs - fortunately, `afex` has us 124 | # covered with a convenient function: `mixed()`. 125 | # 126 | # One thing to note: we must tell `mixed()` (or any other function) the number 127 | # of trials on which the MEAN ACCURACY is based. We do that by passing this 128 | # information to "weights = " (Again, read more here: 129 | # http://singmann.github.io/afex/doc/afex_analysing_accuracy_data.html) 130 | 131 | 132 | 133 | 134 | 135 | # This can take several minutes... 136 | fit_glmm <- mixed( 137 | acc ~ congruency * condition + (congruency * condition | id), 138 | data = stroop_e1, 139 | weights = stroop_e1$n, # how many trials are the mean accuracies based on? 140 | family = "binomial", # the type of distribution 141 | method = "LRT" # this will give us the proper type 3 errors 142 | ) 143 | 144 | fit_glmm 145 | #> Mixed Model Anova Table (Type 3 tests, LRT-method) 146 | #> 147 | #> Model: acc ~ congruency * condition + (congruency * condition | id) 148 | #> Data: stroop_e1 149 | #> Df full model: 14 150 | #> Effect df Chisq p.value 151 | #> 1 congruency 1 321.05 *** <.001 152 | #> 2 condition 1 11.10 *** <.001 153 | #> 3 congruency:condition 1 4.23 * .040 154 | #> --- 155 | #> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '+' 0.1 ' ' 1 156 | 157 | # The interaction is now significant!! 158 | # (Note: We don't have F tests, but Chi-squared tests.) 159 | # 160 | # How can that be?? It is because the scale on which the model is tested is the 161 | # logistic scale - where things look somewhat different! 162 | 163 | 164 | afex_plot(fit_glmm, ~ condition, ~ congruency, CIs = TRUE) 165 | # The interaction is "gone". 166 | 167 | 168 | 169 | 170 | # Follow-up analyses ------------------------------------------------------ 171 | 172 | # Just as with an ANOVA, we can do all the same follow-ups with `emmeans`: 173 | 174 | ## 1. Simple effects: 175 | joint_tests(fit_glmm) 176 | # Note that df2 = Inf. In this case, we can compute Chisq = F.ratio * df1 177 | 178 | 179 | 180 | 181 | 182 | ## 2. Contrasts 183 | emmeans(fit_glmm, ~ condition + congruency) 184 | #> condition congruency emmean SE df asymp.LCL asymp.UCL 185 | #> control congruent 4.16 0.0731 Inf 4.02 4.31 186 | #> deplete congruent 3.87 0.0710 Inf 3.74 4.01 187 | #> control incongruent 2.35 0.0618 Inf 2.23 2.47 188 | #> deplete incongruent 2.27 0.0623 Inf 2.14 2.39 189 | #> 190 | #> Results are given on the logit (not the response) scale. 191 | #> Confidence level used: 0.95 192 | 193 | 194 | # If we want them on the response scale: 195 | em_int <- emmeans(fit_glmm, ~ condition + congruency, type = "response") 196 | em_int 197 | #> condition congruency prob SE df asymp.LCL asymp.UCL 198 | #> control congruent 0.9847 0.001103 Inf 0.9824 0.9867 199 | #> deplete congruent 0.9797 0.001415 Inf 0.9767 0.9823 200 | #> control incongruent 0.9128 0.004920 Inf 0.9026 0.9219 201 | #> deplete incongruent 0.9060 0.005304 Inf 0.8951 0.9159 202 | #> 203 | #> Confidence level used: 0.95 204 | #> Intervals are back-transformed from the logit scale 205 | 206 | 207 | # We can do any contrast we want: 208 | contrast(em_int, "pairwise", by = "condition") 209 | #> congruency = congruent: 210 | #> contrast odds.ratio SE df z.ratio p.value 211 | #> control / deplete 1.33 0.106 Inf 3.647 0.0003 212 | #> 213 | #> congruency = incongruent: 214 | #> contrast odds.ratio SE df z.ratio p.value 215 | #> control / deplete 1.09 0.070 Inf 1.275 0.2023 216 | #> 217 | #> Tests are performed on the log odds ratio scale 218 | 219 | # Note that we get the odds ratio as the estimate, and that we have z values 220 | # instead of t values. 221 | 222 | 223 | 224 | 225 | 226 | # But we can also test contrasts on the response scale... 227 | # Read more (with examples): 228 | # https://shouldbewriting.netlify.app/posts/2020-04-13-estimating-and-testing-glms-with-emmeans/ 229 | 230 | -------------------------------------------------------------------------------- /08 ANOVA and (G)LMMs/ANOVA and (G)LMMs.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | -------------------------------------------------------------------------------- /08 ANOVA and (G)LMMs/stroop_e1.rds: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/08 ANOVA and (G)LMMs/stroop_e1.rds -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial 4.0 International Public 58 | License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial 4.0 International Public License ("Public 63 | License"). To the extent this Public License may be interpreted as a 64 | contract, You are granted the Licensed Rights in consideration of Your 65 | acceptance of these terms and conditions, and the Licensor grants You 66 | such rights in consideration of benefits the Licensor receives from 67 | making the Licensed Material available under these terms and 68 | conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. Copyright and Similar Rights means copyright and/or similar rights 88 | closely related to copyright including, without limitation, 89 | performance, broadcast, sound recording, and Sui Generis Database 90 | Rights, without regard to how the rights are labeled or 91 | categorized. For purposes of this Public License, the rights 92 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 93 | Rights. 94 | d. Effective Technological Measures means those measures that, in the 95 | absence of proper authority, may not be circumvented under laws 96 | fulfilling obligations under Article 11 of the WIPO Copyright 97 | Treaty adopted on December 20, 1996, and/or similar international 98 | agreements. 99 | 100 | e. Exceptions and Limitations means fair use, fair dealing, and/or 101 | any other exception or limitation to Copyright and Similar Rights 102 | that applies to Your use of the Licensed Material. 103 | 104 | f. Licensed Material means the artistic or literary work, database, 105 | or other material to which the Licensor applied this Public 106 | License. 107 | 108 | g. Licensed Rights means the rights granted to You subject to the 109 | terms and conditions of this Public License, which are limited to 110 | all Copyright and Similar Rights that apply to Your use of the 111 | Licensed Material and that the Licensor has authority to license. 112 | 113 | h. Licensor means the individual(s) or entity(ies) granting rights 114 | under this Public License. 115 | 116 | i. NonCommercial means not primarily intended for or directed towards 117 | commercial advantage or monetary compensation. For purposes of 118 | this Public License, the exchange of the Licensed Material for 119 | other material subject to Copyright and Similar Rights by digital 120 | file-sharing or similar means is NonCommercial provided there is 121 | no payment of monetary compensation in connection with the 122 | exchange. 123 | 124 | j. Share means to provide material to the public by any means or 125 | process that requires permission under the Licensed Rights, such 126 | as reproduction, public display, public performance, distribution, 127 | dissemination, communication, or importation, and to make material 128 | available to the public including in ways that members of the 129 | public may access the material from a place and at a time 130 | individually chosen by them. 131 | 132 | k. Sui Generis Database Rights means rights other than copyright 133 | resulting from Directive 96/9/EC of the European Parliament and of 134 | the Council of 11 March 1996 on the legal protection of databases, 135 | as amended and/or succeeded, as well as other essentially 136 | equivalent rights anywhere in the world. 137 | 138 | l. You means the individual or entity exercising the Licensed Rights 139 | under this Public License. Your has a corresponding meaning. 140 | 141 | 142 | Section 2 -- Scope. 143 | 144 | a. License grant. 145 | 146 | 1. Subject to the terms and conditions of this Public License, 147 | the Licensor hereby grants You a worldwide, royalty-free, 148 | non-sublicensable, non-exclusive, irrevocable license to 149 | exercise the Licensed Rights in the Licensed Material to: 150 | 151 | a. reproduce and Share the Licensed Material, in whole or 152 | in part, for NonCommercial purposes only; and 153 | 154 | b. produce, reproduce, and Share Adapted Material for 155 | NonCommercial purposes only. 156 | 157 | 2. Exceptions and Limitations. For the avoidance of doubt, where 158 | Exceptions and Limitations apply to Your use, this Public 159 | License does not apply, and You do not need to comply with 160 | its terms and conditions. 161 | 162 | 3. Term. The term of this Public License is specified in Section 163 | 6(a). 164 | 165 | 4. Media and formats; technical modifications allowed. The 166 | Licensor authorizes You to exercise the Licensed Rights in 167 | all media and formats whether now known or hereafter created, 168 | and to make technical modifications necessary to do so. The 169 | Licensor waives and/or agrees not to assert any right or 170 | authority to forbid You from making technical modifications 171 | necessary to exercise the Licensed Rights, including 172 | technical modifications necessary to circumvent Effective 173 | Technological Measures. For purposes of this Public License, 174 | simply making modifications authorized by this Section 2(a) 175 | (4) never produces Adapted Material. 176 | 177 | 5. Downstream recipients. 178 | 179 | a. Offer from the Licensor -- Licensed Material. Every 180 | recipient of the Licensed Material automatically 181 | receives an offer from the Licensor to exercise the 182 | Licensed Rights under the terms and conditions of this 183 | Public License. 184 | 185 | b. No downstream restrictions. You may not offer or impose 186 | any additional or different terms or conditions on, or 187 | apply any Effective Technological Measures to, the 188 | Licensed Material if doing so restricts exercise of the 189 | Licensed Rights by any recipient of the Licensed 190 | Material. 191 | 192 | 6. No endorsement. Nothing in this Public License constitutes or 193 | may be construed as permission to assert or imply that You 194 | are, or that Your use of the Licensed Material is, connected 195 | with, or sponsored, endorsed, or granted official status by, 196 | the Licensor or others designated to receive attribution as 197 | provided in Section 3(a)(1)(A)(i). 198 | 199 | b. Other rights. 200 | 201 | 1. Moral rights, such as the right of integrity, are not 202 | licensed under this Public License, nor are publicity, 203 | privacy, and/or other similar personality rights; however, to 204 | the extent possible, the Licensor waives and/or agrees not to 205 | assert any such rights held by the Licensor to the limited 206 | extent necessary to allow You to exercise the Licensed 207 | Rights, but not otherwise. 208 | 209 | 2. Patent and trademark rights are not licensed under this 210 | Public License. 211 | 212 | 3. To the extent possible, the Licensor waives any right to 213 | collect royalties from You for the exercise of the Licensed 214 | Rights, whether directly or through a collecting society 215 | under any voluntary or waivable statutory or compulsory 216 | licensing scheme. In all other cases the Licensor expressly 217 | reserves any right to collect such royalties, including when 218 | the Licensed Material is used other than for NonCommercial 219 | purposes. 220 | 221 | 222 | Section 3 -- License Conditions. 223 | 224 | Your exercise of the Licensed Rights is expressly made subject to the 225 | following conditions. 226 | 227 | a. Attribution. 228 | 229 | 1. If You Share the Licensed Material (including in modified 230 | form), You must: 231 | 232 | a. retain the following if it is supplied by the Licensor 233 | with the Licensed Material: 234 | 235 | i. identification of the creator(s) of the Licensed 236 | Material and any others designated to receive 237 | attribution, in any reasonable manner requested by 238 | the Licensor (including by pseudonym if 239 | designated); 240 | 241 | ii. a copyright notice; 242 | 243 | iii. a notice that refers to this Public License; 244 | 245 | iv. a notice that refers to the disclaimer of 246 | warranties; 247 | 248 | v. a URI or hyperlink to the Licensed Material to the 249 | extent reasonably practicable; 250 | 251 | b. indicate if You modified the Licensed Material and 252 | retain an indication of any previous modifications; and 253 | 254 | c. indicate the Licensed Material is licensed under this 255 | Public License, and include the text of, or the URI or 256 | hyperlink to, this Public License. 257 | 258 | 2. You may satisfy the conditions in Section 3(a)(1) in any 259 | reasonable manner based on the medium, means, and context in 260 | which You Share the Licensed Material. For example, it may be 261 | reasonable to satisfy the conditions by providing a URI or 262 | hyperlink to a resource that includes the required 263 | information. 264 | 265 | 3. If requested by the Licensor, You must remove any of the 266 | information required by Section 3(a)(1)(A) to the extent 267 | reasonably practicable. 268 | 269 | 4. If You Share Adapted Material You produce, the Adapter's 270 | License You apply must not prevent recipients of the Adapted 271 | Material from complying with this Public License. 272 | 273 | 274 | Section 4 -- Sui Generis Database Rights. 275 | 276 | Where the Licensed Rights include Sui Generis Database Rights that 277 | apply to Your use of the Licensed Material: 278 | 279 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 280 | to extract, reuse, reproduce, and Share all or a substantial 281 | portion of the contents of the database for NonCommercial purposes 282 | only; 283 | 284 | b. if You include all or a substantial portion of the database 285 | contents in a database in which You have Sui Generis Database 286 | Rights, then the database in which You have Sui Generis Database 287 | Rights (but not its individual contents) is Adapted Material; and 288 | 289 | c. You must comply with the conditions in Section 3(a) if You Share 290 | all or a substantial portion of the contents of the database. 291 | 292 | For the avoidance of doubt, this Section 4 supplements and does not 293 | replace Your obligations under this Public License where the Licensed 294 | Rights include other Copyright and Similar Rights. 295 | 296 | 297 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 298 | 299 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 300 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 301 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 302 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 303 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 304 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 305 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 306 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 307 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 308 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 309 | 310 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 311 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 312 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 313 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 314 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 315 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 316 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 317 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 318 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 319 | 320 | c. The disclaimer of warranties and limitation of liability provided 321 | above shall be interpreted in a manner that, to the extent 322 | possible, most closely approximates an absolute disclaimer and 323 | waiver of all liability. 324 | 325 | 326 | Section 6 -- Term and Termination. 327 | 328 | a. This Public License applies for the term of the Copyright and 329 | Similar Rights licensed here. However, if You fail to comply with 330 | this Public License, then Your rights under this Public License 331 | terminate automatically. 332 | 333 | b. Where Your right to use the Licensed Material has terminated under 334 | Section 6(a), it reinstates: 335 | 336 | 1. automatically as of the date the violation is cured, provided 337 | it is cured within 30 days of Your discovery of the 338 | violation; or 339 | 340 | 2. upon express reinstatement by the Licensor. 341 | 342 | For the avoidance of doubt, this Section 6(b) does not affect any 343 | right the Licensor may have to seek remedies for Your violations 344 | of this Public License. 345 | 346 | c. For the avoidance of doubt, the Licensor may also offer the 347 | Licensed Material under separate terms or conditions or stop 348 | distributing the Licensed Material at any time; however, doing so 349 | will not terminate this Public License. 350 | 351 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 352 | License. 353 | 354 | 355 | Section 7 -- Other Terms and Conditions. 356 | 357 | a. The Licensor shall not be bound by any additional or different 358 | terms or conditions communicated by You unless expressly agreed. 359 | 360 | b. Any arrangements, understandings, or agreements regarding the 361 | Licensed Material not stated herein are separate from and 362 | independent of the terms and conditions of this Public License. 363 | 364 | 365 | Section 8 -- Interpretation. 366 | 367 | a. For the avoidance of doubt, this Public License does not, and 368 | shall not be interpreted to, reduce, limit, restrict, or impose 369 | conditions on any use of the Licensed Material that could lawfully 370 | be made without permission under this Public License. 371 | 372 | b. To the extent possible, if any provision of this Public License is 373 | deemed unenforceable, it shall be automatically reformed to the 374 | minimum extent necessary to make it enforceable. If the provision 375 | cannot be reformed, it shall be severed from this Public License 376 | without affecting the enforceability of the remaining terms and 377 | conditions. 378 | 379 | c. No term or condition of this Public License will be waived and no 380 | failure to comply consented to unless expressly agreed to by the 381 | Licensor. 382 | 383 | d. Nothing in this Public License constitutes or may be interpreted 384 | as a limitation upon, or waiver of, any privileges and immunities 385 | that apply to the Licensor or You, including from the legal 386 | processes of any jurisdiction or authority. 387 | 388 | ======================================================================= 389 | 390 | Creative Commons is not a party to its public 391 | licenses. Notwithstanding, Creative Commons may elect to apply one of 392 | its public licenses to material it publishes and in those instances 393 | will be considered the “Licensor.” The text of the Creative Commons 394 | public licenses is dedicated to the public domain under the CC0 Public 395 | Domain Dedication. Except for the limited purpose of indicating that 396 | material is shared under a Creative Commons public license or as 397 | otherwise permitted by the Creative Commons policies published at 398 | creativecommons.org/policies, Creative Commons does not authorize the 399 | use of the trademark "Creative Commons" or any other trademark or logo 400 | of Creative Commons without its prior written consent including, 401 | without limitation, in connection with any unauthorized modifications 402 | to any of its public licenses or any other arrangements, 403 | understandings, or agreements concerning use of licensed material. For 404 | the avoidance of doubt, this paragraph does not form part of the 405 | public licenses. 406 | 407 | Creative Commons may be contacted at creativecommons.org. 408 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: github_document 3 | --- 4 | 5 | 6 | 7 | ```{r setup, include=FALSE} 8 | library(knitr) 9 | 10 | opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE) 11 | 12 | extract_pkgs <- function(fl) { 13 | if (length(fl) == 1) { 14 | txt <- read.delim(fl, header = FALSE)[[1]] |> 15 | paste0(collapse = "\n") 16 | 17 | pkg_lib <- stringr::str_extract_all(txt, pattern = "(?<=library\\().{1,}?(?=\\))") 18 | 19 | pkg_req <- stringr::str_extract_all(txt, pattern = "(?<=require\\().{1,}?(?=\\))") 20 | 21 | pkg_name <- stringr::str_extract_all(txt, pattern = "[a-z|A-Z|0-9]{1,}(?=\\:\\:)") 22 | 23 | pkgs <- c(pkg_lib, pkg_req, pkg_name) 24 | 25 | } else if (length(fl) > 1) { 26 | pkgs <- sapply(fl, extract_pkgs) 27 | } 28 | 29 | pkgs |> 30 | unlist(recursive = TRUE) |> 31 | as.vector() |> 32 | unique() 33 | } 34 | 35 | make_pkg_table <- function(pkgs) { 36 | # pkgs <- pkgs[sapply(pkgs, function(x) length(x) > 0)] 37 | 38 | new_pkgs <- lapply(seq_along(pkgs), function(i) { 39 | lesson_pkgs <- pkgs[[i]] 40 | prev_lesson_pkgs <- unlist(head(pkgs,i-1)) 41 | 42 | !lesson_pkgs %in% prev_lesson_pkgs 43 | }) 44 | 45 | 46 | ps <- sapply(seq_along(pkgs), function(idx) { 47 | x <- pkgs[[idx]] 48 | is_new <- ifelse(new_pkgs[[idx]], "**", "") 49 | paste0( 50 | glue::glue("[{is_new}`{x}`{is_new}](https://CRAN.R-project.org/package={x})"), 51 | collapse = ", " 52 | ) 53 | }) 54 | 55 | c("|Lesson|Packages|\n|----|----|\n", # header 56 | glue::glue("|[{folder}](/{folder})|{ps}|\n\n", 57 | folder = names(pkgs))) |> 58 | paste0(collapse = "") 59 | } 60 | 61 | get_src <- function(pkg) { 62 | pd <- packageDescription(pkg) 63 | if (is.null(src <- pd$Repository)) { 64 | if (!is.null(src <- pd$GithubRepo)) { 65 | src <- paste0("Github: ",pd$GithubUsername,"/",src) 66 | } else { 67 | src <- "Local version" 68 | } 69 | } 70 | return(src) 71 | } 72 | ``` 73 | 74 | # Analysis of Factorial Designs foR Psychologists 75 | 76 | [![](https://img.shields.io/badge/Open%20Educational%20Resources-Compatable-brightgreen)](https://creativecommons.org/about/program-areas/education-oer/) 77 | [![](https://img.shields.io/badge/CC-BY--NC%204.0-lightgray)](http://creativecommons.org/licenses/by-nc/4.0/) 78 | [![](https://img.shields.io/badge/Language-R-blue)](http://cran.r-project.org/) 79 | 80 | *Last updated `r Sys.Date()`.* 81 | 82 | This Github repo contains all lesson files for *Analysis of Factorial Designs foR Psychologists*. The goal is to impart students with the basic tools to fit and evaluate **statistical models for factorial designs (w/ plots) using [`afex`](https://afex.singmann.science/)**, and and conduct **follow-up analyses (simple effects, planned contrasts, post-hoc test; w/ plots) using [`emmeans`](https://cran.r-project.org/package=emmeans)**. Although the focus is on ANOVAs, the materials regarding follow-up analyses (\~80\% of the course) are applicable to linear mixed models, and even regression with factorial predictors. 83 | 84 | These topics were taught in the graduate-level course ***Analyses of Variance*** (Psych Dep., Ben-Gurion University of the Negev, *Spring, 2019*). This course assumes basic competence in R (importing, regression modeling, plotting, etc.), along the lines of [*Practical Applications in R for Psychologists*](https://github.com/mattansb/Practical-Applications-in-R-for-Psychologists). 85 | 86 | 87 | **Notes:** 88 | 89 | - This repo contains only materials relating to *Practical Applications in R*, and does not contain any theoretical or introductory materials. 90 | - Please note that some code does not work *on purpose*, to force students to learn to debug. 91 | 92 | ## Setup 93 | 94 | 95 | You will need: 96 | 97 | 1. A fresh installation of [**`R`**](https://cran.r-project.org/) (preferably version 4.1 or above). 98 | 2. [RStudio IDE](https://www.rstudio.com/products/rstudio/download/) (optional, but recommended). 99 | 3. The following packages, listed by lesson: 100 | 101 | ```{r, echo=FALSE} 102 | r_list <- list.files(pattern = ".(R|r)$", recursive = TRUE, full.names = TRUE) |> 103 | Filter(f = \(x) !stringr::str_detect(x, pattern = "(SOLUTION|logo)")) 104 | 105 | lesson_names <- stringr::str_extract(r_list, pattern = "(?<=(/)).{1,}(?=(/))") 106 | 107 | r_list <- split(r_list, lesson_names) 108 | 109 | pkgs <- lapply(r_list, extract_pkgs) 110 | 111 | print_pkgs <- make_pkg_table(pkgs) 112 | ``` 113 | 114 | `r print_pkgs` 115 | 116 | *(Bold denotes the first lesson in which the package was used.)* 117 | 118 | You can install all the packages used by running: 119 | 120 | ```{r echo=FALSE, comment = "", warning=FALSE} 121 | unique_pkgs <- pkgs |> 122 | unlist(recursive = TRUE) |> 123 | unique() |> sort() 124 | 125 | packinfo <- installed.packages(fields = c("Package", "Version")) 126 | V <- packinfo[unique_pkgs,"Version"] 127 | src <- sapply(unique_pkgs, get_src) 128 | 129 | cat("# in alphabetical order:") 130 | 131 | cat("pkgs <-", dput(unique_pkgs) |> capture.output(), fill = 80) |> 132 | capture.output() |> 133 | styler::style_text() 134 | 135 | cat('install.packages(pkgs, repos = c("https://easystats.r-universe.dev", getOption("repos")))') 136 | ``` 137 | 138 |
139 | Package Versions 140 | 141 | Run on `r osVersion`, with R version `r packageVersion("base")`. 142 | 143 | The packages used here: 144 | 145 | ```{r, echo=FALSE} 146 | v_info <- paste0(glue::glue(" - `{unique_pkgs}` {V} (*{src}*)"), collapse = "\n") 147 | ``` 148 | 149 | `r v_info` 150 | 151 |
152 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # Analysis of Factorial Designs foR Psychologists 5 | 6 | [![](https://img.shields.io/badge/Open%20Educational%20Resources-Compatable-brightgreen)](https://creativecommons.org/about/program-areas/education-oer/) 7 | [![](https://img.shields.io/badge/CC-BY--NC%204.0-lightgray)](http://creativecommons.org/licenses/by-nc/4.0/) 8 | [![](https://img.shields.io/badge/Language-R-blue)](http://cran.r-project.org/) 9 | 10 | *Last updated 2022-01-31.* 11 | 12 | This Github repo contains all lesson files for *Analysis of Factorial 13 | Designs foR Psychologists*. The goal is to impart students with the 14 | basic tools to fit and evaluate **statistical models for factorial 15 | designs (w/ plots) using [`afex`](https://afex.singmann.science/)**, and 16 | and conduct **follow-up analyses (simple effects, planned contrasts, 17 | post-hoc test; w/ plots) using 18 | [`emmeans`](https://cran.r-project.org/package=emmeans)**. Although the 19 | focus is on ANOVAs, the materials regarding follow-up analyses (\~80% of 20 | the course) are applicable to linear mixed models, and even regression 21 | with factorial predictors. 22 | 23 | These topics were taught in the graduate-level course ***Analyses of 24 | Variance*** (Psych Dep., Ben-Gurion University of the Negev, *Spring, 25 | 2019*). This course assumes basic competence in R (importing, regression 26 | modeling, plotting, etc.), along the lines of [*Practical Applications 27 | in R for 28 | Psychologists*](https://github.com/mattansb/Practical-Applications-in-R-for-Psychologists). 29 | 30 | **Notes:** 31 | 32 | - This repo contains only materials relating to *Practical 33 | Applications in R*, and does not contain any theoretical or 34 | introductory materials. 35 | - Please note that some code does not work *on purpose*, to force 36 | students to learn to debug. 37 | 38 | ## Setup 39 | 40 | You will need: 41 | 42 | 1. A fresh installation of [**`R`**](https://cran.r-project.org/) 43 | (preferably version 4.1 or above). 44 | 2. [RStudio IDE](https://www.rstudio.com/products/rstudio/download/) 45 | (optional, but recommended). 46 | 3. The following packages, listed by lesson: 47 | 48 | | Lesson | Packages | 49 | |---------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 50 | | [01 ANOVA made easy](/01%20ANOVA%20made%20easy) | [**`afex`**](https://CRAN.R-project.org/package=afex), [**`emmeans`**](https://CRAN.R-project.org/package=emmeans), [**`effectsize`**](https://CRAN.R-project.org/package=effectsize), [**`ggeffects`**](https://CRAN.R-project.org/package=ggeffects), [**`tidyr`**](https://CRAN.R-project.org/package=tidyr) | 51 | | [02 ANCOVA](/02%20ANCOVA) | [`afex`](https://CRAN.R-project.org/package=afex) | 52 | | [03 Main and simple effects analysis](/03%20Main%20and%20simple%20effects%20analysis) | [`afex`](https://CRAN.R-project.org/package=afex), [`emmeans`](https://CRAN.R-project.org/package=emmeans), [`ggeffects`](https://CRAN.R-project.org/package=ggeffects) | 53 | | [04 Interaction analysis](/04%20Interaction%20analysis) | [`afex`](https://CRAN.R-project.org/package=afex), [`emmeans`](https://CRAN.R-project.org/package=emmeans), [`ggeffects`](https://CRAN.R-project.org/package=ggeffects) | 54 | | [05 Effect sizes and multiple comparisons](/05%20Effect%20sizes%20and%20multiple%20comparisons) | [`afex`](https://CRAN.R-project.org/package=afex), [`emmeans`](https://CRAN.R-project.org/package=emmeans), [`effectsize`](https://CRAN.R-project.org/package=effectsize) | 55 | | [06 Assumption check and non-parametric tests](/06%20Assumption%20check%20and%20non-parametric%20tests) | [`afex`](https://CRAN.R-project.org/package=afex), [`ggeffects`](https://CRAN.R-project.org/package=ggeffects), [**`performance`**](https://CRAN.R-project.org/package=performance), [**`parameters`**](https://CRAN.R-project.org/package=parameters), [**`permuco`**](https://CRAN.R-project.org/package=permuco), [`emmeans`](https://CRAN.R-project.org/package=emmeans), [**`car`**](https://CRAN.R-project.org/package=car) | 56 | | [07 Accepting nulls](/07%20Accepting%20nulls) | [`afex`](https://CRAN.R-project.org/package=afex), [`effectsize`](https://CRAN.R-project.org/package=effectsize), [**`lme4`**](https://CRAN.R-project.org/package=lme4), [**`bayestestR`**](https://CRAN.R-project.org/package=bayestestR), [`emmeans`](https://CRAN.R-project.org/package=emmeans), [**`dplyr`**](https://CRAN.R-project.org/package=dplyr) | 57 | | [08 ANOVA and (G)LMMs](/08%20ANOVA%20and%20(G)LMMs) | [`afex`](https://CRAN.R-project.org/package=afex), [**`patchwork`**](https://CRAN.R-project.org/package=patchwork), [`emmeans`](https://CRAN.R-project.org/package=emmeans) | 58 | 59 | *(Bold denotes the first lesson in which the package was 60 | used.)* 61 | 62 | You can install all the packages used by running: 63 | 64 | # in alphabetical order: 65 | 66 | pkgs <- c( 67 | "afex", "bayestestR", "car", "dplyr", "effectsize", "emmeans", 68 | "ggeffects", "lme4", "parameters", "patchwork", "performance", 69 | "permuco", "tidyr" 70 | ) 71 | 72 | install.packages(pkgs, repos = c("https://easystats.r-universe.dev", getOption("repos"))) 73 | 74 |
75 | 76 | Package Versions 77 | 78 | 79 | Run on Windows 10 x64 (build 22000), with R version 4.1.1. 80 | 81 | The packages used here: 82 | 83 | - `afex` 1.0-1 (*CRAN*) 84 | - `bayestestR` 0.11.5.1 (*Local version*) 85 | - `car` 3.0-12 (*CRAN*) 86 | - `dplyr` 1.0.7 (*CRAN*) 87 | - `effectsize` 0.4.5-4 (*Local version*) 88 | - `emmeans` 1.7.1-1 (*CRAN*) 89 | - `ggeffects` 1.1.1 (*CRAN*) 90 | - `lme4` 1.1-27.1 (*CRAN*) 91 | - `parameters` 0.16.0 (*CRAN*) 92 | - `patchwork` 1.1.1 (*CRAN*) 93 | - `performance` 0.8.0.1 (**) 94 | - `permuco` 1.1.1 (*CRAN*) 95 | - `tidyr` 1.1.4 (*CRAN*) 96 | 97 |
98 | -------------------------------------------------------------------------------- /logo/Hex.R: -------------------------------------------------------------------------------- 1 | library(hexSticker) 2 | library(tidyverse) 3 | library(afex) 4 | 5 | # Hex --------------------------------------------------------------------- 6 | 7 | data(md_12.1) 8 | fit <- aov_ez("id", "rt", md_12.1, within = c("angle", "noise"), 9 | anova_table=list(correction = "none", es = "none")) 10 | p_dat <- afex_plot(fit, ~angle, ~noise, return = "data", error = "within") 11 | 12 | p_r <- ggplot(p_dat$means, aes(angle, y, color = noise, shape = noise)) + 13 | ggbeeswarm::geom_beeswarm(data = p_dat$data, aes(x = angle, group = noise), 14 | dodge.width = 0.3, color = "black", alpha = 0.7, 15 | shape = 16, size = 0.5) + 16 | geom_violin(data = p_dat$data, trim = F, color = NA, fill = "black", position = position_dodge(0.3), alpha = 0.4) + 17 | # geom_errorbar(aes(ymin = lower.CL, ymax = upper.CL), width = 0.2, position = position_dodge(0.3), color = "black") + 18 | geom_point(position = position_dodge(0.3), size = 2) + 19 | geom_line(aes(group = noise), position = position_dodge(0.3), size = 1) + 20 | 21 | theme_void() + 22 | theme_transparent() + 23 | theme(legend.position = "none") + 24 | scale_color_brewer(type = "qual", palette = 6) + 25 | NULL 26 | 27 | 28 | sticker(p_r, package="ANOVA - Practical Applications in R", 29 | filename = "Hex.png", 30 | s_x = 1, s_y = 0.9, s_width = 2, s_height = 1.2, 31 | p_color = "white", p_size = 8, 32 | h_color = "grey", h_fill = "orange", 33 | spotlight = TRUE, l_y = 1.2) 34 | 35 | -------------------------------------------------------------------------------- /logo/Hex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattansb/Analysis-of-Factorial-Designs-foR-Psychologists/9b0881cc7d212bb58ad3aab575e0f9837d114ed9/logo/Hex.png --------------------------------------------------------------------------------