2. Emotion Word Consolidation
2.1 Define Emotion Lexicon
# Comprehensive emotion word dictionary
emotion_lexicon <- list(
# Positive emotions
positive = c("happy", "excited", "pleased", "satisfied", "joyful", "glad",
"delighted", "grateful", "gratified", "proud", "confident",
"comfortable", "content", "relieved", "encouraged", "hopeful",
"energized", "enlightened", "good", "better", "fine", "okay",
"nice", "reassured", "alleviated", "comforted"),
# Negative emotions
negative = c("sad", "unhappy", "disappointed", "discouraged", "frustrated",
"angry", "annoyed", "upset", "stressed", "anxious", "worried",
"scared", "nervous", "overwhelmed", "confused", "lost",
"embarrassed", "ashamed", "uncomfortable", "tense", "silly"),
# Neutral/Mixed
neutral = c("challenged", "engaged", "focused", "busy", "indifferent",
"uncertain", "ambivalent", "okay", "fine", "meh"),
# Cognitive states
cognitive = c("confused", "clarity", "understanding", "enlightened",
"aware", "lost", "uncertain", "struggling")
)
# Flatten to single vector for matching
all_emotion_words <- unlist(emotion_lexicon)
2.2 Consolidation Function
# Function to consolidate multi-word entries to single words
consolidate_to_single_word <- function(text) {
if (is.na(text) || text == "") return(NA)
# Rule 0: If already single word, return it
if (!str_detect(text, " |,|;|/|\\(|\\)")) {
return(str_to_title(str_trim(text)))
}
# Convert to lowercase for processing
text_lower <- tolower(str_trim(text))
# Rule 1: Handle "and"/"but" patterns
if (str_detect(text_lower, "and|but")) {
parts <- str_split(text_lower, " and | but ")[[1]]
words <- str_extract_all(parts[1], "\\w+")[[1]]
for (word in words) {
if (word %in% all_emotion_words) return(str_to_title(word))
}
}
# Rule 2: Handle comma-separated lists
if (str_detect(text_lower, ",")) {
parts <- str_split(text_lower, ",")[[1]]
first_part <- str_trim(parts[1])
words <- str_extract_all(first_part, "\\w+")[[1]]
for (word in words) {
if (word %in% all_emotion_words) return(str_to_title(word))
}
}
# Rule 3: Extract all words and find emotion words
all_words <- str_extract_all(text_lower, "\\w+")[[1]]
# Remove stop words
stop_words <- c("i", "me", "my", "felt", "feel", "feeling", "was", "were",
"am", "is", "are", "the", "a", "an", "and", "or", "but",
"very", "really", "quite", "somewhat", "kind", "sort", "of",
"little", "bit", "more", "less", "it", "that", "this", "to",
"would", "could", "should", "like", "as", "when", "about",
"with", "working", "worked", "work", "classes", "class",
"classmates", "classmate", "teacher", "teachers", "help",
"helped", "helping", "problems", "problem", "difficult")
meaningful_words <- all_words[!all_words %in% stop_words]
# Rule 4: Find matching emotion words
for (word in meaningful_words) {
if (word %in% all_emotion_words) {
return(str_to_title(word))
}
}
# Rule 5: Look for emotion-indicating suffixes
for (word in meaningful_words) {
if (str_detect(word, "ed$|ing$|ful$")) {
return(str_to_title(word))
}
}
# Rule 6: Special case handling
special_cases <- list(
"a-ha moment" = "Enlightened",
"ah-ha" = "Enlightened",
"struggle bus" = "Struggling",
"meh" = "Indifferent",
"not applicable" = NA,
"pretty good" = "Good",
"pretty easy" = "Comfortable",
"less confused" = "Clarity",
"still confused" = "Confused",
"more reassured" = "Reassured"
)
for (phrase in names(special_cases)) {
if (str_detect(text_lower, fixed(phrase))) {
return(special_cases[[phrase]])
}
}
# Rule 7: Sentiment-based fallback
positive_indicators <- c("good", "better", "best", "easy", "easier", "helpful",
"happy", "glad", "nice", "great", "relief", "relieved")
negative_indicators <- c("hard", "harder", "difficult", "struggle", "struggling",
"bad", "worse", "worst", "scary", "scared")
has_positive <- any(meaningful_words %in% positive_indicators)
has_negative <- any(meaningful_words %in% negative_indicators)
if (has_positive && !has_negative) return("Positive")
if (has_negative && !has_positive) return("Challenged")
# Rule 8: Use first meaningful word
if (length(meaningful_words) > 0) {
return(str_to_title(meaningful_words[1]))
}
# Ultimate fallback
return(str_to_title(str_trim(text)))
}
2.3 Apply Consolidation
# Apply consolidation to the data
all_data <- all_data %>%
mutate(
Emotion_Word_Original = Emotion_Word,
Is_Multi_Word = str_detect(Emotion_Word, " |,|;|/"),
Emotion_Word_Single = sapply(Emotion_Word, consolidate_to_single_word),
Was_Consolidated = Emotion_Word_Original != Emotion_Word_Single
)
# Summary
cat("\n", rep("=", 60), "\n", sep="")
##
## ============================================================
cat("EMOTION WORD CONSOLIDATION SUMMARY\n")
## EMOTION WORD CONSOLIDATION SUMMARY
cat(rep("=", 60), "\n", sep="")
## ============================================================
cat(sprintf("Total rows: %d\n", nrow(all_data)))
## Total rows: 483
cat(sprintf("Rows with emotion words: %d\n", sum(!is.na(all_data$Emotion_Word_Original))))
## Rows with emotion words: 483
cat(sprintf("Rows with multiple words: %d\n", sum(all_data$Is_Multi_Word, na.rm = TRUE)))
## Rows with multiple words: 118
cat(sprintf("Rows consolidated: %d\n", sum(all_data$Was_Consolidated, na.rm = TRUE)))
## Rows consolidated: 260
# Show top emotions
cat("\n", rep("=", 60), "\n", sep="")
##
## ============================================================
cat("TOP 15 MOST FREQUENT CONSOLIDATED EMOTIONS\n")
## TOP 15 MOST FREQUENT CONSOLIDATED EMOTIONS
cat(rep("=", 60), "\n\n", sep="")
## ============================================================
top_emotions <- all_data %>%
filter(!is.na(Emotion_Word_Single)) %>%
count(Emotion_Word_Single, sort = TRUE) %>%
head(15)
print(as.data.frame(top_emotions), row.names = FALSE)
## Emotion_Word_Single n
## Good 49
## Confused 36
## Relieved 24
## Stressed 18
## Helpful 17
## Frustrated 15
## Fine 13
## Great 13
## Confident 12
## Better 11
## Happy 11
## Na 11
## Okay 11
## Calm 8
## Indifferent 7
3. VAD Score Assignment
3.1 Load VAD Lexicon
# Read the VAD lexicon
vad_lexicon <- read_excel("surveys/VAD_Cleaned.xlsx") %>%
select(
Word,
Valence = V.Mean.Sum,
Arousal = A.Mean.Sum,
Dominance = D.Mean.Sum
) %>%
mutate(Word_Lower = tolower(Word))
cat(sprintf("VAD lexicon loaded: %d words\n", nrow(vad_lexicon)))
## VAD lexicon loaded: 13915 words
3.2 Manual VAD Mappings
# Manual mappings for emotions not in the VAD lexicon
manual_vad_mappings <- tibble(
Word_Lower = c(
"relieved", "stressed", "better", "okay", "challenged",
"frstrated", "frusterated", "frustrated.", "releived", "relived",
"encouraged", "energized", "enlightened", "gratified", "helped",
"reassured", "comforted", "alleviated", "struggling", "struggled",
"supported", "accomplished", "intellectually-stimulated", "intimidated",
"panicked", "trepidatious", "cathartic", "collaborative"
),
Valence = c(
7.24, 2.25, 6.50, 5.50, 5.10,
3.00, 3.00, 3.00, 7.24, 7.24,
7.00, 7.20, 7.26, 7.00, 6.80, 7.00, 7.24, 7.24, 3.50, 3.50,
6.80, 7.20, 7.00, 3.00, 2.00, 3.50, 6.50, 6.50
),
Arousal = c(
3.76, 6.68, 3.50, 3.00, 5.10,
5.50, 5.50, 5.50, 3.76, 3.76,
4.50, 6.00, 3.64, 4.00, 3.50, 3.00, 2.76, 3.76, 5.50, 5.50,
3.50, 5.00, 6.00, 5.00, 7.00, 6.00, 4.00, 4.00
),
Dominance = c(
6.41, 3.19, 6.00, 5.50, 5.60,
3.78, 3.78, 3.78, 6.41, 6.41,
6.50, 6.50, 6.59, 6.50, 6.00, 6.50, 7.18, 6.41, 3.50, 3.50,
6.50, 6.80, 6.50, 3.50, 2.50, 3.50, 6.00, 6.00
),
Source = "Manual"
)
# Combine VAD lexicon with manual mappings
vad_complete <- vad_lexicon %>%
select(Word_Lower, Valence, Arousal, Dominance) %>%
mutate(Source = "Lexicon") %>%
bind_rows(manual_vad_mappings %>% select(Word_Lower, Valence, Arousal, Dominance, Source))
cat(sprintf("Total VAD mappings available: %d\n", nrow(vad_complete)))
## Total VAD mappings available: 13943
3.3 Map VAD to Emotions
# Map VAD scores to emotion words
all_data <- all_data %>%
mutate(Emotion_Word_Single_Lower = tolower(Emotion_Word_Single)) %>%
left_join(
vad_complete,
by = c("Emotion_Word_Single_Lower" = "Word_Lower")
) %>%
select(-Emotion_Word_Single_Lower)
# Check mapping success
mapping_summary <- all_data %>%
filter(!is.na(Emotion_Word_Single)) %>%
summarise(
Total_Emotions = n(),
With_VAD = sum(!is.na(Valence)),
Without_VAD = sum(is.na(Valence)),
Percent_Mapped = round(100 * sum(!is.na(Valence)) / n(), 1)
)
cat("\n", rep("=", 60), "\n", sep="")
##
## ============================================================
cat("VAD MAPPING SUMMARY\n")
## VAD MAPPING SUMMARY
cat(rep("=", 60), "\n", sep="")
## ============================================================
cat(sprintf("Total emotion entries: %d\n", mapping_summary$Total_Emotions))
## Total emotion entries: 482
cat(sprintf("Successfully mapped to VAD: %d (%.1f%%)\n",
mapping_summary$With_VAD, mapping_summary$Percent_Mapped))
## Successfully mapped to VAD: 422 (87.6%)
cat(sprintf("Missing VAD scores: %d (%.1f%%)\n",
mapping_summary$Without_VAD, 100 - mapping_summary$Percent_Mapped))
## Missing VAD scores: 60 (12.4%)
# Save processed data
write_xlsx(all_data, "tables/all_data_with_VAD.xlsx")
4. Descriptive Statistics
4.1 Prepare Analysis Data
# Filter and prepare data for analysis
analysis_data <- all_data %>%
filter(!is.na(Valence) & !is.na(Condition)) %>%
mutate(
Timepoint = factor(Timepoint, levels = c("Baseline", "T1", "T2", "T3")),
Condition = factor(Condition, levels = c("PF", "PS")),
Context = factor(Context, levels = c("activity", "group", "social")),
ID = factor(ID)
)
cat("\n", rep("=", 80), "\n", sep="")
##
## ================================================================================
cat("ANALYSIS SAMPLE\n")
## ANALYSIS SAMPLE
cat(rep("=", 80), "\n\n", sep="")
## ================================================================================
cat(sprintf("Total observations: %d\n", nrow(analysis_data)))
## Total observations: 328
cat(sprintf("Unique participants: %d\n", n_distinct(analysis_data$ID)))
## Unique participants: 41
cat(sprintf("PF observations: %d\n", sum(analysis_data$Condition == "PF")))
## PF observations: 172
cat(sprintf("PS observations: %d\n", sum(analysis_data$Condition == "PS")))
## PS observations: 156
4.2 Overall Descriptives
# Overall by Condition
overall_stats <- analysis_data %>%
group_by(Condition) %>%
summarise(
n = n(),
Valence_M = mean(Valence, na.rm = TRUE),
Valence_SD = sd(Valence, na.rm = TRUE),
Arousal_M = mean(Arousal, na.rm = TRUE),
Arousal_SD = sd(Arousal, na.rm = TRUE),
Dominance_M = mean(Dominance, na.rm = TRUE),
Dominance_SD = sd(Dominance, na.rm = TRUE),
.groups = "drop"
)
knitr::kable(overall_stats, digits = 2, caption = "Overall VAD by Condition")
Overall VAD by Condition
| PF |
172 |
5.63 |
2.15 |
4.33 |
1.08 |
5.52 |
1.34 |
| PS |
156 |
6.43 |
1.79 |
4.06 |
1.04 |
6.04 |
1.11 |
4.3 By Condition × Timepoint
condition_time_stats <- analysis_data %>%
group_by(Condition, Timepoint) %>%
summarise(
n = n(),
Valence_M = mean(Valence, na.rm = TRUE),
Valence_SD = sd(Valence, na.rm = TRUE),
Arousal_M = mean(Arousal, na.rm = TRUE),
Arousal_SD = sd(Arousal, na.rm = TRUE),
Dominance_M = mean(Dominance, na.rm = TRUE),
Dominance_SD = sd(Dominance, na.rm = TRUE),
.groups = "drop"
)
knitr::kable(condition_time_stats, digits = 2, caption = "VAD by Condition × Timepoint")
VAD by Condition × Timepoint
| PF |
T1 |
68 |
6.00 |
2.10 |
4.52 |
1.12 |
5.76 |
1.30 |
| PF |
T2 |
51 |
5.30 |
2.13 |
4.29 |
0.97 |
5.32 |
1.33 |
| PF |
T3 |
53 |
5.47 |
2.22 |
4.11 |
1.11 |
5.40 |
1.37 |
| PS |
T1 |
66 |
6.14 |
1.97 |
4.19 |
1.02 |
5.91 |
1.28 |
| PS |
T2 |
55 |
6.85 |
1.50 |
3.99 |
1.05 |
6.23 |
0.88 |
| PS |
T3 |
35 |
6.33 |
1.76 |
3.94 |
1.06 |
5.99 |
1.11 |
4.4 By Condition × Context
condition_context_stats <- analysis_data %>%
group_by(Condition, Context) %>%
summarise(
n = n(),
Valence_M = mean(Valence, na.rm = TRUE),
Valence_SD = sd(Valence, na.rm = TRUE),
Arousal_M = mean(Arousal, na.rm = TRUE),
Arousal_SD = sd(Arousal, na.rm = TRUE),
Dominance_M = mean(Dominance, na.rm = TRUE),
Dominance_SD = sd(Dominance, na.rm = TRUE),
.groups = "drop"
)
knitr::kable(condition_context_stats, digits = 2, caption = "VAD by Condition × Context")
VAD by Condition × Context
| PF |
activity |
62 |
4.32 |
2.10 |
4.93 |
1.22 |
4.68 |
1.34 |
| PF |
group |
55 |
6.57 |
1.70 |
4.04 |
0.86 |
6.07 |
1.05 |
| PF |
social |
55 |
6.17 |
1.91 |
3.93 |
0.80 |
5.91 |
1.13 |
| PS |
activity |
54 |
5.69 |
2.06 |
4.09 |
1.07 |
5.56 |
1.33 |
| PS |
group |
56 |
6.89 |
1.49 |
4.12 |
1.20 |
6.38 |
0.87 |
| PS |
social |
46 |
6.74 |
1.51 |
3.96 |
0.75 |
6.20 |
0.90 |
4.5 Three-Way Interaction
three_way_stats <- analysis_data %>%
group_by(Condition, Timepoint, Context) %>%
summarise(
n = n(),
Valence_M = mean(Valence, na.rm = TRUE),
Valence_SD = sd(Valence, na.rm = TRUE),
Arousal_M = mean(Arousal, na.rm = TRUE),
Arousal_SD = sd(Arousal, na.rm = TRUE),
Dominance_M = mean(Dominance, na.rm = TRUE),
Dominance_SD = sd(Dominance, na.rm = TRUE),
.groups = "drop"
)
knitr::kable(head(three_way_stats, 20), digits = 2,
caption = "VAD by Condition × Timepoint × Context (first 20 rows)")
VAD by Condition × Timepoint × Context (first 20
rows)
| PF |
T1 |
activity |
23 |
5.03 |
2.41 |
5.22 |
1.10 |
5.09 |
1.52 |
| PF |
T1 |
group |
23 |
6.54 |
1.88 |
4.37 |
0.97 |
6.12 |
1.01 |
| PF |
T1 |
social |
22 |
6.45 |
1.65 |
3.95 |
0.92 |
6.09 |
1.07 |
| PF |
T2 |
activity |
19 |
4.10 |
1.91 |
4.84 |
1.18 |
4.58 |
1.34 |
| PF |
T2 |
group |
16 |
6.18 |
1.78 |
3.87 |
0.70 |
5.82 |
1.11 |
| PF |
T2 |
social |
16 |
5.85 |
2.16 |
4.05 |
0.57 |
5.71 |
1.18 |
| PF |
T3 |
activity |
20 |
3.70 |
1.73 |
4.69 |
1.37 |
4.32 |
1.03 |
| PF |
T3 |
group |
16 |
7.00 |
1.33 |
3.72 |
0.68 |
6.26 |
1.04 |
| PF |
T3 |
social |
17 |
6.10 |
2.03 |
3.80 |
0.84 |
5.86 |
1.19 |
| PS |
T1 |
activity |
23 |
5.37 |
2.10 |
4.22 |
1.05 |
5.32 |
1.49 |
| PS |
T1 |
group |
26 |
6.61 |
1.84 |
4.30 |
1.14 |
6.32 |
1.03 |
| PS |
T1 |
social |
17 |
6.47 |
1.75 |
3.96 |
0.78 |
6.09 |
1.06 |
| PS |
T2 |
activity |
19 |
6.21 |
1.94 |
3.90 |
1.11 |
5.92 |
1.15 |
| PS |
T2 |
group |
18 |
7.26 |
1.08 |
4.12 |
1.23 |
6.42 |
0.66 |
| PS |
T2 |
social |
18 |
7.11 |
1.13 |
3.97 |
0.81 |
6.36 |
0.66 |
| PS |
T3 |
activity |
12 |
5.49 |
2.17 |
4.15 |
1.11 |
5.43 |
1.26 |
| PS |
T3 |
group |
12 |
6.94 |
1.08 |
3.72 |
1.31 |
6.44 |
0.82 |
| PS |
T3 |
social |
11 |
6.58 |
1.68 |
3.95 |
0.68 |
6.12 |
1.02 |
5. Inferential Statistics
5.1 Valence Analysis
# Mixed effects model for Valence
valence_model <- lmer(Valence ~ Condition * Timepoint * Context + (1|ID),
data = analysis_data)
cat("Mixed Effects Model: Valence ~ Condition × Timepoint × Context\n")
## Mixed Effects Model: Valence ~ Condition × Timepoint × Context
cat(rep("=", 80), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print(anova(valence_model))
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## Condition 42.705 42.705 1 309.99 13.5309 0.0002766 ***
## Timepoint 1.313 0.656 2 307.98 0.2080 0.8123343
## Context 190.070 95.035 2 279.08 30.1113 1.442e-12 ***
## Condition:Timepoint 23.929 11.965 2 309.88 3.7909 0.0236282 *
## Condition:Context 16.825 8.413 2 278.79 2.6655 0.0713392 .
## Timepoint:Context 9.592 2.398 4 280.83 0.7598 0.5522455
## Condition:Timepoint:Context 5.394 1.349 4 280.14 0.4273 0.7889047
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Main effect of Condition
valence_condition_emm <- emmeans(valence_model, ~ Condition)
valence_condition_contrast <- pairs(valence_condition_emm)
cat("\n\nMain Effect of Condition on Valence:\n")
##
##
## Main Effect of Condition on Valence:
cat(rep("-", 80), "\n")
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print(valence_condition_emm)
## Condition emmean SE df lower.CL upper.CL
## PF 5.66 0.155 79.5 5.35 5.97
## PS 6.42 0.167 84.3 6.09 6.75
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
print(valence_condition_contrast)
## contrast estimate SE df t.ratio p.value
## PF - PS -0.759 0.207 310 -3.658 0.0003
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
# Effect size
valence_condition_d <- cohens_d(Valence ~ Condition, data = analysis_data)
cat("\nCohen's d:\n")
##
## Cohen's d:
print(valence_condition_d)
## Cohen's d | 95% CI
## --------------------------
## -0.41 | [-0.62, -0.19]
##
## - Estimated using pooled SD.
5.2 Arousal Analysis
# Mixed effects model for Arousal
arousal_model <- lmer(Arousal ~ Condition * Timepoint * Context + (1|ID),
data = analysis_data)
cat("Mixed Effects Model: Arousal ~ Condition × Timepoint × Context\n")
## Mixed Effects Model: Arousal ~ Condition × Timepoint × Context
cat(rep("=", 80), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print(anova(arousal_model))
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## Condition 4.4953 4.4953 1 310.00 4.6181 0.032410 *
## Timepoint 5.8013 2.9006 2 308.37 2.9799 0.052263 .
## Context 19.2993 9.6496 2 283.10 9.9133 6.899e-05 ***
## Condition:Timepoint 0.8118 0.4059 2 309.88 0.4170 0.659402
## Condition:Context 13.6779 6.8389 2 282.85 7.0258 0.001052 **
## Timepoint:Context 3.6508 0.9127 4 284.61 0.9376 0.442461
## Condition:Timepoint:Context 0.9786 0.2447 4 284.01 0.2513 0.908713
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Main effect of Condition
arousal_condition_emm <- emmeans(arousal_model, ~ Condition)
arousal_condition_contrast <- pairs(arousal_condition_emm)
cat("\n\nMain Effect of Condition on Arousal:\n")
##
##
## Main Effect of Condition on Arousal:
cat(rep("-", 80), "\n")
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print(arousal_condition_emm)
## Condition emmean SE df lower.CL upper.CL
## PF 4.28 0.0864 78.9 4.11 4.45
## PS 4.03 0.0935 84.0 3.85 4.22
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
print(arousal_condition_contrast)
## contrast estimate SE df t.ratio p.value
## PF - PS 0.246 0.115 310 2.137 0.0334
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
# Effect size
arousal_condition_d <- cohens_d(Arousal ~ Condition, data = analysis_data)
cat("\nCohen's d:\n")
##
## Cohen's d:
print(arousal_condition_d)
## Cohen's d | 95% CI
## ------------------------
## 0.25 | [0.03, 0.47]
##
## - Estimated using pooled SD.
5.3 Dominance Analysis
# Mixed effects model for Dominance
dominance_model <- lmer(Dominance ~ Condition * Timepoint * Context + (1|ID),
data = analysis_data)
cat("Mixed Effects Model: Dominance ~ Condition × Timepoint × Context\n")
## Mixed Effects Model: Dominance ~ Condition × Timepoint × Context
cat(rep("=", 80), "\n")
## = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
print(anova(dominance_model))
## Type III Analysis of Variance Table with Satterthwaite's method
## Sum Sq Mean Sq NumDF DenDF F value Pr(>F)
## Condition 18.050 18.050 1 309.62 14.5787 0.0001624 ***
## Timepoint 0.782 0.391 2 307.42 0.3159 0.7293624
## Context 77.113 38.556 2 282.69 31.1418 5.986e-13 ***
## Condition:Timepoint 7.216 3.608 2 309.82 2.9140 0.0557449 .
## Condition:Context 6.139 3.069 2 282.43 2.4791 0.0856387 .
## Timepoint:Context 3.085 0.771 4 284.45 0.6229 0.6465503
## Condition:Timepoint:Context 2.221 0.555 4 283.75 0.4485 0.7734979
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Main effect of Condition
dominance_condition_emm <- emmeans(dominance_model, ~ Condition)
dominance_condition_contrast <- pairs(dominance_condition_emm)
cat("\n\nMain Effect of Condition on Dominance:\n")
##
##
## Main Effect of Condition on Dominance:
cat(rep("-", 80), "\n")
## - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
print(dominance_condition_emm)
## Condition emmean SE df lower.CL upper.CL
## PF 5.53 0.093 83.3 5.35 5.72
## PS 6.02 0.101 86.0 5.82 6.22
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
## Confidence level used: 0.95
print(dominance_condition_contrast)
## contrast estimate SE df t.ratio p.value
## PF - PS -0.49 0.129 310 -3.796 0.0002
##
## Results are averaged over the levels of: Timepoint, Context
## Degrees-of-freedom method: kenward-roger
# Effect size
dominance_condition_d <- cohens_d(Dominance ~ Condition, data = analysis_data)
cat("\nCohen's d:\n")
##
## Cohen's d:
print(dominance_condition_d)
## Cohen's d | 95% CI
## --------------------------
## -0.42 | [-0.64, -0.20]
##
## - Estimated using pooled SD.
6. Visualizations
library(ggplot2)
library(patchwork)
library(ggthemes) # For Tableau color schemes
# Use Tableau color palette
condition_colors <- tableau_color_pal("Tableau 10")(2)
names(condition_colors) <- c("PF", "PS")
# OPTIONAL: Set default plot size for your R session (comment out if not needed)
# This provides a default size but plots will still resize with window
# options(repr.plot.width=12, repr.plot.height=4) # For Jupyter notebooks
# If using RStudio, you can resize the Plots pane to your preference
6.1 Main Effects by Condition
# Helper function for cleaner bar plots
create_bar_plot <- function(data, y_var, y_label, title, y_limits) {
sd_var <- paste0(sub("_M$", "_SD", y_var))
ggplot(data, aes(x = Condition, y = .data[[y_var]], fill = Condition)) +
geom_col(width = 0.6) + # Slightly wider bars
geom_errorbar(aes(ymin = .data[[y_var]] - .data[[sd_var]],
ymax = .data[[y_var]] + .data[[sd_var]]),
width = 0.2, linewidth = 0.6) + # Slightly wider error bars
scale_fill_manual(values = condition_colors) +
scale_y_continuous(limits = y_limits, expand = expansion(mult = c(0, 0.1))) +
labs(title = title, y = y_label, x = NULL) +
theme_minimal(base_size = 12) +
theme(legend.position = "none",
plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank(),
axis.text = element_text(size = 11),
axis.title.y = element_text(size = 11, face = "bold"))
}
fig1a <- create_bar_plot(overall_stats, "Valence_M", "Valence",
"Valence by Condition", c(0, 8))
fig1b <- create_bar_plot(overall_stats, "Arousal_M", "Arousal",
"Arousal by Condition", c(0, 8))
fig1c <- create_bar_plot(overall_stats, "Dominance_M", "Dominance",
"Dominance by Condition", c(0, 8))
figure1 <- fig1a + fig1b + fig1c +
plot_annotation(
title = "Figure 1: Main Effects of Condition on VAD Dimensions",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure1)

ggsave("figures/VAD_Analysis_Figure1_MainEffects.png", figure1,
width = 12, height = 4, dpi = 300)
6.2 Condition × Timepoint Interaction
# Helper function for cleaner line plots
create_line_plot <- function(data, y_var, y_label, title, y_limits, legend_pos = NULL) {
sd_var <- paste0(sub("_M$", "_SD", y_var))
p <- ggplot(data, aes(x = Timepoint, y = .data[[y_var]],
color = Condition, group = Condition)) +
geom_line(linewidth = 1.2) + # Thicker lines
geom_point(size = 3) + # Larger points
geom_errorbar(aes(ymin = .data[[y_var]] - .data[[sd_var]] / sqrt(n),
ymax = .data[[y_var]] + .data[[sd_var]] / sqrt(n)),
width = 0.15, linewidth = 0.6) +
scale_color_manual(values = condition_colors) +
scale_y_continuous(limits = y_limits) +
labs(title = title, y = y_label, x = "Timepoint") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
axis.text = element_text(size = 11),
axis.title = element_text(size = 11, face = "bold"))
if (!is.null(legend_pos)) {
p <- p + theme(legend.position = legend_pos,
legend.background = element_rect(fill = "white", color = "gray80", linewidth = 0.3),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10),
legend.key.size = unit(0.8, "cm"))
} else {
p <- p + theme(legend.position = "none")
}
return(p)
}
# IMPROVED: Better legend placement to avoid covering data
fig2a <- create_line_plot(condition_time_stats, "Valence_M", "Valence",
"Valence Over Time", c(3, 7.5), legend_pos = c(0.15, 0.2))
fig2b <- create_line_plot(condition_time_stats, "Arousal_M", "Arousal",
"Arousal Over Time", c(3, 6))
fig2c <- create_line_plot(condition_time_stats, "Dominance_M", "Dominance",
"Dominance Over Time", c(3, 7))
figure2 <- fig2a + fig2b + fig2c +
plot_annotation(
title = "Figure 2: Condition × Timepoint Interaction",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure2)

