Structural Brain Correlates of Poor Reading Comprehension.
The 2 matches
- [1] § MATERIALS AND METHODS › Group Classification › Cutoff classification model ↔ Code Files/Mahaffy_Masters_Real_DK__KM_20251223 (1).Rmd, lines 458–565 · score 0.66 · MatchIt, standard score, outlier, IQ, PC, age
- [2] § RESULTS › White Matter › Regression classification method ↔ Code Files/Mahaffy_Masters_Real_DK__KM_20251223 (1).Rmd, lines 1130–1237 · score 0.53 · left AF, right ILF, quadratic, linear, FA, residuals
Paper
Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC
The paper is loaded when this pane is shown.
The authors' code
R Markdown · 1,237 lines · 68 KB · no license · 2 matches
- ---
- title: "Mahaffy_MA_Publication"
- author: "Kelly_Mahaffy"
- date: "`r Sys.Date()`"
- output: word_document
- ---
- # questions from Dan:
- # 1) Is it correct that no models are fit to the matched data?
- # 2) p-value adjustments: What should be grouped? (I don't have access to the folder with those outputs.)
- ```{r setup, include=FALSE}
- knitr::opts_chunk$set(echo = TRUE)
- library(psych)
- library(ggplot2)
- library(readxl)
- library(memisc)
- library(MatchIt)
- library(lm.beta)
- library(ggplot2)
- library(ggforce) # for geom_sina()
- library(gghalves)
- library(ggpubr)
- library(gridExtra) # for grid.arrange()
- # added by DK
- library(reshape2) # for melt()
- library(dplyr) # for rename()
- library(viridisLite) # for easier access to viridis color scale
- ```
- ```{r define dirs, include=FALSE}
- #super_dir <- "~/Mahaffy_PoorComprehenders_Pub"
- super_dir <- "/Users/dank/Documents/Haskins/Other people's projects/Kelly Mahaffy/MA_paper/Code_2024-12-17/"
- #
- # super_dir <- validate_dir(super_dir) # in case the final file separator character was omitted
- #
- data_dir <- paste0(super_dir, "Data_Files/")
- pvals_dir <- paste0(data_dir , "P_Values/" )
- dirs <- list()
- # file structure assumes this script is stored in a separate folder at the same level as Data_Files
- dirs[["code" ]] <- paste0(super_dir, "Code_Files/"); setwd(dirs[["code"]]) # set wd here!
- dirs[["data" ]] <- paste0(".." , .Platform$file.sep, "Data_Files", .Platform$file.sep)
- dirs[["input_brain" ]] <- paste0(dirs[["data"]], "Input_files" , .Platform$file.sep, "Brain_data", .Platform$file.sep)
- dirs[["input_pvals" ]] <- paste0(dirs[["data"]], "Input_files" , .Platform$file.sep, "P_values" , .Platform$file.sep)
- dirs[["input_other" ]] <- paste0(dirs[["data"]], "Input_files" , .Platform$file.sep, "Other" , .Platform$file.sep)
- dirs[["output_data" ]] <- paste0(dirs[["data"]], "Output_files", .Platform$file.sep, "Data" , .Platform$file.sep)
- dirs[["output_models"]] <- paste0(dirs[["data"]], "Output_files", .Platform$file.sep, "Models" , .Platform$file.sep)
- # create output directories that don't already exist
- for(next_output_dir in names(dirs)[grepl("^output_",names(dirs))]) {
- dir.create(dirs[[next_output_dir]], recursive=TRUE, showWarnings=FALSE)
- }
- output_file_prefix <- "Mahaffy_MA_"
- ```
- ```{r define functions, include=FALSE}
- # function which takes a filepath for a .csv, .xls or .xlsx file
- # and returns the data in that file
- read_data_file <- function(file_path, na="NA") {
- file_ext <- tail(strsplit(file_path, "\\.")[[1]], n=1)
- if(file_ext %in% c('xls', 'xlsx')) {
- file_data <- read_excel(file_path, na=na)
- } else if(file_ext %in% 'csv') {
- file_data <- read.csv(file_path, na=na)
- } else {
- error(paste0(file_path, ' file extension must be .csv, .xls, or .xlsx!'))
- }
- return(file_data)
- }
- # function which takes a list of data frames (data_list), a data frame to merge with the others (merge_with_others),
- # and (optionally) a column by which to join them (uses "id" by default).
- # returns a list with all data frames in the list merged with the to-be-merged data frame.
- merge_one_with_all <- function(data_list, merge_with_others, join_by="id") {
- merged_list <- sapply(data_list, function(next_file_data) {merge(next_file_data, merge_with_others, by=join_by)})
- return(merged_list)
- }
- # function which takes a list of data frames (data_list), the name of a data frame in that list to merge with the others (merge_with_others),
- # and (optionally) a column by which to join them (uses "id" by default).
- # returns a list with all data frames except the named one merged with the named data frame.
- # (builds on merge_one_with_all() by breaking out the to-be-merged data frame from the list, then passing them as separate args.)
- merge_one_with_others <- function(data_list, merge_with_others, join_by="id") {
- names_to_merge <- setdiff(names(data_list), merge_with_others)
- merged_list <- merge_one_with_all(data_list[names_to_merge], data_list[[merge_with_others]], join_by)
- return(merged_list)
- }
- # function which takes a list of data frames (e.g., brain_data[["grey"]]), a column to center (e.g., "cnr"), and the name of an output centered column ("cnr_c").
- # returns the list with a centered column in each data frame
- center_col_by_matter_type <- function(df_list, col_to_center, centered_col_name) {
- for(next_file_data in names(df_list)) {
- df_list[[next_file_data]][[centered_col_name]] <- scale(df_list[[next_file_data]][[col_to_center]], scale=F)
- }
- return(df_list)
- }
- ##KM attempt at scaling
- scale_col_by_matter_type <- function(df_list, col_to_scale, scaled_col_name) {
- for(next_file_data in names(df_list)) {
- df_list[[next_file_data]][[scaled_col_name]] <- scale(df_list[[next_file_data]][[col_to_scale]])
- }
- return(df_list)
- }
- # function that takes a list of data frames, a list of data frame names to write,
- # and a directory to write them in, and then writes those data frames as CSVs
- # in that dir. by default, output directory is dirs[["output_data"]]
- write_df_as_csv <- function(df_list, dfs_to_write, write_dir=dirs[["output_data"]]) {
- for(next_df_to_write in dfs_to_write) {
- write.csv(df_list[[next_df_to_write]], paste0(write_dir, next_df_to_write, ".csv"))
- }
- }
- # function that takes a data frame as input and a dependent variable (as a character),
- # then calls matchit() 2x and creates some summary output (which may not be output
- # in a usable way, though this could be easily changed). it returns a data frame
- # with the appropriate columns for matching.
- custom_matchit_calls <- function(df, dv) {
- matchit_formula <- formula(paste0(dv, " ~ age + sex + WISC_NVI_ss"))
- summary(m.out.1 <- matchit(matchit_formula, data=df, method=NULL , distance="glm" ))
- m.out.2 <- matchit(matchit_formula, data=df, method="optimal", distance="mahalanobis", replace=FALSE)
- summary(m.out.2)
- plot(m.out.2, type="qq")
- return(match.data(m.out.2))
- }
- # function to combine a PCPD and a PCTD dataset rather than doing this outside R
- merge_pcpd_pctd <- function(pcpd, pctd) {
- # not an rbind, because a given participant may appear in both, and we only want them to have one row.
- # for a given sub who appears in both, all values are identical (though the _Match col is called PC_PD_Match in one and PC_TD_Match in the other)
- # rename cols
- colnames(pctd)[colnames(pctd)=="PC_TD_Match"] <- "PC_TD"
- colnames(pcpd)[colnames(pcpd)=="PC_PD_Match"] <- "PC_PD"
- # create a df with one sub per row if they appear in at least one of the two matched data sets
- # remove X col, if present -- would need to be treated differently because vals would be different for PCPD and PCTD, and I don't think it's used later anyway
- merged_data <- unique.data.frame(rbind(
- as.data.frame(pctd[,setdiff(colnames(pctd), c("PC_TD", "X"))]),
- as.data.frame(pcpd[,setdiff(colnames(pcpd), c("PC_PD", "X"))])
- ))
- # merge PC_PD and PC_TD col headers
- merged_headers <- merge(pctd[,c("id","PC_TD")], pcpd[,c("id","PC_PD")], all.x=TRUE, all.y=TRUE)
- # add PC_All col headers: 1 if both !is.na(PC_TD) and !is.na(PC_PD), and 0 otherwise
- merged_headers[["PC_All"]] <- 1-apply(sapply(merged_headers[,c("PC_TD","PC_PD")], is.na), 1, sum)
- # merge headers and data
- merged_data <- merge(merged_headers, merged_data)
- # sort: PC_All (descending), PC_TD (descending), PC_PD (descending), id (ascending)
- merged_data <- merged_data[with(merged_data, order(-PC_All, -PC_TD, -PC_PD, id)),]
- # to do (maybe): add X col
- # NOTE: There is an error in the way this is handled in the previous code --
- # X is nested within matched dataset, so a given participant should have
- # either 1 or 2 values (potentially one each for PC_PD and PC_TD).
- # However, X is never used again, so it doesn't really matter. Safe to
- # just omit it for now.
- return(merged_data)
- }
- # function which takes various pieces of information about a set of lm() models to fit,
- # then writes the appropriate files with results and p-values, and returns
- # an object containing the fitted models and p-values
- # df = the data frame to be used in all models
- # dvs = a character vector of DVs (1 per model)
- # pred_pval = the string name of the variable for which to save the p-value
- # preds_other = a character vector of other predictors (not pred_pval) to include in the model
- # id_text = an identifying string to be prepended to the names of files that are written
- # df = get(next_model_df)[[paste0(output_file_prefix, next_model_info[["df"]])]]
- # dvs = dvs[[models_to_fit[next_model_idx,"dvs"]]]
- # pred_pval = next_model_info[["pred_pval"]]
- # preds_other = strsplit(next_model_info[["preds_other"]], " \\+ ")[[1]]
- # id_text = next_model_info[["id_text"]]
- fit_lms <- function(df, dvs, pred_pval, preds_other, id_text) {
- all_preds <- setdiff(c(pred_pval, preds_other), "") # exclude empty preds
- # convert data from wide to long
- df.long <- melt(df[,c(dvs,all_preds)], id.vars=all_preds, variable.name="region", value.name="dv")
- # construct formula -- will be the same for all models
- lm_formula_baseline <- formula(paste0("dv ~ ", paste(setdiff(preds_other, ""), collapse=" + ")))
- lm_formula <- formula(paste0("dv ~ ", paste( all_preds , collapse=" + ")))
- # fit all models
- all_baseline_lms <- lapply(dvs, function(x) {next_lm <- lm.beta(lm(lm_formula_baseline, df.long[df.long[["region"]]==x,]))})
- names(all_baseline_lms) <- dvs
- all_lms <- lapply(dvs, function(x) {next_lm <- lm.beta(lm(lm_formula, df.long[df.long[["region"]]==x,]))})
- names(all_lms) <- dvs
- # get r^2 vals
- rsq_vals <- do.call(rbind.data.frame, lapply(dvs, function(x) {
- rsq <- c(summary(all_lms[[x]])[["r.squared"]], summary(all_baseline_lms[[x]])[["r.squared"]]);
- rsq <- c(rsq, rsq[1]-rsq[2])}))
- rsq.headers <- c("rsq.linear", "rsq.intercept", "rsq.linear.partial")
- colnames(rsq_vals) <- rsq.headers
- #names(all_lms) <- paste0("lm", 1:length(all_lms)) # necessary for mtable() call to work
- # generate and save mtable
- # note: only works if the models are not standardized (due to extra column in model summary)
- # lm_mtable <- do.call(mtable, all_lms)
- # write.mtable(lm_mtable, file=paste0(dirs[["output_models"]], output_file_prefix, id_text, "_Results.csv"), format=c("delim"))
- # make our own output
- all_lms_for_output <- do.call(rbind.data.frame, lapply(1:length(all_lms), function(x) {data.frame(model_num=x, dv=dvs[[x]], effect=rownames(coef(summary(all_lms[[x]]))), coef(summary(all_lms[[x]])), rsq_vals[x,])}))
- rownames(all_lms_for_output) <- NULL
- colnames(all_lms_for_output) <- c("model_num","dv","effect","Estimate","Standardized","SE","t","p",rsq.headers)
- write.table(all_lms_for_output, file=paste0(dirs[["output_models"]], output_file_prefix, id_text, "_Results.csv"), sep=",")
- # extract and save p-values from specified predictor
- lm_pvals <- sapply(1:length(all_lms), function(next_lm_idx) {coef(summary(all_lms[[next_lm_idx]]))[pred_pval,"Pr(>|t|)"]})
- write.csv(lm_pvals, paste0(dirs[["output_models"]], output_file_prefix, id_text, "_PValue.csv"))
- objects_to_return <- list(model=all_lms, rsq.linear=data.frame(dv=dvs, rsq_vals), p=lm_pvals)
- # quad formula if needed
- unique_pred_vals <- unique(df.long[[pred_pval]])
- unique_pred_vals <- unique_pred_vals[!is.na(unique_pred_vals)] # exclude NAs
- pred_is_continuous <- length(unique_pred_vals) > 2
- if(pred_is_continuous) {
- pred_linear_and_quad <- as.data.frame(poly(df.long[[pred_pval]], 2))
- colnames(pred_linear_and_quad) <- paste0(pred_pval, c(".lin", ".quad"))
- df.long <- data.frame(df.long, pred_linear_and_quad)
- # construct quadratic formula
- lm_formula.quad <- formula(paste0("dv ~ ", paste(c(all_preds, paste0(pred_pval, ".quad")), collapse=" + ")))
- all_lms.quad <- lapply(dvs, function(x) {next_lm <- lm(lm_formula.quad, df.long[df.long[["region"]]==x,])})
- names(all_lms.quad) <- dvs
- rsq_vals.quad <- do.call(rbind.data.frame, lapply(1:length(all_lms), function(x) {
- rsq <- summary(all_lms.quad[[x]])[["r.squared"]]
- rsq <- c(rsq, rsq - rsq_vals[x,"rsq.linear"])
- }))
- rsq.quad.headers <- c("rsq.quad", "rsq.quad.partial")
- colnames(rsq_vals.quad) <- rsq.quad.headers
- # compare p-values for each one
- all_quad_stats <- do.call(rbind.data.frame, lapply(1:length(all_lms), function(x) {
- next_quad_comparison <- anova(all_lms[[x]], all_lms.quad[[x]])
- next_quad_comparison.stats <- data.frame(with(next_quad_comparison, data.frame(F=F, df=Df, p=`Pr(>F)`)[2,]), rsq_vals.quad[x,])
- return(next_quad_comparison.stats)
- }))
- objects_to_return[["quad_models" ]] <- all_lms.quad
- objects_to_return[["quad_results"]] <- data.frame(dv=dvs, all_quad_stats)
- }
- return(objects_to_return)
- }
- # function which takes a data frame containing brain data (df) and a data frame
- # (var_info) containing two cols: "match" and "store". for each row in var_info,
- # it averages across all values in columns named "slf[digit(s)]_x" (where x is
- # the value of "match") and stores the results in "slfavg_y" (where y is the
- # value of "store").
- create_slf_avg_vars <- function(df, var_info) {
- # average over appropriately named cols
- new_cols <- sapply(var_info[["match"]], function(next_string) {
- rowMeans(df[,grepl(paste0("^slf\\d+_", next_string, "$"), colnames(df))], na.rm=TRUE)
- })
- # add new cols to df and return
- df[,paste0("slfavg_", var_info[["store"]])] <- new_cols
- return(df)
- }
- create_slf_avg_vars_fake <- function(df, var_info) { # for printing only
- # average over appropriately named cols
- summary_strings <- sapply(1:nrow(var_info), function(next_row_idx) {
- next_match_string <- var_info[next_row_idx,"match"]
- cols_to_combine <- colnames(df)[grepl(paste0("^slf\\d+_", next_match_string, "$"), colnames(df))]
- next_store_string <- paste0("slfavg_", var_info[next_row_idx,"store"], " = mean(", paste(cols_to_combine, collapse=", "), ")")
- return(next_store_string)
- })
- return(summary_strings)
- }
- get_model_resids <- function(fitted_lm, pred_var) {
- outputs <- list()
- outputs[["fitted_lm.formula_split"]] <- as.character(formula(fitted_lm))[c(2,3)]
- outputs[["fitted_lm.dv"]] <- outputs[["fitted_lm.formula_split"]][1]
- outputs[["fitted_lm.preds"]] <- strsplit(outputs[["fitted_lm.formula_split"]][2], ' \\+ ')[[1]]
- outputs[["subset_lm.preds"]] <- setdiff(outputs[["fitted_lm.preds"]], pred_var)
- outputs[["subset_lm.formula"]] <- formula(paste0(outputs[["fitted_lm.dv"]], " ~ ", paste(outputs[["subset_lm.preds"]], collapse=" + ")))
- outputs[["subset_lm"]] <- lm(outputs[["subset_lm.formula"]], data=fitted_lm[["model"]])
- outputs[["subset_lm.resids"]] <- resid(outputs[["subset_lm"]])
- outputs[["pred_lm.formula"]] <- formula(paste0(pred_var, " ~ ", paste(outputs[["subset_lm.preds"]], collapse=" + ")))
- outputs[["pred_lm"]] <- lm(outputs[["pred_lm.formula"]], data=fitted_lm[["model"]])
- outputs[["pred.resids"]] <- resid(outputs[["pred_lm"]])
- return(outputs)
- }
- plot_grouped_vs_continuous <- function(fitted_lm, pred_var, num_quantiles=5, highlighted_quantiles=c(1,3,5), degree=c(1), linetype=NULL) {
- resid_models <- get_model_resids(fitted_lm, pred_var)
- # get plot information, colors, quantiles, etc.
- plot_data <- fitted_lm[["model"]]
- plot.y.label <- paste0(resid_models[["fitted_lm.dv"]], ".resid")
- plot_data[[plot.y.label]] <- resid_models[["subset_lm.resids"]]
- plot.x.label <- paste0(pred_var, ".resid")
- plot_data[[plot.x.label]] <- resid_models[["pred.resids"]]
- plot.quantiles <- quantile(plot_data[[plot.x.label]], probs=(1:(num_quantiles-1))/num_quantiles)
- plot_data[["quantile"]] <- as.factor(as.character(sapply(plot_data[[plot.x.label]], function(x) {1 + sum(x > plot.quantiles)})))
- # compute quantile boundaries as mean of mins/maxes of each quantile
- quantile_boundaries <- lapply(c(min,max), function(x) {aggregate(formula(paste0(plot.x.label, " ~ quantile")), data=plot_data, FUN=x)})
- quantile_boundaries <- data.frame(max=quantile_boundaries[[2]][1:(num_quantiles-1),plot.x.label], min=quantile_boundaries[[1]][2:num_quantiles,plot.x.label])
- quantile_boundaries[["mean"]] <- apply(quantile_boundaries, 1, mean)
- # create color map: grey for non-highlighted quantiles, viridis for highlighted quantiles
- plot_colors <- gray.colors(num_quantiles)
- plot_colors[highlighted_quantiles] <- viridis(num_quantiles)[highlighted_quantiles]
- # get means by quantile (there is SO a better way to do this...)
- summary_stats <- do.call(rbind.data.frame, lapply(c(plot.x.label, plot.y.label), function(y) {
- next_summary_stats <- do.call(cbind, lapply(c(mean,sd,length), function(x) {aggregate(formula(paste0(y, " ~ quantile")), data=plot_data, FUN=x)[,2]}))
- colnames(next_summary_stats) <- c("mean","sd","n")
- #next_summary_stats <- data.frame(var=rep(c("x","y"),each=num_quantiles), next_summary_stats)
- next_summary_stats <- data.frame(quantile=1:num_quantiles, next_summary_stats)
- next_summary_stats
- }))
- summary_stats <- data.frame(var=rep(c("x","y"), each=num_quantiles), summary_stats)
- summary_stats[["se" ]] <- with(summary_stats, sd/sqrt(n))
- summary_stats[["start"]] <- with(summary_stats, mean-se)
- summary_stats[["end" ]] <- with(summary_stats, mean+se)
- # oy
- summary_stats <- do.call(merge, lapply(c("x","y"), function(x) {
- subset_summary_stats <- summary_stats[summary_stats[["var"]]==x,]
- subset_col_names <- setdiff(colnames(subset_summary_stats), c("var","quantile"))
- colnames(subset_summary_stats)[colnames(subset_summary_stats) %in% subset_col_names] <- paste0(subset_col_names, ".", x)
- subset(subset_summary_stats, select=-var)
- }))
- # set figure x-limits at [range] + 10% on each end
- x.range <- range(plot_data[[plot.x.label]])
- x.range <- x.range + (diff(x.range) * .10) * c(-1,1)
- # make plot
- resid_plot <- ggplot(plot_data, aes_string(x=plot.x.label, y=plot.y.label, color="quantile")) +
- geom_point() +
- scale_color_manual(values=plot_colors) +
- theme_bw() +
- scale_x_continuous(limits=x.range)
- for(next_degree_idx in 1:length(degree)) {
- next_degree <- degree[next_degree_idx]
- if(next_degree > 1) {
- stat_smooth.formula <- formula(paste0("y ~ x + ", paste(paste0("I(x^", 2:next_degree, ")"), collapse=" + ")))
- } else {
- stat_smooth.formula <- formula("y ~ x")
- }
- if(is.null(linetype) || length(linetype) != length(degree)) {
- next_linetype <- next_degree
- } else {
- next_linetype <- linetype[next_degree_idx]
- }
- resid_plot <- resid_plot + stat_smooth(aes(group=1), color="black", linetype=next_linetype, method="lm", formula=stat_smooth.formula)
- }
- # add quantile boundaries
- for(next_quantile_boundary_idx in 1:nrow(quantile_boundaries)) {
- resid_plot <- resid_plot + geom_vline(xintercept=quantile_boundaries[next_quantile_boundary_idx,"mean"], color="black", linetype="dashed", alpha=0.2)
- }
- # just add plus signs rather than bars with +/- 1 SEM
- resid_plot <- resid_plot +
- geom_point(data=summary_stats, aes(x=mean.x, y=mean.y), color="black", size=5, shape=3)
- # for(next_quantile_mean_idx in (1:num_quantiles)) {
- # resid_plot <- resid_plot +
- # geom_segment(color=plot_colors[next_quantile_mean_idx], linewidth=1.5, # shows variability in X
- # x =summary_stats[next_quantile_mean_idx,"start.x"],
- # xend=summary_stats[next_quantile_mean_idx,"end.x" ],
- # y =summary_stats[next_quantile_mean_idx,"mean.y" ],
- # yend=summary_stats[next_quantile_mean_idx,"mean.y" ]) +
- # geom_segment(color=plot_colors[next_quantile_mean_idx], linewidth=1.5, # shows variability in Y
- # x =summary_stats[next_quantile_mean_idx,"mean.x" ],
- # xend=summary_stats[next_quantile_mean_idx,"mean.x" ],
- # y =summary_stats[next_quantile_mean_idx,"start.y"],
- # yend=summary_stats[next_quantile_mean_idx,"end.y" ])
- # }
- return(resid_plot)
- }
- ```
- ## Describe
- ```{r}
- Mahaffy_MA_Final <- read_data_file(paste0(dirs[["input_other"]], "Mahaffy_Masters_Final.xlsx"),na = "NA")
- #objects(Mahaffy_MA_Final)
- Mahaffy_MA_Final$age_round<-round(Mahaffy_MA_Final$age,1)
- describe(Mahaffy_MA_Final$age_round)
- sum(Mahaffy_MA_Final$sex)
- describe(Mahaffy_MA_Final$wiat_rc_stand)
- describe(Mahaffy_MA_Final$wiat_rc_raw)
- describe(Mahaffy_MA_Final$wiat_lcrv_ss)
- describe(Mahaffy_MA_Final$wiat_lcrv_raw)
- describe(Mahaffy_MA_Final$towre_pde_ss)
- describe(Mahaffy_MA_Final$towre_pde_raw)
- describe(Mahaffy_MA_Final$WISC_NVI_ss)
- ```
- ## Regression Model
- ```{r}
- BehavioralRegression<-lm(wiat_rc_raw ~ wiat_lcrv_raw + towre_pde_raw + age_round + sex + WISC_NVI_raw, data=Mahaffy_MA_Final)
- summary(BehavioralRegression)
- BehavioralRegression_Interactions<-lm(wiat_rc_raw ~ (wiat_lcrv_raw + towre_pde_raw + age_round + sex + WISC_NVI_raw)^2 , data=Mahaffy_MA_Final)
- summary(BehavioralRegression_Interactions)
- anova(BehavioralRegression, BehavioralRegression_Interactions)
- Mahaffy_MA_Final$Residuals_BehavioralRegression<-resid(BehavioralRegression_Interactions)
- plot(Mahaffy_MA_Final$Residuals_BehavioralRegression)
- hist(Mahaffy_MA_Final$Residuals_BehavioralRegression)
- ```
- ##Preparing Data
- ```{r}
- Mahaffy_MA_Final$age_c<-scale(Mahaffy_MA_Final$age_round, scale=FALSE)
- Mahaffy_MA_Final$word_c<-scale(Mahaffy_MA_Final$towre_pde_raw, scale=FALSE)
- Mahaffy_MA_Final$vocab_c<-scale(Mahaffy_MA_Final$wiat_lcrv_raw, scale=FALSE)
- Mahaffy_MA_Final$iq_c<-scale(Mahaffy_MA_Final$WISC_NVI_raw, scale=FALSE)
- #Import Brain Data
- brain_data <- list()
- #Grey Matter
- # grey matter files
- files.grey <- c("FS7_compiled_lh_area_2009", "FS7_compiled_lh_thickness_2009", "FS7_compiled_rh_area_2009", "FS7_compiled_rh_thicnkess_2009", "FS7_Compiled_Vol_Seg", "T1w_QC_Full")
- # read in files to list
- brain_data[["grey"]] <- lapply(files.grey, function(next_file) {read_data_file(paste0(dirs[["input_brain"]], next_file, ".xlsx"), na = "NA")})
- names(brain_data[["grey"]]) <- files.grey
- #White Matter
- # white matter files
- files.white <- c("WM_FA_MD_Tract_All", "WM_NODDI_All", "WM_QC_All")
- # read in files to list
- brain_data[["white"]] <- lapply(files.white, function(next_file) {read_data_file(paste0(dirs[["input_brain"]], next_file, ".xlsx"), na = "0")})
- names(brain_data[["white"]]) <- files.white
- #Combining Data
- brain_data[["grey" ]] <- merge_one_with_others(brain_data[["grey" ]], "T1w_QC_Full")
- brain_data[["white"]] <- merge_one_with_others(brain_data[["white"]], "WM_QC_All" )
- # rename dfs
- df_names <- c(
- LH_Area = "FS7_compiled_lh_area_2009",
- LH_Thickness = "FS7_compiled_lh_thickness_2009",
- RH_Area = "FS7_compiled_rh_area_2009",
- RH_Thickness = "FS7_compiled_rh_thicnkess_2009",
- Vol = "FS7_Compiled_Vol_Seg",
- Noddi = "WM_NODDI_All",
- MDFA = "WM_FA_MD_Tract_All"
- )
- names(brain_data[["grey" ]]) <- paste0(output_file_prefix, names(df_names)[match(names(brain_data[["grey" ]]), unname(df_names))])
- names(brain_data[["white"]]) <- paste0(output_file_prefix, names(df_names)[match(names(brain_data[["white"]]), unname(df_names))])
- #Centering Data
- # center CNR
- brain_data[["grey" ]] <- center_col_by_matter_type(brain_data[["grey" ]], "cnr" , "cnr_c")
- brain_data[["white"]] <- center_col_by_matter_type(brain_data[["white"]], "Avg _CNR", "cnr_c")
- #Scaling Data
- brain_data[["grey" ]] <- scale_col_by_matter_type(brain_data[["grey" ]], "cnr" , "cnr_c")
- brain_data[["white"]] <- scale_col_by_matter_type(brain_data[["white"]], "Avg _CNR", "cnr_c")
- #Combining Brain and Behavioral Data
- brain_data[["grey" ]] <- merge_one_with_all(brain_data[["grey" ]], Mahaffy_MA_Final)
- brain_data[["white"]] <- merge_one_with_all(brain_data[["white"]], Mahaffy_MA_Final)
- #Making spreadsheets for matching
- #Only using one GM metric here because I know the participants are the same across all GM data
- brain_data[["grey"]][["Mahaffy_GM_PC" ]] <- subset(brain_data[["grey"]][[paste0(output_file_prefix, "LH_Area")]], wiat_rc_stand <= 90 & towre_pde_ss >= 100)
- brain_data[["grey"]][["Mahaffy_GM_Possible_PD"]] <- subset(brain_data[["grey"]][[paste0(output_file_prefix, "LH_Area")]], wiat_rc_stand >= 100 & towre_pde_ss <= 90 & wiat_lcrv_ss >= 100)
- brain_data[["grey"]][["Mahaffy_GM_Possible_TD"]] <- subset(brain_data[["grey"]][[paste0(output_file_prefix, "LH_Area")]], wiat_rc_stand >= 100 & towre_pde_ss >= 100 & wiat_lcrv_ss >= 100)
- write_df_as_csv(brain_data[["grey"]], c("Mahaffy_GM_PC", "Mahaffy_GM_Possible_PD", "Mahaffy_GM_Possible_TD"))
- #Outside of R, I checked for and removed duplicate values, combined all there of the above into a single spreadsheet, and dummy coded both PC_PD and PC_TD variables, with poor comprehenders always being 1. Spreadsheet re-uploaded as Mahaffy_MatchGroups. We also identified one PC who was an outlier (a RC standard score more that 10 points from the next lowest SS) and created a second spreadsheet for group matching without this outlier. Spreadsheets were split into a PC_PD and PC_TD spreadsheet (with all poor comprehenders and possible PDs and TDs, respectively, for compliance with MatchIt! package used below.)
- # matched data files
- match_pool_files.grey <- c("Mahaffy_MatchGroups_PCPD", "Mahaffy_MatchGroups_PCTD", "Mahaffy_MatchGroups_NoOut_PCPD", "Mahaffy_MatchGroups_NoOut_PCTD")
- # read in files to list
- match_pool_data <- list()
- match_pool_data[["grey"]] <- lapply(match_pool_files.grey, function(next_file) {read_data_file(paste0(dirs[["input_other"]], next_file, ".xlsx"), na = "NA")})
- names(match_pool_data[["grey"]]) <- match_pool_files.grey
- #Repeating the Process for White Matter
- #Here, doing this process once for WM metrics from FSL and once for NODDI because the participants are not all the same
- brain_data[["white"]][["Mahaffy_WM_PC" ]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "MDFA" )]], wiat_rc_stand <= 90 & towre_pde_ss >= 100)
- brain_data[["white"]][["Mahaffy_WM_Possible_PD" ]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "MDFA" )]], wiat_rc_stand >= 100 & towre_pde_ss <= 90 & wiat_lcrv_ss >= 100)
- brain_data[["white"]][["Mahaffy_WM_Possible_TD" ]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "MDFA" )]], wiat_rc_stand >= 100 & towre_pde_ss >= 100 & wiat_lcrv_ss >= 100)
- brain_data[["white"]][["Mahaffy_Noddi_PC" ]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]], wiat_rc_stand <= 90 & towre_pde_ss >= 100)
- brain_data[["white"]][["Mahaffy_Noddi_Possible_PD"]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]], wiat_rc_stand >= 100 & towre_pde_ss <= 90 & wiat_lcrv_ss >= 100)
- brain_data[["white"]][["Mahaffy_Noddi_Possible_TD"]] <- subset(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]], wiat_rc_stand >= 100 & towre_pde_ss >= 100 & wiat_lcrv_ss >= 100)
- write_df_as_csv(brain_data[["white"]], c("Mahaffy_WM_PC" , "Mahaffy_WM_Possible_PD" , "Mahaffy_WM_Possible_TD" ,
- "Mahaffy_Noddi_PC", "Mahaffy_Noddi_Possible_PD", "Mahaffy_Noddi_Possible_TD"))
- match_pool_files.white <- c("Mahaffy_WM_MatchGroups_PCPD", "Mahaffy_WM_MatchGroups_PCTD", "Mahaffy_Noddi_MatchGroups_PCPD", "Mahaffy_Noddi_MatchGroups_PCTD")
- match_pool_data[["white"]] <- lapply(match_pool_files.white, function(next_file) {read_data_file(paste0(dirs[["input_other"]], next_file, ".csv"), na = "NA")})
- names(match_pool_data[["white"]]) <- match_pool_files.white
- ```
- ##Matching For Cutoff
- ```{r}
- # create a data frame with all parameters needed, then call custom_matchit_calls() via an apply function to match everything at once
- data_to_match <- data.frame(rbind(
- c("grey" , "Mahaffy_MatchGroups_PCPD" , "PC_PD_Match", "Matched_PCPD_GM" )
- , c("grey" , "Mahaffy_MatchGroups_PCTD" , "PC_TD_Match", "Matched_PCTD_GM" )
- , c("grey" , "Mahaffy_MatchGroups_NoOut_PCPD", "PC_PD_Match", "Matched_PCPD_NoOut")
- , c("grey" , "Mahaffy_MatchGroups_NoOut_PCTD", "PC_TD_Match", "Matched_PCTD_NoOut")
- , c("white", "Mahaffy_WM_MatchGroups_PCPD" , "PC_PD_Match", "Matched_PCPD_WM" )
- , c("white", "Mahaffy_WM_MatchGroups_PCTD" , "PC_TD_Match", "Matched_PCTD_WM" )
- , c("white", "Mahaffy_Noddi_MatchGroups_PCPD", "PC_PD_Match", "Matched_PCPD_Noddi")
- , c("white", "Mahaffy_Noddi_MatchGroups_PCTD", "PC_TD_Match", "Matched_PCTD_Noddi")
- ))
- names(data_to_match) <- c("matter_type", "df_name", "dv", "id_text")
- # run all models in data_to_match
- matched_group_data <- lapply(1:nrow(data_to_match), function(next_data_to_match_idx) {
- next_match_info <- data_to_match[next_data_to_match_idx,]
- next_matched_data <- custom_matchit_calls(match_pool_data[[next_match_info[["matter_type"]]]][[next_match_info[["df_name"]]]], next_match_info[["dv"]])
- return(next_matched_data)
- })
- names(matched_group_data) <- paste0(output_file_prefix, data_to_match[["id_text"]])
- # write CSVs
- write_df_as_csv(matched_group_data, data_to_match[["id_text"]])
- #Outside of R, I combined each of these into a single spreadsheet with all contrasts of interest, which resulted in three spreadsheets- one for GM, one for WM, and one for Noddi
- ## we can do that in R!
- # define the four file groupings
- matched_group_data.groupings <- c("GM","WM","Noddi","NoOut")
- # for each grouping, combine the two dfs
- matched_group_data.merged <- lapply(matched_group_data.groupings, function(next_data) {
- # algorithmically construct the names of the fields to pull
- ordered_pcpd_pctd <- c("PCPD","PCTD")
- matched_data_names <- paste0(output_file_prefix, "Matched_", ordered_pcpd_pctd, "_", next_data)
- names(matched_data_names) <- ordered_pcpd_pctd
- # run custom function which combines the data
- merged_data <- merge_pcpd_pctd(pcpd=matched_group_data[[matched_data_names["PCPD"]]], pctd=matched_group_data[[matched_data_names["PCTD"]]])
- return(merged_data)
- })
- # name the fields in the resulting list
- names(matched_group_data.merged) <- paste0(output_file_prefix, "Matched_", matched_group_data.groupings)
- #Writing a CSV to prepare groups for mixed regression (save the lowest, middle, and highest 20% of residual scores with the lowest group being dummy coded as 1 and all others as 0) and create a list to merge with other spreadsheets like for previous. Done separately for GM, WM, and Noddi.
- write_df_as_csv(c(brain_data[["grey"]], brain_data[["white"]]), paste0(output_file_prefix, c("LH_Area", "MDFA", "Noddi"))) # mix of GM & WM
- # reading these in from the output directory (?) because they were just created
- brain_data[["grey" ]][[paste0(output_file_prefix, "LH_Area")]] <- read_data_file(paste0(dirs[["output_data"]], output_file_prefix, "LH_Area.csv"), na = "NA")
- brain_data[["white"]][[paste0(output_file_prefix, "MDFA" )]] <- read_data_file(paste0(dirs[["output_data"]], output_file_prefix, "MDFA.csv" ), na = "NA")
- brain_data[["white"]][[paste0(output_file_prefix, "Noddi" )]] <- read_data_file(paste0(dirs[["output_data"]], output_file_prefix, "Noddi.csv" ), na = "NA")
- #Reading in lists to merge so that group contrasts can be merged with other data files
- merged_data_files <- data.frame(rbind( # keep all info about each file together
- c("GM_Matched_ToMerge.xlsx" , "GM_Cutoff_Matched_Merge", "_Cutoff" , 1)
- , c("Mixed_Merge.csv" , "Mixed_Merge" , "" , 2)
- , c("Matched_NoOut_ToMerge.csv", "Matched_NoOut_Merge" , "_Cutoff_NoOut", 1)
- ))
- names(merged_data_files) <- c("filename", "field_name", "suffix", "merge_order")
- merged_data_files[["merge_order"]] <- as.numeric(merged_data_files[["merge_order"]])
- data_for_merging <- lapply(merged_data_files[["filename"]],
- function(next_filename) {read_data_file(paste0(dirs[["input_other"]], output_file_prefix, next_filename), na="NA")}
- )
- names(data_for_merging) <- paste0(output_file_prefix, merged_data_files[["field_name"]])
- ## comprehensively merge brain data files with matching files
- data_for_analysis <- list()
- for(df_1_name in paste0(output_file_prefix, c("LH_Thickness","RH_Thickness","LH_Area","RH_Area","Vol"))) {
- df_1 <- brain_data[["grey"]][[df_1_name]]
- for(df_2_idx in 1:nrow(merged_data_files)) {
- df_2 <- data_for_merging[[paste0(output_file_prefix, merged_data_files[df_2_idx,"field_name"])]]
- next_merge_order <- merged_data_files[df_2_idx,"merge_order"]
- if(next_merge_order==1) {
- next_merged_df <- merge(df_2, df_1, by="id")
- } else if(next_merge_order==2) {
- next_merged_df <- merge(df_1, df_2, by="id")
- } else {
- next_merged_df <- NULL
- }
- data_for_analysis[[paste0(df_1_name, merged_data_files[df_2_idx,"suffix"])]] <- next_merged_df
- }
- }
- #WM
- #Creating SLF Avg variables
- # store info about col names to match ("match" col) and the name of the column
- # in which averages should be stored ("stored")
- slf_avg_var_ids <- list()
- slf_avg_var_ids[["wm"]] <- data.frame(rbind(
- c("l_FA" , "l_FA" )
- , c("l_MD" , "l_MD" )
- , c("r_FA" , "r_FA" )
- , c("r_MD" , "r_MD" )
- ))
- slf_avg_var_ids[["noddi"]] <- data.frame(rbind(
- c("l_Neurite_density", "l_Neurite_density" )
- , c("l_ODI" , "l_ODI")
- , c("r_Neurite_density", "r_Neurite_density" )
- , c("r_ODI" , "r_ODI")
- ))
- for(next_df_name in names(slf_avg_var_ids)) {
- colnames(slf_avg_var_ids[[next_df_name]]) <- c("match","store")
- }
- ##MD and FA
- brain_data[["white"]][[paste0(output_file_prefix, "MDFA")]] <- create_slf_avg_vars(brain_data[["white"]][[paste0(output_file_prefix, "MDFA")]] , slf_avg_var_ids[["wm"]] )
- matched_group_data.merged[[paste0(output_file_prefix, "Matched_WM")]] <- create_slf_avg_vars(matched_group_data.merged[[paste0(output_file_prefix, "Matched_WM")]] , slf_avg_var_ids[["wm"]] )
- ##ND and ODI
- brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]] <- create_slf_avg_vars(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]] , slf_avg_var_ids[["noddi"]])
- matched_group_data.merged[[paste0(output_file_prefix, "Matched_Noddi")]] <- create_slf_avg_vars(matched_group_data.merged[[paste0(output_file_prefix, "Matched_Noddi")]], slf_avg_var_ids[["noddi"]])
- ## FOR DEBUGGING PURPOSES:
- ## just printing out the col names being averaged here:
- slf_summary_str <- c()
- slf_summary_str <- c(slf_summary_str, paste0(paste(c("brain_data", "white", paste0(output_file_prefix, "MDFA")), collapse="$"), "$",
- create_slf_avg_vars_fake(brain_data[["white"]][[paste0(output_file_prefix, "MDFA")]], slf_avg_var_ids[["wm"]])))
- slf_summary_str <- c(slf_summary_str, paste0(paste(c("matched_group_data.merged", paste0(output_file_prefix, "Matched_WM")), collapse="$"), "$",
- create_slf_avg_vars_fake(matched_group_data.merged[[paste0(output_file_prefix, "Matched_WM")]], slf_avg_var_ids[["wm"]])))
- slf_summary_str <- c(slf_summary_str, paste0(paste(c("brain_data", "white", paste0(output_file_prefix, "Noddi")), collapse="$"), "$",
- create_slf_avg_vars_fake(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]], slf_avg_var_ids[["noddi"]])))
- slf_summary_str <- c(slf_summary_str, paste0(paste(c("matched_group_data.merged", paste0(output_file_prefix, "Matched_Noddi")), collapse="$"), "$",
- create_slf_avg_vars_fake(matched_group_data.merged[[paste0(output_file_prefix, "Matched_Noddi")]], slf_avg_var_ids[["noddi"]])))
- slf_summary_str <- do.call(rbind.data.frame, strsplit(slf_summary_str, "="))
- colnames(slf_summary_str) <- c("store","avg")
- # slf_summary_str[c(2,4,1,3,9,11,10,12),] # order of rowMeans averaging
- # slf_summary_str[c(1,3,2,4,9,11,10,12),] # order of mod1-8
- data_for_merging[[paste0(output_file_prefix, "MDFA_Mixed_Merge")]] <- read_data_file(paste0(dirs[["input_other"]], output_file_prefix, "MDFA_Mixed_ToMerge.csv"), na = "NA")
- data_for_analysis[[paste0(output_file_prefix, "MDFA" )]] <- merge(brain_data[["white"]][[paste0(output_file_prefix, "MDFA" )]], data_for_merging[[paste0(output_file_prefix, "MDFA_Mixed_Merge")]])
- data_for_analysis[[paste0(output_file_prefix, "Noddi")]] <- merge(brain_data[["white"]][[paste0(output_file_prefix, "Noddi")]], data_for_merging[[paste0(output_file_prefix, "MDFA_Mixed_Merge")]])
- ```
- ## Preparing Brain Data
- ```{r}
- dvs <- list()
- # Most of these (all except Vol) can be determined algorithmically from column names in data files
- dvs[["lh_area" ]] <- data_for_analysis[[paste0(output_file_prefix, "LH_Area" )]] %>% colnames %>% .[grepl("^lh_" , .)]
- dvs[["rh_area" ]] <- data_for_analysis[[paste0(output_file_prefix, "RH_Area" )]] %>% colnames %>% .[grepl("^rh_" , .)]
- dvs[["lh_thick"]] <- data_for_analysis[[paste0(output_file_prefix, "LH_Thickness")]] %>% colnames %>% .[grepl("^lh_.*_thickness$", .)]
- dvs[["rh_thick"]] <- data_for_analysis[[paste0(output_file_prefix, "RH_Thickness")]] %>% colnames %>% .[grepl("^rh_.*_thickness$", .)]
- # can't determine this one algorithmically
- dvs[["vol" ]] <- c("Brain_Stem", "CC_Anterior", "CC_Central", "CC_Mid_Anterior", "CC_Mid_Posterior", "CC_Posterior", "Left_Accumbens_area" , "Left_Amygdala", "Left_Caudate", "Left_Cerebellum_Cortex" , "Left_Cerebellum_White_Matter", "Left_choroid_plexus", "Left_Hippocampus", "Left_Pallidum" , "Left_Putamen" , "Left_Thalamus", "Optic_Chiasm", "Right_Accumbens_area" , "Right_Amygdala" , "Right_Caudate" , "Right_Cerebellum_Cortex" , "Right_Cerebellum_White_Matter", "Right_choroid_plexus", "Right_Hippocampus" , "Right_Lateral_Ventricle" , "Right_Pallidum", "Right_Putamen", "Right_Thalamus", "TotalGrayVol")
- dvs[["mdfa" ]] <- data_for_analysis[[paste0(output_file_prefix, "MDFA" )]] %>% colnames %>% .[(grepl("_FA$" , .) | grepl("_MD$" , .)) & !grepl("^slf[0-9]", .)]
- dvs[["noddi" ]] <- data_for_analysis[[paste0(output_file_prefix, "Noddi" )]] %>% colnames %>% .[(grepl("_Neurite_density$", .) | grepl("_ODI$", .)) & !grepl("^slf[0-9]", .)]
- ```
- ```{r All Models}
- # define predictors which are used as pred_others
- cnr_pred <- "cnr_c"
- control_preds <- c("age_c","sex","iq_c")
- all_preds <- paste(c(control_preds, cnr_pred), collapse=" + ")
- # create a data frame with all parameters needed, then call fit.lm() via an apply function to call everything at once!
- models_to_fit <- data.frame(rbind(
- # df dvs pred_pval pred_others id_text
- ## grey matter
- c("LH_Area" , "lh_area" , "Residuals_BehavioralRegression", cnr_pred , "LH_Area_Regression" )
- , c("Matched_GM" , "lh_area" , "PC_TD" , all_preds , "LH_Area_PC_TD" )
- , c("LH_Area_Cutoff_NoOut" , "lh_area" , "PC_TD_NoOut" , all_preds , "LH_Area_PC_TD_NoOut" )
- , c("Matched_GM" , "lh_area" , "PC_PD" , all_preds , "LH_Area_PC_PD" )
- , c("LH_Area_Cutoff_NoOut" , "lh_area" , "PC_PD_NoOut" , all_preds , "LH_Area_PC_PD_NoOut" )
- , c("Matched_GM" , "lh_area" , "PC_All" , all_preds , "LH_Area_PC_All" )
- , c("LH_Area_Cutoff_NoOut" , "lh_area" , "PC_All_NoOut" , all_preds , "LH_Area_PC_All_NoOut" )
- , c("LH_Area" , "lh_area" , "UPC_UGC" , all_preds , "LH_Area_UPC_UGC" )
- , c("LH_Area" , "lh_area" , "UPC_EC" , all_preds , "LH_Area_UPC_EC" )
- , c("LH_Area" , "lh_area" , "UPC_All" , all_preds , "LH_Area_UPC_All" )
- , c("RH_Area" , "rh_area" , "Residuals_BehavioralRegression", cnr_pred , "RH_Area_Regression" )
- , c("RH_Area_Cutoff" , "rh_area" , "PC_TD" , all_preds , "RH_Area_PC_TD" )
- , c("RH_Area_Cutoff_NoOut" , "rh_area" , "PC_TD_NoOut" , all_preds , "RH_Area_PC_TD_NoOut" )
- , c("RH_Area_Cutoff" , "rh_area" , "PC_PD" , all_preds , "RH_Area_PC_PD" )
- , c("RH_Area_Cutoff_NoOut" , "rh_area" , "PC_PD_NoOut" , all_preds , "RH_Area_PC_PD_NoOut" )
- , c("RH_Area_Cutoff" , "rh_area" , "PC_All" , all_preds , "RH_Area_PC_All" )
- , c("RH_Area_Cutoff_NoOut" , "rh_area" , "PC_All_NoOut" , all_preds , "RH_Area_PC_All_NoOut" )
- , c("RH_Area" , "rh_area" , "UPC_UGC" , all_preds , "RH_Area_UPC_UGC" )
- , c("RH_Area" , "rh_area" , "UPC_EC" , all_preds , "RH_Area_UPC_EC" )
- , c("RH_Area" , "rh_area" , "UPC_All" , all_preds , "RH_Area_UPC_All" )
- , c("LH_Thickness" , "lh_thick", "Residuals_BehavioralRegression", cnr_pred , "LH_Thick_Regression" )
- , c("LH_Thickness_Cutoff" , "lh_thick", "PC_TD" , all_preds , "LH_Thick_PC_TD" )
- , c("LH_Thickness_Cutoff_NoOut", "lh_thick", "PC_TD_NoOut" , all_preds , "LH_Thick_PC_TD_NoOut" )
- , c("LH_Thickness_Cutoff" , "lh_thick", "PC_PD" , all_preds , "LH_Thick_PC_PD" )
- , c("LH_Thickness_Cutoff_NoOut", "lh_thick", "PC_PD_NoOut" , all_preds , "LH_Thick_PC_PD_NoOut" )
- , c("LH_Thickness_Cutoff" , "lh_thick", "PC_All" , all_preds , "LH_Thick_PC_All" )
- , c("LH_Thickness_Cutoff_NoOut", "lh_thick", "PC_All_NoOut" , all_preds , "LH_Thick_PC_All_NoOut")
- , c("LH_Thickness" , "lh_thick", "UPC_UGC" , all_preds , "LH_Thick_UPC_UGC" )
- , c("LH_Thickness" , "lh_thick", "UPC_EC" , all_preds , "LH_Thick_UPC_EC" )
- , c("LH_Thickness" , "lh_thick", "UPC_All" , all_preds , "LH_Thick_UPC_All" )
- , c("RH_Thickness" , "rh_thick", "Residuals_BehavioralRegression", cnr_pred , "RH_Thick_Regression" )
- , c("RH_Thickness_Cutoff" , "rh_thick", "PC_TD" , all_preds , "RH_Thick_PC_TD" )
- , c("RH_Thickness_Cutoff_NoOut", "rh_thick", "PC_TD_NoOut" , all_preds , "RH_Thick_PC_TD_NoOut" )
- , c("RH_Thickness_Cutoff" , "rh_thick", "PC_PD" , all_preds , "RH_Thick_PC_PD" )
- , c("RH_Thickness_Cutoff_NoOut", "rh_thick", "PC_PD_NoOut" , all_preds , "RH_Thick_PC_PD_NoOut" )
- , c("RH_Thickness_Cutoff" , "rh_thick", "PC_All" , all_preds , "RH_Thick_PC_All" )
- , c("RH_Thickness_Cutoff_NoOut", "rh_thick", "PC_All_NoOut" , all_preds , "RH_Thick_PC_All_NoOut")
- , c("RH_Thickness" , "rh_thick", "UPC_UGC" , all_preds , "RH_Thick_UPC_UGC" )
- , c("RH_Thickness" , "rh_thick", "UPC_EC" , all_preds , "RH_Thick_UPC_EC" )
- , c("RH_Thickness" , "rh_thick", "UPC_All" , all_preds , "RH_Thick_UPC_All" )
- , c("Vol" , "vol" , "Residuals_BehavioralRegression", cnr_pred , "Vol_Regression" )
- , c("Vol_Cutoff" , "vol" , "PC_TD" , all_preds , "Vol_PC_TD" )
- , c("Vol_Cutoff_NoOut" , "vol" , "PC_TD_NoOut" , all_preds , "Vol_PC_TD_NoOut" )
- , c("Vol_Cutoff" , "vol" , "PC_PD" , all_preds , "Vol_PC_PD" )
- , c("Vol_Cutoff_NoOut" , "vol" , "PC_PD_NoOut" , all_preds , "Vol_PC_PD_NoOut" )
- , c("Vol_Cutoff" , "vol" , "PC_All" , all_preds , "Vol_PC_All" )
- , c("Vol_Cutoff_NoOut" , "vol" , "PC_All_NoOut" , all_preds , "Vol_PC_All_NoOut" )
- , c("Vol" , "vol" , "UPC_UGC" , all_preds , "Vol_UPC_UGC" )
- , c("Vol" , "vol" , "UPC_EC" , all_preds , "Vol_UPC_EC" )
- , c("Vol" , "vol" , "UPC_All" , all_preds , "Vol_UPC_All" )
- ## white matter
- , c("Matched_WM" , "mdfa" , "PC_PD" , all_preds , "MDFA_PC_PD" )
- , c("Matched_WM" , "mdfa" , "PC_TD" , all_preds , "MDFA_PC_TD" )
- , c("Matched_WM" , "mdfa" , "PC_All" , all_preds , "MDFA_PC_All" ) # NOT THE SAME DATA AS KELLY'S
- , c("MDFA" , "mdfa" , "UPC_EC" , all_preds , "MDFA_UPC_EC" )
- , c("MDFA" , "mdfa" , "UPC_UGC" , all_preds , "MDFA_UPC_UGC" )
- , c("MDFA" , "mdfa" , "UPC_All" , all_preds , "MDFA_UPC_All" )
- , c("MDFA" , "mdfa" , "Residuals_BehavioralRegression", cnr_pred , "MDFA_Regression" )
- ## NODDI
- , c("Matched_Noddi" , "noddi" , "PC_PD" , all_preds , "Noddi_PC_PD" )
- , c("Matched_Noddi" , "noddi" , "PC_TD" , all_preds , "Noddi_PC_TD" )
- , c("Matched_Noddi" , "noddi" , "PC_All" , all_preds , "Noddi_PC_All" )
- , c("Noddi" , "noddi" , "UPC_EC" , all_preds , "Noddi_UPC_EC" )
- , c("Noddi" , "noddi" , "UPC_UGC" , all_preds , "Noddi_UPC_UGC" )
- , c("Noddi" , "noddi" , "UPC_All" , all_preds , "Noddi_UPC_All" )
- , c("Noddi" , "noddi" , "Residuals_BehavioralRegression", cnr_pred , "Noddi_Regression" )
- ))
- names(models_to_fit) <- c("df", "dvs", "pred_pval", "preds_other", "id_text")
- ## COMMENTED OUT FOR DEBUGGING
- # # run all models in models_to_fit
- # all_models <- lapply(1:nrow(models_to_fit), function(next_model_idx) {
- # # extract info about next model
- # next_model_info <- models_to_fit[next_model_idx,]
- # # if next df name contains the word "Matched", look for it in matched_group_data.merged; otherwise, look for it in data_for_analysis
- # next_model_df <- ifelse(grepl("Matched", next_model_info[["df"]]), "matched_group_data.merged", "data_for_analysis")
- # # fit next set of models
- # next_fit_models <- fit_lms(
- # df = get(next_model_df)[[paste0(output_file_prefix, next_model_info[["df"]])]],
- # dvs = dvs[[models_to_fit[next_model_idx,"dvs"]]],
- # pred_pval = next_model_info[["pred_pval"]],
- # preds_other = strsplit(next_model_info[["preds_other"]], " \\+ ")[[1]],
- # id_text = next_model_info[["id_text"]]
- # )
- # return(next_fit_models)
- # })
- # names(all_models) <- models_to_fit[["id_text"]]
- # ENABLED FOR DEBUGGING
- all_models <- list()
- for(next_model_idx in 1:nrow(models_to_fit)) {
- print(paste0(next_model_idx, " / ", nrow(models_to_fit), "..."))
- # extract info about next model
- next_model_info <- models_to_fit[next_model_idx,]
- # if next df name contains the word "Matched", look for it in matched_group_data.merged; otherwise, look for it in data_for_analysis
- next_model_df <- ifelse(grepl("Matched", next_model_info[["df"]]), "matched_group_data.merged", "data_for_analysis")
- # fit next set of models
- next_fit_models <- fit_lms(
- df = get(next_model_df)[[paste0(output_file_prefix, next_model_info[["df"]])]],
- dvs = dvs[[models_to_fit[next_model_idx,"dvs"]]],
- pred_pval = next_model_info[["pred_pval"]],
- preds_other = strsplit(next_model_info[["preds_other"]], " \\+ ")[[1]],
- id_text = next_model_info[["id_text"]]
- )
- all_models[[next_model_info[["id_text"]]]] <- next_fit_models
- }
- # extract key features of linear results into a single df
- all_linear_results <- do.call(rbind.data.frame, lapply(names(all_models), function(model) {
- data.frame(model, all_models[[model]][["rsq.linear"]], p=all_models[[model]][["p"]])
- }))
- # extract all quadratic results into a single df
- models_with_quad_results_binary <- sapply(names(all_models), function(x) {"quad_results" %in% names(all_models[[x]])})
- all_quad_results <- do.call(rbind.data.frame, lapply(names(all_models)[models_with_quad_results_binary], function(model) {
- data.frame(model, all_models[[model]][["quad_results"]])
- }))
- all_rsq_results <- do.call(rbind.data.frame, lapply(names(all_models), function(model) {
- data.frame(model, all_models[[model]][["rsq.linear"]])
- }))
- all_rsq_results <- merge(all_rsq_results, all_quad_results[,colnames(all_quad_results) %in% colnames(all_rsq_results) | grepl("^rsq\\.",colnames(all_quad_results))], all.x=TRUE)
- #all_quad_results # contains all p-values from comparing linear & quadratic models
- # shows proportion of p-values <= .05 separately for each model
- sapply(unique(all_quad_results[["model"]]), function(model) {mean(all_quad_results[all_quad_results[["model"]]==model,"p"] <= .05)})
- # figure of same
- all_quad_results[["model"]] <- factor(all_quad_results[["model"]], unique(all_quad_results[["model"]]))
- all_quad_results[["p_thresh"]] <- as.factor(ifelse(all_quad_results[["p"]] <= .05, 1, 0))
- # for MDFA: subdivide based on l vs. r (or neither), and MD vs. FA
- # for NODDI: subdivide based on l vs. r (or neither), and ODI vs. NeuriteDensity
- all_quad_results.for_plotting <- all_quad_results
- all_quad_results.for_plotting[["model_group"]] <- gsub("^LH_", "l_", gsub("^RH_", "r_", gsub("_Regression$","",all_quad_results.for_plotting[["model"]])))
- # reorder measure and hemisphere designations...
- all_quad_results.for_plotting[["model_group"]] <- sapply(all_quad_results.for_plotting[["model_group"]], function(x) {paste(rev(strsplit(x, "_")[[1]]), collapse="_")})
- # oy!
- mdfa_split <- sapply(all_quad_results.for_plotting[all_quad_results.for_plotting[["model_group"]]=="MDFA","dv"], function(x) {strsplit(x, "_")})
- mdfa_num_fields <- sapply(mdfa_split, function(x) {length(x)})
- mdfa_groups <- sapply(1:length(mdfa_split), function(y) {paste(mdfa_split[[y]][mdfa_num_fields[y]:2], collapse="_")})
- all_quad_results.for_plotting[all_quad_results.for_plotting[["model_group"]]=="MDFA","model_group"] <- mdfa_groups
- noddi_split <- sapply(gsub("Neurite_density","NeuriteDensity",all_quad_results.for_plotting[all_quad_results.for_plotting[["model_group"]]=="Noddi","dv"]), function(x) {strsplit(x, "_")})
- noddi_num_fields <- sapply(noddi_split, function(x) {length(x)})
- noddi_groups <- sapply(1:length(noddi_split), function(y) {paste(noddi_split[[y]][noddi_num_fields[y]:2], collapse="_")})
- all_quad_results.for_plotting[all_quad_results.for_plotting[["model_group"]]=="Noddi","model_group"] <- noddi_groups
- ordered_model_groups <- c("Area_l", "Area_r", "Thick_l", "Thick_r", "Vol", "FA_l", "FA", "FA_r", "MD_l", "MD", "MD_r", "NeuriteDensity_l", "NeuriteDensity", "NeuriteDensity_r", "ODI_l", "ODI", "ODI_r")
- all_quad_results.for_plotting[["model_group"]] <- factor(all_quad_results.for_plotting[["model_group"]], ordered_model_groups)
- # determine where to add vertical lines for visual breaks in the more complex figure
- model_group_prefixes <- sapply(strsplit(ordered_model_groups, "_"), function(x) {x[[1]]})
- model_group_breaks <- which(model_group_prefixes[2:length(model_group_prefixes)]!=model_group_prefixes[1:(length(model_group_prefixes)-1)])+0.5
- # plot p-values by model (simpler)
- quad_p.plot <- ggplot(all_quad_results, aes(x=model, y=p, color=p_thresh)) +
- ggforce::geom_sina(size=1, scale=F, method="density", maxwidth=0.3, position=position_identity()) +
- scale_color_manual(values=c("black","red")) +
- geom_hline(yintercept=0.05, linetype="dashed", color="black") +
- theme_bw() +
- xlab("Model") +
- ylab("p")
- quartz(width=11, height=5); quad_p.plot
- # plot p-values by model, measure, hemisphere... (more complex)
- quad_p.for_plotting.plot <- ggplot(all_quad_results.for_plotting, aes(x=model_group, y=p, color=p_thresh)) +
- ggforce::geom_sina(size=1, scale=F, method="density", maxwidth=0.2, position=position_identity()) +
- scale_color_manual(values=c("black","red")) +
- geom_hline(yintercept=0.05, linetype="dashed", color="black") +
- geom_vline(xintercept=model_group_breaks, linetype="dashed", color="black") +
- theme_bw() +
- xlab("Model") +
- ylab("p")
- quartz(width=18, height=5); quad_p.for_plotting.plot
- ```
- ## How to access stored models
- ```{r Access model objects}
- # all_models is a list with 64 objects:
- # LH_Area_Regression, LH_Area_PCTD, ... RH_Area_Regression, RH_Area_PCTD, ...
- # LH_Thick_Regression, LH_Thick_PCTD, ... RH_Thick_Regression, RH_Thick_PCTD, ...
- # Vol_Regression, Vol_PCTD, ... MDFA_PCPD, MDFA_PCTD, ... Noddi_PCPD, Noddi_PCTD, ...
- # One of those can be accessed via (e.g.) all_models[["LH_Area_Regression"]]
- # That is itself a list with 3 objects:
- # "model" (a list of 75 fitted models); can be accessed via all_models[["LH_Area_Regression"]][["model"]]
- # "p" (a vector of 75 p-values, 1 per model); can be accessed via all_models[["LH_Area_Regression"]][["p"]]
- # "quad_results" (a data frame of results from quadratic tests); can be accessed via all_models[["LH_Area_Regression"]][["quad_results"]]
- # Unfortunately, the names of the model fields had to be changed to work with mtable(). So, at the moment, they're all "lm1", "lm2", ... "lm75".
- # You can see this via names(all_models[["LH_Area_Regression"]][["model"]])
- # So, in order to match those up with names which are actually useful, you can grab the list of dependent variables which they correspond to.
- # Those can be found in dvs[["lh_area"]] which returns a list of 75 area names. This means that, for example,
- # all_models[["LH_Area_Regression"]][["model"]][[1]] (or, equivalently, all_models[["LH_Area_Regression"]][["model"]][["lm1"]] )
- # will return the LH area regression model for dvs[["lh_area"]][[1]] , which is "lh_G_and_S_frontomargin_area" .
- # You can see the list of different sets of DVs via names(dvs) ,
- # and the list of everything that was matched up for model fitting is in models_to_fit
- # We can also make an enormous df of model p-values, as follows...
- all_model_results.flat <- suppressWarnings(lapply(1:nrow(models_to_fit), function(idx) {data.frame(subset(models_to_fit[idx,], select=-dvs), dv=dvs[[models_to_fit[idx,"dvs"]]], p=all_models[[idx]][["p"]])})) # warnings regard row names
- all_model_results.flat <- do.call(rbind.data.frame, all_model_results.flat)
- # all_model_results.flat now has everything in one place!
- all_rsq_results <- merge(all_model_results.flat, all_rsq_results, by.x=c("id_text","dv"), by.y=c("model","dv"))
- ```
- #FDR Correction for Multiple Comparisons
- ```{r MultipleComparisonsCorrection}
- dv_pcorr_groupings <- unique.data.frame(rbind(all_linear_results[,c("model","dv")],all_quad_results[,c("model","dv")])) # quad should be a subset, but just in case...
- model_suffixes <- c(
- "Regression"
- , "UPC_UGC"
- , "UPC_EC"
- , "UPC_All"
- , "PC_PD"
- , "PC_TD"
- , "PC_All"
- , "PC_PD_NoOut"
- , "PC_TD_NoOut"
- , "PC_All_NoOut"
- )
- dv_pcorr_groupings[["model_type"]] <- sapply(dv_pcorr_groupings[["model"]], function(x) {
- match_idx <- which(sapply(model_suffixes, function(y) { grepl(paste0("_", y, "$"), x) }))
- ifelse(length(match_idx)==1, return(model_suffixes[match_idx]), stop(paste0("Not exactly 1 match! : ", x, " has ", length(match_idx), " matches")))
- })
- dv_pcorr_groupings[["dv_type"]] <- sapply(strsplit(dv_pcorr_groupings[["dv"]], "_"), function(x) {x[length(x)]})
- dv_pcorr_groupings[["dv_type"]][!dv_pcorr_groupings[["dv_type"]] %in% c("area","thickness","MD","FA","ODI","density")] <- "volume"
- dv_pcorr_groupings[["pcorr_group_id"]] <- with(dv_pcorr_groupings, paste0(model_type, "_", dv_type))
- # count how many models are in each group
- unique_model_groups <- unique.data.frame(dv_pcorr_groupings[,c("model_type","dv_type","pcorr_group_id")])
- unique_model_groups[["num_models_in_group"]] <- sapply(unique_model_groups[["pcorr_group_id"]], function(x) {sum(dv_pcorr_groupings[["pcorr_group_id"]]==x)})
- # show # models per group and # of groups
- dplyr::rename(aggregate(pcorr_group_id ~ dv_type + num_models_in_group, data=unique_model_groups, FUN=length), num_groups="pcorr_group_id")
- ```
- ```{r Explore sig. quadratic models}
- # quad correction
- all_quad_results <- merge(all_quad_results, dv_pcorr_groupings[,c("model","dv","pcorr_group_id")])
- quad_summary <- do.call(rbind.data.frame, lapply(unique(all_quad_results[["pcorr_group_id"]]), function(pcorr_group_id) {
- matching_rows <- all_quad_results[all_quad_results[["pcorr_group_id"]]==pcorr_group_id,]
- c(nrow(matching_rows), sum(matching_rows[["p"]] < .05))}))
- colnames(quad_summary) <- c("n","sig")
- # correct p-values in groups
- for(next_group in unique(all_quad_results[["pcorr_group_id"]])) {
- temp_p_list <- all_quad_results[all_quad_results[["pcorr_group_id"]]==next_group,"p"]
- all_quad_results[all_quad_results[["pcorr_group_id"]]==next_group,"p.corr"] <- p.adjust(temp_p_list, method="fdr")
- all_quad_results[all_quad_results[["pcorr_group_id"]]==next_group,"n.corr"] <- length(temp_p_list)
- }
- sig_quad_results <- all_quad_results[all_quad_results[["p.corr"]] < .05,] # sig. post-correction
- # plot post-correction sig. p-values by model
- all_sig_quad_plots <- list()
- for(next_sig_quad_result_idx in 1:nrow(sig_quad_results)) {
- next_sig_quad_model_info <- sig_quad_results[next_sig_quad_result_idx,]
- next_sig_quad_model <- with(next_sig_quad_model_info, all_models[[as.character(model)]][["model"]][[dv]]) # (1) this is the linear model, not the quad model; (2) do I need this?
- #next_sig_quad_model <- with(next_sig_quad_model_info, all_models[[as.character(model)]][["quad_models"]][[as.character(dv)]]) # oy
- #next_sig_quad_plot <- plot_grouped_vs_continuous(fitted_lm=next_sig_quad_model, pred_var=c("Residuals_BehavioralRegression", "Residuals_BehavioralRegression.quad"))
- next_sig_quad_plot <- plot_grouped_vs_continuous(fitted_lm=next_sig_quad_model, pred_var="Residuals_BehavioralRegression", degree=c(1,2))
- next_sig_quad_plot <- next_sig_quad_plot + ggtitle(next_sig_quad_model_info[["dv"]])
- all_sig_quad_plots[[next_sig_quad_result_idx]] <- next_sig_quad_plot
- }
- all_sig_quad_plots.together <- grid.arrange(grobs=all_sig_quad_plots, ncol=5)
- ggsave(filename=paste0(super_dir, "all_sig_quad_plots.png"), plot=all_sig_quad_plots.together, width=30, height=15, units="in")
- all_sig_quad_plots.quad_models <- list()
- for(next_sig_quad_result_idx in 1:nrow(sig_quad_results)) {
- next_sig_quad_model_info <- sig_quad_results[next_sig_quad_result_idx,]
- # next_sig_quad_model <- with(next_sig_quad_model_info, all_models[[as.character(model)]][["model"]][[dv]]) # (1) this is the linear model, not the quad model; (2) do I need this?
- next_sig_quad_model <- with(next_sig_quad_model_info, all_models[[as.character(model)]][["quad_models"]][[as.character(dv)]]) # oy
- all_sig_quad_plots.quad_models[[next_sig_quad_result_idx]] <- next_sig_quad_model
- }
- all_sig_quad_plots.quad_models <- lapply(1:length(all_sig_quad_plots.quad_models), function(x) {coef(summary(all_sig_quad_plots.quad_models[[x]]))})
- sig_quad_results[["quad_coeff"]] <- sapply(1:length(all_sig_quad_plots.quad_models), function(x) {all_sig_quad_plots.quad_models[[x]][grepl("\\.quad$", rownames(all_sig_quad_plots.quad_models[[x]])),"Estimate"]})
- sig_quad_results
- ```
- ```{r Explore sig. linear models}
- # linear correction
- all_linear_results <- merge(all_linear_results, dv_pcorr_groupings[,c("model","dv","pcorr_group_id")])
- linear_summary <- do.call(rbind.data.frame, lapply(unique(all_linear_results[["pcorr_group_id"]]), function(pcorr_group_id) {
- matching_rows <- all_linear_results[all_linear_results[["pcorr_group_id"]]==pcorr_group_id,]
- c(nrow(matching_rows), sum(matching_rows[["p"]] < .05))}))
- colnames(linear_summary) <- c("n","sig")
- # correct p-values in groups
- for(next_group in unique(all_linear_results[["pcorr_group_id"]])) {
- temp_p_list <- all_linear_results[all_linear_results[["pcorr_group_id"]]==next_group,"p"]
- all_linear_results[all_linear_results[["pcorr_group_id"]]==next_group,"p.corr"] <- p.adjust(temp_p_list, method="fdr")
- all_linear_results[all_linear_results[["pcorr_group_id"]]==next_group,"n.corr"] <- length(temp_p_list)
- }
- sig_linear_results <- all_linear_results[all_linear_results[["p.corr"]] < .05,] # sig. post-correction
- # get p-values for quadratic plots for these same models, to identify models that only show linear but not quadratic components...
- compare_linear_and_quad <- merge(rename(sig_linear_results, p.linear="p", p.corr.linear="p.corr", n.corr.linear="n.corr"), subset(rename(all_quad_results, p.quad="p", p.corr.quad="p.corr", n.corr.quad="n.corr"), select=-c(F,df,p_thresh)), by.all=c("model","dv","group"), all.x=TRUE) # lol
- # remove models for which a quadratic model was not fit
- compare_linear_and_quad <- compare_linear_and_quad[!is.na(compare_linear_and_quad[["rsq.quad"]]),] # only 6!
- # remove models with sig. quad fits
- compare_linear_and_quad <- compare_linear_and_quad[compare_linear_and_quad[["p.corr.quad"]] > .05,]
- # plot quadratic fits for these models (not a typo!)
- all_sig_linear_nonsig_quad_plots <- list()
- for(next_result_idx in 1:nrow(compare_linear_and_quad)) {
- next_model_info <- compare_linear_and_quad[next_result_idx,]
- next_model <- with(next_model_info, all_models[[as.character(model)]][["model"]][[dv]])
- next_plot <- plot_grouped_vs_continuous(fitted_lm=next_model, pred_var="Residuals_BehavioralRegression", degree=c(1,2))
- next_plot <- next_plot + ggtitle(next_model_info[["dv"]])
- all_sig_linear_nonsig_quad_plots[[next_result_idx]] <- next_plot
- }
- all_sig_linear_nonsig_quad_plots <- grid.arrange(grobs=all_sig_linear_nonsig_quad_plots, ncol=3)
- ggsave(filename=paste0(super_dir, "all_sig_linear_nonsig_quad_plots.png"), plot=all_sig_linear_nonsig_quad_plots, width=18, height=10, units="in")
- ```
- ```{r Figs for paper}
- # added 12/23/25
- # First: Sinaplots for the AF, SLF, and R ILF for the UPC vs EC contrast (that one has the largest effect sizes) with mean lines by group (all MD)
- regions_for_sinaplots <- c(af_l_MD="MD - Left AF", af_r_MD="MD - Right AF", ilf_r_MD="MD - Right ILF", slfavg_l_MD="MD - Left SLF", slfavg_r_MD="MD - Right SLF")
- models_for_sinaplots <- data.frame(model="MDFA_UPC_EC", dv=names(regions_for_sinaplots), desc=unname(regions_for_sinaplots), pred.crit="UPC_EC")
- # overly complicated function for computing multiple stats on a given df
- agg_df <- function(char_funs=c("min","max","mean","sd"), df, x.col, y.col) {
- # uses aggregate() to apply each of the functions listed (in character format) in char_funs
- # to formula(y.col ~ [all of the vars in x.cols]) in data frame df, then returns the merged result
- resids_by_group <- Reduce(function(x,y) merge(x, y, by="group", all=TRUE), lapply(char_funs, function(fun) {agg <- aggregate(formula(paste0(y.col, " ~ ", paste(x.col, collapse=" + "))), FUN=eval(parse(text=fun)), data=df); colnames(agg) <- c(x.col,fun); return(agg) })) # !!!
- return(resids_by_group)
- }
- group_colors <- viridis(4)[2:3] # group colors are defined here -- first for UPC, second for EC
- names(group_colors) <- c("UPC","EC")
- x_margin <- 0.15
- all_sinaplots <- list()
- set.seed(123) # sinaplots involve random jitter; setting the seed for reproducibility
- for(next_model_for_sinaplot_idx in 1:nrow(models_for_sinaplots)) {
- next_model <- with(models_for_sinaplots[next_model_for_sinaplot_idx,], all_models[[model]][["model"]][[dv]]) # extract appropriate model
- # fit subset models to get residualized predictor & DV
- next_model.resids <- get_model_resids(next_model, models_for_sinaplots[next_model_for_sinaplot_idx,"pred.crit"])
- # combine df with residualized DV
- next_model.for_plotting <- data.frame(next_model[["model"]], dv.resid=next_model.resids[["subset_lm.resids"]], pred.resids=next_model.resids[["pred.resids"]])
- # this hard-codes in something which is earlier assumed to be a variable... the key col may not be UPC_EC
- next_model.for_plotting[["group"]] <- as.factor(with(next_model.for_plotting, ifelse(UPC_EC==0, "UPC", "EC"))) # it's either 0 or 1
- next_model.for_plotting[["group"]] <- factor(next_model.for_plotting[["group"]], c("UPC", "EC"))
- # compute means for separate plotting
- next_model.for_plotting.means <- dplyr::rename(agg_df(char_funs=c("mean","sd","length"), df=next_model.for_plotting, x.col=c("group"), y="dv.resid"), n="length")
- next_model.for_plotting.means[["se"]] <- with(next_model.for_plotting.means, sd/sqrt(n))
- next_model.for_plotting.means <- next_model.for_plotting.means[order(next_model.for_plotting.means[["group"]]),] # order (important!)
- next_plot <- ggplot(next_model.for_plotting, aes(x=group, y=dv.resid, color=group)) +
- ggforce::geom_sina(size=1, scale=F, method="density", maxwidth=0.3, position=position_identity()) +
- scale_color_manual(values=group_colors) +
- theme_bw() +
- xlab("Group") +
- ylab(paste0(models_for_sinaplots[next_model_for_sinaplot_idx,"desc"], " (resid.)")) +
- theme(panel.grid.major.x=element_blank(), legend.position="none") # no vertical lines
- # add means
- for(next_group_mean_idx in 1:nrow(next_model.for_plotting.means)) {
- next_plot <- next_plot +
- geom_segment(color="black", linewidth=0.5, # horizontal line at y-mean
- x = with(next_model.for_plotting.means[next_group_mean_idx,], next_group_mean_idx-x_margin),
- xend = with(next_model.for_plotting.means[next_group_mean_idx,], next_group_mean_idx+x_margin),
- y = with(next_model.for_plotting.means[next_group_mean_idx,], mean ),
- yend = with(next_model.for_plotting.means[next_group_mean_idx,], mean )) +
- geom_segment(color="black", linewidth=1.5, # vertical line to show +/- 1 SE variability
- x = next_group_mean_idx, xend=next_group_mean_idx,
- y = with(next_model.for_plotting.means[next_group_mean_idx,], mean-se),
- yend = with(next_model.for_plotting.means[next_group_mean_idx,], mean+se))
- }
- # store plot in list
- all_sinaplots[[next_model_for_sinaplot_idx]] <- next_plot
- }
- # combine plots and save
- all_sinaplots.together <- grid.arrange(grobs=all_sinaplots, ncol=3)
- ggsave(filename=paste0(super_dir, "all_sinaplots.png"), plot=all_sinaplots.together, width=9, height=6, units="in")
- # Second: Scatterplots with a nonlinear fit line for thel AF (af_l_fa) and the right SLF (slf_r_Neurite_density) with colors from the groups
- # note: the second one is not sig. (p = .051)
- models_for_quadplots <- data.frame(rbind(c("MDFA_Regression" , "af_l_FA" , "FA - Left AF" , "Residuals_BehavioralRegression"),
- c("Noddi_Regression", "slfavg_r_Neurite_density", "NODDI - Right SLF", "Residuals_BehavioralRegression")))
- colnames(models_for_quadplots) <- c("model","dv","desc","pred.crit")
- all_quad_results[(all_quad_results[["dv"]] %in% models_for_quadplots[["dv"]]),] # show info for these two models
- # generate plots for these quadratic models
- all_paper_quad_plots <- list()
- for(next_paper_quad_result_idx in 1:nrow(models_for_quadplots)) {
- next_paper_quad_model_info <- models_for_quadplots[next_paper_quad_result_idx,]
- next_paper_quad_model <- with(next_paper_quad_model_info, all_models[[as.character(model)]][["model"]][[dv]])
- next_paper_quad_plot <- plot_grouped_vs_continuous(fitted_lm=next_paper_quad_model, pred_var=next_paper_quad_model_info[["pred.crit"]], degree=c(2), linetype=c(1)) # to show both linear AND quad, change degree to c(1,2)
- next_paper_quad_plot <- next_paper_quad_plot +
- xlab("Comprehension regression residuals (resid.)") +
- ylab(paste0(next_paper_quad_model_info[["desc"]], " (resid.)"))
- all_paper_quad_plots[[next_paper_quad_result_idx]] <- next_paper_quad_plot
- }
- all_paper_quad_plots.together <- grid.arrange(grobs=all_paper_quad_plots, ncol=2)
- ggsave(filename=paste0(super_dir, "all_paper_quad_plots.png"), plot=all_paper_quad_plots.together, width=12, height=5, units="in")
- ```
Mahaffy_Masters_Real_DK__KM_20251223 (1).Rmd, no license · at the source
Overview
- Department of Psychological Sciences, University of Connecticut, Storrs, CT, USA
- Child Study Center, Yale School of Medicine, New Haven, CT, USA
- Brain Imaging Research Core, University of Connecticut, Storrs, CT, USA
- The Nathan S. Klein Institute for Psychiatric Research, Orangeburg, NY, USA
Abstract
Poor comprehenders (PCs) have typical word reading skill and intelligence but poorer than expected reading comprehension. While the prevalence of PCs is similar to that of poor decoders (individuals who struggle fluently converting written text into spoken language), less is known about the neurobiological substrates of poor comprehension. Extant studies have found small differences in gray matter volume between PCs and poor or typically reading peers. However, a detailed quantification of cortical morphometric features and white matter integrity remains unexplored. Data from 2,100 children (1,200 with imaging data), aged 8–16 years were analyzed to determine if there is a distinct neuroanatomy associated with poor reading comprehension across three common methods for classifying PCs. We computed gray matter volume, cortical thickness, and surface area, as well as white matter measures (mean diffusivity, fractional anisotropy, neurite orientation, and neurite density) for PCs and compared these measures with those of poor decoders and typical readers. Results revealed small but widespread white matter differences, but no gray matter differences between PCs and other readers. PCs showed decreased white matter integrity (increased mean diffusivity, decreased neurite density) in tracts previously associated with reading, including the superior longitudinal fasciculus and inferior longitudinal fasciculus, and in tracts that have been associated with cognitive performance such as the uncinate fasciculus. These results suggest that diffuse structural connectivity differences may underlie reading comprehension weaknesses in the face of intact decoding skills. This is consistent with the behavioral profile of PCs who exhibit a broad pattern of subclinical impairments in language and integrative cognitive processes.
Reproduced under the paper's license (CC BY), from the paper cited above.
Repository
Its files are read in the Code ↔ Paper reader above, with 2 matches between paragraphs and lines of code.
OSF 2ng57
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
- 27 September 2026: the link answers (HTTP 200)
1 file
- Code Files/
Mahaffy_Masters_Real_DK_ , R, 1,237 lines, 2 matches_KM_20251223 (1).Rmd
The paper's code and data availability statement is in the Data section.
Tracing map
Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.
What the map holds:
- 1 repository of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
- 1 script, each with its path and the digest of its content;
- 2 matches between paragraphs of the paper and lines of the code (method lexical-v1);
- neither the text of the paper nor the code itself.
Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.
Data
No dataset and no data link were found in the paper.
Data availability statement
All data and code used for this project, along with project preregistration, are publicly available in an OSF repository: https://
Reproduced under the paper's license (CC BY), from the paper cited above.
Versions
The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.
Version 1, 27 September 2026: the first record
Recorded: type, language, journal, volume, pages, dates, 4 authors, 4 keywords, 1 funder, 96 references.
Cite
This paper
Mahaffy, K., Koirala, N., Kleinman, D., & Landi, N. (2026). Structural Brain Correlates of Poor Reading Comprehension. Neurobiology of language (Cambridge, Mass.), 7, NOL.a.265. https://
BibTeX
@article{mahaffy2026stru
author = {Mahaffy, Kelly and Koirala, Nabin and Kleinman, Daniel and Landi, Nicole},
title = {{Structural Brain Correlates of Poor Reading Comprehension}},
journal = {Neurobiology of language (Cambridge, Mass.)},
year = {2026},
month = jul,
volume = {7},
pages = {NOL.a.265},
publisher = {MIT Press},
issn = {2641-4368},
doi = {10.1162/
url = {https://
pmid = {42492006},
pmcid = {PMC13379302}
}
RIS
TY - JOUR
AU - Mahaffy, Kelly
AU - Koirala, Nabin
AU - Kleinman, Daniel
AU - Landi, Nicole
TI - Structural Brain Correlates of Poor Reading Comprehension
T2 - Neurobiology of language (Cambridge, Mass.)
J2 - Neurobiol Lang (Camb)
PY - 2026
DA - 2026/
VL - 7
SP - NOL.a.265
SN - 2641-4368
PB - MIT Press
DO - 10.1162/
UR - https://
LA - en
ER -
CSL-JSON
{
"id": "10.1162/
"type": "article-journal",
"title": "Structural Brain Correlates of Poor Reading Comprehension",
"container-title": "Neurobiology of language (Cambridge, Mass.)",
"author": [
{
"family": "Mahaffy",
"given": "Kelly"
},
{
"family": "Koirala",
"given": "Nabin"
},
{
"family": "Kleinman",
"given": "Daniel"
},
{
"family": "Landi",
"given": "Nicole"
}
],
"container-title-short":
"volume": "7",
"page": "NOL.a.265",
"DOI": "10.1162/
"PMID": "42492006",
"PMCID": "PMC13379302",
"ISSN": "2641-4368",
"publisher": "MIT Press",
"URL": "https://
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
1
]
]
}
}
The tracing map gets a citation of its own once an author has validated it and it has a DOI.
Similar papers
The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.
- [1] doi:10.1162/imag.a.1303
- Composite reaction time and variability correlate with whole-brain white-matter characteristics.Journal: Imaging neuroscience (Cambridge, Mass.)In common: structural MRI / diffusion, 14 references
- [2] doi:10.3389/frai.2026.1771088 [code]
- Few-shot deployment of pretrained MRI transformers in brain imaging tasks.Journal: Frontiers in artificial intelligenceIn common: structural MRI / diffusion, 13 references
- [3] doi:10.3389/fpain.2026.1850836 [code]
- Diffusion tensor imaging in chronic tension-type headache.Journal: Frontiers in pain research (Lausanne, Switzerland)In common: structural MRI / diffusion, 11 references
- [4] doi:10.1038/s41593-026-02359-0 [code]
- The cross-site reproducibility of MRI morphometric phenotypes in psychiatric disorders.Journal: Nature neuroscienceIn common: structural MRI / diffusion, 9 references
- [5] doi:10.1038/s41467-026-73262-2 [code]
- Robust but independent sex differences in human brain function, structure, and behavior.Journal: Nature communicationsIn common: reshape2, ggpubr, ggplot2, 1 other tool, structural MRI / diffusion, 4 references
- [6] doi:10.1016/j.nicl.2026.104012 [code]
- Structural-functional multilayer brain network properties and outcome of combined repetitive transcranial magnetic stimulation and psychotherapy for obsessive-compulsive disorder.Journal: NeuroImage. ClinicalIn common: psych, reshape2, ggpubr, 2 other tools, structural MRI / diffusion, 2 references
- [7] doi:10.7554/elife.108109 [code]
- Multimodal MRI marker of cognition explains the association between cognition and mental health in the UK Biobank.Journal: eLifeIn common: psych, ggplot2, structural MRI / diffusion, cognitive, 5 references
- [8] doi:10.1162/netn.a.536 [code]
- Increased sensitivity in identifying language-related functional connectivity using jackknife resampling analyses.Journal: Network neuroscience (Cambridge, Mass.)In common: psych, reshape2, ggpubr, 2 other tools, 2 references
- [9] doi:10.1038/s41593-026-02363-4 [code]
- Cortical thickness changes precede high levels of amyloid by at least 7 years.Journal: Nature neuroscienceIn common: ggpubr, ggplot2, tidyverse, structural MRI / diffusion, 4 references
- [10] doi:10.1007/s00415-026-14034-2
- Similarities and differences in the late-onset GM2 gangliosidoses: Tay-Sachs and Sandhoff diseases.Journal: Journal of neurologyIn common: structural MRI / diffusion, 7 references
Contribute
The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.
Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.
Claim this paper
Correct its record
Say what each link of this record is, remove the ones that are not the paper's, add the ones that are missing. The correction becomes a new version of the record, in its Versions section.
Validate its tracing map
You validate the map as this page shows it: 1 repository of the authors' code, each at its verified commit and with its license, 1 script, and 2 matches between paragraphs and code (see the Code and Map sections). It then receives a DOI on Zenodo, with you (your ORCID iD) and OSCR as its creators; the code itself is not deposited.
The map's fingerprint: sha256:8f58d23377d76285…
Add the badge to its README
The badge links the code to this page. Copy one of these into the README of the paper's code: only you decide where it goes, and nothing is changed for you.
Markdown
[.
Discussion, reproductions, activity
Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.
Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.
Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.
