OSCR

Cerebral Cortical Structural Variation and General Cognitive Ability: Evidence From Mendelian Randomization.

Code ↔ Paper

1 match 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 1 match
  1. [1] § Materials and Methods › Mendelian Randomization ↔ R/gsmr.R, lines 392–489 · score 0.59 · Mendelian Randomization, genome wide association, Linkage, GWAS, GSMR, instrumental

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 · 542 lines · 26 KB · no license · 1 match

  1. # gsmr: A tool for GSMR and HEIDI analysis
  2. # The gsmr package Perform Generalized Summary-data-based Mendelian Randomization analysis (GSMR)
  3. # and HEterogeneity In Dependent Instruments analysis to remove pleiotropic outliers (HEIDI-outlier).
  4. # @author Zhihong Zhu <[email hidden]>
  5. # @author Zhili Zheng <[email hidden]>
  6. # @author Futao Zhang <[email hidden]>
  7. # @author Jian Yang <[email hidden]>
  8. eps = 1e-6;
  9. # ************************************************** #
  10. # check data is missing or not #
  11. # ************************************************** #
  12. check_element <- function(vals, argue) {
  13. vals = vals[which(is.finite(vals))]
  14. if(length(vals)==0) {
  15. stop("None of the ", argue, " is found. Please check.")
  16. }
  17. }
  18. # ************************************************** #
  19. # variance-covariane matrix of bXY #
  20. # ************************************************** #
  21. cov_bXY <- function(bzx, bzx_se, bzy, bzy_se, ldrho) {
  22. bXY = bzy / bzx
  23. zscoreZX = bzx / bzx_se
  24. nsnp = dim(ldrho)[1]
  25. covbXY = diag(nsnp)
  26. if(nsnp>1) {
  27. zszxij = zscoreZX%*%t(zscoreZX)
  28. bxyij = bXY%*%t(bXY)
  29. sezyij = bzy_se%*%t(bzy_se)
  30. bzxij = bzx%*%t(bzx)
  31. covbXY = ldrho*sezyij/bzxij + ldrho*bxyij/zszxij
  32. }
  33. return(covbXY)
  34. }
  35. # ************************************************** #
  36. # HEIDI test #
  37. # ************************************************** #
  38. #' @importFrom survey pchisqsum
  39. heidi <- function(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho,
  40. heidi_thresh = pchisq(10, 1, lower.tail=F),
  41. nSNPs_thresh=10, maxid=integer(0) ) {
  42. remain_index = seq(1, length(bzx))
  43. m = length(remain_index)
  44. # recaculate the zscore_ZX for vector has been changed
  45. zscore_ZX = bzx / bzx_se
  46. bXY = bzy / bzx
  47. seSMR = sqrt( (bzy_se^2*bzx^2 + bzx_se^2*bzy^2) / bzx^4 )
  48. # remap the maxid according to filtered data
  49. maxid = which(remain_index==maxid)
  50. if(length(maxid) != 1) {
  51. stop("The top SNP for the HEIDI analysis is missing.")
  52. }
  53. # diff = bXY_top - bXY_-i
  54. dev = bXY[maxid] - bXY[-maxid]
  55. # v matrix
  56. covbXY = cov_bXY(bzx, bzx_se, bzy, bzy_se, ldrho)
  57. tmp1 = diag(covbXY)[maxid]
  58. tmp2 = covbXY[-maxid, -maxid]
  59. tmp3 = covbXY[maxid, -maxid]
  60. vdev = tmp1 + tmp2 - tmp3
  61. vdev = t(t(vdev) - tmp3)
  62. diag(vdev) = diag(vdev) + eps
  63. # variance of diff
  64. vardev = diag(covbXY)[-maxid] + tmp1 - 2*tmp3
  65. vardev = vardev + eps
  66. chisq_dev = dev^2 / vardev
  67. if(m>2) {
  68. # correlation matrix of diff
  69. corr_dev = diag(m-1)
  70. # more than 2 instruments
  71. for( i in 1 : (m-2) ) {
  72. for( j in (i+1) : (m-1) ) {
  73. corr_dev[i,j] = corr_dev[j,i] =
  74. vdev[i,j] / sqrt(vdev[i,i]*vdev[j,j])
  75. }
  76. }
  77. # estimate the p value
  78. lambda = eigen(corr_dev, symmetric=TRUE, only.values=TRUE)$values
  79. t = length(lambda)
  80. pHet = pchisqsum(sum(chisq_dev)+eps, df=rep(1,t), a=lambda, method="sadd", lower.tail=F)
  81. } else {
  82. pHet = pchisq(chisq_dev, 1, lower.tail=F)
  83. }
  84. return(list(pheidi=pHet, nsnps=m, used_index=remain_index))
  85. }
  86. # ************************************************** #
  87. # Iterations for HEIDI-ouliter #
  88. # ************************************************** #
  89. heidi_outlier_iter <- function(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, gwas_thresh, heidi_thresh, remain_index) {
  90. # remove pleiotropic instruments
  91. m = length(remain_index)
  92. # grab reference instrument
  93. bxy = bzy/bzx
  94. bxy_q = quantile(bxy, probs = seq(0, 1, 0.2))
  95. min_bxy = as.numeric(bxy_q[3]); max_bxy = as.numeric(bxy_q[4]);
  96. slctindx = which(bxy <= max_bxy & bxy >= min_bxy)
  97. if(length(slctindx)==0) {
  98. stop("The top SNP for the HEIDI-outlier analysis is missing. None SNPs are in the third quintile of the distribution of bxy.");
  99. }
  100. refid = slctindx[which.min(bzx_pval[slctindx])]
  101. pheidi = as.numeric()
  102. for( i in 1 : m ) {
  103. if( i==refid ) next
  104. heidi_result = heidi(bzx[c(refid,i)], bzx_se[c(refid,i)], bzx_pval[c(refid,i)],
  105. bzy[c(refid,i)], bzy_se[c(refid,i)],
  106. ldrho[c(refid,i), c(refid,i)],
  107. gwas_thresh, 2, 1)
  108. pheidi[i] = as.numeric(heidi_result$pheidi)
  109. }
  110. remain_index = remain_index[sort(c(refid,which(pheidi>=heidi_thresh)))]
  111. return(remain_index)
  112. }
  113. # ************************************************** #
  114. # Test identical elements #
  115. # ************************************************** #
  116. check_vec_elements_eq <- function(dim_vec){
  117. return(all(dim_vec == dim_vec[1]))
  118. }
  119. # ************************************************** #
  120. # LD pruning #
  121. # ************************************************** #
  122. # LD pruning, removing pairs of SNPs that LD r2 > threshold
  123. # return index: the index be used for further analysis
  124. snp_ld_prune = function(ldrho, ld_r2_thresh) {
  125. # initialization
  126. nsnp = dim(ldrho)[1]
  127. diag(ldrho) = 0
  128. ldrho[upper.tri(ldrho)]=0
  129. include_id = c(1:nsnp)
  130. # save the index which have ld r^2 > threshold
  131. indx = which(ldrho^2>ld_r2_thresh, arr.ind=T)
  132. if(length(indx)==0) return(NULL);
  133. indx1 = as.numeric(indx[,2])
  134. indx2 = as.numeric(indx[,1])
  135. slct_indx = c(indx1, indx2)
  136. indxbuf = unique(sort(slct_indx))
  137. # count how many SNPs in high LD
  138. nproc = length(indxbuf)
  139. n_slct_snp = as.numeric()
  140. for( i in 1 : nproc) {
  141. n_slct_snp[i] = length(which(slct_indx==indxbuf[i]))
  142. }
  143. # decide the index to remove
  144. nproc = length(indx1)
  145. if(nproc==0) return(NULL);
  146. for( i in 1 : nproc ) {
  147. n1 = n_slct_snp[which(indxbuf==indx1[i])]
  148. n2 = n_slct_snp[which(indxbuf==indx2[i])]
  149. if(n1 < n2) {
  150. t = indx1[i]; indx1[i] = indx2[i]; indx2[i] = t
  151. }
  152. }
  153. indx1 = unique(sort(indx1))
  154. return(indx1)
  155. }
  156. # ************************************************** #
  157. # Filter the summary data #
  158. # ************************************************** #
  159. # filter the input to se and zscore
  160. # parameters see HEIDI or SMR_Multi functions
  161. # return index: the index be used for further analysis
  162. # NOTICE: the parameters will be revised in place after run!!!!! TAKE CARE
  163. filter_summdat <- function(snp_id, bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, n_ref, nsnps_thresh, pvalue_thresh, ld_r2_thresh, fdr_thresh){
  164. # make sure they are numeric numbers
  165. m = length(bzx)
  166. message("There are ", m, " SNPs in the dataset.")
  167. remain_index = seq(1,m)
  168. snpIDp = as.character(snp_id);
  169. bZXp = as.numeric(as.character(bzx))
  170. seZXp = as.numeric(as.character(bzx_se));
  171. pZXp = as.numeric(as.character(bzx_pval));
  172. bZYp = as.numeric(as.character(bzy))
  173. seZYp = as.numeric(as.character(bzy_se));
  174. ldrhop = matrix(as.numeric(as.character(unlist(ldrho))), m, m)
  175. # remove SNPs with missing value
  176. na_snps=c()
  177. indx = which(!is.finite(bZXp) | !is.finite(seZXp) | !is.finite(pZXp) | !is.finite(bZYp) | !is.finite(seZYp) |
  178. is.na(bZXp) | is.na(seZXp) | is.na(pZXp) | is.na(bZYp) | is.na(seZYp))
  179. if(length(indx)>0) {
  180. na_snps = snpIDp[indx];
  181. bZXp = bZXp[-indx]; seZXp = seZXp[-indx]; pZXp = pZXp[-indx];
  182. bZYp = bZYp[-indx]; seZYp = seZYp[-indx];
  183. ldrhop = ldrhop[-indx, -indx];
  184. remain_index = remain_index[-indx]
  185. snpIDp = snpIDp[-indx]
  186. warning(length(indx), " SNPs were removed due to missing estimates in the summary data.")
  187. }
  188. m = length(bZXp)
  189. if(m < nsnps_thresh) stop("At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnps_thresh\".");
  190. # remove SNPs with very small SE
  191. indx = which(seZXp < eps | seZYp < eps )
  192. if(length(indx)>0) {
  193. na_snps = c(na_snps, snpIDp[indx]);
  194. bZXp = bZXp[-indx]; seZXp = seZXp[-indx]; pZXp = pZXp[-indx];
  195. bZYp = bZYp[-indx]; seZYp = seZYp[-indx];
  196. ldrhop = ldrhop[-indx, -indx];
  197. remain_index = remain_index[-indx]
  198. snpIDp = snpIDp[-indx]
  199. warning(length(indx), " SNPs were removed due to extremely small standard error. Please check that data.")
  200. }
  201. m = length(bZXp)
  202. if(m < nsnps_thresh) stop("At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnps_thresh\".");
  203. # remove SNPs with missing LD
  204. indx = which(is.na(ldrhop[upper.tri(ldrhop)]))
  205. if(length(indx) > 0)
  206. stop("LD correlations between ", length(indx), " pairs of SNPs are missing. Please check the MAF of the SNPs and the missingness rate in the reference sample.")
  207. # z score of bzx
  208. weak_snps = c()
  209. indx = which(pZXp > pvalue_thresh)
  210. if(length(indx)>0) {
  211. weak_snps = snpIDp[indx];
  212. bZXp = bZXp[-indx]; seZXp = seZXp[-indx]; pZXp = pZXp[-indx];
  213. bZYp = bZYp[-indx]; seZYp = seZYp[-indx];
  214. ldrhop = ldrhop[-indx, -indx];
  215. remain_index = remain_index[-indx];
  216. snpIDp = snpIDp[-indx];
  217. warning(length(indx), " non-significant SNPs were removed.")
  218. }
  219. m = length(bZXp)
  220. if(m < nsnps_thresh) stop("At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnps_thresh\".");
  221. # check LD r
  222. linkage_snps = c()
  223. indx = snp_ld_prune(ldrhop, ld_r2_thresh)
  224. if(length(indx) > 0) {
  225. linkage_snps = snpIDp[indx]
  226. bZXp = bZXp[-indx]; seZXp = seZXp[-indx]; pZXp = pZXp[-indx];
  227. bZYp = bZYp[-indx]; seZYp = seZYp[-indx];
  228. ldrhop = ldrhop[-indx, -indx];
  229. remain_index = remain_index[-indx]
  230. snpIDp = snpIDp[-indx]
  231. warning("There were SNPs in high LD. After LD pruning with a LD r2 threshold of ", ld_r2_thresh, ", ", length(indx), " SNPs were removed. Note: The threshold of LD can be changed by the \"ld_r2_thresh\".")
  232. }
  233. m = length(bZXp)
  234. if(m < nsnps_thresh) stop("At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnps_thresh\".");
  235. # update LD correlation matrix
  236. var_rho = 1/n_ref
  237. pval_rho = pchisq(ldrhop[upper.tri(ldrhop)]^2/var_rho, 1, lower.tail=F)
  238. qval_rho = p.adjust(pval_rho, method = "fdr")
  239. qval_mat = matrix(0, m, m)
  240. qval_mat[upper.tri(qval_mat)] = qval_rho; qval_mat = t(qval_mat); qval_mat[upper.tri(qval_mat)] = qval_rho;
  241. rho_index = which(qval_mat >= fdr_thresh, arr.ind=T)
  242. ldrhop[rho_index] = 0
  243. message(length(remain_index), " SNPs were retained after filtering.")
  244. # replace the parameters in place
  245. eval.parent(substitute(bzx <- bZXp))
  246. eval.parent(substitute(bzy <- bZYp))
  247. eval.parent(substitute(bzx_se <- seZXp))
  248. eval.parent(substitute(bzy_se <- seZYp))
  249. eval.parent(substitute(bzx_pval <- pZXp))
  250. eval.parent(substitute(ldrho <- ldrhop))
  251. return(list(remain_index=remain_index, na_snps=na_snps, weak_snps=weak_snps, linkage_snps=linkage_snps))
  252. }
  253. # ************************************************** #
  254. # standardization of b and s.e. #
  255. # ************************************************** #
  256. #' @title Standardization of effect size and its standard error
  257. #' @description Standardization of SNP effect and its standard error using z-statistic, allele frequency and sample size
  258. #' @usage std_effect(snp_freq, b, se, n)
  259. #' @param snp_freq vector, allele frequencies
  260. #' @param b vector, SNP effects on risk factor
  261. #' @param se vector, standard errors of b
  262. #' @param n vector, per-SNP sample sizes for GWAS of the risk factor
  263. #' @examples
  264. #' data("gsmr")
  265. #' std_effects = std_effect(gsmr_data$a1_freq, gsmr_data$bzx, gsmr_data$bzx_se, gsmr_data$bzx_n)
  266. #'
  267. #' @return Standardised effect (b) and standard error (se)
  268. #' @export
  269. std_effect <- function(snp_freq, b, se, n) {
  270. # double check the counts
  271. # if data set is empty
  272. check_element(snp_freq,"Minor Allele Frequency")
  273. check_element(b,"Effect Size")
  274. check_element(se,"Standard Error")
  275. check_element(n,"Sample Size")
  276. message("std effect: ", length(b), " instruments loaded.")
  277. # length is different or not
  278. len_vec <- c(length(snp_freq),length(b),length(se),length(n))
  279. if (!check_vec_elements_eq(len_vec)){
  280. stop("Lengths of the input vectors are different. Please check.");
  281. }
  282. # check missing values
  283. indx = which(!is.finite(snp_freq) | !is.finite(b) | !is.finite(se) | !is.finite(n) | is.na(snp_freq) | is.na(b) | is.na(se) | is.na(n))
  284. if (length(indx)>0) {
  285. stop("There are ", length(indx), " SNPs with missing estimates in the summary data. Please check.");
  286. }
  287. # make sure they are numeric numbers
  288. snpfreq = as.numeric(as.character(snp_freq))
  289. b = as.numeric(as.character(b))
  290. se = as.numeric(as.character(se))
  291. n = as.numeric(as.character(n))
  292. zscore = b / se
  293. b_p = zscore / sqrt(2*snp_freq*(1-snp_freq)*(n+zscore^2))
  294. se_p = 1 / sqrt(2*snp_freq*(1-snp_freq)*(n+zscore^2))
  295. return(list(b=b_p,se=se_p))
  296. }
  297. # ************************************************** #
  298. # HEIDI-outlier analysis #
  299. # ************************************************** #
  300. #' @title HEIDI-outlier analysis
  301. #' @description An analysis to detect and eliminate from the analysis instruments that show significant pleiotropic effects on both risk factor and disease
  302. #' @usage heidi_outlier(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, snpid, n_ref, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10, ld_fdr_thresh=0.05)
  303. #' @param bzx vector, SNP effects on risk factor
  304. #' @param bzx_se vector, standard errors of bzx
  305. #' @param bzx_pval vector, p values for bzx
  306. #' @param bzy vector, SNP effects on disease
  307. #' @param bzy_se vector, standard errors of bzy
  308. #' @param ldrho LD correlation matrix of the SNPs
  309. #' @param snpid genetic instruments
  310. #' @param n_ref sample size of the reference sample
  311. #' @param gwas_thresh threshold p-value to select instruments from GWAS for risk factor
  312. #' @param heidi_outlier_thresh threshold p-value to remove pleiotropic outliers (the default value is 0.01)
  313. #' @param nsnps_thresh the minimum number of instruments required for the GSMR analysis (we do not recommend users to set this number smaller than 10)
  314. #' @param ld_r2_thresh LD r2 threshold to remove SNPs in high LD
  315. #' @param ld_fdr_thresh FDR threshold to remove the chance correlations between SNP instruments
  316. #' @examples
  317. #' data("gsmr")
  318. #' filtered_index = heidi_outlier(gsmr_data$bzx, gsmr_data$bzx_se, gsmr_data$bzx_pval, gsmr_data$bzy, gsmr_data$bzy_se, ldrho, gsmr_data$SNP, n_ref, 5e-8, 0.01, 10, 0.1, 0.05)
  319. #'
  320. #' @return Retained index of genetic instruments, SNPs with missing values, with non-significant p-values and those in LD.
  321. #' @export
  322. heidi_outlier <- function(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, snpid,
  323. n_ref, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10, ld_r2_thresh = 0.1, ld_fdr_thresh=0.05) {
  324. # Subset of LD r matrix
  325. len1 = length(Reduce(intersect, list(snpid, colnames(ldrho))))
  326. len2 = length(snpid)
  327. len_vec <- c(len1, len2)
  328. if (!check_vec_elements_eq(len_vec)){
  329. stop(paste(len2 - len1, " SNPs are missing in the LD correlation matrix. Please check.", sep=""))
  330. }
  331. ldrho = ldrho[snpid, snpid]
  332. # double check the counts
  333. len_vec <- c(length(bzx),length(bzx_se),length(bzx_pval),length(bzy),length(bzy_se),dim(ldrho)[1],dim(ldrho)[2])
  334. if (!check_vec_elements_eq(len_vec)){
  335. stop("Lengths of the input vectors are different. Please check.");
  336. }
  337. message("HEIDI-outlier: ", length(bzx), " instruments loaded.")
  338. # filter dataset
  339. resbuf <- filter_summdat(snpid, bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, n_ref, nsnps_thresh, gwas_thresh, ld_r2_thresh, ld_fdr_thresh)
  340. remain_index <- resbuf$remain_index;
  341. na_snps <- resbuf$na_snps; weak_snps <- resbuf$weak_snps; linkage_snps <- resbuf$linkage_snps
  342. if(length(remain_index) < nsnps_thresh) {
  343. stop("Not enough SNPs for the HEIDI-outlier analysis. At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnp_thresh\".");
  344. }
  345. # Perform HEIDI-outlier
  346. pleio_snps <- NULL
  347. remain_index_tmp <- remain_index
  348. remain_index <- heidi_outlier_iter(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, gwas_thresh, heidi_outlier_thresh, remain_index)
  349. if(length(remain_index) < length(remain_index_tmp)) {
  350. removed_index <- remain_index_tmp[-match(remain_index, remain_index_tmp)]
  351. pleio_snps <- snpid[removed_index]
  352. }
  353. message(length(remain_index), " SNPs were retained after the HEIDI-outlier analysis.")
  354. return(list(remain_index=remain_index, na_snps=na_snps, weak_snps=weak_snps, linkage_snps=linkage_snps, pleio_snps=pleio_snps))
  355. }
  356. # ************************************************** #
  357. # GSMR analysis #
  358. # ************************************************** #
  359. #' @title Generalized Summary-data-based Mendelian Randomization analysis
  360. #' @description GSMR (Generalised Summary-data-based Mendelian Randomisation) is a flexible and powerful approach that utilises multiple genetic instruments to test for causal association between a risk factor and disease using summary-level data from independent genome-wide association studies.
  361. #' @usage gsmr(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, snpid, heidi_outlier_flag=T, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10)
  362. #' @param bzx vector, SNP effects on risk factor
  363. #' @param bzx_se vector, standard errors of bzx
  364. #' @param bzx_pval vector, p values for bzx
  365. #' @param bzy vector, SNP effects on disease
  366. #' @param bzy_se vector, standard errors of bzy
  367. #' @param ldrho LD correlation matrix of the SNPs
  368. #' @param snpid genetic instruments
  369. #' @param n_ref sample size of the reference sample
  370. #' @param heidi_outlier_flag flag for HEIDI-outlier analysis
  371. #' @param gwas_thresh threshold p-value to select instruments from GWAS for risk factor
  372. #' @param heidi_outlier_thresh HEIDI-outlier threshold
  373. #' @param nsnps_thresh the minimum number of instruments required for the GSMR analysis (we do not recommend users to set this number smaller than 10)
  374. #' @param ld_r2_thresh LD r2 threshold to remove SNPs in high LD
  375. #' @param ld_fdr_thresh FDR threshold to remove the chance correlations between SNP instruments
  376. #' @examples
  377. #' data("gsmr")
  378. #' gsmr_result = gsmr(gsmr_data$bzx, gsmr_data$bzx_se, gsmr_data$bzx_pval, gsmr_data$bzy, gsmr_data$bzy_se, ldrho, gsmr_data$SNP, n_ref, T, 5e-8, 0.01, 10, 0.1, 0.05)
  379. #'
  380. #' @return Estimate of causative effect of risk factor on disease (bxy), the corresponding standard error (bxy_se), p-value (bxy_pval), SNP index (used_index), SNPs with missing values, with non-significant p-values and those in LD.
  381. #' @export
  382. gsmr <- function(bzx, bzx_se, bzx_pval, bzy, bzy_se,
  383. ldrho, snpid, n_ref, heidi_outlier_flag=T, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10, ld_r2_thresh=0.1, ld_fdr_thresh=0.05) {
  384. # subset of LD r matrix
  385. len1 = length(Reduce(intersect, list(snpid, colnames(ldrho))))
  386. len2 = length(snpid)
  387. len_vec <- c(len1, len2)
  388. if (!check_vec_elements_eq(len_vec)){
  389. stop(paste(len2 - len1, " SNPs are missing in the LD correlation matrix. Please check.", sep=""))
  390. }
  391. ldrho = ldrho[snpid, snpid]
  392. # double check the counts
  393. len_vec <- c(length(bzx),length(bzx_se),length(bzy),length(bzy_se),dim(ldrho)[1],dim(ldrho)[2])
  394. if (!check_vec_elements_eq(len_vec)){
  395. stop("Lengths of the input vectors are different. Please check.");
  396. }
  397. message("GSMR analysis: ", length(bzx), " instruments loaded.")
  398. # filter dataset
  399. resbuf <- filter_summdat(snpid, bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, n_ref, nsnps_thresh, gwas_thresh, ld_r2_thresh, ld_fdr_thresh)
  400. remain_index<-resbuf$remain_index;
  401. na_snps<-resbuf$na_snps; weak_snps<-resbuf$weak_snps; linkage_snps<-resbuf$linkage_snps;
  402. if(length(remain_index) < nsnps_thresh) {
  403. stop("Not enough SNPs for the GSMR analysis. At least ", nsnps_thresh, " SNPs are required. Note: this hard limit can be changed by the \"nsnps_thresh\".");
  404. }
  405. pleio_snps=NULL;
  406. if(heidi_outlier_flag) {
  407. # Perform HEIDI-outlier
  408. remain_index2 <- heidi_outlier_iter(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, gwas_thresh, heidi_outlier_thresh, remain_index)
  409. if(length(remain_index2) < nsnps_thresh) {
  410. stop("Not enough SNPs for the GSMR analysis. At least ", nsnps_thresh, " are required. Note: this hard limit can be changed by \"nsnps_thresh\".");
  411. } else {
  412. message(length(remain_index2), " SNPs were retained after the HEIDI-outlier analysis.")
  413. }
  414. # Save pleiotropic SNPs
  415. if(length(remain_index2) < length(remain_index)) {
  416. removed_index <- remain_index[-match(remain_index2, remain_index)]
  417. pleio_snps <- snpid[removed_index]
  418. }
  419. # Update estimates
  420. remain_index2_tmp = remain_index2
  421. remain_index2 = match(remain_index2, remain_index)
  422. bzx = bzx[remain_index2]; bzx_se = bzx_se[remain_index2];
  423. bzy = bzy[remain_index2]; bzy_se = bzy_se[remain_index2];
  424. ldrho = ldrho[remain_index2,remain_index2];
  425. remain_index <- remain_index2_tmp
  426. }
  427. # do the SMR test with multiple instruments
  428. message("Computing the estimate of bxy at each instrument.")
  429. bXY = bzy/bzx
  430. message("Estimating the variance-covariance matrix for bxy.")
  431. covbXY = cov_bXY(bzx, bzx_se, bzy, bzy_se, ldrho)
  432. diag(covbXY) = diag(covbXY) + eps
  433. # Eigen decomposition
  434. resbuf = eigen(covbXY, symmetric=TRUE)
  435. eval = as.numeric(resbuf$values)
  436. evec = resbuf$vectors
  437. if(min(abs(eval)) < eps) {
  438. stop("The variance-covariance matrix for bxy is not invertible!");
  439. }
  440. covbXY_inv = evec%*%diag(1/eval)%*%t(evec)
  441. message("Estimating bxy using all the instruments.")
  442. vec_1 = rep(1, length(bzx))
  443. num_1_v_1 = as.numeric(solve(t(vec_1)%*%covbXY_inv%*%vec_1))
  444. vec_1_v = as.numeric(t(vec_1)%*%covbXY_inv)
  445. bXY_GLS = num_1_v_1*vec_1_v%*%bXY
  446. varbXY_GLS = num_1_v_1
  447. chisqbXY_GLS = bXY_GLS^2/varbXY_GLS
  448. pbXY_GLS = pchisq(chisqbXY_GLS, 1, lower.tail=F)
  449. message("GSMR analysis is completed.")
  450. return(list(bxy=bXY_GLS, bxy_se=sqrt(varbXY_GLS), bxy_pval=pbXY_GLS, used_index=remain_index,
  451. na_snps=na_snps, weak_snps=weak_snps, linkage_snps=linkage_snps, pleio_snps=pleio_snps))
  452. }
  453. # ************************************************** #
  454. # Bi-directional GSMR analysis #
  455. # ************************************************** #
  456. #' @title Bi-directional GSMR analysis
  457. #' @description Bi-directional GSMR analysis is composed of a forward-GSMR analysis and a reverse-GSMR analysis that uses SNPs associated with the disease (e.g. at < 5e-8) as the instruments to test for putative causal effect of the disease on the risk factor.
  458. #' @usage bi_gsmr(bzx, bzx_se, bzx_pval, bzy, bzy_se, bzy_pval, ldrho, snpid, heidi_outlier_flag=T, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10)
  459. #' @param bzx vector, SNP effects on risk factor
  460. #' @param bzx_se vector, standard errors of bzx
  461. #' @param bzx_pval vector, p values for bzx
  462. #' @param bzy vector, SNP effects on disease
  463. #' @param bzy_se vector, standard errors of bzy
  464. #' @param bzy_pval vector, p values for bzy
  465. #' @param ldrho LD correlation matrix of the SNPs
  466. #' @param snpid genetic instruments
  467. #' @param n_ref sample size of the reference sample
  468. #' @param heidi_outlier_flag flag for HEIDI-outlier analysis
  469. #' @param gwas_thresh threshold p-value to select instruments from GWAS for risk factor
  470. #' @param heidi_outlier_thresh HEIDI-outlier threshold
  471. #' @param nsnps_thresh the minimum number of instruments required for the GSMR analysis (we do not recommend users to set this number smaller than 10)
  472. #' @param ld_r2_thresh LD r2 threshold to remove SNPs in high LD
  473. #' @param ld_fdr_thresh FDR threshold to remove the chance correlations between SNP instruments
  474. #' @examples
  475. #' data("gsmr")
  476. #' gsmr_result = bi_gsmr(gsmr_data$bzx, gsmr_data$bzx_se, gsmr_data$bzx_pval, gsmr_data$bzy, gsmr_data$bzy_se, gsmr_data$bzy_pval, ldrho, gsmr_data$SNP, n_ref, T, 5e-8, 0.01, 10, 0.1, 0.05)
  477. #'
  478. #' @return Estimate of causative effect of risk factor on disease (forward_bxy), the corresponding standard error (forward_bxy_se), p-value (forward_bxy_pval) and SNP index (forward_index), and estimate of causative effect of disease on risk factor (reverse_bxy), the corresponding standard error (reverse_bxy_se), p-value (reverse_bxy_pval), SNP index (reverse_index), SNPs with missing values, with non-significant p-values and those in LD.
  479. #' @export
  480. bi_gsmr <- function(bzx, bzx_se, bzx_pval, bzy, bzy_se, bzy_pval,
  481. ldrho, snpid, n_ref, heidi_outlier_flag=T, gwas_thresh=5e-8, heidi_outlier_thresh=0.01, nsnps_thresh=10, ld_r2_thresh=0.1, ld_fdr_thresh=0.05) {
  482. ## Forward GSMR
  483. message("Forward GSMR analysis...")
  484. gsmr_result=gsmr(bzx, bzx_se, bzx_pval, bzy, bzy_se, ldrho, snpid, n_ref, heidi_outlier_flag, gwas_thresh, heidi_outlier_thresh, nsnps_thresh, ld_r2_thresh, ld_fdr_thresh)
  485. bxy1 = gsmr_result$bxy; bxy1_se = gsmr_result$bxy_se; bxy1_pval = gsmr_result$bxy_pval;
  486. bxy1_index = gsmr_result$used_index;
  487. na_snps = gsmr_result$na_snps; weak_snps = gsmr_result$weak_snps; linkage_snps = gsmr_result$linkage_snps; pleio_snps = gsmr_result$pleio_snps;
  488. ## Reverse GSMR
  489. message("Reverse GSMR analysis...")
  490. gsmr_result=gsmr(bzy, bzy_se, bzy_pval, bzx, bzx_se, ldrho, snpid, n_ref, heidi_outlier_flag, gwas_thresh, heidi_outlier_thresh, nsnps_thresh, ld_r2_thresh, ld_fdr_thresh)
  491. bxy2 = gsmr_result$bxy; bxy2_se = gsmr_result$bxy_se; bxy2_pval = gsmr_result$bxy_pval;
  492. bxy2_index = gsmr_result$used_index;
  493. na_snps = c(na_snps, gsmr_result$na_snps);
  494. weak_snps = c(weak_snps, gsmr_result$weak_snps);
  495. linkage_snps = c(linkage_snps, gsmr_result$linkage_snps);
  496. pleio_snps = c(pleio_snps, gsmr_result$pleio_snps);
  497. return(list(forward_bxy=bxy1, forward_bxy_se=bxy1_se,
  498. forward_bxy_pval=bxy1_pval, forward_index=bxy1_index,
  499. reverse_bxy=bxy2, reverse_bxy_se=bxy2_se,
  500. reverse_bxy_pval=bxy2_pval, reverse_index=bxy2_index,
  501. na_snps=na_snps, weak_snps=weak_snps, linkage_snps=linkage_snps, pleio_snps=pleio_snps))
  502. }