ggsave("figures/VAD_Analysis_Figure2_TimeInteraction.png", figure2,
width = 12, height = 4, dpi = 300)
6.3 Condition × Context Interaction
# Helper function for grouped bar plots
create_grouped_bar <- function(data, y_var, y_label, title, y_limits, legend_pos = NULL) {
sd_var <- paste0(sub("_M$", "_SD", y_var))
p <- ggplot(data, aes(x = Context, y = .data[[y_var]], fill = Condition)) +
geom_col(position = position_dodge(0.8), width = 0.7) + # Better spacing
geom_errorbar(aes(ymin = .data[[y_var]] - .data[[sd_var]] / sqrt(n),
ymax = .data[[y_var]] + .data[[sd_var]] / sqrt(n)),
position = position_dodge(0.8), width = 0.2, linewidth = 0.6) +
scale_fill_manual(values = condition_colors) +
scale_y_continuous(limits = y_limits, expand = expansion(mult = c(0, 0.1))) +
labs(title = title, y = y_label, x = "Context") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
panel.grid.major.x = element_blank(),
axis.text = element_text(size = 11),
axis.title = element_text(size = 11, face = "bold"))
if (!is.null(legend_pos)) {
p <- p + theme(legend.position = legend_pos,
legend.background = element_rect(fill = "white", color = "gray80", linewidth = 0.3),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10),
legend.key.size = unit(0.6, "cm"))
} else {
p <- p + theme(legend.position = "none")
}
return(p)
}
# IMPROVED: Better legend placement
fig3a <- create_grouped_bar(condition_context_stats, "Valence_M", "Valence",
"Valence by Context", c(0, 7.5), legend_pos = c(0.15, 0.85))
fig3b <- create_grouped_bar(condition_context_stats, "Arousal_M", "Arousal",
"Arousal by Context", c(0, 6.5))
fig3c <- create_grouped_bar(condition_context_stats, "Dominance_M", "Dominance",
"Dominance by Context", c(0, 7))
figure3 <- fig3a + fig3b + fig3c +
plot_annotation(
title = "Figure 3: Condition × Context Interaction",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure3)

