Analysis for Study 1 - participants performed 3 trials * 53 target facial action units sensed by a Meta Quest Pro, each trial lasting around 3 seconds in the “do/hold this as comfortably and accurately as possible” or some such. In each trial, we logged not just the FAU they were asked to perform, but all the other 52 FAU “scores” (a magic activation number between 0..1 reported by the Meta SDK).
##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:plyr':
##
## arrange, count, desc, failwith, id, mutate, rename, summarise,
## summarize
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
##
## Attaching package: 'purrr'
## The following object is masked from 'package:scales':
##
## discard
## The following object is masked from 'package:plyr':
##
## compact
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ forcats 1.0.0 ✔ stringr 1.5.1
## ✔ lubridate 1.9.3 ✔ tibble 3.2.1
## ✔ readr 2.1.5 ✔ tidyr 1.3.1
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::arrange() masks plyr::arrange()
## ✖ readr::col_factor() masks scales::col_factor()
## ✖ purrr::compact() masks plyr::compact()
## ✖ dplyr::count() masks plyr::count()
## ✖ dplyr::desc() masks plyr::desc()
## ✖ purrr::discard() masks scales::discard()
## ✖ dplyr::failwith() masks plyr::failwith()
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::id() masks plyr::id()
## ✖ dplyr::lag() masks stats::lag()
## ✖ dplyr::mutate() masks plyr::mutate()
## ✖ dplyr::rename() masks plyr::rename()
## ✖ dplyr::summarise() masks plyr::summarise()
## ✖ dplyr::summarize() masks plyr::summarize()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
Lots of csv files split in folders by participant. Common headers we’ll use are PID for participant ID; and AUofFocus for the AU they were performing during the trial/task.
Grab the released data as “resting face” data for the model, written to “data.recording_released.csv”.
data.recording_released <- data.recording_raw %>% filter(TimeInTrial > 3500 & TrialPhase == "released") # %>% select (-c(TrialNo, TrialPhase, TimeInTrial, EndOfTrialFrame, TrialFrameNo))
# get the various IDs we need for later
FAUs <- levels(factor(data.recording_released$AUofFocus))
PIDs <- levels(factor(data.recording_released$PID))
for( i in rownames(data.recording_released) ){
data.recording_released[i, "AUofFocusValue"] = data.recording_released[i, data.recording_released[i, "AUofFocus", drop=TRUE], drop=TRUE]
}
write.csv(data.recording_released, "r_outputs/data.recording_released.csv", row.names=FALSE)
data.recording_released
## # A tibble: 38,693 × 61
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <chr> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowere… 1 released 3506 FALSE 89
## 2 1 BrowLowere… 1 released 3534 FALSE 90
## 3 1 BrowLowere… 1 released 3575 FALSE 91
## 4 1 BrowLowere… 1 released 3617 FALSE 92
## 5 1 BrowLowere… 1 released 3659 FALSE 93
## 6 1 BrowLowere… 1 released 3701 FALSE 94
## 7 1 BrowLowere… 1 released 3728 FALSE 95
## 8 1 BrowLowere… 1 released 3770 FALSE 96
## 9 1 BrowLowere… 1 released 3811 FALSE 97
## 10 1 BrowLowere… 1 released 3839 FALSE 98
## # ℹ 38,683 more rows
## # ℹ 54 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>, …
Filter the recording data based on a sensible window within the trial, to account for noise at the start/end of the FAU, and extract the values for the AUofFocus from the massive table of FAU values for simplicity
data.recording_filtered <- data.recording_raw %>% filter(TimeInTrial > 500 & TimeInTrial <2500 & TrialPhase == "holding") # %>% select (-c(TrialNo, TrialPhase, TimeInTrial, EndOfTrialFrame, TrialFrameNo))
# get the various IDs we need for later
FAUs <- levels(factor(data.recording_filtered$AUofFocus))
PIDs <- levels(factor(data.recording_filtered$PID))
for( i in rownames(data.recording_filtered) ){
data.recording_filtered[i, "AUofFocusValue"] = data.recording_filtered[i, data.recording_filtered[i, "AUofFocus", drop=TRUE], drop=TRUE]
}
head(data.recording_filtered)
## # A tibble: 6 × 61
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <chr> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 541 FALSE 14
## 2 1 BrowLowererL 1 holding 583 FALSE 15
## 3 1 BrowLowererL 1 holding 625 FALSE 16
## 4 1 BrowLowererL 1 holding 653 FALSE 17
## 5 1 BrowLowererL 1 holding 693 FALSE 18
## 6 1 BrowLowererL 1 holding 721 FALSE 19
## # ℹ 54 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
write.csv(data.recording_filtered, "r_outputs/data.recording_filtered.csv", row.names=FALSE)
For each AU captured alongside the AUofFocus, we want to calculate the value - resting face value captured at the start of the study
data.recording_filtered_resting_face = NULL
# Overwrite the FAU values to be trial value - resting face value
for(i in PIDs ){
recorded_subset = subset(data.recording_filtered, PID == i)
for (FAU in FAUs){
resting_face_subset <- data.resting_face_raw %>% filter(PID == i & AUofFocus == FAU)
resting_face_default <- resting_face_subset$`Calibration Threshold`
recorded_subset[paste0(FAU)] <- recorded_subset[paste0(FAU)] - resting_face_default
}
data.recording_filtered_resting_face <- rbind(data.recording_filtered_resting_face, recorded_subset)
}
# zero any negative values as this ranges from 0..1
#FAUs_RF <- paste(FAUs, "_Minus_RF", sep="")
data.recording_filtered_resting_face[FAUs] <- lapply(data.recording_filtered_resting_face[FAUs], function(x) ifelse(x < 0.0, 0.0, x))
# then overwrite the AUofFocusValue data with the new values
for( i in rownames(data.recording_filtered_resting_face) ){
data.recording_filtered_resting_face[i, "AUofFocusValue"] = data.recording_filtered_resting_face[i, data.recording_filtered_resting_face[i, "AUofFocus", drop=TRUE], drop=TRUE]
}
# order AUofFocus alphabetically
data.recording_filtered_resting_face$AUofFocus <- factor(data.recording_filtered_resting_face$AUofFocus)
head(data.recording_filtered_resting_face)
## # A tibble: 6 × 61
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <fct> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 541 FALSE 14
## 2 1 BrowLowererL 1 holding 583 FALSE 15
## 3 1 BrowLowererL 1 holding 625 FALSE 16
## 4 1 BrowLowererL 1 holding 653 FALSE 17
## 5 1 BrowLowererL 1 holding 693 FALSE 18
## 6 1 BrowLowererL 1 holding 721 FALSE 19
## # ℹ 54 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
write.csv(data.recording_filtered_resting_face, "r_outputs/data.recording_filtered_resting_face.csv", row.names=FALSE)
Calculate a per-participant per-FAU activation threshold based on the participant’s performance data
Reasonable approach is to calculate a confidence interval for the values and pick a threshold based on the lower bound.
# standard error
se <- function(x, na.rm=FALSE) {
if (na.rm) x <- na.omit(x)
sqrt(var(x)/length(x))
}
# confidence interval
conf <- 0.01
quantile <- qnorm(1-conf/2)
data.thresholds <- data.recording_filtered_resting_face %>%
group_by(PID, AUofFocus) %>%
select(c(AUofFocusValue)) %>%
summarise(across(everything(), list(mean=mean, sd=sd, sum=sum, count=~ n(), max=max, se=se)))
## Adding missing grouping variables: `PID`, `AUofFocus`
## `summarise()` has grouped output by 'PID'. You can override using the `.groups`
## argument.
# what's a reasonable way of calculating everyones activation thresholds based on their performance???
# could take a percentage of peak?
data.thresholds$AUofFocus_CalculatedThreshold_Peak <- 0.8 * data.thresholds$AUofFocusValue_max
# or mean - std dev?
data.thresholds$AUofFocus_CalculatedThreshold_Mean <- data.thresholds$AUofFocusValue_mean - data.thresholds$AUofFocusValue_sd
# or base it on a lower CI?
data.thresholds$AUofFocus_CIUpper <- data.thresholds$AUofFocusValue_mean + (quantile* data.thresholds$AUofFocusValue_se)
data.thresholds$AUofFocus_CILower <- data.thresholds$AUofFocusValue_mean - (quantile* data.thresholds$AUofFocusValue_se)
# whatever it is we pick, stick it into this column
data.thresholds$AUofFocus_Threshold <- data.thresholds$AUofFocus_CILower
#data.thresholds$AUofFocus_Threshold <- data.thresholds$AUofFocus_CalculatedThreshold_Mean
data.thresholds <- data.thresholds %>% select(c(PID, AUofFocus, AUofFocus_Threshold))
# can't have thresholds lower than 0, and if a threshold is 0 then the AU is effectively unactivatable...
data.thresholds$AUofFocus_Threshold <- ifelse(data.thresholds$AUofFocus_Threshold < 0.0, 0.0, data.thresholds$AUofFocus_Threshold)
head(data.thresholds)
## # A tibble: 6 × 3
## # Groups: PID [1]
## PID AUofFocus AUofFocus_Threshold
## <dbl> <fct> <dbl>
## 1 1 BrowLowererL 0.0205
## 2 1 BrowLowererR 0.185
## 3 1 CheekPuffL 0.0534
## 4 1 CheekPuffR 0.0181
## 5 1 CheekRaiserL 0.0564
## 6 1 CheekRaiserR 0.0936
write.csv(data.thresholds, "r_outputs/data.thresholds.csv", row.names=FALSE)
For each AU captured alongside the AUofFocus, calculate whether it met the calibrated activation threshold
data.recording_filtered_with_thresholds = NULL
# hacky, but doing this in a loop as brain is fried
for(i in PIDs ){
recorded_subset = subset(data.recording_filtered_resting_face, PID == i)
for (FAU in FAUs){
threshold_subset <- data.thresholds %>% filter(PID == i & AUofFocus == FAU)
threshold_value <- threshold_subset$`AUofFocus_Threshold`
recorded_subset[paste0(FAU, "_Activation")] <- ifelse(recorded_subset[FAU] > threshold_value , 1, 0)
}
data.recording_filtered_with_thresholds <- rbind(data.recording_filtered_with_thresholds, recorded_subset)
}
head(data.recording_filtered_with_thresholds)
## # A tibble: 6 × 114
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <fct> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 541 FALSE 14
## 2 1 BrowLowererL 1 holding 583 FALSE 15
## 3 1 BrowLowererL 1 holding 625 FALSE 16
## 4 1 BrowLowererL 1 holding 653 FALSE 17
## 5 1 BrowLowererL 1 holding 693 FALSE 18
## 6 1 BrowLowererL 1 holding 721 FALSE 19
## # ℹ 107 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
And do the same calculation for the AUofFocus (wrote this first, but no harm doing it this way as well - but this should end up just reproducing same results as if you looked up the AUofFocus’s Activation column from the previous block…). We’ll also join the “AU_categorisation.csv” data at this stage.
categories <- read.csv("Raw Data/AU_categorisation.csv", stringsAsFactors = FALSE)
# Join the threshold data with the results
data.recording_filtered_with_thresholds <- data.recording_filtered_with_thresholds %>%
left_join(., data.thresholds, by = join_by(PID, AUofFocus))
# And join the AUofFocus data with our categorisation for plotting based on regions (if we want that? who knows!)
data.recording_filtered_with_thresholds <- data.recording_filtered_with_thresholds %>%
left_join(., categories, by = join_by(AUofFocus))
# Calculate AUofFocus activations based on threshold
data.recording_filtered_with_thresholds$AUofFocus_PositiveActivations = ifelse(data.recording_filtered_with_thresholds$AUofFocusValue > data.recording_filtered_with_thresholds$`AUofFocus_Threshold`, 1, 0)
#recording_data_filtered$PositiveActivationDuration = recording_data_filtered$PositiveActivations * 38 # rough average of milliseconds between each frame
head(data.recording_filtered_with_thresholds)
## # A tibble: 6 × 119
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <chr> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 541 FALSE 14
## 2 1 BrowLowererL 1 holding 583 FALSE 15
## 3 1 BrowLowererL 1 holding 625 FALSE 16
## 4 1 BrowLowererL 1 holding 653 FALSE 17
## 5 1 BrowLowererL 1 holding 693 FALSE 18
## 6 1 BrowLowererL 1 holding 721 FALSE 19
## # ℹ 112 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
write.csv(data.recording_filtered_with_thresholds, "r_outputs/data.recording_filtered_with_thresholds.csv", row.names=FALSE)
To examine responsiveness, find the first timepoint for each PID/trial where they hit the threshold for the AUofFocus
library(dplyr)
library(data.table)
##
## Attaching package: 'data.table'
## The following objects are masked from 'package:lubridate':
##
## hour, isoweek, mday, minute, month, quarter, second, wday, week,
## yday, year
## The following object is masked from 'package:purrr':
##
## transpose
## The following objects are masked from 'package:dplyr':
##
## between, first, last
data.responsiveness = data.recording_filtered_with_thresholds %>%
group_by(PID, AUofFocus, TrialNo) %>%
mutate(want = ifelse(row_number()==min(which((AUofFocus_PositiveActivations >= 1) == TRUE)),
"yes", NA_character_)) %>% filter(want=="yes")
## Warning: There were 864 warnings in `mutate()`.
## The first warning was:
## ℹ In argument: `want = ifelse(...)`.
## ℹ In group 19: `PID = 1`, `AUofFocus = "CheekSuckL"`, `TrialNo = 1`.
## Caused by warning in `min()`:
## ! no non-missing arguments to min; returning Inf
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 863 remaining warnings.
# this data is missing rows at this point because in some cases they never hit the threshold
# so we need to complete the data
head(data.responsiveness)
## # A tibble: 6 × 120
## # Groups: PID, AUofFocus, TrialNo [6]
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <dbl> <chr> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 1418 FALSE 36
## 2 1 BrowLowererL 2 holding 516 FALSE 14
## 3 1 BrowLowererL 3 holding 501 FALSE 12
## 4 1 BrowLowererR 1 holding 1086 FALSE 28
## 5 1 BrowLowererR 2 holding 512 FALSE 13
## 6 1 BrowLowererR 3 holding 514 FALSE 13
## # ℹ 113 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
df1 <- expand.grid(PID = PIDs, AUofFocus = FAUs, TrialNo = c(1,2,3), TimeInTrial = 2000)
df1$PID <- factor(df1$PID)
data.responsiveness$PID <-factor(data.responsiveness$PID)
data.responsiveness = data.responsiveness %>% select(c(PID, AUofFocus, TrialNo, TimeInTrial))
data.responsiveness = data.responsiveness %>% full_join(df1, by=join_by(PID, AUofFocus, TrialNo))
data.responsiveness = data.responsiveness %>%
rowwise() %>%
mutate(TimeToActivation = min(TimeInTrial.x, TimeInTrial.y)) %>%
mutate(across(everything(), ~ ifelse(is.na(.), 2000, .))) %>%
select(-c(TimeInTrial.x, TimeInTrial.y))
head(data.responsiveness)
## # A tibble: 6 × 4
## # Rowwise: PID, AUofFocus, TrialNo
## PID AUofFocus TrialNo TimeToActivation
## <fct> <chr> <dbl> <dbl>
## 1 1 BrowLowererL 1 1418
## 2 1 BrowLowererL 2 516
## 3 1 BrowLowererL 3 501
## 4 1 BrowLowererR 1 1086
## 5 1 BrowLowererR 2 512
## 6 1 BrowLowererR 3 514
data.recording_filtered_with_thresholds$PID <- factor(data.recording_filtered_with_thresholds$PID)
data.recording_filtered_with_thresholds = data.recording_filtered_with_thresholds %>% left_join(data.responsiveness, by=join_by(PID, AUofFocus, TrialNo))
head(data.recording_filtered_with_thresholds)
## # A tibble: 6 × 120
## PID AUofFocus TrialNo TrialPhase TimeInTrial EndOfTrialFrame TrialFrameNo
## <fct> <chr> <dbl> <chr> <dbl> <lgl> <dbl>
## 1 1 BrowLowererL 1 holding 541 FALSE 14
## 2 1 BrowLowererL 1 holding 583 FALSE 15
## 3 1 BrowLowererL 1 holding 625 FALSE 16
## 4 1 BrowLowererL 1 holding 653 FALSE 17
## 5 1 BrowLowererL 1 holding 693 FALSE 18
## 6 1 BrowLowererL 1 holding 721 FALSE 19
## # ℹ 113 more variables: BrowLowererL <dbl>, BrowLowererR <dbl>,
## # CheekPuffL <dbl>, CheekPuffR <dbl>, CheekRaiserL <dbl>, CheekRaiserR <dbl>,
## # CheekSuckL <dbl>, CheekSuckR <dbl>, ChinRaiserB <dbl>, ChinRaiserT <dbl>,
## # DimplerL <dbl>, DimplerR <dbl>, InnerBrowRaiserL <dbl>,
## # InnerBrowRaiserR <dbl>, JawDrop <dbl>, JawSidewaysLeft <dbl>,
## # JawSidewaysRight <dbl>, JawThrust <dbl>, LidTightenerL <dbl>,
## # LidTightenerR <dbl>, LipCornerDepressorL <dbl>, …
Now we want to average by participant and AUOfFocus and calculate some percentages for the amount of activations of each FAU over the trials
# generate summary stats
data.recording_means <- data.recording_filtered_with_thresholds %>%
group_by(PID, AUofFocus) %>%
select(-c(BodyRegion, Side, TrialPhase, "AUofFocusSymmetrical")) %>%
relocate (c(AUofFocusValue, AUofFocus_PositiveActivations), .after = AUofFocus) %>% # move the new calculated columns to the front before all the interference stuff
summarise(across(everything(), list(mean=mean, sd=sd, sum=sum, count=~ n())))
## `summarise()` has grouped output by 'PID'. You can override using the `.groups`
## argument.
# calculate the percentage of time across the three trials the AUofFocus was active
data.recording_means$PositiveActivationPercentage <- (data.recording_means$AUofFocus_PositiveActivations_sum / data.recording_means$AUofFocus_PositiveActivations_count) * 100
# and do the same calculation for all the other FAUs to see what accidental activations occurred
for (FAU in FAUs){
data.recording_means[paste0(FAU, "_Activation_Percentage")] <- (data.recording_means[paste0(FAU, "_Activation_sum")] / data.recording_means[paste0(FAU, "_Activation_count")]) * 100
}
# figure out the columns needed to sum/count over all FAUs activation summary stats
SumFAUs <- paste(FAUs, "_Activation_sum", sep="")
CountFAUs <- paste(FAUs, "_Activation_count", sep="")
# and then calculate summaries for total accidental activations (excluding the AUofFocus)
data.recording_means <- data.recording_means %>%
mutate(TotalAccidentalActivations_Sum = rowSums(pick(SumFAUs)), na.rm = TRUE) %>%
mutate(TotalAccidentalActivations_Count = rowSums(pick(CountFAUs)), na.rm = TRUE)
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `TotalAccidentalActivations_Sum = rowSums(pick(SumFAUs))`.
## Caused by warning:
## ! Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(SumFAUs)
##
## # Now:
## data %>% select(all_of(SumFAUs))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## Warning: There was 1 warning in `mutate()`.
## ℹ In argument: `TotalAccidentalActivations_Count = rowSums(pick(CountFAUs))`.
## Caused by warning:
## ! Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(CountFAUs)
##
## # Now:
## data %>% select(all_of(CountFAUs))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
data.recording_means$AccidentalActivationsPercentage <- ( (data.recording_means$TotalAccidentalActivations_Sum - data.recording_means$AUofFocus_PositiveActivations_sum) / (data.recording_means$TotalAccidentalActivations_Count-data.recording_means$AUofFocus_PositiveActivations_count) ) * 100
write.csv(data.recording_means, "r_outputs/data.recording_means.csv", row.names=FALSE)
head(data.recording_means)
## # A tibble: 6 × 516
## # Groups: PID [1]
## PID AUofFocus AUofFocusValue_mean AUofFocusValue_sd AUofFocusValue_sum
## <fct> <chr> <dbl> <dbl> <dbl>
## 1 1 BrowLowererL 0.0281 0.0363 4.27
## 2 1 BrowLowererR 0.197 0.0540 28.8
## 3 1 CheekPuffL 0.0677 0.0688 10.3
## 4 1 CheekPuffR 0.0221 0.0192 3.34
## 5 1 CheekRaiserL 0.0663 0.0476 10.1
## 6 1 CheekRaiserR 0.0987 0.0242 14.9
## # ℹ 511 more variables: AUofFocusValue_count <int>,
## # AUofFocus_PositiveActivations_mean <dbl>,
## # AUofFocus_PositiveActivations_sd <dbl>,
## # AUofFocus_PositiveActivations_sum <dbl>,
## # AUofFocus_PositiveActivations_count <int>, TrialNo_mean <dbl>,
## # TrialNo_sd <dbl>, TrialNo_sum <dbl>, TrialNo_count <int>,
## # TimeInTrial_mean <dbl>, TimeInTrial_sd <dbl>, TimeInTrial_sum <dbl>, …
Now we want to create the combined dataset - recorded data (with activation thresholds/responsiveness calculated), questionnaire data (after each set of trials), and calibration data - all grouped by PID and AUofFocus.
Values captured: Average Calibration Value Calibration Value STD %Calibration Value STD Avg Discomfort (lower is better) Avg Discomfort STD Avg Borg RPE (lower is better) Avg Borg RPE STD Avg Performance Avg Performance STD Usability Score (lower is better)
We also calculate our overall score here based on fatigue, performance, discomfort, and responsiveness.
data.all_results <- data.recording_means %>%
# full_join(calibration_data, by = join_by(PID, AUofFocus)) %>%
full_join(data.questionnaire_raw, by = join_by(PID, AUofFocus)) %>%
#relocate (c("Calibration Threshold", "Discomfort", "Borg RPE", "Performance", "PositiveActivationPercentage"), .after = PositiveActivationDuration_count) %>%
rename(BorgRPE = "Borg RPE")
# calculate a score to assess the "best" FAUs based on our measures
data.all_results$Score <- data.all_results$BorgRPE + data.all_results$Performance + data.all_results$Discomfort + ((data.all_results$TimeToActivation_mean/2000))*10 + ((1.0-data.all_results$AUofFocusValue_mean)*10)
data.all_results$TimeToActivation_mean = data.all_results$TimeToActivation_mean/1000
data.all_results <- data.all_results %>%
left_join(., categories, by = join_by(AUofFocus))
write.csv(data.all_results, "r_outputs/data.all_results.csv", row.names=FALSE)
head(data.all_results)
## # A tibble: 6 × 523
## # Groups: PID [1]
## PID AUofFocus AUofFocusValue_mean AUofFocusValue_sd AUofFocusValue_sum
## <fct> <chr> <dbl> <dbl> <dbl>
## 1 1 BrowLowererL 0.0281 0.0363 4.27
## 2 1 BrowLowererR 0.197 0.0540 28.8
## 3 1 CheekPuffL 0.0677 0.0688 10.3
## 4 1 CheekPuffR 0.0221 0.0192 3.34
## 5 1 CheekRaiserL 0.0663 0.0476 10.1
## 6 1 CheekRaiserR 0.0987 0.0242 14.9
## # ℹ 518 more variables: AUofFocusValue_count <int>,
## # AUofFocus_PositiveActivations_mean <dbl>,
## # AUofFocus_PositiveActivations_sd <dbl>,
## # AUofFocus_PositiveActivations_sum <dbl>,
## # AUofFocus_PositiveActivations_count <int>, TrialNo_mean <dbl>,
## # TrialNo_sd <dbl>, TrialNo_sum <dbl>, TrialNo_count <int>,
## # TimeInTrial_mean <dbl>, TimeInTrial_sd <dbl>, TimeInTrial_sum <dbl>, …
Generate the boxplots for each measure.
data.plots = data.all_results
data.plots$AUofFocus = factor(data.plots$AUofFocus)
data.plots$AUofFocusSymmetrical = factor(data.plots$AUofFocusSymmetrical)
data.plots = data.plots %>% mutate(AUofFocusSymmetrical = fct_rev(AUofFocusSymmetrical))
plot_AUofFocusVar <- function(plot_data, measure, reorder=FALSE){
#plot_data <- data
#plot_data$AUofFocus = plot_data$AUofFocusSymmetrical
#plot_data$AUofFocus = as.factor(plot_data$AUofFocus)
#plot_data <- plot_data %>% mutate(AUofFocus = factor(plot_data$AUofFocus))
#plot_data$AUofFocus <- factor(plot_data$AUofFocus, levels=rev(levels(plot_data$AUofFocus)))
if (reorder){
p <- ggplot(plot_data, aes(y=reorder(AUofFocusSymmetrical, .data[[measure]], mean), x=.data[[measure]], colour=AUofFocusSymmetrical))
} else {
p <- ggplot(plot_data, aes(y=AUofFocusSymmetrical, x=.data[[measure]], colour=AUofFocusSymmetrical))
}
p<- p + geom_boxplot(outlier.colour=NULL,
outlier.size=1) + theme(legend.position = "none") + ggtitle(measure) + stat_summary(fun = mean, geom = "point", size = 2) + geom_point(pch = ".", size = 3) + theme(plot.margin = margin(10, 10, 10, 10, "pt"))
print(p)
return(p)
}
plot.AUofFocusValue_mean = plot_AUofFocusVar(data.plots, "AUofFocusValue_mean") +xlab("Mean Activation") +ggtitle("Mean Activation") +labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
#plot.Thresholds = plot_AUofFocusVar(data.thresholds, "AUofFocus_Threshold") + xlab("Threshold")+ ggtitle("Threshold")
plot.TimeToActivation = plot_AUofFocusVar(data.plots, "TimeToActivation_mean") + xlab("Seconds")+ ggtitle("Responsiveness")+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.PositiveActivationPercentage = plot_AUofFocusVar(data.plots, "PositiveActivationPercentage") + xlim(0,100) + xlab("%")+ ggtitle("Activation %")+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.FalseActivationPercentage = plot_AUofFocusVar(data.plots, "AccidentalActivationsPercentage") + xlim(0,100)+ xlab("%")+ ggtitle("Other FAU\n Activations %")+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.Discomfort = plot_AUofFocusVar(data.plots, "Discomfort") + scale_x_continuous(breaks=c(0,2.5,5.0,7.5,10), limits=c(0,10), labels=c("Low", "", "", "", "High")) + ggtitle("Perceived\nDiscomfort")+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.BorgRPE = plot_AUofFocusVar(data.plots, "BorgRPE") + ggtitle("Perceived\nEffort") + xlab("Effort") + scale_x_continuous(breaks=c(0,2.5,5.0,7.5,10), limits=c(0,10), labels=c("Low", "", "", "", "High"))+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.Performance = plot_AUofFocusVar(data.plots, "Performance") + ggtitle("Perceived\nPerformance") + scale_x_continuous(breaks=c(0,2.5,5.0,7.5,10), limits=c(0,10), labels=c("Best", "", "", "", "Worst "))+labs(y="AUs of Focus - Alphabetical Order", colour=" - Alphabetical Order")
plot.Score = plot_AUofFocusVar(data.plots, "Score", reorder=TRUE) + scale_x_continuous(breaks=c(0,10,20,30,40,50), limits=c(0,50), labels=c("Best", "", "", "", "", "Worst ")) + theme(text = element_text(size = 14)) +labs(y="AUs of Focus - Descending Order", colour="AUs of Focus - Descending Order")
## Combi-plot for Paper - Figure 2 Layout 1.
#combi_plot <- plot.AUofFocusValue_mean + plot.TimeToActivation + plot.PositiveActivationPercentage + plot.FalseActivationPercentage + plot.Discomfort +plot.BorgRPE + #plot.Performance +plot.Score + plot_layout(ncol = 9, guides = "collect", axes= "collect")
#ExportPlot(combi_plot, "r_outputs/combi_plot", 14, 9)
layout <- "
ABCD
AEF#
"
combi_plot <- plot.Score + plot.AUofFocusValue_mean + plot.TimeToActivation + plot.Discomfort +plot.BorgRPE + plot.Performance + plot_layout(ncol = 3, guides = "collect", axes= "collect", design = layout)
ExportPlot(combi_plot, "r_outputs/combi_plot", 11, 11)
print(combi_plot)
Layout 2.
combi_plot = plot.AUofFocusValue_mean + plot.TimeToActivation + plot.Discomfort +plot.BorgRPE + plot.Performance +plot.Score + plot_layout(ncol = 6, guides = "collect", axes= "collect")
ExportPlot(combi_plot, "r_outputs/paper_combi_plot", 13, 7)
print(combi_plot)
Plot by activation percentage.
#cols_to_select <- paste0(FAUs, "_Activation_Percentage", sep="")
cols_to_select = paste0(FAUs, "_mean", sep="")
cols_to_select <- prepend(cols_to_select, "AUofFocus")
## Warning: `prepend()` was deprecated in purrr 1.0.0.
## ℹ Please use append(after = 0) instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
cols_to_select <- prepend(cols_to_select, "PID")
# average everything together
data.corr_plot <- data.all_results %>%
select(all_of(c(cols_to_select))) %>%
rename_with(~str_remove(., '_mean')) %>%
group_by(AUofFocus) %>%
summarise(across(FAUs, list(mean=mean))) %>%
rename_with(~str_remove(., '_mean'))# %>%
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(FAUs, list(mean = mean))`.
## Caused by warning:
## ! Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(FAUs)
##
## # Now:
## data %>% select(all_of(FAUs))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
#select(-c(PID))
library(reshape2)
##
## Attaching package: 'reshape2'
## The following objects are masked from 'package:data.table':
##
## dcast, melt
## The following object is masked from 'package:tidyr':
##
## smiths
melted_cormat <- melt(data.corr_plot, id=c("AUofFocus"))
melted_cormat <- rename(melted_cormat, ActivatedAUs = variable)
#head(data.corr_plot)
#head(melted_cormat)
tile_plot_1 <- ggplot(data = melted_cormat, aes(x=AUofFocus, y=ActivatedAUs, fill=value)) +
geom_tile() + theme(axis.text.x = element_text(angle = 90, vjust = 1, hjust=1)) + scale_fill_viridis_c(direction = -1, option = "plasma", limits=c(0,0.5))+ labs(fill="Mean\nActivation") + xlab("FAU of Focus") + ylab("Activated FAUs")
print(tile_plot_1)
ExportPlot(tile_plot_1, "r_outputs/tile_plot_activation", 10, 8)
# x / along the way tells you if the FAU was robust to false activation
# y / up and down tells you the FAUs that were likely falsely activated when trying that FAU
Calculate correlations between the AUofFOcus and the other FAUs at the time the FAU gesture was being performed.
subset_FAUs <- FAUs # c("MouthLeft", "MouthRight", "CheekPuffL", "CheekPuffR", "JawDrop", "LidTightenerL", "LidTightenerR")
cols_to_select = paste0(subset_FAUs, "_mean", sep="")
cols_to_select <- prepend(cols_to_select, "AUofFocus")
cols_to_select <- prepend(cols_to_select, "PID")
data.corr_plot = data.all_results %>%
select(all_of(c(cols_to_select))) %>%
rename_with(~str_remove(., '_mean')) %>%
group_by(AUofFocus, PID) %>%
summarise(across(subset_FAUs, list(mean=mean))) %>%
rename_with(~str_remove(., '_mean')) %>% filter(AUofFocus %in% subset_FAUs)
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(subset_FAUs, list(mean = mean))`.
## Caused by warning:
## ! Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
## # Was:
## data %>% select(subset_FAUs)
##
## # Now:
## data %>% select(all_of(subset_FAUs))
##
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
## `summarise()` has grouped output by 'AUofFocus'. You can override using the
## `.groups` argument.
# get the AU of focus value specifically for that row
for( i in rownames(data.corr_plot) ){
data.corr_plot[i, "AUofFocusValue"] = data.corr_plot[i, data.corr_plot[i, "AUofFocus", drop=TRUE], drop=TRUE]
}
#library(smplot2)
#ggplot(data = data.corr_plot, mapping = aes(x = MouthLeft, y = AUofFocusValue)) +
# geom_point(shape = 21, fill = "#0f993d", color = "white", size = 3) +
# sm_statCorr(
# color = "#0f993d", corr_method = "spearman",
# linetype = "dashed"
# ) +facet_wrap(.~AUofFocus)
data.corr_plot
## # A tibble: 1,060 × 56
## # Groups: AUofFocus [53]
## AUofFocus PID BrowLowererL BrowLowererR CheekPuffL CheekPuffR CheekRaiserL
## <chr> <fct> <dbl> <dbl> <dbl> <dbl> <dbl>
## 1 BrowLower… 1 0.0281 0.0404 0 0 0.00561
## 2 BrowLower… 3 0.679 0.658 0 0 0.121
## 3 BrowLower… 7 0.165 0.118 0 0 0.00509
## 4 BrowLower… 8 0.000568 0.00118 0 0 0
## 5 BrowLower… 9 0.0260 0.00543 0 0 0
## 6 BrowLower… 10 0.0158 0.0128 1.40e-45 1.40e-45 0.0128
## 7 BrowLower… 12 0.108 0.0311 0 7.48e- 5 0.0815
## 8 BrowLower… 13 0.0547 0.0373 1.08e- 4 9.51e- 5 0.0159
## 9 BrowLower… 14 0.651 0.554 2.53e-19 3.14e-33 0.285
## 10 BrowLower… 15 0.560 0.492 0 0 0.277
## # ℹ 1,050 more rows
## # ℹ 49 more variables: CheekRaiserR <dbl>, CheekSuckL <dbl>, CheekSuckR <dbl>,
## # ChinRaiserB <dbl>, ChinRaiserT <dbl>, DimplerL <dbl>, DimplerR <dbl>,
## # InnerBrowRaiserL <dbl>, InnerBrowRaiserR <dbl>, JawDrop <dbl>,
## # JawSidewaysLeft <dbl>, JawSidewaysRight <dbl>, JawThrust <dbl>,
## # LidTightenerL <dbl>, LidTightenerR <dbl>, LipCornerDepressorL <dbl>,
## # LipCornerDepressorR <dbl>, LipCornerPullerL <dbl>, …
# convert data to a matrix format for plotting
library(Hmisc)
##
## Attaching package: 'Hmisc'
## The following objects are masked from 'package:dplyr':
##
## src, summarize
## The following objects are masked from 'package:plyr':
##
## is.discrete, summarize
## The following objects are masked from 'package:base':
##
## format.pval, units
mat <- outer(1:53, 1:53)
colnames(mat)<- FAUs
rownames(mat)<- FAUs
for( FAUofFocus in FAUs ){
for (FAUActivated in FAUs){
data.subset <- subset(data.corr_plot, AUofFocus== FAUofFocus)
x <- data.subset$AUofFocusValue
y <- data.subset[FAUActivated]
mycor<-cor(x=x, y=y, method = c("pearson"))
mat[FAUofFocus, FAUActivated] = mycor
}
}
# mat
## convert to tibble, add row identifier, and shape "long"
dat2 <-
mat %>%
as_tibble(rownames = "Var1") %>%
pivot_longer(-Var1, names_to = "Var2", values_to = "value")
tile_plot_2 <- ggplot(data = dat2, aes(x=Var1, y=Var2, fill=value)) +
geom_tile() + theme(axis.text.x = element_text(angle = 90, vjust = 1, hjust=1)) + scale_fill_viridis_c(limits=c(-1, 1), direction = -1, option = "H") + xlab("FAU of Focus") + ylab("Activated FAUs") + labs(fill="Pearson\nCorrelation")
tile_plot_2
ExportPlot(tile_plot_2, "r_outputs/tile_plot_correlation", 10, 8)
tile_combi <- tile_plot_1 + tile_plot_2 + plot_layout(ncol = 1, guides = "collect", axes= "collect")
tile_combi
ExportPlot(tile_combi, "r_outputs/tile_combi", 10, 13)
ART Anovas to show differences here by AUofFocus (discomfort, exertion, performance, score)
data_symmetrical <- data.plots %>%
group_by(PID, AUofFocusSymmetrical) %>%
select(-c(AUofFocus, BodyRegion)) %>%
summarise(across(everything(), list(mean=mean, sd=sd)))
## Warning: There were 1120 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `across(everything(), list(mean = mean, sd = sd))`.
## ℹ In group 1: `PID = 1` and `AUofFocusSymmetrical = UpperLipRaiserLR`.
## Caused by warning in `mean.default()`:
## ! argument is not numeric or logical: returning NA
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 1119 remaining warnings.
## `summarise()` has grouped output by 'PID'. You can override using the `.groups`
## argument.
fau_art <- function(data, variable){
m <- art(as.formula(paste(variable, "~ AUofFocusSymmetrical + Error(PID)")), data=data)
m.anova <- anova(m)
print(m.anova)
contrasts = art.con(m, "AUofFocusSymmetrical") %>%
summary() %>%
mutate(sig = ifelse(p.value < 0.05, "*", "")) #%>%
#filter(p.value <0.05 & grepl('JawDrop|CheekPuff|MouthLR|LidTightener', contrast) )
# print(contrasts)
return(contrasts)
}
discomfort_contrasts = fau_art(data_symmetrical, "Discomfort_mean")
## Analysis of Variance of Aligned Rank Transformed Data
##
## Table Type: Repeated Measures Analysis of Variance Table (Type I)
## Model: Repeated Measures (aov)
## Response: art(Discomfort_mean)
##
## Error Df Df.res F value Pr(>F)
## 1 AUofFocusSymmetrical Withn 27 513 6.8568 < 2.22e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
rpe_contrasts = fau_art(data_symmetrical, "BorgRPE_mean")
## Analysis of Variance of Aligned Rank Transformed Data
##
## Table Type: Repeated Measures Analysis of Variance Table (Type I)
## Model: Repeated Measures (aov)
## Response: art(BorgRPE_mean)
##
## Error Df Df.res F value Pr(>F)
## 1 AUofFocusSymmetrical Withn 27 513 10.115 < 2.22e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
score_contrasts = fau_art(data_symmetrical, "Score_mean")
## Analysis of Variance of Aligned Rank Transformed Data
##
## Table Type: Repeated Measures Analysis of Variance Table (Type I)
## Model: Repeated Measures (aov)
## Response: art(Score_mean)
##
## Error Df Df.res F value Pr(>F)
## 1 AUofFocusSymmetrical Withn 27 513 12.487 < 2.22e-16 ***
## ---
## Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
write.csv(score_contrasts, "r_outputs/score_contrasts", row.names=FALSE)
library(rPref)
##
## Attaching package: 'rPref'
## The following object is masked from 'package:data.table':
##
## between
## The following object is masked from 'package:dplyr':
##
## between
## The following objects are masked from 'package:plyr':
##
## empty, true
pareto_data = data_symmetrical %>% group_by(AUofFocusSymmetrical) %>% select(c(AUofFocusSymmetrical, AUofFocusValue_mean_mean, TimeToActivation_mean_mean, Discomfort_mean, BorgRPE_mean, Performance_mean)) %>% summarise(across(everything(), list(mean=mean)))
result = psel(pareto_data, high(AUofFocusValue_mean_mean_mean) * low(TimeToActivation_mean_mean_mean) * low(Discomfort_mean_mean) * low(BorgRPE_mean_mean) * low(Performance_mean_mean), top=100, show_level=TRUE)
write.csv(result, "r_outputs/rpref_result.csv", row.names=FALSE)
print(result)
## # A tibble: 28 × 7
## AUofFocusSymmetrical AUofFocusValue_mean_mean_mean TimeToActivation_mean_me…¹
## <fct> <dbl> <dbl>
## 1 LidTightenerLR 0.463 0.846
## 2 JawDrop 0.499 0.882
## 3 UpperLipRaiserLR 0.242 0.871
## 4 LipSuckLT/RT 0.106 0.976
## 5 JawThrust 0.212 1.08
## 6 MouthLR 0.384 1.14
## 7 LipSuckLB/RB 0.184 0.968
## 8 DimplerLR 0.0753 1.01
## 9 CheekPuffLR 0.148 1.02
## 10 LowerLipDepressorLR 0.174 0.970
## # ℹ 18 more rows
## # ℹ abbreviated name: ¹TimeToActivation_mean_mean_mean
## # ℹ 4 more variables: Discomfort_mean_mean <dbl>, BorgRPE_mean_mean <dbl>,
## # Performance_mean_mean <dbl>, .level <dbl>