www.sciencemag.org/content/351/6278/aad8008/suppl/DC1 Supplementary Materials for Comment on “Math at home adds up to achievement in school” Michael C. Frank E-mail: mcfrank@stanford.edu Published 11 March 2015, Science 351, aad8008 (2016) DOI: 10.1126/science.aad8008 This PDF file includes: Methods Other Supplementary Material for this manuscript includes the following: (available at www.sciencemag.org/content/351/6278/aad8008/suppl/DC1) Data File (RMD) Berkowitz et al. (2015) Technical Comment Supplementary Materials Michael C. Frank 2016-01-12 This document reports code for a Technical Comment on Berkowitz et al. (2015). Data are loaded from Berkowitz et al (2015)’s supplementary materials. This PDF file was generated from an R Markdown script (.Rmd file), which can be opened in any text editor and is also attached as part of the Supplementary Materials for the Technical Comment. When the script is “knitted” using the knitr package, it will generate this PDF file. d <- read.csv("aac7427-Accessory-Data-File-S1.csv") %>% mutate(condition = ifelse(cond.dum == 1, "Math App","Reading App")) Exclusions and Data Preparation We need to exclude: • twins (twin) • dropouts during the first year (year1dropout) We also see that we need to include 34 children due to experimenter errors (24 fall, 16 math and 8 reading; 10 in spring, 9 math and 1 reading). This is in the filter variables (e.g., wjappliedfa13_filter). These are binary variables that presumably could be conveying whether the children’s scores should be included because they get basal and ceiling items. We do this after excluding twins and dropouts. sum(d$wjappliedfa13_filter[d$twin == 0 & d$year1dropout == 0] == 0) ## [1] 24 sum(d$WJ.applied.Sp14.filter[d$twin == 0 & d$year1dropout == 0] == 0) ## [1] 11 These numbers are very close to the paper’s numbers, though we do get 11 in the spring (not 10). Let’s do the same for reading, where there were 25 exclusions (7 in fall, 4 math and 3 reading; 18 in spring, 12 math and 6 reading). sum(d$WJletterwordFa13_filter[d$twin == 0 & d$year1dropout == 0] == 0) ## [1] 7 sum(d$WJ.letterword.Sp14.filter[d$twin == 0 & d$year1dropout == 0] == 0) ## [1] 19 1 Again we’re one off (18 vs. 19 in spring), but quite close. Note there is a separate issue in exclusions for analyses that use math anxiety: where math anxiety questionnaire data is missing (~20% of cases), you have to exclude data. I’m assuming the authors just dropped non-responding families for these analyses. dt.excl <- d %>% mutate(applied.w.fa13 = ifelse(wjappliedfa13_filter, WJ.Applied.Problems.W.Score.Fa13, NA), applied.w.sp14 = ifelse(WJ.applied.Sp14.filter, WJ.Applied.Problems_W.Score_Sp14, NA), reading.w.fa13 = ifelse(WJletterwordFa13_filter, WJ.Letter.Word.ID.W.Score.Fa13, NA), reading.w.sp14 = ifelse(WJ.letterword.Sp14.filter, WJ.LetterWord.Wscore.Sp14, NA)) %>% filter(!twin, !year1dropout) %>% gather(measure, score, applied.w.fa13, applied.w.sp14, reading.w.fa13, reading.w.sp14) %>% separate(measure, c("measure","time"), sep="w\\.") %>% mutate(year = as.factor(str_sub(time, 4, 5)), measure = ifelse(grepl( "applied", measure), "Math", "Reading")) Let’s check the means now that we are doing exclusions. Table S1 of the original paper gives means, albeit using a median split on math anxiety (e.g. not grand means). Low anxious: • math grp: 463 (19) in fall and 479 (21) in spring • reading grp: 459 (18) in fall and 475 (19) in spring High anxious: • math grp: 459 (16) in fall and 474 (19) in spring • reading grp: 458 (14) in fall and 469 (19) in spring dt.excl %>% filter(measure == "Math") %>% filter(!is.na(parentMA_mediansplit)) %>% group_by(parentMA_mediansplit, condition, year) %>% summarise(mean = mean(score, na.rm=TRUE), sd = sd(score, na.rm=TRUE)) ## ## ## ## ## ## ## Source: local data frame [8 x 5] Groups: parentMA_mediansplit, condition [?] parentMA_mediansplit (dbl) 1 1 2 1 condition year mean sd (chr) (fctr) (dbl) (dbl) Math App 3 462.7468 18.71507 Math App 4 478.4348 21.11036 2 ## ## ## ## ## ## 3 4 5 6 7 8 1 1 2 2 2 2 Reading Reading Math Math Reading Reading App App App App App App 3 4 3 4 3 4 458.8571 475.3134 459.0562 474.3836 457.4800 469.2157 18.08212 19.25602 15.98633 18.67978 13.56352 18.68402 We get only minor numerical differences between the data I’m using and the data reported by the authors (e.g., <= 1 W score unit). Difference scores. diffs.excl <- dt.excl %>% select(ChildID, condition, year, measure, score) %>% mutate(measure_year = interaction(measure, year)) %>% select(-year, -measure) %>% spread(measure_year, score) %>% mutate(math_gain = Math.4 - Math.3, reading_gain = Reading.4 - Reading.3) %>% select(-Math.3, -Math.4, -Reading.3, -Reading.4) %>% gather(measure, score, math_gain, reading_gain) Do the whole thing with grade equivalents. dt.excl.ge <- d %>% mutate(applied.w.fa13 = ifelse(wjappliedfa13_filter, WJ.Applied.Problems.GE.Fa13, NA), applied.w.sp14 = ifelse(WJ.applied.Sp14.filter, WJ.Applied.Problem_GE_Sp14, NA), reading.w.fa13 = ifelse(WJletterwordFa13_filter, WJ.Letter.Word.ID.GE.Fa13, NA), reading.w.sp14 = ifelse(WJ.letterword.Sp14.filter, WJ.LetterWord_GE_Sp14, NA)) %>% filter(!twin, !year1dropout) %>% gather(measure, score, applied.w.fa13, applied.w.sp14, reading.w.fa13, reading.w.sp14) %>% separate(measure, c("measure","time"), sep="w\\.") %>% mutate(year = as.factor(str_sub(time, 4, 5)), measure = ifelse(grepl( "applied", measure), "Math", "Reading")) diffs.excl.ge <- dt.excl.ge %>% select(ChildID, condition, year, measure, score) %>% mutate(measure_year = interaction(measure, year)) %>% select(-year, -measure) %>% spread(measure_year, score) %>% mutate(math_gain = Math.4 - Math.3, reading_gain = Reading.4 - Reading.3) %>% select(-Math.3, -Math.4, -Reading.3, -Reading.4) %>% gather(measure, score, math_gain, reading_gain) 3 And join back in other measures. diffs.full <- left_join(diffs.excl, d %>% select(ChildID, ParentMAaverage.Fa13, parentMA_mediansplit, avg.use, use.012.groups)) %>% left_join(diffs.excl.ge %>% rename(score.ge = score)) And compute means for plotting. ms.diffs.excl <- diffs.excl %>% group_by(condition, measure) %>% multi_boot_standard(column = "score", na.rm=TRUE) ms.diffs.excl.ge <- diffs.excl.ge %>% group_by(condition, measure) %>% multi_boot_standard(column = "score", na.rm=TRUE) %>% ungroup %>% mutate(measure = factor(measure, levels = c("math_gain","reading_gain"), labels = c("WJ Applied Problems\n(Math)", "WJ Letter-Word\n(Reading)")), Condition = condition) Technical Comment Analyses This section will now reproduce the analyses in the Technical Comment. Figure 1. ggplot(ms.diffs.excl.ge, aes(x = measure, y = mean, ymin = ci_lower, ymax = ci_upper, fill = Condition)) + geom_bar(stat="identity", position = "dodge") + geom_linerange(position = position_dodge(width = .9)) + scale_fill_solarized() + ylab("Improvement (Grade Equivalents)") + xlab("Measure") + ylim(c(0,1)) 4 Improvement (Grade Equivalents) 1.00 0.75 Condition Math App 0.50 Reading App 0.25 0.00 WJ Applied Problems (Math) WJ Letter−Word (Reading) Measure t-tests for Figure 1. kable(tidy(with(diffs.excl.ge, t.test(score[condition measure score[condition measure == == == == "Math App" & "math_gain"], "Reading App" & "math_gain"], var.equal = TRUE)))) estimate1 estimate2 statistic p.value parameter conf.low conf.high 0.852973 0.77 1.061383 0.2890304 498 -0.0706193 0.2365653 kable(tidy(with(diffs.excl.ge, t.test(score[condition measure score[condition measure == == == == "Math App" & "reading_gain"], "Reading App" & "reading_gain"], var.equal = TRUE)))) estimate1 estimate2 statistic p.value parameter conf.low conf.high 0.8337731 0.8192308 0.2728889 0.7850497 507 -0.0901546 0.1192392 Main longitudinal models (for paragraph beginning “first”). GE scores and then W scores. kable(summary(lmer(score ~ condition * time + (1 | ChildID) 5 + (time | TeachID.Year1), data = filter(dt.excl.ge, measure == "Math")))$coefficients, digits = 3) Estimate Std. Error t value 1.985 -0.075 0.842 -0.072 0.088 0.169 0.051 0.098 22.656 -0.444 16.650 -0.733 (Intercept) conditionReading App timesp14 conditionReading App:timesp14 kable(summary(lmer(score ~ condition * time + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl, measure == "Math")))$coefficients, digits = 3) (Intercept) conditionReading App timesp14 conditionReading App:timesp14 Estimate Std. Error t value 458.914 -1.518 15.368 -1.003 1.735 3.356 0.897 1.742 264.453 -0.452 17.141 -0.576 Three-way interaction models (for paragraph beginning “second”). GE and then W scores, as above. kable(summary(lmer(score ~ condition * time * avg.use + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl.ge, measure == "Math")))$coefficients, digits = 3) (Intercept) conditionReading timesp14 avg.use conditionReading conditionReading timesp14:avg.use conditionReading App App:timesp14 App:avg.use App:timesp14:avg.use Estimate Std. Error t value 1.770 0.068 0.650 0.174 0.088 -0.128 0.157 -0.137 0.103 0.199 0.070 0.046 0.134 0.073 0.040 0.063 17.184 0.340 9.324 3.770 0.658 -1.756 3.968 -2.190 kable(summary(lmer(score ~ condition * time * avg.use + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl, measure == "Math")))$coefficients, digits = 3) 6 (Intercept) conditionReading timesp14 avg.use conditionReading conditionReading timesp14:avg.use conditionReading App App:timesp14 App:avg.use App:timesp14:avg.use Estimate Std. Error t value 454.559 0.964 12.897 3.546 1.226 -2.381 2.021 -1.867 2.002 3.875 1.237 0.858 2.379 1.361 0.703 1.109 227.015 0.249 10.429 4.132 0.515 -1.749 2.876 -1.684 Figure 2. ggplot(filter(diffs.full, !is.na(parentMA_mediansplit), condition == "Math App"), aes(x = avg.use, y = score.ge, col = factor(parentMA_mediansplit))) + geom_point() + geom_smooth(se=TRUE, method = "lm") + scale_colour_solarized() + xlab("Average Weekly App Usage") + ylab("Improvement (Grade Equivalents)") Improvement (Grade Equivalents) 3 2 factor(parentMA_mediansplit) 1 2 1 0 −1 0 1 2 3 4 Average Weekly App Usage Math anxiety three- and four-way interactions (for paragraph beginning “third”). 7 kable(summary(lmer(score ~ time * condition * ParentMAaverage.Fa13 + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl.ge, measure == "Math")))$coefficients, digits = 3) (Intercept) timesp14 conditionReading App ParentMAaverage.Fa13 timesp14:conditionReading App timesp14:ParentMAaverage.Fa13 conditionReading App:ParentMAaverage.Fa13 timesp14:conditionReading App:ParentMAaverage.Fa13 Estimate Std. Error t value 2.281 1.036 -0.038 -0.112 0.167 -0.085 -0.020 -0.119 0.163 0.126 0.324 0.062 0.249 0.053 0.127 0.107 14.010 8.201 -0.117 -1.802 0.670 -1.611 -0.157 -1.117 Estimate Std. Error t value 464.190 17.718 -0.948 -1.937 3.740 -1.082 -0.280 -2.370 3.073 2.211 6.120 1.152 4.374 0.915 2.355 1.860 151.051 8.014 -0.155 -1.682 0.855 -1.183 -0.119 -1.275 kable(summary(lmer(score ~ time * condition * ParentMAaverage.Fa13 + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl, measure == "Math")))$coefficients, digits = 3) (Intercept) timesp14 conditionReading App ParentMAaverage.Fa13 timesp14:conditionReading App timesp14:ParentMAaverage.Fa13 conditionReading App:ParentMAaverage.Fa13 timesp14:conditionReading App:ParentMAaverage.Fa13 kable(summary(lmer(score ~ time * condition * ParentMAaverage.Fa13 * avg.use + + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl, measure == "Math")))$coefficients, digits = 3) (Intercept) timesp14 conditionReading App ParentMAaverage.Fa13 avg.use timesp14:conditionReading App timesp14:ParentMAaverage.Fa13 conditionReading App:ParentMAaverage.Fa13 timesp14:avg.use 8 Estimate Std. Error t value 459.443 12.277 -4.837 -1.589 3.049 10.657 0.000 2.647 3.899 4.621 3.598 8.722 1.832 2.876 6.722 1.488 3.472 2.339 99.432 3.412 -0.555 -0.867 1.061 1.585 0.000 0.762 1.667 conditionReading App:avg.use ParentMAaverage.Fa13:avg.use timesp14:conditionReading App:ParentMAaverage.Fa13 timesp14:conditionReading App:avg.use timesp14:ParentMAaverage.Fa13:avg.use conditionReading App:ParentMAaverage.Fa13:avg.use timesp14:conditionReading App:ParentMAaverage.Fa13:avg.use Estimate Std. Error t value 3.203 -0.071 -3.990 -4.899 -0.759 -2.413 1.137 4.750 1.252 2.816 3.857 1.018 2.037 1.655 0.674 -0.056 -1.417 -1.270 -0.746 -1.184 0.687 kable(summary(lmer(score ~ time * condition * ParentMAaverage.Fa13 * avg.use + + (1 | ChildID) + (time | TeachID.Year1), data = filter(dt.excl.ge, measure == "Math")))$coefficients, digits = 3) (Intercept) timesp14 conditionReading App ParentMAaverage.Fa13 avg.use timesp14:conditionReading App timesp14:ParentMAaverage.Fa13 conditionReading App:ParentMAaverage.Fa13 timesp14:avg.use conditionReading App:avg.use ParentMAaverage.Fa13:avg.use timesp14:conditionReading App:ParentMAaverage.Fa13 timesp14:conditionReading App:avg.use timesp14:ParentMAaverage.Fa13:avg.use conditionReading App:ParentMAaverage.Fa13:avg.use timesp14:conditionReading App:ParentMAaverage.Fa13:avg.use 9 Estimate Std. Error t value 2.038 0.596 -0.142 -0.089 0.156 0.629 0.013 0.091 0.321 0.096 -0.008 -0.226 -0.333 -0.072 -0.093 0.077 0.247 0.205 0.466 0.099 0.155 0.382 0.085 0.187 0.134 0.256 0.068 0.161 0.220 0.058 0.110 0.095 8.249 2.910 -0.305 -0.904 1.004 1.645 0.150 0.488 2.405 0.374 -0.117 -1.410 -1.513 -1.233 -0.848 0.818