ggsave("figures/VAD_Analysis_Figure3_ContextInteraction.png", figure3,
width = 12, height = 4, dpi = 300)
6.4 Emotional Space (Valence-Arousal)
# IMPROVED: Better legend placement and clearer quadrant lines
figure4 <- ggplot(analysis_data, aes(Valence, Arousal, color = Condition)) +
geom_point(alpha = 0.20, size = 2) +
stat_ellipse(level = 0.95, linewidth = 1.2) +
scale_color_manual(values = condition_colors) +
geom_vline(xintercept = 5, linetype = "dashed", alpha = 0.35) +
geom_hline(yintercept = 5, linetype = "dashed", alpha = 0.35) +
# Annotations in the four quadrants with proper alignment
annotate("text", x = 0.5, y = 9.5, label = "High Arousal\nLow Valence", hjust = 0, vjust = 1, size = 4, alpha = 0.7) +
annotate("text", x = 0.5, y = 0.5, label = "Low Arousal\nLow Valence", hjust = 0, vjust = 0, size = 4, alpha = 0.7) +
annotate("text", x = 9.5, y = 9.5, label = "High Arousal\nHigh Valence", hjust = 1, vjust = 1, size = 4, alpha = 0.7) +
annotate("text", x = 9.5, y = 0.5, label = "Low Arousal\nHigh Valence", hjust = 1, vjust = 0, size = 4, alpha = 0.7) +
scale_x_continuous(expand = expansion(mult = 0.02)) +
scale_y_continuous(expand = expansion(mult = 0.02)) +
theme_minimal(base_size = 14) +
theme(
axis.title = element_text(face = "bold"),
plot.title = element_text(size = 16, face = "bold", hjust = 0.5),
legend.position = "right"
)
print(figure4)