gsmr.R at commit 53f5034, no license · at the source

Overview

  1. Department of Radiology University of California, San Diego San Diego California USA
  2. Department of Bioengineering University of California, San Diego San Diego California USA
  3. Division of Biostatistics University of Minnesota School of Public Health Minneapolis Minnesota USA
  4. Department of Psychiatry Harvard Medical School Boston Massachusetts USA
  5. College of Science Northeastern University Boston Massachusetts USA
  6. Institute for Molecular Medicine Finland (FIMM), Helsinki Institute of Life Science, University of Helsinki Helsinki Finland
Journal: Human brain mapping, volume 47, issue 13, article e70635
Dates: received 25 February 2026; accepted 16 August 2026; published online 30 August 2026; in print September 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1002/hbm.70635 · PMID 42669588 · PMCID PMC13526639 · OpenAlex W7204736479
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), structural MRI / diffusion (modality), human (organism), cognitive (subfield)
Methods: Statistics, Smoothing, state filtering, decompositions
Keywords: cortical morphology, general cognitive ability, genetically‐informed parcellation, genome‐wide association study, Mendelian randomization
MeSH: Aptitude*, Cerebral Cortex*, Cognition*, Individuality*, Aged, Brain Cortical Thickness, Female, Genome-Wide Association Study, Humans, Magnetic Resonance Imaging, Male, Mendelian Randomization Analysis, Middle Aged, UK Biobank (* major topic)
Topic: Cognitive Abilities and Testing (Experimental and Cognitive Psychology, Psychology), according to OpenAlex
Funding: NIH (R01MH132783); Sigrid Jusélius Foundation and the Research Council of Finland (314639, 345988)
Citations: not cited yet (Europe PMC); 55 references in the paper

