OSCR

Dynamic Functional Synchronization Profiles in Autism Differ by Spatial Scale and Along Hierarchical Cortical Gradients.

Code ↔ Paper

7 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 7 matches
  1. [1] § Methods and Materials › Turbulence Analysis ↔ code/turbulence.m, lines 69–167 · score 0.89 · analytic signal, Euclidean distance, Hilbert transform, kernel function, Kuramoto local, summed
  2. [2] § Methods and Materials › Statistical Analysis ↔ code/Turbulence_postproc_analysis.Rmd, lines 634–692 · score 0.73 · information cascade flow, linear models, information transfer, sex, Cohen, ADOS
  3. [3] § Methods and Materials › Turbulence Analysis ↔ code/Turbulence_postproc_analysis.Rmd, lines 634–692 · score 0.67 · standard deviation, information cascade flow, information transfer, vector, linear, row
  4. [4] § Results › Variability of Parcel-Level Functional Synchronization Maps Onto S-A and Functional Gradients in Autism ↔ code/gradient_computations.ipynb, lines 115–193 · score 0.58 · Spearman rho, turbulence maps, functional gradient, fitted, ranks, Cohen
  5. [5] § Methods and Materials › Turbulence Analysis ↔ code/turbulence.m, lines 69–167 · score 0.55 · Kuramoto local, information transfer, vector, row, dimensional, computation
  6. [6] § Results › Variability of Parcel-Level Functional Synchronization Maps Onto S-A and Functional Gradients in Autism ↔ code/gradient_computations.ipynb, lines 115–193 · score 0.55 · turbulence maps, 0.07 mm, Spearman, permutation, spin, gradients
  7. [7] § Results › Weaker, Less Spatially Correlated, and Rapidly Decaying Functional Synchronization Over Space in Autism ↔ code/Turbulence_postproc_analysis.Rmd, lines 341–442 · score 0.51 · ADOS percentage scores, information transfer, intermediate, KLOP, parcels

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,459 lines · 57 KB · no license · 3 matches

  1. ---
  2. title: "Turbulence_final_revision"
  3. output: html_document
  4. date: "2026-03-21"
  5. ---
  6. Post-Processing analysis of Turbulence Project on Abide data.
  7. ```{r, message=FALSE, warning=FALSE}
  8. ### Loading libraries
  9. library("easypackages")
  10. libraries("scales","reticulate", "ppcor","gganimate", "patchwork","ggseg","ggsegSchaefer","car","gvlma","performance","effectsize", "grid","gridExtra","MatchIt","gt","lme4","lsr", "effsize", "ggsignif","tidyverse","here","sva","R.matlab","car","ggplot2", "tidyverse","ggeasy","ggpackets","emmeans", "plotly", "fmsb", "ggradar", "ggseg3d", "minpack.lm", "psych")
  11. ### Handling paths
  12. root = Sys.getenv("ROOT_PATH")
  13. #root = "/Users/bened/Library/CloudStorage/OneDrive-FondazioneIstitutoItalianoTecnologia/Desktop/University/PhD_IIT/turbulence/Turbulence_Autisms/Turbulence_Project/turbulence_final_folder" # For local processing
  14. figures_directory = paste0(root,"/figures")
  15. ### Loading functions and data
  16. #Geom_scatterbox for plotting
  17. geom_scatterbox <- ggpacket() +
  18. geom_jitter(size = 1.5, width = 0.25) +
  19. geom_boxplot(fill = NA, colour = "#000000", outlier.shape = NA, size = 1)
  20. #Loading in the phenotypical and qualitative subject and scanner data
  21. filenames = list.files(file.path(root, "data","postproc"))
  22. motionTable = read.csv(file.path(root, "data", "pheno", "qc_preproc_abideI_abideII.csv"))
  23. qcLombardo = read.csv(file.path(root, "data", "pheno", "qc_preproc_mvlombardo.csv"))
  24. mri_data = read.csv2((file.path(root, "data", "pheno", "mriparams_summary_abide.csv")))
  25. pheno = read.csv(file.path(root, "data", "pheno","pheno_abide_I_abide_II.csv"))
  26. #### Data Filtering
  27. #Filtering data for FD and Quality-checks
  28. #Merging dataframes by subid, site and dataset
  29. mergedPheno = merge.data.frame(motionTable,pheno, by = c("dataset", "site","subid"))
  30. mergedPheno = merge.data.frame(mergedPheno, qcLombardo, c("dataset", "site","subid"))
  31. mergedPheno = merge.data.frame(mergedPheno, mri_data, c("dataset", "site"))
  32. #Extracting all the IDs for the subjects that pass the quality check and have FD < 0.5
  33. filteredPheno = mergedPheno %>%
  34. dplyr::filter(pass == "YES") %>%
  35. dplyr::filter(mean_fd < 0.5)
  36. #Check visually for distributions of diagnosis over sites
  37. table(filteredPheno$site, filteredPheno$diagnosis)
  38. # merge LEUVEN, NYU, and UM labels, to even distributions in these sites
  39. filteredPheno$site_orig = filteredPheno$site
  40. filteredPheno$site[is.element(filteredPheno$site, c("NYU_1","NYU_2"))] = "NYU"
  41. filteredPheno$site[is.element(filteredPheno$site, c("LEUVEN_1","LEUVEN_2"))] = "LEUVEN"
  42. filteredPheno$site[is.element(filteredPheno$site, c("UM_1","UM_2"))] = "UM"
  43. filteredPheno$site[is.element(filteredPheno$site, c("UCLA_1","UCLA_2"))] = "UCLA"
  44. filteredPheno$site = factor(filteredPheno$site)
  45. #Check distributions again
  46. table(filteredPheno$site, filteredPheno$diagnosis)
  47. ```
  48. ```{r warning=FALSE}
  49. # remove UM, since it has an odds ratio of almost 9:1
  50. mask = filteredPheno$site != "UM"
  51. filteredPheno = filteredPheno %>% dplyr::filter(mask)
  52. filteredPheno$site = factor(filteredPheno$site)
  53. filteredPheno$mean_fd = as.numeric(filteredPheno$mean_fd)
  54. #Check, if site and diagnosis are equally distributed across the sites with a chi-square test
  55. chisq.test(filteredPheno$site, filteredPheno$diagnosis)
  56. ```
  57. p-value > 0.05, so we can go on.
  58. ```{r}
  59. # Checking distributions of framewise displacement.
  60. t.test(mean_fd ~ as.factor(diagnosis), data = filteredPheno)
  61. ```
  62. Significant (p < 0.001). Autistic subjects move more in the scanner.
  63. ```{r}
  64. #Plotting the association of FD and diagnosis
  65. t_meanfd = t.test(data = filteredPheno, mean_fd ~ diagnosis)
  66. p_meanfd = t_meanfd$p.value
  67. tab = table(filteredPheno$diagnosis)
  68. d_meanfd = round(t_meanfd$statistic / sqrt((as.double(tab[1])*as.double(tab[2]))/(as.double(tab[1])+as.double(tab[2]))),3)
  69. mean_fd_dx_plot_before = ggplot(data = filteredPheno, aes(x = diagnosis, y = mean_fd, colour = diagnosis)) +
  70. geom_scatterbox() +
  71. geom_signif(comparisons = list(c("Autism", "Control")),
  72. map_signif_level = TRUE,
  73. test = "t.test",
  74. textsize = 8,
  75. color = "black",
  76. size = 1.3) +
  77. ylab("Mean Framewise Displacement (FD) mm") +
  78. xlab("Diagnosis") + guides(colour = "none") +
  79. ylim(NA, max(filteredPheno$mean_fd) + 0.1) +
  80. theme_minimal() +
  81. theme(
  82. axis.title = element_blank(),
  83. plot.title = element_blank()
  84. ) +
  85. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  86. ggtitle("Mean Framewise displacement of ASD vs. Control")
  87. mean_fd_dx_plot_before
  88. ```
  89. Addressing the increased movement of autism vs TD via matching the subjects from both groups with a similarity of +- 0.05mm.
  90. ```{r}
  91. #factorising the diagnosis and sex variables
  92. filteredPheno$sex = as.factor(filteredPheno$sex)
  93. filteredPheno$diagnosis = as.factor(filteredPheno$diagnosis)
  94. #matching the two groups on FD
  95. m_1 = matchit(formula = diagnosis ~ mean_fd + sex + age_years,
  96. data = filteredPheno,
  97. method = "nearest",
  98. caliper = 0.05)
  99. summary(m_1)
  100. plot(m_1,
  101. type = "density",
  102. which.xs = ~mean_fd,
  103. interactive = FALSE)
  104. ```
  105. ```{r}
  106. # Successful matching; extracting the data
  107. filteredPheno = match_data(m_1)
  108. t.test(data= filteredPheno, mean_fd ~ diagnosis)
  109. t_meanfd = t.test(data = filteredPheno, mean_fd ~ diagnosis)
  110. p_meanfd = t_meanfd$p.value
  111. d_meanfd = round(t_meanfd$statistic / sqrt((as.double(tab[1])*as.double(tab[2]))/(as.double(tab[1])+as.double(tab[2]))),3)
  112. mean_fd_dx_plot_after = ggplot(data = filteredPheno, aes(x = diagnosis, y = mean_fd, colour = diagnosis)) +
  113. geom_scatterbox() +
  114. geom_signif(comparisons = list(c("Autism", "Control")),
  115. map_signif_level = TRUE,
  116. test = "t.test",
  117. textsize = 8,
  118. color = "black",
  119. size = 1.3) +
  120. ylab("Mean Framewise Displacement (FD) mm") +
  121. xlab("Diagnosis") + guides(colour = "none") +
  122. ylim(NA, max(filteredPheno$mean_fd) + 0.1) +
  123. theme_minimal() +
  124. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  125. ggtitle("Mean Framewise displacement of Autism vs. Control")
  126. mean_fd_dx_plot_after
  127. ```
  128. ```{r}
  129. ### Loading turbulence data
  130. #Variable pre-allocation
  131. flattenedData = c()
  132. turbData = list()
  133. allIDs = list()
  134. out_data = list()
  135. counter = 0
  136. names_turb = names(turbData)
  137. names_allIDs = as.character(allIDs$subid)
  138. ### Exclusion of Subjects based on fallible Hilbert-transormation
  139. #For 5 of the subjects, the time-series extraction did not work. The phases matrix is empty. Exclude these from the analysis.
  140. outliers_subid = c(51468, 28793, 50726, 28939, 51193)
  141. for (sub in outliers_subid){
  142. currID = as.character(sub)
  143. currName = paste0(currID,"_turbulence_measures.mat")
  144. matData = readMat(file.path(root, "data","postproc", currName))
  145. matData = matData[["output"]]
  146. matData = data.frame(matData)
  147. matData = matData$X1.1
  148. out_data[[as.character(sub)]] = matData
  149. }
  150. #Looping over the subjects of the phenotypical data to read in Turbulence measures. This step is necessary since the subids in the phenotypical are not matching with all the subids of which fMRI data was available.
  151. for (subject in 1:length(filteredPheno$subid)){
  152. #Extract the current subject-ID
  153. currID = filteredPheno$subid[subject]
  154. currName = paste0(currID,"_turbulence_measures.mat")
  155. #Skip outliers
  156. if (currID %in% outliers_subid){
  157. next}
  158. #If that ID is in the list of subjects on which the turbulence analysis was applied, go on, otherwise, exit
  159. if (currName %in% filenames){
  160. #Reading the Turbulence values of current subject
  161. matData = readMat(file.path(root, "data","postproc", currName))
  162. matData = matData[["output"]]
  163. matData = data.frame(matData)
  164. matData = matData$X1.1
  165. #Deleting the first entry of the Information.Cascade flow (because they are all NAs, due to formal, theoretical reasons)
  166. matData[[6]] = matData[[6]][-1]
  167. #For the information transfer, we still have to subtract the values calculated by the MATLAB script
  168. #from a constant value k (k=2). The reason for that is, to keep the rational consistent with the other measures:
  169. #higher values mean the information travels across longer distances (originally in the MATLAB script
  170. #higher values meant steeper slope which in turn means information travels across smaller distances)
  171. #For all that, see methods of Cruzat et al., 2022)
  172. matData[[4]] = 2 - matData[[4]]
  173. if (length(matData) == 11){
  174. #Compute the mean across time of the KLOP for each parcel
  175. matData$mean_KLOP = apply(matData$LocalKuramoto, c(1,2), FUN = mean)
  176. names(matData)[c(9,12)] = names(matData)[c(12,9)]
  177. matData[c(9, 12)] = matData[c(12, 9)]
  178. }
  179. #Delete these two, because not needed
  180. matData$Phases = NULL
  181. matData$LocalKuramoto = NULL
  182. #Some of the subjects have fallible node turbulence (only extracted for 1000 nodes). Leave them out.
  183. if (dim(matData$Turbulence.Node)[1] == 1054){
  184. counter = counter + 1
  185. allIDs[[length(allIDs)+1]] = currID #Save that ID in a list
  186. #Saving the entry in a new list
  187. turbData[[as.character(currID)]] = matData #List-Variable with all the Turbulence measures for all subjects
  188. }
  189. }
  190. }
  191. ### Performing batch correction to control for differences in scanning sites
  192. # Extracting number of parcels and lambda values from turbulence data
  193. nParcels = dim(matData$Turbulence.Node)[1]
  194. nLambda = dim(matData$Turbulence.Node)[2]
  195. dim_node = nParcels*nLambda
  196. dim_RSN = dim(matData$Turbulence.RSN)[1] * nLambda
  197. dim_output = length(matData)
  198. #Create data.frame for subject IDs
  199. allIDs = unlist(allIDs)
  200. allIDs = data.frame(allIDs)
  201. colnames(allIDs) = "subid"
  202. #Merge the analyzed subjects with the phenotypical data
  203. filteredPheno = merge.data.frame(filteredPheno,allIDs, by = "subid")
  204. #Order it in the same way as my Turbulence matrix
  205. filteredPheno = filteredPheno[match(allIDs$subid, filteredPheno$subid),]
  206. #Factorise the covariates site, sex and diagnosis and numerise mean_fd and age
  207. filteredPheno = filteredPheno %>% mutate(
  208. sex = as.factor(sex),
  209. site = as.factor(site),
  210. diagnosis = as.factor(diagnosis)
  211. )
  212. ### Run Batch Correction
  213. #Pre-allocation
  214. flattenedData = c()
  215. my_lengths = list()
  216. #For each subject, flatten out the vector to prepare for the batch correction
  217. for (sub in 1:length(filteredPheno$subid)){
  218. currVec = c()
  219. for (m in 1:dim_output){
  220. currVec = c(currVec, as.vector(unlist(turbData[[sub]][m])))
  221. }
  222. my_lengths[sub] = length(currVec)
  223. flattenedData = cbind(flattenedData, currVec)
  224. }
  225. #Vector-length of flattened participant data
  226. length_flattened_data = dim(flattenedData)[1]
  227. #Remove Lambdas from flattened data for the batch correction. Add the lambda data later.
  228. lambda_flattened = flattenedData[(length_flattened_data-nLambda+1):length_flattened_data,][,1]
  229. flattenedData = flattenedData[1:(length_flattened_data-nLambda),]
  230. #Batch variable
  231. batch = filteredPheno$site
  232. #Running the batch correction with the ComBat function
  233. model = model.matrix(~ mean_fd + sex + age_years + diagnosis + diagnosis:age_years, data = filteredPheno)
  234. correctedData = ComBat(dat=flattenedData, mod=model, batch=batch, par.prior=TRUE)
  235. #Testing if Batch correction worked with PCA
  236. #Extracting first PC from uncorrected data
  237. pca_uncorrected = prcomp(t(flattenedData), center = TRUE, scale. = TRUE)
  238. first_PC_uncorrected = pca_uncorrected$x[,1]
  239. #Run Anova for uncorrected PCA
  240. anova_model = aov(first_PC_uncorrected ~ batch)
  241. summary(anova_model)
  242. ```
  243. highly significant (p < 0.001)
  244. ```{r}
  245. #Extracting first PC from corrected data
  246. pca_corrected = prcomp(t(correctedData), center = TRUE, scale. = TRUE)
  247. first_PC_corrected = pca_corrected$x[,1]
  248. #Run Anova for corrected PCA
  249. anova_model = aov(first_PC_corrected ~ batch)
  250. summary(anova_model)
  251. ```
  252. Batch correction worked (p = 0.395)
  253. Plotting the results of the Batch correction
  254. ```{r}
  255. #Plotting results of the PCA before Batch correction
  256. PC_data = data.frame(batch, first_PC_corrected, first_PC_uncorrected)
  257. PC_beforeBatchCorrection = ggplot(PC_data, aes(x = batch, y = first_PC_uncorrected, color = batch)) +
  258. geom_scatterbox() +
  259. ylab("PC1") +
  260. xlab("Site") +
  261. guides(colour = "none") +
  262. easy_rotate_x_labels(angle = 45, side = "right") +
  263. ggtitle("Before batch correction") +
  264. theme(
  265. axis.title = element_text(size = 24), # axis labels
  266. axis.text = element_text(size = 18), # x and y axis tick labels
  267. plot.title = element_text(size = 24, hjust = 0.5) # title centered and larger
  268. )
  269. PC_beforeBatchCorrection
  270. ##ggsave(paste0(figures_directory,"/supplementary/PC_before_batch.pdf"), PC_beforeBatchCorrection, width = 16, height = 8)
  271. ```
  272. ```{r}
  273. #And after the batch correction
  274. PC_afterBatchCorrection = ggplot(PC_data, aes(x = batch, y = first_PC_corrected, color = batch)) +
  275. geom_scatterbox() +
  276. ylab("PC1") +
  277. xlab("Site") +
  278. guides(colour = "none") +
  279. easy_rotate_x_labels(angle = 45, side = "right") +
  280. ggtitle("After batch correction") +
  281. theme(
  282. axis.title = element_text(size = 24), # axis labels
  283. axis.text = element_text(size = 18), # x and y axis tick labels
  284. plot.title = element_text(size = 24, hjust = 0.5) # title centered and larger
  285. )
  286. PC_afterBatchCorrection
  287. ##ggsave(paste0(figures_directory,"/supplementary/PC_after_batch.pdf"), PC_afterBatchCorrection, width = 16, height = 8)
  288. ```
  289. ```{r}
  290. ### Reshaping
  291. # Reshaping the batch-corrected data into the different measures
  292. # Pre-allocation
  293. correctedTurbData = list()
  294. turbDataTest = list() #list to test if the dimensions line up with the initial data
  295. intermediate_num = (dim_node+nLambda+dim_RSN)
  296. # Loop over all subjects to save the batch-corrected data
  297. for (k in 1:length(filteredPheno$subid)){
  298. Turbulence = correctedData[1:nLambda,k]
  299. TurbulenceTest = flattenedData[1:nLambda,k]
  300. TurbulenceNode = matrix(correctedData[(nLambda+1):(dim_node+nLambda),k],nrow = nParcels, ncol = nLambda)
  301. TurbulenceNodeTest = matrix(flattenedData[(nLambda+1):(dim_node+nLambda),k],nrow = nParcels, ncol = nLambda)
  302. TurbulenceRSN = matrix(correctedData[(dim_node+nLambda+1):(dim_node+nLambda+dim_RSN),k], nrow = dim_RSN/nLambda, ncol = nLambda)
  303. TurbulenceRSNTest = matrix(flattenedData[(dim_node+nLambda+1):(dim_node+nLambda+dim_RSN),k], nrow = dim_RSN/nLambda, ncol = nLambda)
  304. InformationTransfer = correctedData[(intermediate_num+1):(intermediate_num+nLambda),k]
  305. InformationTransferTest = flattenedData[(intermediate_num+1):(intermediate_num+nLambda),k]
  306. InformationCascade = correctedData[intermediate_num+nLambda+1,k]
  307. InformationCascadeTest = flattenedData[intermediate_num+nLambda+1,k]
  308. InformationCascadeflow = correctedData[(intermediate_num+nLambda+2):(intermediate_num+(2*(nLambda))),k]
  309. InformationCascadeflowTest = flattenedData[(intermediate_num+nLambda+2):(intermediate_num+(2*(nLambda))),k]
  310. GlobalKuramoto = correctedData[intermediate_num+(2*(nLambda)+1),k]
  311. GlobalKuramotoTest = flattenedData[intermediate_num+(2*(nLambda)+1),k]
  312. Metastability = correctedData[intermediate_num+(2*(nLambda)+2),k]
  313. MetastabilityTest = flattenedData[intermediate_num+(2*(nLambda)+2),k]
  314. mean_KLOP = matrix(correctedData[(intermediate_num+(2*(nLambda)+3)):(dim(correctedData)[1]),k], ncol = nParcels, nrow = nLambda)
  315. mean_KLOPTest = matrix(flattenedData[(intermediate_num+(2*(nLambda)+3)):(dim(correctedData)[1]),k], ncol = nParcels, nrow = nLambda)
  316. Lambda = lambda_flattened
  317. LambdaTest = lambda_flattened
  318. subjectData = list(Turbulence,TurbulenceNode,TurbulenceRSN,InformationTransfer,InformationCascade,InformationCascadeflow,GlobalKuramoto,Metastability,mean_KLOP, Lambda)
  319. subjectDataTest = list(TurbulenceTest,TurbulenceNodeTest,TurbulenceRSNTest,InformationTransferTest,InformationCascadeTest,InformationCascadeflowTest,GlobalKuramotoTest,MetastabilityTest, mean_KLOPTest,LambdaTest)
  320. currentID = filteredPheno$subid[k]
  321. correctedTurbData[[as.character(currentID)]] = subjectData
  322. turbDataTest[[as.character(currentID)]] = subjectDataTest
  323. }
  324. #Extracting each measures
  325. amplTurb = t(as.data.frame(lapply(correctedTurbData, function(row) row[[1]])))
  326. nodeTurb = lapply(correctedTurbData, function(row) row[[2]])
  327. rsnTurb = lapply(correctedTurbData, function(row) row[[3]])
  328. transfer = t(as.data.frame(lapply(correctedTurbData, function(row) row[[4]])))
  329. cascade = as.numeric(unlist(lapply(correctedTurbData, function(row) row[[5]])))
  330. cascadeflow = t(as.data.frame(lapply(correctedTurbData, function(row) row[[6]])))
  331. gloKur = as.numeric(unlist(lapply(correctedTurbData, function(row) row[[7]])))
  332. meta = as.numeric(unlist(lapply(correctedTurbData, function(row) row[[8]])))
  333. mean_KLOP = t(as.data.frame(lapply(correctedTurbData, function(row) apply(row[[9]],1, FUN = mean))))
  334. nodeSync = lapply(correctedTurbData, function(row) t(row[[9]]))
  335. subid = as.factor(names(correctedTurbData))
  336. #Defining global datameasures
  337. global_data = data.frame(
  338. global_kuramoto = gloKur,
  339. metastability = meta,
  340. cascade = cascade,
  341. filteredPheno
  342. )
  343. ### Transforming ADOS values into percentage scores ###
  344. # Adding ADOS columns to the data frame
  345. global_data = global_data %>%
  346. mutate(
  347. ados2_socaff_css = NaN,
  348. ados2_rrb_css = NaN,
  349. ados2_total_css = NaN,
  350. ados_comb = NaN,
  351. ados_version = NaN
  352. )
  353. #Loop over subjects to compute the ADOS percentage score
  354. for (i in 1:nrow(global_data)){
  355. if (!is.na(global_data$ados2_total[i]) & !is.na(global_data$ados_module[i])){
  356. if (global_data$ados_module[i] == 4){
  357. denominator = 29
  358. }
  359. else{
  360. denominator = 28}
  361. global_data$ados_comb[i] = (global_data$ados2_total[i]/denominator)*100
  362. global_data$ados_version[i] = "ados"
  363. }
  364. else if(!is.na(global_data$ados_total[i]) & !is.na(global_data$ados_module[i])){
  365. denominator = 32
  366. global_data$ados_comb[i] = (global_data$ados_total[i]/denominator)*100
  367. global_data$ados_version[i] = "ados"}
  368. }
  369. # Filtering for subjects with existing total score
  370. raw_ados = global_data %>% dplyr::filter(!is.na(ados_comb))
  371. raw_ados$ados_version = as.factor(raw_ados$ados_version)
  372. ```
  373. Demographic data
  374. ```{r}
  375. ASD_data = filteredPheno %>% filter(diagnosis == "Autism")
  376. TD_data = filteredPheno %>% filter(diagnosis == "Control")
  377. #Ranges
  378. demo_summary = filteredPheno %>%
  379. filter(diagnosis %in% c("Autism", "Control")) %>%
  380. group_by(diagnosis) %>%
  381. summarise(
  382. age_min = min(age_years, na.rm = TRUE),
  383. age_max = max(age_years, na.rm = TRUE),
  384. age_mean = mean(age_years, na.rm = TRUE),
  385. age_sd = sd(age_years, na.rm = TRUE),
  386. FIQ_min = min(fiq, na.rm = TRUE),
  387. FIQ_max = max(fiq, na.rm = TRUE),
  388. FIQ_mean = mean(fiq, na.rm = TRUE),
  389. FIQ_sd = sd(fiq, na.rm = TRUE),
  390. VIQ_min = min(viq, na.rm = TRUE),
  391. VIQ_max = max(viq, na.rm = TRUE),
  392. VIQ_mean = mean(viq, na.rm = TRUE),
  393. VIQ_sd = sd(viq, na.rm = TRUE),
  394. PIQ_min = min(piq, na.rm = TRUE),
  395. PIQ_max = max(piq, na.rm = TRUE),
  396. PIQ_mean = mean(piq, na.rm = TRUE),
  397. PIQ_sd = sd(piq, na.rm = TRUE)
  398. )
  399. ### --------- age --------- ###
  400. #Histograms
  401. #ASD
  402. asd_age_hist = ggplot(filteredPheno[filteredPheno$diagnosis == "Autism",], aes(x = age_years)) +
  403. geom_histogram(alpha = 0.8, position = "identity", bins = 70, fill = "#ff7f0e", color = "black") +
  404. ylim(c(0,50))+
  405. xlim(c(0,65)) +
  406. theme_minimal() +
  407. theme(
  408. axis.title = element_blank(),
  409. axis.text = element_blank()
  410. )
  411. #TD
  412. td_age_hist = ggplot(filteredPheno[filteredPheno$diagnosis == "Control",], aes(x = age_years)) +
  413. geom_histogram(alpha = 0.8, position = "identity", bins = 70, fill = "#1f77b4", color = "black") +
  414. ylim(c(0,50))+
  415. xlim(c(0,65)) +
  416. theme_minimal() +
  417. theme(
  418. axis.title = element_blank(),
  419. axis.text = element_blank()
  420. )
  421. #Both
  422. comb_age_hist = ggplot(filteredPheno, aes(x = age_years, fill = diagnosis)) +
  423. geom_histogram(alpha = 0.5, position = "identity", bins = 70, color = "black") +
  424. scale_fill_manual(values = c("Autism" = "#ff7f0e", "Control" = "#1f77b4")) +
  425. ylim(c(0,50))+
  426. xlim(c(0,65)) +
  427. theme_minimal() +
  428. theme(
  429. axis.title = element_blank(),
  430. axis.text = element_blank(),
  431. legend.position = "none"
  432. )
  433. ### --- Printing and Saving -- ###
  434. print(td_age_hist)
  435. print(asd_age_hist)
  436. print(comb_age_hist)
  437. #ggsave(paste0(figures_directory, "/supplementary/age/td_age_hist.jpg"), width = 10, height = 6, td_age_hist, dpi = 300)
  438. #ggsave(paste0(figures_directory, "/supplementary/age/asd_age_hist.jpg"),width = 10, height = 6 ,asd_age_hist, dpi = 300)
  439. #ggsave(paste0(figures_directory, "/supplementary/age/comb_age_hist.jpg"),width = 10, height = 6, comb_age_hist, dpi = 300)
  440. ```
  441. Overview of the Turbulence measures in the data list (with dimensions in brackets):
  442. 1: Amplitude Turbulence [10,1], 2: Parcel-level Turbulence [1054,10], 3: RSN Turbulence [8,10]
  443. 4: Information Transfer [10,1], 5: Information Cascade [1], 6: Information Cascadeflow [9,1]
  444. 7: GolbalKuramoto [1], 8: Metastability [1], 9: Node_Synchronization, 10: Lambda values [1,10]
  445. Lambda values range from high to low, so small to large distances (1=0.28, 2=0.25, ... ,10 = 0.01)
  446. 1.0: Statistical analysis of whole-brain summary statistics: Global Synchronization, Metastability
  447. ```{r, warning=FALSE, message=FALSE}
  448. #Variable handling and pre-allocation
  449. counter = 0
  450. global_measures = c("global_kuramoto", "metastability")
  451. axes_labels = c("Global Synchronization", "Metastability")
  452. global_statistics = list()
  453. #Loop over all global measures
  454. for (curr_measure in global_measures){
  455. counter = counter+1
  456. ### TESTS
  457. # 1) Diagnosis as IV
  458. # ANOVAs
  459. diag_mod = lm(formula = global_data[[curr_measure]] ~ mean_fd + sex + age_years + diagnosis + diagnosis:age_years, data = global_data) #create model
  460. diag_ANOVA = anova(diag_mod)
  461. #Compute Cohens D:
  462. #Group sizes
  463. n1 = sum(global_data$diagnosis == "Control")
  464. n2 = sum(global_data$diagnosis == "Autism")
  465. diag_f = diag_ANOVA["diagnosis", "F value"]
  466. diag_d = round(sqrt(diag_f * (n1 + n2) / (n1 * n2)), 3)
  467. diag_eta = c(round(effectsize(diag_ANOVA)$Eta2_partial,3),NA)
  468. # 2) ADOS as IV
  469. #ANOVAs
  470. ados_mod = lm(formula = raw_ados[[curr_measure]] ~ mean_fd + sex + age_years + ados_comb + ados_comb:age_years,
  471. data = raw_ados)
  472. ados_ANOVA = anova(ados_mod)
  473. ados_f = ados_ANOVA["ados_comb", "F value"]
  474. ados_d = round(sqrt(ados_f * (n1 + n2) / (n1 * n2)), 3)
  475. # Correlations of ADOS with measures
  476. # Compute correlation test
  477. curr_corr = cor.test(raw_ados[[curr_measure]], raw_ados$ados_comb)
  478. # Correlations of FD with measures differences between ASD and TD
  479. curr_ASD = global_data %>% filter(diagnosis == "Autism")
  480. curr_TD = global_data %>% filter(diagnosis == "Control")
  481. ASD_corr = cor.test(curr_ASD[[curr_measure]], curr_ASD$mean_fd)
  482. TD_corr = cor.test(curr_TD[[curr_measure]], curr_TD$mean_fd)
  483. curr_paired_r = paired.r(ASD_corr$estimate, TD_corr$estimate, NULL, nrow(curr_ASD), nrow(curr_TD))
  484. curr_paired_p = curr_paired_r$p
  485. curr_paired_z = curr_paired_r$z
  486. curr_paired_stats = data.frame(
  487. ASD_corr$estimate, TD_corr$estimate, curr_paired_z, curr_paired_p)
  488. #Merging output
  489. global_statistics[[curr_measure]] = list(data.frame(diag_ANOVA),
  490. paste("d value diagnosis",diag_d),
  491. data.frame(ados_ANOVA),
  492. paste("d value ados",ados_d),
  493. data.frame(curr_corr$estimate,
  494. curr_corr$p.value,
  495. curr_corr$statistic),
  496. curr_paired_stats)
  497. ### PLOTS ###
  498. # Boxplot of measure dx diagnosis
  499. curr_boxplot = ggplot(data = global_data, aes(x = diagnosis, y = global_data[[curr_measure]], colour = diagnosis)) +
  500. geom_scatterbox() +
  501. ylab(axes_labels[counter]) +
  502. xlab("Diagnosis") +
  503. guides(colour = "none") +
  504. scale_x_discrete(labels = c("Autism" = "Autism", "Control" = "TD"))+
  505. theme_minimal() +
  506. ylim(NA, max(global_data[[curr_measure]])+0.1) +
  507. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  508. theme(
  509. axis.title = element_blank(),
  510. axis.text = element_blank(),
  511. )
  512. #Correlation of the measure dx ados
  513. curr_corr_plot = ggplot(raw_ados, aes(x = ados_comb, y = .data[[curr_measure]])) +
  514. geom_jitter(size = 5) +
  515. geom_smooth(method = "lm", linewidth = 3) +
  516. theme_minimal() +
  517. theme(
  518. axis.title = element_blank(),
  519. axis.text = element_blank(),
  520. )
  521. curr_corr_plot = curr_corr_plot +
  522. theme(plot.margin = margin(0, 15, 0, 0))
  523. # Printing and saving
  524. print(curr_boxplot)
  525. print(curr_corr_plot)
  526. ##ggsave(paste0(figures_directory,"/Figure_3/",curr_measure,".jpg"), curr_boxplot, width = 8, height = 6)
  527. #ggsave(paste0(figures_directory,"/supplementary/S3/",curr_measure,"_ADOS_corr.jpg"), curr_corr_plot, width = 10, height = 6, dpi = 300)
  528. ##ggsave(paste0(figures_directory,"/Figure_7/",curr_measure,".jpg"), curr_boxplot, width = 8, height = 6)
  529. }
  530. #Change the list names
  531. for (c in names(global_statistics)){
  532. names(global_statistics[[c]]) = c("ANOVA_diagnosis", "diagnosis_d", "ANOVA_ados", "ados_d", "correlation_ados")}
  533. ```
  534. 2.0 Statistical analysis of local measures: Scale-specific Synchronization, Amplitude Turbulence, Information Cascadeflow, Information Transfer
  535. ```{r, warning=FALSE, message=FALSE}
  536. #Variable handling and pre-allocation
  537. local_statistics = list()
  538. turbulence_ados_stats = list()
  539. corr_meanFD_summary = list()
  540. local_measures = c("turbulence", "transfer", "cascadeflow", "scale_synchronization")
  541. local_vectors = list(
  542. "turbulence" = unlist(lapply(amplTurb, function(row) row[1])),
  543. "transfer" = unlist(lapply(transfer, function(row) row[1])),
  544. "cascadeflow" = unlist(lapply(cascadeflow, function(row) row[1])),
  545. "scale_synchronization" = unlist(lapply(mean_KLOP, function(row) row[1]))
  546. )
  547. plot_labels = c("Amplitude turbulence", "Information transfer", "Information cascade flow", "Scale-specific synchronization")
  548. counter = 0
  549. curr_measure = "turbulence"
  550. for (curr_measure in local_measures){
  551. counter = counter +1
  552. if (curr_measure == "cascadeflow"){
  553. nLambda = 9
  554. Lambda = lambda_flattened[-1]
  555. } else{
  556. nLambda = 10
  557. Lambda = lambda_flattened
  558. }
  559. #Data initialization
  560. curr_data = data.frame(
  561. subid = as.factor(rep(subid, times = nLambda)),
  562. lambda = as.numeric(rep(Lambda, each = length(global_data$subid))),
  563. measure = local_vectors[[curr_measure]],
  564. diagnosis = factor(rep(global_data$diagnosis, times = nLambda)),
  565. sex = factor(rep(global_data$sex, times = nLambda)),
  566. mean_fd = rep(global_data$mean_fd, times = nLambda),
  567. age_years = rep(global_data$age_years, times = nLambda),
  568. ados = rep(global_data$ados_comb, times = nLambda))
  569. #1) Diagnosis as IV
  570. #ANOVA
  571. diag_mod = lmerTest::lmer(formula = measure ~ mean_fd + sex + age_years + lambda + diagnosis + diagnosis:lambda + diagnosis:age_years + (1+lambda|subid), data = curr_data) #Model initialization
  572. diag_ANOVA = anova(diag_mod, type = 1) #Step-wise Anova
  573. diag_eta = round(effectsize(diag_ANOVA)$Eta2_partial,3)
  574. diag_emm_curr = emmeans(diag_mod, ~ diagnosis | lambda, at = list(lambda = sort(unique(curr_data$lambda)))) #Posthoc tests for each Lambda level
  575. diag_stat = summary(pairs(diag_emm_curr))
  576. curr_lambda = diag_stat$lambda
  577. diag_p = diag_stat$p.value
  578. diag_d = summary(eff_size(diag_emm_curr, sigma = sigma(diag_mod), edf = df.residual(diag_mod))) #Cohens d in linear models is d = mean_1 - mean2 / sigma, where sigma is residual standard deviation
  579. diag_d = diag_d$effect.size
  580. diag_means = summary(diag_emm_curr)$emmean
  581. means_ASD = diag_means[seq(1,length(diag_p)*2, by = 2)]
  582. means_TD = diag_means[seq(2,length(diag_p)*2, by = 2)]
  583. diag_stat_summary = data.frame(
  584. means_ASD = means_ASD,
  585. means_TD = means_TD,
  586. means_diff = means_ASD - means_TD,
  587. p = p.adjust(diag_p, method = "BH"),
  588. d = diag_d,
  589. lambda = curr_lambda
  590. )
  591. #ANOVA results saving
  592. diag_ANOVA_results = cbind(data.frame(diag_ANOVA),
  593. eta = diag_eta)
  594. #2) ADOS as IV
  595. #Filtering data to only autisms with existing ados percentage score
  596. curr_ados_data = curr_data %>% dplyr::filter(!is.na(ados))
  597. ados_mod = lmerTest::lmer(formula = measure ~ mean_fd + sex + age_years + lambda + ados + ados:lambda + ados:age_years + (1+lambda|subid), data = curr_ados_data) #Model initialization
  598. ados_ANOVA = anova(ados_mod, type = 1) #Step-wise Anova
  599. ados_trends = emtrends(ados_mod, ~ lambda, var = "ados",
  600. at = list(lambda = sort(unique(curr_data$lambda))))
  601. ados_stats = summary(ados_trends, infer = TRUE)
  602. # Correlations of measures with ados
  603. curr_ados_data = curr_data %>%
  604. group_by(subid, age_years, diagnosis, ados) %>%
  605. summarise(overall_mean = mean(measure))
  606. curr_ados_data = curr_ados_data %>% filter(!is.na(ados))
  607. curr_ados_corr = cor.test(curr_ados_data$ados, curr_ados_data$overall_mean)
  608. # Saving in data frame
  609. local_statistics[[curr_measure]] = list(
  610. diag_stat_summary,
  611. diag_ANOVA_results,
  612. data.frame(ados_ANOVA),
  613. ados_stats,
  614. data.frame(ados_corr_p = curr_ados_corr$p.value,
  615. ados_corr_r = curr_ados_corr$estimate))
  616. # Investigating age effects
  617. curr_age_data = curr_data %>% group_by(subid, age_years, diagnosis) %>% summarise(overall_mean = mean(measure))
  618. curr_age_data_ASD = curr_age_data %>% filter(diagnosis == "Autism")
  619. curr_age_data_TD = curr_age_data %>% filter(diagnosis == "Control")
  620. ASD_cor_test = cor.test(curr_age_data_ASD$age_years, curr_age_data_ASD$overall_mean)
  621. TD_cor_test = cor.test(curr_age_data_TD$age_years, curr_age_data_TD$overall_mean)
  622. a = paired.r(ASD_cor_test$estimate,TD_cor_test$estimate, NULL, 504,505)
  623. #Investigating effects of meanFD on metrics
  624. curr_meanFD_data = curr_data %>%
  625. group_by(subid, mean_fd, diagnosis) %>%
  626. summarise(overall_mean = mean(measure))
  627. #Correlation meanFD ~ autism
  628. asd_data = curr_meanFD_data %>%
  629. filter(diagnosis == "Autism")
  630. corr_autism_meanFD = cor.test(asd_data$overall_mean, asd_data$mean_fd)
  631. #Correlation meanFD ~ TD
  632. control_data = curr_meanFD_data %>%
  633. filter(diagnosis == "Control")
  634. corr_td_meanFD = cor.test(control_data$overall_mean, control_data$mean_fd)
  635. #Fisher z-test
  636. curr_paired_r = paired.r(corr_autism_meanFD$estimate, corr_td_meanFD$estimate, NULL,
  637. nrow(asd_data), nrow(control_data))
  638. curr_paired_p = curr_paired_r$p
  639. curr_paired_z = curr_paired_r$z
  640. curr_paired_stats = data.frame(
  641. corr_autism_meanFD$estimate, corr_td_meanFD$estimate, curr_paired_z, curr_paired_p)
  642. #Saving
  643. corr_meanFD_summary[[curr_measure]] = curr_paired_stats
  644. ### PLOTS
  645. #Inverting Lambda-levels
  646. curr_data$lambda = as.factor(curr_data$lambda) #Only for the plots!
  647. #curr_data$lambda = factor(curr_data$lambda, levels = rev(levels(curr_data$lambda)))
  648. #Measure dx meanFD
  649. curr_meanFD_plot = ggplot(data = curr_meanFD_data, aes(x = mean_fd, y = overall_mean, colour = diagnosis, group = diagnosis)) +
  650. geom_jitter() +
  651. geom_smooth(method = "lm") +
  652. ylab(plot_labels[counter]) +
  653. xlab("mean framewise displacement") +
  654. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  655. theme_minimal() +
  656. theme(
  657. axis.title = element_text(size = 30),
  658. axis.text = element_text(size = 30),
  659. legend.text = element_blank(),
  660. legend.title = element_blank(),
  661. legend.position = "none")
  662. #Boxplot of measure and Lambda by diagnosis
  663. curr_dx_Lambda_by_diagnosis_boxplot = ggplot(data = curr_data,
  664. aes(x = lambda,
  665. y = measure,
  666. color = diagnosis)) +
  667. geom_jitter(position = position_jitterdodge(jitter.width = 0.5, dodge.width = 1), alpha = 0.5, size = 1.5) +
  668. geom_boxplot(aes(group = interaction(lambda, diagnosis)), colour = "black",fill = NA,position=position_dodge(width=1),outlier.shape = NA, size = 1.5) +
  669. ylab(plot_labels[counter]) +
  670. xlab("Lambda (in 1/mm)") +
  671. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  672. theme_minimal() +
  673. theme(
  674. axis.title = element_text(size = 30),
  675. axis.text = element_text(size = 30),
  676. legend.text = element_blank(),
  677. legend.title = element_blank(),
  678. legend.position = "none")
  679. #Measure dx age
  680. curr_age_plot = ggplot(data = curr_age_data, aes(x = age_years, y = overall_mean, colour = diagnosis, group = diagnosis)) +
  681. geom_jitter(size = 4) +
  682. geom_smooth(method = "lm", linewidth = 4) +
  683. scale_color_manual(values = c("Control" = "#1f77b4", "Autism" = "#ff7f0e")) +
  684. theme_minimal() +
  685. theme(
  686. axis.title = element_blank(),
  687. axis.text = element_blank(),
  688. legend.position = "none")
  689. #Measure dx ADOS
  690. curr_ados_plot = ggplot(data = curr_ados_data, aes(x = ados, y = overall_mean)) +
  691. geom_jitter(size = 5) +
  692. geom_smooth(method = "lm", linewidth = 3) +
  693. theme_minimal() +
  694. theme(
  695. axis.text = element_blank(),
  696. axis.title = element_blank(),
  697. legend.position = "none")
  698. # Correlation interaction for Turbulence
  699. if (curr_measure == "turbulence"){
  700. lambdas_to_plot = c(0.01,0.28)
  701. for (curr_lambda in lambdas_to_plot){
  702. curr_lam_data = curr_data %>% filter(lambda == curr_lambda)
  703. curr_ados_lambda_plot = ggplot(data = curr_lam_data, aes(x = ados, y = measure)) +
  704. geom_jitter(size = 5) +
  705. geom_smooth(method = "lm", linewidth = 3) +
  706. theme_minimal() +
  707. theme(
  708. axis.text = element_blank(),
  709. axis.title = element_blank(),
  710. legend.position = "none")
  711. #Correlation Tests
  712. # Correlations of measures with ados
  713. curr_ados_corr = cor.test(curr_lam_data$ados, curr_lam_data$measure)
  714. turbulence_ados_stats[[paste0(curr_lambda)]] = data.frame(
  715. ados_corr_p = curr_ados_corr$p.value,
  716. ados_corr_r = curr_ados_corr$estimate)
  717. print(curr_ados_lambda_plot)
  718. ##ggsave(paste0(figures_directory,"/supplementary/S3/",curr_measure,"_",curr_lambda,"_ados_corr.jpg"), curr_ados_lambda_plot, height = 6, width = 10, dpi = 300)
  719. }
  720. }
  721. #Printing and saving
  722. print(curr_dx_Lambda_by_diagnosis_boxplot)
  723. print(curr_age_plot)
  724. #print(curr_ados_plot)
  725. #print(curr_meanFD_plot)
  726. ##ggsave(paste0(figures_directory,"/Figure_6/box_",curr_measure,".pdf"), curr_dx_Lambda_by_diagnosis_boxplot, height = 12, width = 16)
  727. ##ggsave(paste0(figures_directory,"/Figure_6/diff_",curr_measure,".pdf"), curr_diff_plot, height = 12, width = 16)
  728. ##ggsave(paste0(figures_directory,"/supplementary/S3/",curr_measure,"_ados_corr.jpg"), curr_ados_plot, height = 6, width = 10, dpi = 300)
  729. #ggsave(paste0(figures_directory,"/supplementary/S5/",curr_measure,"_age_corr.jpg"), curr_age_plot, height = 6, width = 10, dpi = 300)
  730. }
  731. ```
  732. 3. Network-wise analysis
  733. In this part of the analysis, we want to investigate if there are Turbulence patterns of ASD vs. TD in different RSNs.
  734. To do so, we leverage the node-turbulence and RSN turbulence matrices.
  735. 3.1. Node-wise RSN analysis. Starting from individual node turbulence matrices (nNodes*nLambda). Computing average for ASD and TD and calculate the difference.
  736. The highest 10% values of differences are extracted for each lambda. These 100 nodes are then associated to the RSNs.
  737. ```{R, message=FALSE, warning=FALSE}
  738. ### Loading and initializing data
  739. #Mapping from RSN number to label:
  740. rsns = c("VIS", "SOM", "DAN", "VAN", "LIM", "CON", "DMN", "SUB")
  741. rsnStats = list()
  742. rsn_labels = unlist(readMat(file.path(root, "code/labels.mat"))) #Loading RSN-labels of parcellation
  743. schaef_regions = read.table(file.path(root, "code/Schaefer2018_1000Parcels_7Networks_order.txt")) #Load Schaefer region names
  744. tian_regions = read.table(file.path(root, "code/Tian_Subcortex_S4_3T_label.txt")) #Load Tian region names
  745. Lambda = c(0.28,0.25,0.22,0.19,0.16,0.13,0.10,0.07,0.04,0.01)
  746. #Pre-allocation
  747. parcel_indices = list()
  748. parcel_ind_TD_higher = list()
  749. nodeTurb_residuals = array(NA,dim = c(nParcels, nLambda, nrow(filteredPheno)))
  750. # Testing if parcel level turbulence is associated to FD differently between ASD and TD
  751. Autism_data = nodeTurb[which(filteredPheno$diagnosis == "Autism")]
  752. TD_data = nodeTurb[which(filteredPheno$diagnosis == "Control")]
  753. my_ps = data.frame()
  754. my_zs = data.frame()
  755. for (curr_parcel in 1:nParcels){
  756. for (curr_lambda in 1:nLambda){
  757. ### -------- Correlations of parcel-level turbulence with FD -------- ###
  758. #Correlation of parcel turbulence with FD for ASD
  759. currASD_data = drop_na(data.frame(
  760. brain = sapply(Autism_data, function(x) {x[curr_parcel,curr_lambda]}),
  761. fd = filteredPheno[filteredPheno$diagnosis == "Autism",]$mean_fd))
  762. curr_cor_ASD = cor.test(currASD_data$brain,currASD_data$fd)
  763. #Correlation of parcel turbulence with FD for TD
  764. currTD_data = drop_na(data.frame(
  765. brain = sapply(TD_data, function(x) {x[curr_parcel,curr_lambda]}),
  766. fd = filteredPheno[filteredPheno$diagnosis == "Control",]$mean_fd))
  767. curr_cor_TD = cor.test(currTD_data$brain,currTD_data$fd)
  768. # Ztest of difference
  769. curr_paired = paired.r(curr_cor_ASD$estimate, curr_cor_TD$estimate, NULL, nrow(currASD_data), nrow(currTD_data))
  770. my_ps[curr_parcel,curr_lambda] = curr_paired$p
  771. my_zs[curr_parcel,curr_lambda] = curr_paired$z
  772. ### ------ computation of parcel-level turbulence residuals after covariate regression ------ ###
  773. curr_Parcel_Turb_data = drop_na(data.frame(
  774. parcel_turb = sapply(nodeTurb, function(x) {x[curr_parcel,curr_lambda]}),
  775. fd = filteredPheno$mean_fd,
  776. sex = as.factor(filteredPheno$sex),
  777. age = filteredPheno$age_years))
  778. my_model = lm(formula = parcel_turb ~ fd + sex + age, data = curr_Parcel_Turb_data)
  779. curr_residuals = resid(my_model)
  780. nodeTurb_residuals[curr_parcel, curr_lambda,] = curr_residuals
  781. }
  782. }
  783. #Compute FDR
  784. my_ps_fdr = data.frame(matrix(
  785. p.adjust(flatten(my_ps), method = "BH"),
  786. nrow = nParcels,
  787. ncol = length(lambda_flattened)
  788. ))
  789. #write_csv(my_zs, paste0(root,"/code/z_values_meanFD_parcelTurb.csv"))
  790. #write_csv(my_ps_fdr, paste0(root,"/code/p_values_meanFD_parcelTurb.csv"))
  791. ### No significant differences in correlations between groups
  792. #Set data to node Turbulence residuals
  793. nSubjects = dim(nodeTurb_residuals)[3]
  794. nodeTurb_residuals = lapply(
  795. 1:nSubjects,
  796. function(s) nodeTurb_residuals[ , , s]
  797. )
  798. names(nodeTurb_residuals) = names(nodeTurb)
  799. #Extracting masks of ASD and control
  800. ASD_labels = which(filteredPheno$diagnosis == "Autism")
  801. TD_labels = which(filteredPheno$diagnosis == "Control")
  802. ASD_node = nodeTurb_residuals[ASD_labels]
  803. TD_node = nodeTurb_residuals[TD_labels]
  804. parcel_labels = c(unlist(schaef_regions$V2), tian_regions$V1)
  805. #Computing means and effect sizes
  806. ASD_mean = Reduce("+", ASD_node) / length(ASD_labels)
  807. TD_mean = Reduce("+", TD_node) / length(TD_labels)
  808. ASD_3d = simplify2array(ASD_node)
  809. TD_3d = simplify2array(TD_node)
  810. n_asd = length(ASD_node)
  811. n_td = length(TD_node)
  812. #Standard deviation
  813. pooled_sd = sqrt((
  814. apply(ASD_3d, c(1,2), var)*(n_asd-1) +
  815. apply(TD_3d, c(1,2), var)*(n_td-1)
  816. )/(n_asd+n_td-2))
  817. #Save Cohen's D
  818. turb_CohensD = (TD_mean-ASD_mean)/pooled_sd
  819. ### Network-wise analysis and radar plots
  820. #Absolute node-diff
  821. node_diff = abs(TD_mean-ASD_mean)
  822. # Number of parcels to be investigated
  823. n_Top_Parcels = 200
  824. #Extracting top 10% of absolute node-difference for each lamba
  825. ind = apply(node_diff, 2, function(col) {
  826. top = sort(col, decreasing = TRUE)[1:n_Top_Parcels]
  827. col %in% top})
  828. #Save in list
  829. parcel_indices = ind
  830. rsn_ind = apply(ind, 2, function(col) rsn_labels[col]) #Assign to each of these parcels its RSN according to the Schaefer parcellation
  831. counts = apply(rsn_ind, 2, table) #Compute for each Lambda its counts
  832. #Add missing values in the counts with 0:
  833. list_names = sapply(counts, names)
  834. for (i in 1:length(counts)){
  835. for (ii in 1:length(rsns)){
  836. if (!(as.character(ii) %in% list_names[[i]])){
  837. counts[[i]][as.character(ii)] = 0
  838. }
  839. }
  840. to_order = counts[[i]]
  841. counts[[i]] = to_order[order(as.numeric(names(to_order)))]
  842. }
  843. #Inverting the list to save in a data-frame.
  844. #Also, scale each count with the maximum number of parcels of that RSN. Result is a percentage score for each RSN
  845. n_parcels_RSN = as.numeric(table(rsn_labels))
  846. counts_new = matrix(nrow = length(Lambda), ncol = length(rsns))
  847. for (i in 1:length(counts)){
  848. counts_new[i,] = as.numeric(counts[[i]])#/n_parcels_RSN
  849. }
  850. #counts_new = counts_new*(1/max(counts_new))
  851. counts = data.frame(Lambda, counts_new)
  852. colnames(counts) = c("Lambda",rsns)
  853. rownames(counts) = Lambda
  854. ##### Directed analysis, i.e., checking which networks are higher in ASD vs. TD
  855. #Compute directed node-difference
  856. node_direct = TD_mean-ASD_mean
  857. #Checking for each lambda, how many of the values are higher
  858. #preallocation
  859. ind_TD_higher = ind
  860. rsn_diff = 0
  861. for (lam in 1:10){
  862. count = 0
  863. curr_vec = node_direct[,lam]
  864. for (parcel in 1:length(curr_vec)){
  865. ind_TD_higher[parcel,lam] = FALSE
  866. if (curr_vec[parcel] > 0 && ind[parcel,lam]){
  867. count = count+1
  868. ind_TD_higher[parcel,lam] = TRUE
  869. }
  870. }
  871. rsn_diff[lam] = count
  872. }
  873. parcel_ind_TD_higher = ind_TD_higher
  874. ### Plotting
  875. color_vec = colorRampPalette(c("#1f77b4", "black", "#ff7f0e"))(10)
  876. #1) Combined radarplot
  877. combined_radar = ggradar(counts,
  878. axis.label.size = 5,
  879. legend.position = "right",
  880. group.point.size = 4,
  881. group.colours = (color_vec),
  882. group.line.width = 1.2,
  883. values.radar = c(0,max(counts)/2,max(counts)),
  884. base.size = 10,
  885. label.gridline.min = FALSE,
  886. grid.mid = max(counts)/2,
  887. grid.max = max(counts),
  888. legend.text.size = 10,
  889. legend.title = "Lambda",
  890. background.circle.colour = "white",
  891. background.circle.transparency = 0.2) +
  892. theme(
  893. #axis.title = element_text(size = 18), # axis labels
  894. #axis.text = element_text(size = 18), # x and y axis tick labels
  895. #plot.title = element_text(size = 18, hjust = 0.5), # title centered and larger
  896. legend.text = element_blank(), # legend label font size
  897. legend.title = element_blank(),
  898. legend.position = "none")
  899. #2) Barplot which shows the distribution of contribution of autism and TD as a function of lambda
  900. # Adjust rsn_diff so the reference point is 50
  901. dist_data = data.frame(
  902. Lambda = rep(Lambda,2),
  903. values = c((n_Top_Parcels - rsn_diff),rsn_diff),
  904. cohort = rep(c("TD>Autism", "Autism>TD"), each = nLambda)
  905. )
  906. distribution_plot = ggplot(dist_data, aes(x = Lambda, y = values, fill = cohort)) +
  907. geom_bar(stat = "identity") +
  908. scale_fill_manual(values = c("TD>Autism" = "#00CED1", "Autism>TD" = "#A0522D")) +
  909. scale_x_reverse() +
  910. labs(
  911. x = "Lambda (in 1/mm)",
  912. y = "#parcels TD > ASD (in %)",
  913. fill = "Distribution"
  914. ) +
  915. theme_minimal() +
  916. theme(
  917. axis.title = element_blank(),
  918. axis.text = element_blank(),
  919. axis.ticks.x = element_blank(),
  920. axis.ticks.y = element_blank(),
  921. plot.title = element_blank(),
  922. legend.position = "none",
  923. panel.background = element_rect(fill = "white", color = NA),
  924. panel.grid.major.y = element_line(color = "grey75", size = 0.5),
  925. panel.grid.minor.y = element_line(color = "grey75", size = 0.25),
  926. panel.grid.minor.x = element_blank(),
  927. panel.grid.major.x = element_blank()
  928. )
  929. #Printing and saving
  930. print(distribution_plot)
  931. print(combined_radar)
  932. #ggsave(paste0(figures_directory,"/Figure_4/RSN_distribution_",n_Top_Parcels, "parcels.jpg"), distribution_plot, height = 12, width = 16, dpi = 300)
  933. #ggsave(paste0(figures_directory,"/Figure_4/RSN_radar_",n_Top_Parcels, "parcels.jpg"), combined_radar, height = 6, width = 6,dpi = 300)
  934. ```
  935. 3.2 Association of 100 Nodes with biggest differences to Sydnor gradients
  936. ```{R}
  937. #First, we need to compute the rankings of the 1054 parcels. We do this with a python script. The input to the python script are the parcel-level effect sizes of both, the synchronization strength, as well as the turbulence. Here we save these into the directory in which the code of the python script operates.
  938. #Turbulence data
  939. currAutism_data = nodeTurb_residuals[which(filteredPheno$diagnosis == "Autism")]
  940. currAutism_data = data.frame(Reduce("+", currAutism_data) / length(currAutism_data))
  941. currTD_data = nodeTurb_residuals[which(filteredPheno$diagnosis == "Control")]
  942. currTD_data = data.frame(Reduce("+", currTD_data) / length(currTD_data))
  943. curr_d = turb_CohensD
  944. #Save csv files
  945. write_csv(data.frame(currAutism_data), paste0(root,"/data/gradient_analysis/", paste0("parcel_turb","_autism_1054", ".csv")))
  946. write_csv(data.frame(currTD_data), paste0(root,"/data/gradient_analysis/", paste0("parcel_turb","_TD_1054", ".csv")))
  947. write_csv(data.frame(curr_d), paste0(root,"/data/gradient_analysis/", paste0("parcel_turb","_CohensD_1054", ".csv")))
  948. #Run the python script with the reticulate option
  949. use_virtualenv("~/pyenvs/reticulate_arm", required = TRUE)
  950. use_python("/Users/bened/pyenvs/reticulate_arm/bin/python", required = TRUE)
  951. py_run_file(paste0(root, "/code/", "gradient_computations.py"))
  952. ```
  953. Association of parcel-level turbulence levels with S-A and functional gradients
  954. ```{R}
  955. #Loading in data
  956. weighted_sydnor = read.csv(paste0(root, "/data/gradient_analysis/gradients_1054.csv"))
  957. weighted_sydnor = weighted_sydnor[1:1000,c(1:11)]#only cortical regions
  958. names(weighted_sydnor) = c("Anatomical T1w/T2w", "Functional Gradient", "Cortical Expansion", "Allometric Scaling",
  959. "Aerobic Glycolysis", "CBF", "Gene Expression", "NeuroSynth", "Externopyramidization", "Cortical Thickness",
  960. "Sensorimotor-Association Gradient")
  961. plotnames = c("Anatomical_T1wT2w", "Functional_Gradient", "Cortical_Expansion", "Allometric_Scaling",
  962. "Aerobic_Glycolysis", "CBF", "Gene_Expression", "NeuroSynth", "Externopyramidization", "Cortical_Thickness",
  963. "Sensorimotor_Association_Gradient")
  964. #Directions of the gradient for each measure: True from high to low, False from Low to high
  965. gradient_directions = data.frame(
  966. gradient = names(weighted_sydnor),
  967. direction = c(FALSE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, FALSE, TRUE, TRUE)
  968. )
  969. #Since we are doing the comparison of the parcel-level turbulence with the S-A axes in the Schaefer1000 parcellation, we have to compute the parcels with the biggest differences again on that parcellation
  970. rsn_labels = rsn_labels[1:1000] #Change labels to only the first 1000
  971. parcel_labels = c(unlist(schaef_regions$V2))
  972. node_cortical = lapply(nodeTurb_residuals, function(row) row[1:1000,])
  973. ASD_node = node_cortical[ASD_labels]
  974. TD_node = node_cortical[TD_labels]
  975. #Computing means and effect sizes
  976. ASD_mean = Reduce("+", ASD_node) / length(ASD_labels)
  977. TD_mean = Reduce("+", TD_node) / length(TD_labels)
  978. ASD_3d = simplify2array(ASD_node)
  979. TD_3d = simplify2array(TD_node)
  980. n_asd = length(ASD_node)
  981. n_td = length(TD_node)
  982. pooled_sd = sqrt((
  983. apply(ASD_3d, c(1,2), var)*(n_asd-1) +
  984. apply(TD_3d, c(1,2), var)*(n_td-1)
  985. )/(n_asd+n_td-2))
  986. #Save Cohen's D
  987. turb_CohensD = (TD_mean-ASD_mean)/pooled_sd
  988. ### Network-wise analysis and radar plots
  989. #Absolute node-diff
  990. node_diff = abs(TD_mean-ASD_mean)
  991. #Extracting top 10% of absolute node-difference for each lamba
  992. ind = apply(node_diff, 2, function(col) {
  993. top = sort(col, decreasing = TRUE)[1:100]
  994. col %in% top})
  995. parcel_indices = ind
  996. ### Directed analysis
  997. #Compute directed node-difference
  998. node_direct = TD_mean-ASD_mean
  999. #Checking for each lambda, how many of the values are higher
  1000. #preallocation
  1001. ind_TD_higher = ind
  1002. rsn_diff = 0
  1003. for (lam in 1:10){
  1004. count = 0
  1005. curr_vec = node_direct[,lam]
  1006. for (parcel in 1:length(curr_vec)){
  1007. ind_TD_higher[parcel,lam] = FALSE
  1008. if (curr_vec[parcel] > 0 && ind[parcel,lam]){
  1009. count = count+1
  1010. ind_TD_higher[parcel,lam] = TRUE
  1011. }
  1012. }
  1013. rsn_diff[lam] = count
  1014. }
  1015. parcel_ind_TD_higher = ind_TD_higher
  1016. #### Assign to each of the 100 highest nodes a label, if TD is higher -> True
  1017. my_parcels = matrix(sapply(1:ncol(ind), function(j) {
  1018. parcel_ind_TD_higher[parcel_indices[, j], j]
  1019. }), nrow = 100, ncol = 10)
  1020. gradient_mod_comp = list()
  1021. ### Statistical analysis. Looping through the (selected) gradients to statistically test which model fits best
  1022. for (metric in c(2,11)){
  1023. #Pre-allocation
  1024. curr_csv = weighted_sydnor
  1025. data_sensory_assoc = data.frame()
  1026. #Loading data for that specific gradient
  1027. for (i in 1:10){
  1028. curr_data = data.frame(
  1029. score = curr_csv[[metric]][parcel_indices[,i]],
  1030. cohens_d = turb_CohensD[parcel_indices[,i],i],
  1031. lambda = as.factor(rep(lambda_flattened[i],100)),
  1032. parcel = c(1:1000)[parcel_indices[,i]])
  1033. curr_data$group = as.vector(my_parcels[,i])
  1034. data_sensory_assoc = rbind(data_sensory_assoc, curr_data)
  1035. }
  1036. #Grouping by lambda
  1037. data_sensory_assoc = data_sensory_assoc %>%
  1038. group_by(lambda)
  1039. data_sensory_assoc$lambda = as.numeric(as.character(data_sensory_assoc$lambda))
  1040. # Model Fitting
  1041. # 1) Logistic models (modelling the sigmoidal shape)
  1042. # Selection/Computation of initial values for upper, lower asymptode, slope and inflection point
  1043. asymp_up_start = max(data_sensory_assoc$score) # upper asymptode
  1044. asymp_low_start = min(data_sensory_assoc$score) # lower asymptode
  1045. x0_start = mean(range(data_sensory_assoc$lambda)) # inflection point
  1046. k_start = 1 # slope of inflection. negative for decreasing shape
  1047. # Fit 4-parameter logistic model
  1048. logistic_model_4 = nlsLM(
  1049. formula = score ~ asymp_low + (asymp_up - asymp_low) / (1 + exp(k * (lambda - x0))),
  1050. data = data_sensory_assoc,
  1051. start = list(
  1052. asymp_up = asymp_up_start,
  1053. asymp_low = asymp_low_start,
  1054. k = k_start,
  1055. x0 = x0_start
  1056. ),
  1057. trace = TRUE
  1058. )
  1059. print(summary(logistic_model_4))
  1060. #Fitted values for logistic models
  1061. data_sensory_assoc$fitted_log4 = predict(logistic_model_4)
  1062. # 2) Linear Model
  1063. linear_model = lm(score ~ lambda, data = data_sensory_assoc)
  1064. data_sensory_assoc$fitted_linmod = predict(linear_model)
  1065. # 3) Polynomial model
  1066. poly_model = lm(score ~ stats::poly(lambda, 3, raw = TRUE), data = data_sensory_assoc)
  1067. data_sensory_assoc$fitted_poly = predict(poly_model)
  1068. #Comparing the models
  1069. #RSS: raw Model fit., AIC: balance between fit and complexity, BIC: penalizes model complexity more
  1070. c_log_4 = c(sum(residuals(logistic_model_4)^2), AIC(logistic_model_4), BIC(logistic_model_4))
  1071. c_lin = c(sum(residuals(linear_model)^2), AIC(linear_model), BIC(linear_model))
  1072. c_poly = c(sum(residuals(poly_model)^2), AIC(poly_model), BIC(poly_model))
  1073. model_comp = data.frame(
  1074. linear = c_lin,
  1075. logistic_4 = c_log_4,
  1076. polynomial = c_poly
  1077. )
  1078. rownames(model_comp) = c("RSS", "AIC", "BIC")
  1079. gradient_mod_comp[["measures"]] = rownames(model_comp)
  1080. gradient_mod_comp[[names(weighted_sydnor)[metric]]] = model_comp
  1081. gradient_mod_comp[[paste0(names(weighted_sydnor)[metric],"_", "models")]] = list(linear_model,poly_model,logistic_model_4)
  1082. ### Plotting
  1083. # 1 is Control, 0 is TD
  1084. data_sensory_assoc$group = factor(data_sensory_assoc$group,
  1085. levels = c(0, 1),
  1086. labels = c("Autism", "Control"))
  1087. #Compute median for each lambda for plot
  1088. my_medians = data_sensory_assoc %>%
  1089. group_by(lambda) %>%
  1090. summarise(median_score = median(score))
  1091. my_medians$lambda = as.numeric(my_medians$lambda)
  1092. data_sensory_assoc$lambda = as.factor(data_sensory_assoc$lambda)
  1093. data_sensory_assoc$lambda = factor(data_sensory_assoc$lambda, levels = rev(levels(data_sensory_assoc$lambda)))
  1094. #Direction of gradient for that specific metric
  1095. if (gradient_directions$direction[metric]){
  1096. colors = c("goldenrod1","white","#6f1282" )
  1097. } else{
  1098. colors = c("#6f1282","white", "goldenrod1")}
  1099. #Plotting the association of node-turb with the gradient scores for each lambda
  1100. curr_plot = ggplot(aes(x = lambda, y = score), data = data_sensory_assoc) +
  1101. geom_jitter(aes(fill = score, size = cohens_d),
  1102. shape = 21, stroke = 2, colour = "black", width = 0.1, show.legend = c(fill = TRUE, size = FALSE)) +
  1103. labs(size = "Cohen's d") +
  1104. scale_fill_gradientn(colors = colors, guide = guide_colorbar(barwidth = 1.5, barheight = 10)) +
  1105. scale_size_continuous(range = c(-4,10)) +
  1106. geom_boxplot(aes(x = lambda, y = score), size = 1.5, colour = "black", fill = NA,
  1107. position = position_dodge(width = 1), outlier.shape = NA) +
  1108. ylab(names(weighted_sydnor)[metric]) +
  1109. xlab("Lambda (in 1/mm)") +
  1110. theme_minimal() +
  1111. theme(
  1112. axis.title = element_blank(),
  1113. axis.text = element_text(size = 30),
  1114. plot.title = element_blank(),
  1115. axis.title.x = element_blank(),
  1116. axis.text.x = element_blank(),
  1117. axis.ticks.x = element_blank(),
  1118. legend.title = element_blank(),
  1119. legend.text = element_text(size = 30)
  1120. ) +
  1121. geom_line(data = data_sensory_assoc, aes(x = lambda, y = fitted_log4, group = 1), color = "black", linetype = "solid", linewidth = 4)
  1122. print(curr_plot)
  1123. #ggsave(paste0(figures_directory,"/Figure_5/",names(weighted_sydnor)[metric],"_S_SA.jpg"), curr_plot, width = 13.4, height = 8.4, dpi = 300)
  1124. }
  1125. ```
  1126. 3.3 Printing the association of Node-Turbulence to Sydnor gradients
  1127. ```{R}
  1128. #Loading data. The computation of the effect sizes of the 400 parcels has been done with the above python script and is only imported here. The matrices have the size (400x10), where each row corresponds to a parcel and each column to a Lambda value. The matrices are filled with Cohen's d effect sizes (TD-autism)
  1129. SA_data_path = paste0(root,"/data/gradient_analysis/")
  1130. #Loading the converted parcel-level data (for Schaefer400).
  1131. parcel_names = c("parcel_turb_autism_s400", "parcel_turb_TD_s400", "parcel_turb_cohensD_s400")
  1132. parcel_data = list()
  1133. for (currName in parcel_names){
  1134. parcel_data[[currName]] = data.frame(read.csv(paste0(SA_data_path,currName,".csv")))
  1135. }
  1136. # Load schafer atlas
  1137. data("schaefer17_400")
  1138. df = schaefer17_400$data
  1139. my_labs = c(schaefer17_400_3d$ggseg_3d[[1]]$label[-1], schaefer17_400_3d$ggseg_3d[[2]]$label[-1])
  1140. sens_assoc = read_csv(paste0(root,"/code/Sensorimotor_Association_Axis_AverageRanks.csv"))
  1141. brainmaps_gradients = read_csv(paste0(root,"/code","/brainmaps_schaefer.csv"))
  1142. #Preallocation
  1143. curr_lambda_plots = list()
  1144. for (currLam in 1:10){
  1145. #plot init
  1146. curr_data = data.frame(
  1147. dat = parcel_data$parcel_turb_cohensD_s400[,currLam])
  1148. df_gradients = cbind(curr_data, brainmaps_gradients, sens_assoc)
  1149. # Step 1: Filter atlas to labels in my_labs
  1150. df_filtered = df %>%
  1151. dplyr::filter(label %in% my_labs)
  1152. # Step 2: Match each label with its corresponding value
  1153. df_ordered = df_filtered %>%
  1154. left_join(df_gradients, by = "label")
  1155. # Step 3: rearrange the atlas
  1156. schaefer17_400$data = df_ordered
  1157. colors = c("goldenrod1","white","#6f1282" )
  1158. #Adjust color scale
  1159. # Map to quantiles for smoother gradient
  1160. probs = seq(0, 1, length.out = 100)
  1161. quant_vals = quantile(df_ordered$dat, probs, na.rm = TRUE)
  1162. # curr_plot
  1163. curr_plot = ggseg(
  1164. atlas = schaefer17_400,
  1165. mapping = aes(fill = dat),
  1166. color = "black",
  1167. size = 0.3
  1168. ) +
  1169. scale_fill_gradientn(
  1170. colors = colors,
  1171. values = scales::rescale(quant_vals, from = range(quant_vals)),
  1172. limits = range(quant_vals),
  1173. oob = scales::squish
  1174. ) +
  1175. theme_void() +
  1176. theme(
  1177. legend.title = element_blank(),
  1178. legend.text = element_blank(),
  1179. legend.key.height = unit(0.4, "cm"),
  1180. legend.key.width = unit(0.4, "cm"),
  1181. legend.position = "none"
  1182. )
  1183. #Add to list
  1184. curr_lambda_plots[[currLam]] = curr_plot
  1185. }
  1186. # S–A gradient
  1187. SA_plot = ggseg(
  1188. atlas = schaefer17_400,
  1189. mapping = aes(fill = finalrank.wholebrain),
  1190. color = "black",
  1191. size = 0.3) +
  1192. scale_fill_gradientn(colors = colors, values = c(0, 0.5, 1)) +
  1193. theme_void() +
  1194. theme(legend.position = "none")
  1195. #Functional gradient
  1196. FUNC_plot = ggseg(
  1197. atlas = schaefer17_400,
  1198. mapping = aes(fill = G1.fMRI),
  1199. color = "black",
  1200. size = 0.3) +
  1201. scale_fill_gradientn(colors = colors, values = c(0, 0.5, 1)) +
  1202. theme_void() +
  1203. theme(legend.position = "none")
  1204. #Plotting
  1205. plot_selected_lambdas = curr_lambda_plots[[1]]/curr_lambda_plots[[4]]/curr_lambda_plots[[7]]/curr_lambda_plots[[10]]/SA_plot/FUNC_plot
  1206. print(plot_selected_lambdas)
  1207. #ggsave(paste0(figures_directory,"/Figure_5/parcel_turb_S400.jpg"), plot_selected_lambdas, width = 16, height = 16, dpi = 300)
  1208. #Heatmaps of correlation values for SA and func gradient with parcel-level turbulence
  1209. #load rho values and p-values
  1210. corr_results = read.csv(paste0(root, "/data/gradient_analysis/corr_results.csv"))
  1211. #DF for plotting
  1212. df_SA_heatmap = data.frame(
  1213. Variable = rep(factor(Lambda), 2),
  1214. rho = round(c(corr_results$rho_SA, corr_results$rho_func),2),
  1215. neglog10p = c(-log10(corr_results$p_SA), -log10(corr_results$p_func)),
  1216. x = rep(c(0,0.6), each = 10)
  1217. )
  1218. #Create the colormap
  1219. SA_heatmaps = ggplot(df_SA_heatmap, aes(x = x, y = Variable, fill = neglog10p)) +
  1220. geom_tile(color = "white", width = 0.6) +
  1221. geom_text(aes(label = sprintf("%.2f", rho)), size = 10) +
  1222. scale_fill_distiller(palette = "OrRd", direction = 1) +
  1223. scale_x_discrete(expand = c(0, 0.1)) +
  1224. labs(
  1225. x = "",
  1226. y = "",
  1227. fill = "-log10(p)"
  1228. ) +
  1229. theme_minimal() +
  1230. theme(
  1231. axis.text.x = element_blank(),
  1232. axis.ticks.x = element_blank(),
  1233. axis.text.y = element_blank(),
  1234. axis.ticks.y = element_blank(),
  1235. panel.grid = element_blank(),
  1236. legend.position = "none",
  1237. plot.margin = margin(5, 5, 5, 5)
  1238. ) +
  1239. coord_fixed(ratio = 0.25)
  1240. print(SA_heatmaps)
  1241. ##ggsave(paste0(figures_directory,"/Figure_5/SA_heatmaps.jpg"), SA_heatmaps, dpi = 300)
  1242. ```