# Save with fixed size for publication, but display is responsive
ggsave("figures/VAD_Analysis_Figure4_EmotionalSpace.png", figure4,
width = 8, height = 8, dpi = 300)
6.5 Three-Way Interaction Heatmaps
# Helper function for heatmaps with improved text visibility
create_heatmap <- function(data, fill_var, title, color_low, color_high, midpoint, limits) {
ggplot(data, aes(x = Timepoint, y = Context, fill = .data[[fill_var]])) +
geom_tile(color = "white", linewidth = 1) + # Thicker tile borders
# IMPROVED: Dynamic text color for better contrast
geom_text(aes(label = sprintf("%.1f", .data[[fill_var]]),
color = ifelse(.data[[fill_var]] > midpoint + 0.8 |
.data[[fill_var]] < midpoint - 0.8,
"white", "black")),
fontface = "bold", size = 3.5, show.legend = FALSE) +
scale_color_identity() +
scale_fill_gradient2(low = color_low, mid = "grey80", high = color_high,
midpoint = midpoint, limits = limits) +
facet_wrap(~ Condition, ncol = 2) +
labs(title = title, x = "Timepoint", y = "Context", fill = title) +
theme_minimal(base_size = 11) +
theme(plot.title = element_text(hjust = 0.5, face = "bold", size = 12),
axis.text.x = element_text(angle = 0, hjust = 0.5, size = 10), # Horizontal text
axis.text.y = element_text(size = 10),
axis.title = element_text(size = 11, face = "bold"),
panel.grid = element_blank(),
strip.text = element_text(size = 11, face = "bold"),
strip.background = element_rect(fill = "grey95", color = "grey70"),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 9))
}
fig5a <- create_heatmap(three_way_stats, "Valence_M", "Valence",
"#d7191c", "#2b83ba", 5, c(3, 7))
fig5b <- create_heatmap(three_way_stats, "Arousal_M", "Arousal",
"#2b83ba", "#d7191c", 4.5, c(3, 6))
fig5c <- create_heatmap(three_way_stats, "Dominance_M", "Dominance",
"#d7191c", "#2b83ba", 5, c(3, 7))
figure5 <- fig5a / fig5b / fig5c +
plot_annotation(
title = "Figure 5: Three-Way Interaction (Condition × Timepoint × Context)",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure5)