Abstract

Understanding the cortical architecture underlying individual differences in general cognitive ability (GCA) remains a central question in cognitive neuroscience. Prior work has established associations between global brain size and GCA, yet the regional effects and directionality of these relationships remain debated. Using a genetically informed cortical parcellation in 11,289 UK Biobank participants, we examined associations between cortical surface area (SA), cortical thickness (CT), and GCA measured via verbal–numerical reasoning. Total SA showed a robust positive association with GCA. At the regional level, dorsolateral prefrontal and superior temporal SA exhibited the strongest positive associations, which persisted after adjustment for global SA. In contrast, CT showed comparatively modest associations. Using Mendelian randomization (MR) with genome‐wide significant genetic instruments, we observed evidence consistent with a bidirectional relationship between total SA and GCA. At the regional level, dorsolateral prefrontal and temporal SA demonstrated evidence of MR‐inferred directional effects on GCA, while GCA showed evidence of MR‐inferred directional effects on total SA and perisylvian thickness. These findings support a polyregional SA architecture underlying GCA, with prominent contributions from prefrontal and temporal association cortices. Our results refine global brain–GCA models and highlight the value of genetically informed parcellation for identifying regional cortical contributions.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repository

Its files are read in the Code ↔ Paper reader above, with 1 match between paragraphs and lines of code.

