# Analysis script for "Effects of face masks on speech recognition" # J.C. Toscano and C.M. Toscano, Villanova University library(lme4) library(plyr) ################################################################################################## # Set up data frame for analysis # Load data data <- as.data.frame(read.delim('data.csv', sep=',', header=T)) # Omit practice trials data2 <- subset(data, hintlist!='NA') # Omit trials with RT > 60 seconds data2 <- subset(data2, rt<60) # Convert subject and sentence columns to factors data2$subject <- as.factor(data2$subject) data2$hintlist <- as.factor(data2$hintlist) data2$hintsentence <- as.factor(data2$hintsentence) # Create HINT 'list_sentence' column data2$hint <- as.factor(paste0(data2$hintlist, '_', data2$hintsentence)) # Create SNR and talker variables data2$snrNum <- ifelse(data2$snr==3,-0.5,0.5) data2$talkerNum <- ifelse(data2$talker=='jt',-0.5,0.5) # Create effect codes for mask type # Key: # dispose = disposable surgical mask # fabric1 = fitted cloth mask # fabric2 = pleated cloth mask # n95mask = N95 respirator data2$maskDispose <- ifelse(data2$mask=='dispose',0.8,-0.2) data2$maskFabric1 <- ifelse(data2$mask=='fabric1',0.8,-0.2) data2$maskFabric2 <- ifelse(data2$mask=='fabric2',0.8,-0.2) data2$maskN95Mask <- ifelse(data2$mask=='n95mask',0.8,-0.2) # Create proportion correct column data2$propcorrect <- data2$wordscorrect / data2$numwords # Set glmer options glmeropt = glmerControl(optimizer='bobyqa', optCtrl = list(maxfun = 200000)) ################################################################################################## # Descriptive Statistics # Total number of trials nrow(data2) # [1] 3564 # Total number of subjects length(unique(data2$subject)) # [1] 181 # SNR ddply(data2, .(snr), summarize, mean=mean(propcorrect), sd=sd(propcorrect)) # snr mean sd # 1 3 0.3688941 0.3673171 # 2 13 0.9229677 0.1988491 # Talker ddply(data2, .(talker), summarize, mean=mean(propcorrect), sd=sd(propcorrect)) # talker mean sd # 1 ct 0.6834107 0.3914347 # 2 jt 0.6126850 0.4142413 # Talker x SNR ddply(data2, .(talker,snr), summarize, mean=mean(propcorrect), sd=sd(propcorrect)) # talker snr mean sd # 1 ct 3 0.4138122 0.3733912 # 2 ct 13 0.9485159 0.1582753 # 3 jt 3 0.3238742 0.3556851 # 4 jt 13 0.8973055 0.2298350 # Mask type ddply(data2, .(mask,snr), summarize, mean=mean(propcorrect), sd=sd(propcorrect)) # mask snr mean sd # 1 dispose 3 0.4238229 0.3751347 # 2 dispose 13 0.9350444 0.1839943 # 3 fabric1 3 0.2698052 0.3014067 # 4 fabric1 13 0.8878891 0.2323401 # 5 fabric2 3 0.3513878 0.3543970 # 6 fabric2 13 0.9181812 0.2045042 # 7 n95mask 3 0.3460698 0.3636381 # 8 n95mask 13 0.9309524 0.1838518 # 9 no_mask 3 0.4522263 0.4070690 # 10 no_mask 13 0.9426648 0.1813404 ################################################################################################## # Mixed-effects Models model <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskDispose + maskFabric1 + maskFabric2 + maskN95Mask + snrNum + talkerNum + snrNum:talkerNum + maskDispose:snrNum + maskFabric1:snrNum + maskFabric2:snrNum + maskN95Mask:snrNum + maskDispose:talkerNum + maskFabric1:talkerNum + maskFabric2:talkerNum + maskN95Mask:talkerNum + maskDispose:snrNum:talkerNum + maskFabric1:snrNum:talkerNum + maskFabric2:snrNum:talkerNum + maskN95Mask:snrNum:talkerNum + (1|subject) + (1|hint) + (0+snrNum|subject) + (0+snrNum|hint) + (0+talkerNum|subject) + (0+talkerNum|hint) + (0+snrNum:talkerNum|subject) + (0+snrNum:talkerNum|hint) + (0+maskDispose|subject) + (0+maskFabric1|subject) + (0+maskFabric2|subject) + (0+maskN95Mask|subject) + (0+maskDispose:snrNum|subject) + (0+maskFabric1:snrNum|subject) + (0+maskFabric2:snrNum|subject) + (0+maskN95Mask:snrNum|subject) + (0+maskDispose:talkerNum|subject) + (0+maskFabric1:talkerNum|subject) + (0+maskFabric2:talkerNum|subject) + (0+maskN95Mask:talkerNum|subject) + (0+maskDispose|hint) + (0+maskFabric1|hint) + (0+maskFabric2|hint) + (0+maskN95Mask|hint) + (0+maskDispose:snrNum|hint) + (0+maskFabric1:snrNum|hint) + (0+maskFabric2:snrNum|hint) + (0+maskN95Mask:snrNum|hint) + (0+maskDispose:talkerNum|hint) + (0+maskFabric1:talkerNum|hint) + (0+maskFabric2:talkerNum|hint) + (0+maskN95Mask:talkerNum|hint), data=data2, family=binomial, control=glmeropt) summary(model) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 1.728076 0.179320 9.637 < 2e-16 *** # maskDispose 0.002214 0.123851 0.018 0.98573 # maskFabric1 -1.070332 0.146447 -7.309 2.70e-13 *** Fitted # maskFabric2 -0.542202 0.176781 -3.067 0.00216 ** Pleated # maskN95Mask -0.380902 0.167531 -2.274 0.02299 * N95 # snrNum 5.479648 0.212302 25.811 < 2e-16 *** SNR # talkerNum 1.008158 0.196543 5.129 2.91e-07 *** Talker # snrNum:talkerNum 0.505536 0.252203 2.004 0.04502 * SNR x Talker # maskDispose:snrNum 0.285615 0.319579 0.894 0.37147 # maskFabric1:snrNum 0.597503 0.302610 1.974 0.04833 * Fitted x SNR # maskFabric2:snrNum 0.572854 0.275046 2.083 0.03727 * Pleated x SNR # maskN95Mask:snrNum 0.829566 0.356007 2.330 0.01980 * N95 x SNR # maskDispose:talkerNum 0.221199 0.356517 0.620 0.53496 # maskFabric1:talkerNum -0.424661 0.335666 -1.265 0.20582 # maskFabric2:talkerNum 0.793616 0.326684 2.429 0.01513 * Pleated x Talker # maskN95Mask:talkerNum -0.184253 0.253929 -0.726 0.46808 # maskDispose:snrNum:talkerNum 0.029926 0.456605 0.066 0.94774 # maskFabric1:snrNum:talkerNum 0.377307 0.426782 0.884 0.37666 # maskFabric2:snrNum:talkerNum 0.164940 0.449775 0.367 0.71383 # maskN95Mask:snrNum:talkerNum 1.262623 0.459155 2.750 0.00596 ** N95 x SNR x Talker # SNR x talker follow-up data3 <- subset(data2, snrNum < 0) modelA <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ talkerNum + (1|subject) + (1|hint) + (0+talkerNum|subject) + (0+talkerNum|hint), data=data3, family=binomial, control=glmeropt) summary(modelA) # Estimate Std. Error z value Pr(>|z|) # (Intercept) -0.8293 0.1703 -4.869 1.12e-06 *** # talkerNum 0.5961 0.1703 3.501 0.000464 *** Talker (low SNR) data3 <- subset(data2, snrNum > 0) modelB <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ talkerNum + (1|subject) + (1|hint) + (0+talkerNum|subject) + (0+talkerNum|hint), data=data3, family=binomial, control=glmeropt) summary(modelB) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 3.8677 0.1966 19.669 < 2e-16 *** # talkerNum 1.0736 0.2534 4.236 2.27e-05 *** Talker (high SNR) # Pleated mask x talker follow-up data3 <- subset(data2, (mask=='fabric2' | mask=='no_mask') & talkerNum < 0) data3$maskFabric2 <- ifelse(data3$mask=='fabric2', 0.5, -0.5) modelA <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric2 + (1|subject) + (1|hint) + (0+maskFabric2|subject) + (0+maskFabric2|hint), data=data3, family=binomial, control=glmeropt) summary(modelA) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 0.6015 0.1107 5.436 5.45e-08 *** # maskFabric2 -0.4905 0.1491 -3.291 0.000999 *** Pleated (male talker) data3 <- subset(data2, (mask=='fabric2' | mask=='no_mask') & talkerNum > 0) data3$maskFabric2 <- ifelse(data3$mask=='fabric2', 0.5, -0.5) modelB <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric2 + (1|subject) + (1|hint) + (0+maskFabric2|subject) + (0+maskFabric2|hint), data=data3, family=binomial, control=glmeropt) summary(modelB) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 1.25877 0.13621 9.242 <2e-16 *** # maskFabric2 -0.20541 0.09985 -2.057 0.0397 * Pleated (female talker) # Fitted mask x SNR follow-up data3 <- subset(data2, (mask=='fabric1' | mask=='no_mask') & snrNum < 0) data3$maskFabric1 <- ifelse(data3$mask=='fabric1', 0.5, -0.5) modelA <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric1 + (1|subject) + (1|hint) + (0+maskFabric1|subject) + (0+maskFabric1|hint), data=data3, family=binomial, control=glmeropt) summary(modelA) # Estimate Std. Error z value Pr(>|z|) # (Intercept) -0.9468 0.2166 -4.371 1.24e-05 *** # maskFabric1 -1.1816 0.2276 -5.192 2.08e-07 *** Fitted (low SNR) data3 <- subset(data2, (mask=='fabric1' | mask=='no_mask') & snrNum > 0) data3$maskFabric1 <- ifelse(data3$mask=='fabric1', 0.5, -0.5) modelB <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric1 + (1|subject) + (1|hint) + (0+maskFabric1|subject) + (0+maskFabric1|hint), data=data3, family=binomial, control=glmeropt) summary(modelB) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 4.4351 0.3300 13.438 < 2e-16 *** # maskFabric1 -1.2474 0.3502 -3.562 0.000369 *** Fitted (high SNR) # Pleated mask x SNR follow-up data3 <- subset(data2, (mask=='fabric2' | mask=='no_mask') & snrNum < 0) data3$maskFabric2 <- ifelse(data3$mask=='fabric2', 0.5, -0.5) modelA <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric2 + (1|subject) + (1|hint) + (0+maskFabric2|subject) + (0+maskFabric2|hint), data=data3, family=binomial, control=glmeropt) summary(modelA) # Estimate Std. Error z value Pr(>|z|) # (Intercept) -0.7091 0.2122 -3.342 0.000832 *** # maskFabric2 -0.6683 0.2070 -3.228 0.001248 ** Pleated (low SNR) data3 <- subset(data2, (mask=='fabric2' | mask=='no_mask') & snrNum > 0) data3$maskFabric2 <- ifelse(data3$mask=='fabric2', 0.5, -0.5) modelB <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskFabric2 + (1|subject) + (1|hint) + (0+maskFabric2|subject) + (0+maskFabric2|hint), data=data3, family=binomial, control=glmeropt) summary(modelB) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 4.8290 0.3688 13.094 <2e-16 *** # maskFabric2 -0.7763 0.4147 -1.872 0.0612 . Pleated (high SNR, marginal) # N95 x SNR x talker follow-up data3 <- subset(data2, (mask=='n95mask' | mask=='no_mask') & snrNum < 0 & talkerNum < 0) data3$maskN95Mask <- ifelse(data3$mask=='n95mask', 0.5, -0.5) modelA <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskN95Mask + (1|subject) + (1|hint) + (0+maskN95Mask|hint), data=data3, family=binomial, control=glmeropt) summary(modelA) # Estimate Std. Error z value Pr(>|z|) # (Intercept) -1.0837 0.3160 -3.430 0.000604 *** # maskN95Mask -0.4408 0.2170 -2.032 0.042186 * N95 (low SNR, male talker) data3 <- subset(data2, (mask=='n95mask' | mask=='no_mask') & snrNum < 0 & talkerNum > 0) data3$maskN95Mask <- ifelse(data3$mask=='n95mask', 0.5, -0.5) modelB <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskN95Mask + (1|subject) + (1|hint) + (0+maskN95Mask|hint), data=data3, family=binomial, control=glmeropt) summary(modelB) # Estimate Std. Error z value Pr(>|z|) # (Intercept) -0.5169 0.2550 -2.027 0.0426 * # maskN95Mask -1.2121 0.2086 -5.812 6.18e-09 *** N95 (low SNR, female talker) data3 <- subset(data2, (mask=='n95mask' | mask=='no_mask') & snrNum > 0 & talkerNum < 0) data3$maskN95Mask <- ifelse(data3$mask=='n95mask', 0.5, -0.5) modelC <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskN95Mask + (1|subject) + (1|hint) + (0+maskN95Mask|hint), data=data3, family=binomial, control=glmeropt) summary(modelC) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 4.3892 0.4161 10.549 <2e-16 *** # maskN95Mask -0.4306 0.4795 -0.898 0.369 data3 <- subset(data2, (mask=='n95mask' | mask=='no_mask') & snrNum > 0 & talkerNum > 0) data3$maskN95Mask <- ifelse(data3$mask=='n95mask', 0.5, -0.5) modelD <- glmer(cbind(wordscorrect, numwords-wordscorrect) ~ maskN95Mask + (1|subject) + (1|hint) + (0+maskN95Mask|hint), data=data3, family=binomial, control=glmeropt) summary(modelD) # Estimate Std. Error z value Pr(>|z|) # (Intercept) 8.4950 0.9670 8.785 <2e-16 *** # maskN95Mask -0.3394 0.6695 -0.507 0.612