ggsave("figures/VAD_Analysis_Figure5_ThreeWayInteraction.png", figure5,
width = 10, height = 11, dpi = 300)
6.6 Valence Trajectories by Context
# Create plots for each context with improved spacing and legend
contexts <- c("activity", "group", "social")
context_titles <- c("Activity", "Group", "Social")
plots <- lapply(1:3, function(i) {
y_label <- if (i == 1) "Valence" else ""
p <- ggplot(three_way_stats %>% filter(Context == contexts[i]),
aes(x = Timepoint, y = Valence_M, color = Condition, group = Condition)) +
geom_line(linewidth = 1.2) + # Thicker lines
geom_point(size = 3) + # Larger points
geom_errorbar(aes(ymin = Valence_M - Valence_SD / sqrt(n),
ymax = Valence_M + Valence_SD / sqrt(n)),
width = 0.15, linewidth = 0.6) +
scale_color_manual(values = condition_colors) +
scale_y_continuous(limits = c(3, 8), breaks = seq(3, 7, 1)) + # More breaks
labs(title = context_titles[i], y = y_label, x = "Timepoint") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
axis.text = element_text(size = 10),
axis.title = element_text(size = 11, face = "bold"))
# IMPROVED: Better legend placement for middle panel
if (i == 2) {
p <- p + theme(legend.position = c(0.85, 0.25), # Lower right to avoid data
legend.background = element_rect(fill = "white", color = "gray80", linewidth = 0.3),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10),
legend.key.size = unit(0.7, "cm"))
} else {
p <- p + theme(legend.position = "none")
}
return(p)
})
figure6 <- plots[[1]] + plots[[2]] + plots[[3]] +
plot_annotation(
title = "Figure 6: Valence Trajectories by Context",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure6)