jianyang-lab/gsmr

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 53f5034f4ebde2d149930a924449b57f6a36cb85, 13 September 2023
Languages: R (5)
Size: 18 files, 5 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, environment (DESCRIPTION), documentation, 2 notebooks
Not found: license file, CITATION.cff, tests, continuous integration
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
6 files

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

This study utilizes individual‐level genetic and imaging data from the UK Biobank (https://www.ukbiobank.ac.uk/). The genome‐wide association data for cortical regions were provided from our previously published studies, which can be accessed via the GWAS Catalog (https://www.ebi.ac.uk/gwas/publications/35113692 and https://www.ebi.ac.uk/gwas/publications/36893272). The genome‐wide association data for intelligence were obtained directly from the authors upon request, as a means of avoiding overlap with UKB samples, and the original source was from the Psychiatric Genomics Consortium (PGC) (https://pgc.unc.edu/) or the GWAS Catalog (https://www.ebi.ac.uk/gwas/publications/29942086). GSMR method and code are publicly available via the GCTA software repository on GitHub (https://github.com/JianYang‐Lab/gsmr/releases (https://github.com/JianYang-Lab/gsmr/releases)).

Reproduced under the paper's license (CC BY-NC), 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, issue, pages, dates, 5 authors, 5 keywords, 14 MeSH terms, 2 funders, 55 references.

Cite

This paper

Chou, C., Fiecas, M., del Re, E. C., Vuoksimaa, E., & Chen, C. (2026). Cerebral Cortical Structural Variation and General Cognitive Ability: Evidence From Mendelian Randomization. Human brain mapping, 47(13), e70635. https://doi.org/10.1002/hbm.70635

BibTeX

@article{chou2026cerebral,
author = {Chou, Chun‐Ju and Fiecas, Mark and del Re, Elisabetta C. and Vuoksimaa, Eero and Chen, Chi‐Hua},
title = {{Cerebral Cortical Structural Variation and General Cognitive Ability: Evidence From Mendelian Randomization}},
journal = {Human brain mapping},
year = {2026},
month = sep,
volume = {47},
number = {13},
pages = {e70635},
publisher = {Wiley},
issn = {1065-9471},
doi = {10.1002/hbm.70635},
url = {https://doi.org/10.1002/hbm.70635},
pmid = {42669588},
pmcid = {PMC13526639}
}

RIS

TY - JOUR
AU - Chou, Chun‐Ju
AU - Fiecas, Mark
AU - del Re, Elisabetta C.
AU - Vuoksimaa, Eero
AU - Chen, Chi‐Hua
TI - Cerebral Cortical Structural Variation and General Cognitive Ability: Evidence From Mendelian Randomization
T2 - Human brain mapping
J2 - Hum Brain Mapp
PY - 2026
DA - 2026/09/01
VL - 47
IS - 13
SP - e70635
SN - 1065-9471
PB - Wiley
DO - 10.1002/hbm.70635
UR - https://doi.org/10.1002/hbm.70635
LA - en
ER -

CSL-JSON

{
"id": "10.1002/hbm.70635",
"type": "article-journal",
"title": "Cerebral Cortical Structural Variation and General Cognitive Ability: Evidence From Mendelian Randomization",
"container-title": "Human brain mapping",
"author": [
{
"family": "Chou",
"given": "Chun‐Ju"
},
{
"family": "Fiecas",
"given": "Mark"
},
{
"family": "del Re",
"given": "Elisabetta C."
},
{
"family": "Vuoksimaa",
"given": "Eero"
},
{
"family": "Chen",
"given": "Chi‐Hua"
}
],
"container-title-short": "Hum Brain Mapp",
"volume": "47",
"issue": "13",
"page": "e70635",
"DOI": "10.1002/hbm.70635",
"PMID": "42669588",
"PMCID": "PMC13526639",
"ISSN": "1065-9471",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/hbm.70635",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
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.1038/s42003-026-10131-0 [code]
Shared genetic architecture between the topology of brain white matter structural connectome and fluid intelligence.
Journal: Communications biology
In common: structural MRI / diffusion, genetics / omics, 8 references
[2] doi:10.1371/journal.pcbi.1014422 [code]
Deciphering cell type-specific causal genetic effects on brain imaging-derived phenotypes and disorders with single-cell Mendelian randomization.
Journal: PLoS computational biology
In common: genetics / omics, 7 references
[3] doi:10.1038/s41467-026-74274-8 [code]
Regional sex differences in human cortical anatomy vary in their morphometric bases and overlap with sex chromosomal and gonadal influences.
Journal: Nature communications
In common: structural MRI / diffusion, 7 references
[4] doi:10.1038/s41467-026-73714-9 [code]
The genetic architecture of cortical similarity networks.
Journal: Nature communications
In common: structural MRI / diffusion, genetics / omics, 5 references
[5] doi:10.1038/s42003-026-09956-6 [code]
Linking changes in sulcal morphometry to cognitive development from childhood to adolescence.
Journal: Communications biology
In common: structural MRI / diffusion, 5 references
[6] doi:10.3390/life16081356
Regional Brain Volume Variation Across Adulthood: A Cross-Sectional MRI Analysis of Age, Sex, and Hemispheric Asymmetry.
Journal: Life (Basel, Switzerland)
In common: structural MRI / diffusion, 4 references
[7] doi:10.1038/s43856-026-01510-z [code]
Mapping genetic convergence across brain structure, mental health, and cardiometabolic disease.
Journal: Communications medicine
In common: genetics / omics, 4 references
[8] doi:10.1038/s41467-026-71738-9 [code]
Genetic landscape of adult executive function reveals a cell-type-specific developmental origin.
Journal: Nature communications
In common: cognitive, genetics / omics, 4 references
[9] doi:10.1162/imag.a.1235 [code]
Intracranial volume: To adjust or not to adjust? It is not a matter of if, but how.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: structural MRI / diffusion, 4 references
[10] doi:10.1038/s41380-026-03641-0
Cerebral cortical alterations in adolescent early-onset psychosis: a surface-based morphometry mega-analysis.
Journal: Molecular psychiatry
In common: structural MRI / diffusion, 4 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.