Turbulence_postproc_analysis.Rmd at commit 15de1d4, no license · at the source

Overview

Authors: Benedikt P Fuhr1,2, Yonatan Sanz Perl3,4, Ines Severino1,2, Morten L Kringelbach5,6,7,8, Gustavo Deco3,4,6, Henricus G Ruhé9,10, Michael V Lombardo1
  1. Laboratory for Autism and Neurodevelopmental Disorders, Center for Neuroscience and Cognitive Systems, Istituto Italiano di Tecnologia, Rovereto, Italy
  2. Center for Mind/Brain Sciences, University of Trento, Rovereto, Italy
  3. Center for Brain and Cognition, Computational Neuroscience Group, Faculty of Medicine and Life Sciences, Universitat Pompeu Fabra, Barcelona, Spain
  4. Institució Catalana de la Recerca i Estudis Avançats (ICREA), Barcelona, Spain
  5. Centre for Eudaimonia and Human Flourishing, Linacre College, University of Oxford, Oxford, United Kingdom
  6. International Centre for Flourishing, Universities of Oxford (United Kingdom), Aarhus (Denmark), and Pompeu Fabra (Spain)
  7. Department of Clinical Medicine, Aarhus University, Aarhus, Denmark
  8. Department of Psychiatry, University of Oxford, Oxford, United Kingdom
  9. Department of Psychiatry, Radboudumc, Nijmegen, the Netherlands
  10. Donders Institute for Brain, Cognition and Behavior, Radboud University, Nijmegen, the Netherlands