ggsave("figures/VAD_Analysis_Figure6_ValenceTrajectories.png", figure6,
width = 12, height = 4.5, dpi = 300)
6.7 Arousal Trajectories by Context
# Create plots for each context - Arousal
contexts <- c("activity", "group", "social")
context_titles <- c("Activity", "Group", "Social")
plots_arousal <- lapply(1:3, function(i) {
y_label <- if (i == 1) "Arousal" else ""
p <- ggplot(three_way_stats %>% filter(Context == contexts[i]),
aes(x = Timepoint, y = Arousal_M, color = Condition, group = Condition)) +
geom_line(linewidth = 1.2) + # Thicker lines
geom_point(size = 3) + # Larger points
geom_errorbar(aes(ymin = Arousal_M - Arousal_SD / sqrt(n),
ymax = Arousal_M + Arousal_SD / sqrt(n)),
width = 0.15, linewidth = 0.6) +
scale_color_manual(values = condition_colors) +
scale_y_continuous(limits = c(3, 6), breaks = seq(3, 6, 1)) + # Arousal range
labs(title = context_titles[i], y = y_label, x = "Timepoint") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
axis.text = element_text(size = 10),
axis.title = element_text(size = 11, face = "bold"))
# Better legend placement for middle panel
if (i == 2) {
p <- p + theme(legend.position = c(0.85, 0.25),
legend.background = element_rect(fill = "white", color = "gray80", linewidth = 0.3),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10),
legend.key.size = unit(0.7, "cm"))
} else {
p <- p + theme(legend.position = "none")
}
return(p)
})
figure7 <- plots_arousal[[1]] + plots_arousal[[2]] + plots_arousal[[3]] +
plot_annotation(
title = "Figure 7: Arousal Trajectories by Context",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure7)

