├── .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://creativecommons.org/about/program-areas/education-oer/)
77 | [](http://creativecommons.org/licenses/by-nc/4.0/)
78 | [](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://creativecommons.org/about/program-areas/education-oer/)
7 | [](http://creativecommons.org/licenses/by-nc/4.0/)
8 | [](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
--------------------------------------------------------------------------------