Journal: Biological psychiatry global open science, volume 6, issue 5, article 100787
Dates: received 20 March 2026; accepted 26 June 2026; published online 8 July 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1016/j.bpsgos.2026.100787 · PMID 42602560 · PMCID PMC13475223 · OpenAlex W4417290912
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: fMRI (modality), autism (population), systems (subfield)
Methods: Statistics, Spectral & time-frequency, fMRI & imaging
Keywords: Autism, Brain dynamics, Cortical gradients, Functional connectivity, Imaging, Resting-state fMRI
Topic: Functional Brain Connectivity Studies (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: European Research Council
Citations: not cited yet (Europe PMC); 65 references in the paper

Abstract

Background: Prevailing theories propose that autism is characterized by local cortical overconnectivity and long-range underconnectivity, but empirical evidence remains mixed.

Methods: Here, we applied the turbulence dynamics framework to the ABIDE (Autism Brain Imaging Data Exchange) dataset (N = 1009) to examine how functional synchronization profiles dynamically change over time and across the cortex over different spatial scales.

Results: Autistic individuals showed increased short-range and reduced long-range functional synchronization variability over time, as well as reduced synchronization strength across all spatial scales. Synchronization also decayed more rapidly with distance and exerted weaker influence across scales in autism. These distance-specific alterations suggest that local hyperconnectivity may be associated with turbulent synchronization dynamics that fail to propagate coherently across the cortex, resulting in an overly rigid brain organization at longer distances. Mapping these effects onto the sensorimotor–association cortical gradient revealed increased variability in sensorimotor regions and decreased variability in the association cortex.

Conclusions: Together, we found evidence of disturbances in functional synchronization dynamics at different spatial scales and along hierarchical brain gradients in autistic individuals. These results consolidate ideas about dynamic functional connectomic organization in autism and situate these alterations along hierarchical brain gradients that are closely linked to neurodevelopmental 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 7 matches between paragraphs and lines of code.

gitlab.iit.it/bmp006-public/land_iit/turbulence_abide

License: none: the authors keep all their rights
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 15de1d443f70190d86a707b7ff8543f64b2f26b3, 21 March 2026
Languages: Python (6), MATLAB (3), Shell (3), Jupyter (2), R (1)
Size: 2,032 files, 15 scripts
Software Heritage: not archived
Found in: the acknowledgements
Holds: README, environment (requirements.txt), 2 notebooks
Not found: license file, CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (5 files), pandas (5 files), AFNI (3 files), BrainSpace (3 files), SciPy (3 files), Statistics and Machine Learning Toolbox (2 files), Matplotlib (2 files), FSL (1 file), lmerTest (1 file), Optimization Toolbox (1 file), Signal Processing Toolbox (1 file), NiBabel (1 file), tidyverse (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
16 files

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;
  • 15 scripts, each with its path and the digest of its content;
  • 7 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.

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 2, 28 September 2026

  • Authors: added Benedikt P Fuhr (0009-0005-5879-9198); Michael V Lombardo (0000-0001-6780-8619); removed Benedikt P Fuhr; Michael V Lombardo

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 6 keywords, 1 funder, 62 references.

Cite

This paper

Fuhr, B. P., Perl, Y. S., Severino, I., Kringelbach, M. L., Deco, G., Ruhé, H. G., & Lombardo, M. V. (2026). Dynamic Functional Synchronization Profiles in Autism Differ by Spatial Scale and Along Hierarchical Cortical Gradients. Biological psychiatry global open science, 6(5), 100787. https://doi.org/10.1016/j.bpsgos.2026.100787

BibTeX

@article{fuhr2026dynamic,
author = {Fuhr, Benedikt P and Perl, Yonatan Sanz and Severino, Ines and Kringelbach, Morten L and Deco, Gustavo and Ruhé, Henricus G and Lombardo, Michael V},
title = {{Dynamic Functional Synchronization Profiles in Autism Differ by Spatial Scale and Along Hierarchical Cortical Gradients}},
journal = {Biological psychiatry global open science},
year = {2026},
month = jul,
volume = {6},
number = {5},
pages = {100787},
publisher = {Elsevier},
issn = {2667-1743},
doi = {10.1016/j.bpsgos.2026.100787},
url = {https://doi.org/10.1016/j.bpsgos.2026.100787},
pmid = {42602560},
pmcid = {PMC13475223}
}

RIS

TY - JOUR
AU - Fuhr, Benedikt P
AU - Perl, Yonatan Sanz
AU - Severino, Ines
AU - Kringelbach, Morten L
AU - Deco, Gustavo
AU - Ruhé, Henricus G
AU - Lombardo, Michael V
TI - Dynamic Functional Synchronization Profiles in Autism Differ by Spatial Scale and Along Hierarchical Cortical Gradients
T2 - Biological psychiatry global open science
J2 - Biol Psychiatry Glob Open Sci
PY - 2026
DA - 2026/07/08
VL - 6
IS - 5
SP - 100787
SN - 2667-1743
PB - Elsevier
DO - 10.1016/j.bpsgos.2026.100787
UR - https://doi.org/10.1016/j.bpsgos.2026.100787
LA - en
ER -

CSL-JSON

{
"id": "10.1016/j.bpsgos.2026.100787",
"type": "article-journal",
"title": "Dynamic Functional Synchronization Profiles in Autism Differ by Spatial Scale and Along Hierarchical Cortical Gradients",
"container-title": "Biological psychiatry global open science",
"author": [
{
"family": "Fuhr",
"given": "Benedikt P"
},
{
"family": "Perl",
"given": "Yonatan Sanz"
},
{
"family": "Severino",
"given": "Ines"
},
{
"family": "Kringelbach",
"given": "Morten L"
},
{
"family": "Deco",
"given": "Gustavo"
},
{
"family": "Ruhé",
"given": "Henricus G"
},
{
"family": "Lombardo",
"given": "Michael V"
}
],
"container-title-short": "Biol Psychiatry Glob Open Sci",
"volume": "6",
"issue": "5",
"page": "100787",
"DOI": "10.1016/j.bpsgos.2026.100787",
"PMID": "42602560",
"PMCID": "PMC13475223",
"ISSN": "2667-1743",
"publisher": "Elsevier",
"URL": "https://doi.org/10.1016/j.bpsgos.2026.100787",
"language": "en",
"issued": {
"date-parts": [
[
2026,
7,
8
]
]
}
}

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.1038/s41593-026-02287-z [code]
Autism subtypes identified using cross-species functional connectivity analyses.
Journal: Nature neuroscience
In common: AFNI, FSL, NiBabel, 2 other tools, autism, fMRI, 12 references, author Michael V Lombardo
[2] doi:10.1038/s41467-026-76011-7 [code]
Human cortex organizes dynamic co-fluctuations along the sensorimotor-association axis.
Journal: Nature communications
In common: BrainSpace, AFNI, FSL, 7 other tools, systems, 6 references
[3] doi:10.1038/s42003-026-10276-y [code]
The cellular correlates and adolescent reorganisation of cortical myelination networks in the common marmoset.
Journal: Communications biology
In common: BrainSpace, AFNI, Optimization Toolbox, 8 other tools, 4 references
[4] doi:10.1038/s41531-026-01354-3 [code]
Neuromodulation-induced normalization of cortical metastable dynamics signatures in Parkinson's disease.
Journal: NPJ Parkinson's disease
In common: BrainSpace, Signal Processing Toolbox, NiBabel, 5 other tools, 8 references
[5] doi:10.1038/s41467-026-73668-y [code]
Convergent and divergent brain-cognition development in early adolescence.
Journal: Nature communications
In common: AFNI, FSL, Signal Processing Toolbox, 7 other tools, fMRI, 6 references
[6] doi:10.1038/s41467-026-71270-w [code]
Spatiotemporal dynamics of the human cortical functional hierarchy across the lifespan.
Journal: Nature communications
In common: BrainSpace, FSL, NiBabel, 5 other tools, fMRI, 6 references
[7] doi:10.64898/2026.03.12.710517 [code]
Cortical excitability inversely modulates fMRI connectivity via low-frequency neuronal coupling
Journal: bioRxiv (preprint)
In common: AFNI, Optimization Toolbox, FSL, 6 other tools, fMRI, systems, 3 references
[8] doi:10.1038/s41586-026-10631-3 [code]
A prognostic human brain network for diffuse midline glioma.
Journal: Nature
In common: BrainSpace, Optimization Toolbox, FSL, 7 other tools, 3 references
[9] doi:10.1038/s41593-026-02205-3 [code]
Competitive interactions shape mammalian brain network dynamics and computation.
Journal: Nature neuroscience
In common: FSL, Signal Processing Toolbox, NiBabel, 5 other tools, 5 references
[10] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: BrainSpace, NiBabel, Statistics and Machine Learning Toolbox, 4 other tools, 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.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

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.