ggsave("figures/VAD_Analysis_Figure7_ArousalTrajectories.png", figure7,
width = 12, height = 4.5, dpi = 300)
6.8 Dominance Trajectories by Context
# Create plots for each context - Dominance
contexts <- c("activity", "group", "social")
context_titles <- c("Activity", "Group", "Social")
plots_dominance <- lapply(1:3, function(i) {
y_label <- if (i == 1) "Dominance" else ""
p <- ggplot(three_way_stats %>% filter(Context == contexts[i]),
aes(x = Timepoint, y = Dominance_M, color = Condition, group = Condition)) +
geom_line(linewidth = 1.2) + # Thicker lines
geom_point(size = 3) + # Larger points
geom_errorbar(aes(ymin = Dominance_M - Dominance_SD / sqrt(n),
ymax = Dominance_M + Dominance_SD / sqrt(n)),
width = 0.15, linewidth = 0.6) +
scale_color_manual(values = condition_colors) +
scale_y_continuous(limits = c(3, 7.5), breaks = seq(3, 7, 1)) + # Dominance range
labs(title = context_titles[i], y = y_label, x = "Timepoint") +
theme_minimal(base_size = 12) +
theme(plot.title = element_text(hjust = 0.5, size = 12, face = "bold"),
panel.grid.minor = element_blank(),
axis.text = element_text(size = 10),
axis.title = element_text(size = 11, face = "bold"))
# Better legend placement for middle panel
if (i == 2) {
p <- p + theme(legend.position = c(0.85, 0.25),
legend.background = element_rect(fill = "white", color = "gray80", linewidth = 0.3),
legend.title = element_text(size = 10, face = "bold"),
legend.text = element_text(size = 10),
legend.key.size = unit(0.7, "cm"))
} else {
p <- p + theme(legend.position = "none")
}
return(p)
})
figure8 <- plots_dominance[[1]] + plots_dominance[[2]] + plots_dominance[[3]] +
plot_annotation(
title = "Figure 8: Dominance Trajectories by Context",
theme = theme(plot.title = element_text(size = 14, face = "bold", hjust = 0.5))
)
print(figure8)

ggsave("figures/VAD_Analysis_Figure8_DominanceTrajectories.png", figure8,
width = 12, height = 4.5, dpi = 300)
# Save descriptive statistics
write_xlsx(list(
Overall = overall_stats,
ConditionByTime = condition_time_stats,
ConditionByContext = condition_context_stats,
ThreeWay = three_way_stats
), "tables/VAD_Analysis_DescriptiveStats.xlsx")
# Extract and save model results
valence_anova <- anova(valence_model)
arousal_anova <- anova(arousal_model)
dominance_anova <- anova(dominance_model)
# Make sure ANOVA results are data frames
valence_df <- as.data.frame(valence_anova)
arousal_df <- as.data.frame(arousal_anova)
dominance_df <- as.data.frame(dominance_anova)
# Example: assuming "Condition" is the effect of interest
# You can adjust the row name ("Condition") to match your actual ANOVA term
get_f <- function(df, term) {
if (term %in% rownames(df)) df[term, "F value"] else NA
}
get_p <- function(df, term) {
if (term %in% rownames(df)) df[term, "Pr(>F)"] else NA
}
# Build summary table
model_summary <- tibble(
Model = c("Valence", "Arousal", "Dominance"),
Condition_F = c(
get_f(valence_df, "Condition"),
get_f(arousal_df, "Condition"),
get_f(dominance_df, "Condition")
),
Condition_p = c(
get_p(valence_df, "Condition"),
get_p(arousal_df, "Condition"),
get_p(dominance_df, "Condition")
)
)
# Save ANOVA summaries to Excel
write_xlsx(list(
DescriptiveStats = overall_stats,
ConditionByTime = condition_time_stats,
ConditionByContext = condition_context_stats,
ThreeWay = three_way_stats,
ANOVA_Summary = model_summary
), "tables/VAD_Analysis_Results.xlsx")