OSCR

An integrative mendelian randomisation and drug mechanism framework for target prioritisation and therapeutic repurposing in major depression.

Code ↔ Paper

5 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 5 matches
  1. [1] § Materials and methods › Instrument selection ↔ gwas_norm/normalise.py, lines 503–962 · score 0.60 · chi square, minor allele frequencies, minimal, GWAS
  2. [2] § Materials and methods › Instrument selection ↔ merit/coloc/coloc_core.py, lines 1367–1445 · score 0.60 · minor allele frequencies, removing variants, squares, selection, trait, correlations
  3. [3] § Materials and methods › Sensitivity analyses and target annotation › Colocalisation ↔ merit/coloc/coloc_core.py, lines 334–437 · score 0.58 · posterior probability, PPH3, abf, PPH4, colocalisation, traits
  4. [4] § Materials and methods › Primary Mendelian randomisation (MR) analysis ↔ merit/mr/ivw.py, lines 21–164 · score 0.57 · inverse variance weighted, IVW, instrumented, correlated, MR, variant
  5. [5] § Materials and methods › Sensitivity analyses and target annotation › Estimated therapeutic relevance ↔ bio_misc/drug_lookups/therapeutic_direction.py, lines 102–230 · score 0.54 · therapeutic directional, ChEMBL, activator, inhibitor, unknown, risk

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

Python · 2,566 lines · 104 KB · GPL-3.0 · 2 matches

  1. """
  2. Python implementation of the coloc R package.
  3. See the original R code here (06-04-2021 last update):
  4. `Here <https://github.com/chr1swallace/coloc/blob/main/R/split.R>`__.
  5. Relevant documentation:
  6. `Here <https://chr1swallace.github.io/coloc/articles/a02_data.html>`__.
  7. And relevant papers:
  8. `Here <https://journals.plos.org/plosgenetics/article?id=10.1371/journal.pgen.1004383>`__.
  9. `Here <https://journals.plos.org/plosgenetics/article?id=10.1371/journal.pgen.1008720>`__.
  10. `Here <https://www.biorxiv.org/content/10.1101/2021.02.23.432421v1>`__.
  11. Note, the `Sum of Single Effects` SuSIE version is not yet implementied
  12. Attribution
  13. -----------
  14. All the credit goes towards the original authors.
  15. All mistakes are our own.
  16. Note:
  17. -----
  18. Some function were omitted, because there were not used in R coloc:
  19. -. est.cond.nometa
  20. -. bin2lin.nometa
  21. -. estgeno.1.ctl
  22. -. estgeno.1.cse
  23. """
  24. import numpy as np
  25. import pandas as pd
  26. import statsmodels.formula.api as smf
  27. import warnings
  28. import itertools
  29. import scipy.stats
  30. # from pandas.core.frame import DataFrame
  31. from collections import OrderedDict
  32. from merit.errors import(
  33. _process_corr_mat,
  34. are_columns_in_df,
  35. are_Series_equal,
  36. error_on_non_finite_pd_corr_matrix,
  37. is_type,
  38. )
  39. from merit import constants as c
  40. from typing import Any, List, Type, Union, Tuple
  41. SEP = ''
  42. """
  43. TODO:
  44. Nothing ...
  45. """
  46. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  47. # Constants
  48. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  49. class Coloc_Expected(object):
  50. """
  51. Collecting some expected constants, getting around all the `SEP.join` that
  52. would otherwise clutter the code.
  53. Might move this to merit.constants, depending on the development of
  54. the coloc implementation into separate scripts.
  55. Notice:
  56. -------
  57. In R coloc `standard_deviation`, `sample_size` and `event_rate` are floats,
  58. not columns. Here we have included them as columns and addapted to code to
  59. take the average.
  60. """
  61. # defining names
  62. exp_name = 'exposure'
  63. out_name = 'outcome'
  64. VARIANT_ID=c.UNIVERSAL_ID.name
  65. EXPOSURE_EFFECT_SIZE= SEP.join(
  66. [c.EFFECT_SIZE.name, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  67. OUTCOME_EFFECT_SIZE= SEP.join(
  68. [c.EFFECT_SIZE.name, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  69. # simply se^2
  70. EXPOSURE_VARBETA= SEP.join(
  71. [c.COLOC_VARBETA, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  72. OUTCOME_VARBETA= SEP.join(
  73. [c.COLOC_VARBETA, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  74. # standard deviation of the gwas trait
  75. EXPOSURE_STANDARD_DEVIATION = SEP.join(
  76. [c.COLOC_STANDARD_DEVIATION, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  77. OUTCOME_STANDARD_DEVIATION = SEP.join(
  78. [c.COLOC_STANDARD_DEVIATION, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  79. # GWAS sample size
  80. EXPOSURE_SAMPLE_SIZE = SEP.join(
  81. [c.COLOC_SAMPLE_SIZE, c.MERGE_SUFFIX_DELIMITER_UP, exp_name]
  82. )
  83. OUTCOME_SAMPLE_SIZE = SEP.join(
  84. [c.COLOC_SAMPLE_SIZE, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  85. # PROPORTION OF CASES
  86. EXPOSURE_EVENT_RATE = SEP.join(
  87. [c.COLOC_EVENT_RATE, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  88. OUTCOME_EVENT_RATE = SEP.join(
  89. [c.COLOC_EVENT_RATE, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  90. # z-statistics
  91. EXPOSURE_Z_STATISTICS = SEP.join(
  92. [c.COLOC_NORMAL_DEVIATE, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  93. OUTCOME_Z_STATISTICS = SEP.join(
  94. [c.COLOC_NORMAL_DEVIATE, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  95. # Shrinkage factor: ratio of the prior variance to the total variance
  96. EXPOSURE_SHRINKAGE_FACTOR = SEP.join(
  97. [c.COLOC_SHRINKAGE_FACTOR, c.MERGE_SUFFIX_DELIMITER_UP, exp_name])
  98. OUTCOME_SHRINKAGE_FACTOR = SEP.join(
  99. [c.COLOC_SHRINKAGE_FACTOR, c.MERGE_SUFFIX_DELIMITER_UP, out_name])
  100. # The log (approximate) Bayes Factor for colocalization
  101. EXPOSURE_ABF = SEP.join(
  102. [c.COLOC_LOG_APPROX_BAYES_FACTOR, c.MERGE_SUFFIX_DELIMITER_UP,
  103. exp_name])
  104. OUTCOME_ABF = SEP.join(
  105. [c.COLOC_LOG_APPROX_BAYES_FACTOR, c.MERGE_SUFFIX_DELIMITER_UP,
  106. out_name])
  107. # minor allele frequence
  108. # NOTE does this need to be exp/outcome stratified?
  109. MINOR_ALLELE_FREQ = c.MINOR_ALLELE_FREQ.name
  110. # types of gwas traits
  111. TRAIT_TYPES = ['quant', 'bin']
  112. # methods
  113. METHODS=["single", "cond", "mask"]
  114. # modes
  115. MODES=["iterative", "allbutone"]
  116. # colnames for dataset 1
  117. exposure_columns = [
  118. VARIANT_ID,
  119. EXPOSURE_EFFECT_SIZE,
  120. EXPOSURE_VARBETA,
  121. EXPOSURE_STANDARD_DEVIATION,
  122. EXPOSURE_SAMPLE_SIZE,
  123. EXPOSURE_EVENT_RATE,
  124. MINOR_ALLELE_FREQ
  125. ]
  126. # names for dataset 2
  127. outcome_columns = [
  128. VARIANT_ID,
  129. OUTCOME_EFFECT_SIZE,
  130. OUTCOME_VARBETA,
  131. OUTCOME_STANDARD_DEVIATION,
  132. OUTCOME_SAMPLE_SIZE,
  133. OUTCOME_EVENT_RATE,
  134. MINOR_ALLELE_FREQ
  135. ]
  136. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  137. # Example datasets
  138. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  139. def _coloc_example_data():
  140. """
  141. Returns a coloc example dataset, and corr_matrix, for five variants
  142. """
  143. nvariants = 5
  144. idx = ['snp_' + str(l) for l in range(1, 6)]
  145. # dataset
  146. dataset = pd.DataFrame(
  147. {
  148. Coloc_Expected.VARIANT_ID: idx,
  149. Coloc_Expected.EXPOSURE_EFFECT_SIZE : [ 1.14517929e+00,
  150. 7.90320821e-01,
  151. -1.33679243e-02,
  152. 1.78273663e-01,
  153. -3.77606312e-01],
  154. Coloc_Expected.EXPOSURE_VARBETA : [0.03924328, 0.03880072,
  155. 0.04455547, 0.04602953,
  156. 0.04237982],
  157. Coloc_Expected.EXPOSURE_SAMPLE_SIZE : [1000] * nvariants,
  158. Coloc_Expected.EXPOSURE_STANDARD_DEVIATION : [2.32924174]*nvariants,
  159. Coloc_Expected.EXPOSURE_EVENT_RATE : [0.10] * nvariants,
  160. Coloc_Expected.OUTCOME_EFFECT_SIZE : [ 1.07128929, 0.92979073,
  161. -0.14149679, -0.02395321,
  162. -0.03075124],
  163. Coloc_Expected.OUTCOME_VARBETA : [0.04028734, 0.04478292,
  164. 0.04728015, 0.04748579,
  165. 0.04415969],
  166. Coloc_Expected.OUTCOME_SAMPLE_SIZE : [1000] * nvariants,
  167. Coloc_Expected.OUTCOME_STANDARD_DEVIATION : [2.33203611]*nvariants,
  168. Coloc_Expected.OUTCOME_EVENT_RATE : [0.10, 0.11, 0.09, 0.12, 0.08],
  169. Coloc_Expected.MINOR_ALLELE_FREQ : [0.1349454, 0.11783439,
  170. 0.13162119, 0.12019231, 0.125]
  171. }, index = idx
  172. )
  173. dataset.index_name=Coloc_Expected.VARIANT_ID
  174. # correlation matrix
  175. corr_matrix = pd.DataFrame(
  176. {
  177. idx[0] : [1.000000, 0.738238, 0.634454, 0.555827, 0.478299],
  178. idx[1] : [0.738238, 1.000000, 0.730387, 0.636565, 0.550489],
  179. idx[2] : [0.634454, 0.730387, 1.000000, 0.735947, 0.618572],
  180. idx[3] : [0.555827, 0.636565, 0.735947, 1.000000, 0.746561],
  181. idx[4] : [0.478299, 0.550489, 0.618572, 0.746561, 1.000000]
  182. }, index = idx)
  183. # return
  184. return dataset, corr_matrix
  185. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  186. # empty results dataframe
  187. def _empty_coloc_results():
  188. '''
  189. Returns an empty coloc results pd.DataFrame
  190. '''
  191. results = pd.DataFrame(columns = [c.COLOC_HIT1, c.COLOC_HIT2, c.MR_NSNPS,
  192. c.COLOC_PPH0, c.COLOC_PPH1, c.COLOC_PPH2,
  193. c.COLOC_PPH3, c.COLOC_PPH4, c.COLOC_BEST1,
  194. c.COLOC_BEST2, c.COLOC_BEST4,
  195. c.COLOC_ZSTAT_HIT1, c.COLOC_ZSTAT_HIT2
  196. ], index=[0])
  197. return results
  198. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  199. # Helper functions
  200. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  201. def _add_missing_columns(check_data: pd.DataFrame, complete_data: pd.DataFrame,
  202. columns : list, index_col: Coloc_Expected.VARIANT_ID
  203. ):
  204. '''
  205. Add missing columns
  206. Arguments
  207. ---------
  208. data : pd.DataFrame
  209. columns : list of strings
  210. index_col : str
  211. '''
  212. # Shared index
  213. check_data.set_index(check_data[index_col], drop=False, inplace=True)
  214. complete_data.set_index(complete_data[index_col], drop=False, inplace=True)
  215. check_data.index.name = None
  216. complete_data.index.name = None
  217. # mapp columns
  218. for col in columns:
  219. if not col in check_data.columns:
  220. check_data[col] = complete_data[col]
  221. # returns stuff
  222. return check_data
  223. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  224. def _logsum(x):
  225. """
  226. Calculate the sum of terms.
  227. Used by the approximate Bayes factor function to calculate the posterior
  228. probabilities. Depends on numpy (np).
  229. Arguments
  230. ---------
  231. x : pd.Series, numpy.array or some other kind of numeric data type
  232. """
  233. max_res = np.max(x)
  234. results = max_res + np.log(np.sum(np.exp(x - max_res)))
  235. return results
  236. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  237. def _logdiff(x, y):
  238. """
  239. Calculate the difference of between two vectors.
  240. Used by the approximate Bayes factor function to calculate the posterior
  241. probablities. Depends on numpy (np), used in combination with _logsum.
  242. In the case of coloc its output should be a constant.
  243. Arguments
  244. ---------
  245. x, y : pd.Series, numpy.array or some other kind of numeric data type
  246. Returns
  247. -------
  248. A pd.Series with the element wise difference of x-y after relevant
  249. transformation. Results will be NA if x-y < 0.
  250. """
  251. max_res = np.max(x + y)
  252. results = max_res + np.log(np.exp(x - max_res) - np.exp(y - max_res))
  253. return results
  254. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  255. # Functions originally in coloc/split.R
  256. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  257. # DONE
  258. def sd_pheno_approx(variancebeta: pd.Series, maf: pd.Series,
  259. n: Union[pd.Series, float]):
  260. """
  261. Get an estimate of the standard deviation of the phenotype distribution.
  262. Note
  263. ----
  264. Implements the `sdY.est` function of R coloc.
  265. Arguments
  266. ---------
  267. variancebeta : pd.Series
  268. Squared standard errors of the relevant point estimates.
  269. maf : pd.Series
  270. The minor allele frequencies of the supplied variants.
  271. n : pd.Series
  272. The GWAS sample size.
  273. Returns
  274. -------
  275. np.float64, reflecting an estimate of the phenotypic standard deviation
  276. (of 'Y').
  277. """
  278. # @@@ check input
  279. is_type(variancebeta, pd.Series)
  280. is_type(maf, pd.Series)
  281. is_type(n,(pd.Series, float))
  282. if not variancebeta.shape[0] == maf.shape[0]:
  283. raise IndexError('Input does not have the same shape')
  284. # @@@ Do the actual calculations
  285. oneover = np.divide(1, variancebeta)
  286. # remove accidental inf
  287. oneover = oneover[~np.isinf(oneover)]
  288. if isinstance(n, pd.Series):
  289. n = n.mean()
  290. nvx = np.multiply(2 * n, maf * np.subtract(1, maf))
  291. # OLS to approximate the SD of the phenotype
  292. ols_res = smf.ols(
  293. formula="nvx ~ oneover -1",
  294. data=pd.DataFrame({"nvx": nvx, "oneover": oneover}),
  295. missing='drop'
  296. )
  297. cf = ols_res.fit().params["oneover"]
  298. if cf < 0:
  299. raise ValueError("The standard deviation is negative. \
  300. Please provide your own estimate of the phenotype SD")
  301. else:
  302. return np.sqrt(cf)
  303. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  304. # DONE
  305. def approx_bf(ln_abf1: pd.Series, ln_abf2: pd.Series,
  306. p1: float=10**-4,
  307. p2: float=10**-4,
  308. p12: float=5*10**-6,
  309. extra_info: bool=True,
  310. masking_ln_abf3: pd.Series=pd.Series(dtype=float),
  311. masking_ln_abf4: pd.Series=pd.Series(dtype=float)):
  312. """
  313. Calculate the posterior probabilities of colocalized signals.
  314. Depends on numpy (np). Its arguments include the natural logarithm of the
  315. base factor for GWAS data set 1 and GWAS data set 2. P1-2 are constants (
  316. or column vectors of length equal to the number of variants) representing
  317. the probability of a random variant being associated only with GWAS
  318. phenotype 1 or 2, but not both. P12 on the other hand is the prior
  319. probability of colocalization.
  320. Note
  321. ----
  322. Implements `.f`, from `coloc.process` from coloc R:
  323. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L419>`__.
  324. Implements `combine.abf` coloc R:
  325. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/claudia.R#L115>`__.
  326. Arguments
  327. ---------
  328. ln_abf1, ln_abf2 : pd.Series
  329. The log approximate base factor for phenotypes 1 or 2.
  330. p1, p2, p12 : floats
  331. The prior probabilities that a random variant is associated with either
  332. trait 1 or 2, or both, respectively.
  333. extra_info : boolean, default True
  334. If we want addiontal detail on the best variants per hypothesis.
  335. Similar to the information provided by R coloc.detail and the `.f`
  336. function in R coloc.process.
  337. masking_ln_abf3, masking_ln_abf4 : pd.Series
  338. the log approximate Bayes factors when using `method=mask`
  339. Returns
  340. -------
  341. Returns a dictionary of posterior probabilities
  342. """
  343. # @@@ CHECKS
  344. # TODO
  345. # @@@ actual calculations
  346. lsum = ln_abf1 + ln_abf2
  347. # the log posterior probability
  348. lh0_abf = 0
  349. lh1_abf = np.add(np.log(p1), _logsum(ln_abf1))
  350. lh2_abf = np.add(np.log(p2), _logsum(ln_abf2))
  351. # NOTE the _logdiff here is specific to `combine.abf` and not repeated
  352. # below when extra_info == True
  353. lh3_abf = np.add(
  354. np.add(np.log(p1), np.log(p2)),
  355. _logdiff(_logsum(ln_abf1) + _logsum(ln_abf2), _logsum(lsum))
  356. )
  357. lh4_abf = np.add(np.log(p12), _logsum(lsum))
  358. # returning a summary
  359. temp_tuple = (lh0_abf, lh1_abf, lh2_abf, lh3_abf, lh4_abf)
  360. denom = _logsum(temp_tuple)
  361. # if extra_info is True
  362. if extra_info == True:
  363. best1 = ln_abf1.idxmax()
  364. best2 = ln_abf2.idxmax()
  365. best4 = lsum.idxmax()
  366. # NOTE _logsum(ln_abf1) + _logsum(ln_abf2)) == lbf3
  367. # NOTE this does NOT include an _logdiff following `coloc.process`
  368. # see https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L423
  369. lh3_abf = np.add(np.add(np.log(p1), np.log(p2)),
  370. _logsum(ln_abf1) + _logsum(ln_abf2))
  371. if masking_ln_abf3.size != 0:
  372. lh3_abf = np.add(np.add(np.log(p1), np.log(p2)),
  373. _logsum(masking_ln_abf3))
  374. if masking_ln_abf4.size != 0:
  375. lh4_abf = np.add(np.log(p12), _logsum(masking_ln_abf4))
  376. best4 = masking_ln_abf4.idxmax()
  377. temp_tuple = (lh0_abf, lh1_abf, lh2_abf, lh3_abf, lh4_abf)
  378. denom = _logsum(temp_tuple)
  379. pp_abf = np.exp(temp_tuple - denom)
  380. # NOTE best? are the variants that match to their respective hypotheses.
  381. results = {c.COLOC_PPH0: pp_abf[0],
  382. c.COLOC_PPH1: pp_abf[1],
  383. c.COLOC_PPH2: pp_abf[2],
  384. c.COLOC_PPH3: pp_abf[3],
  385. c.COLOC_PPH4: pp_abf[4],
  386. c.COLOC_BEST1: best1,
  387. c.COLOC_BEST2: best2,
  388. c.COLOC_BEST4: best4
  389. }
  390. else:
  391. # if extra_info is False
  392. posterior = np.exp(np.subtract(temp_tuple, denom))
  393. results = {c.COLOC_PPH0: posterior[0],
  394. c.COLOC_PPH1: posterior[1],
  395. c.COLOC_PPH2: posterior[2],
  396. c.COLOC_PPH3: posterior[3],
  397. c.COLOC_PPH4: posterior[4]
  398. }
  399. return results
  400. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  401. # DONE
  402. def vestgeno_1_ctl(f: pd.Series):
  403. '''
  404. Helper function to estimate the relative allele frequency of genotypes
  405. 0, 1, 2 in control subjects.
  406. Note
  407. ----
  408. Implements `vestgeno.1.ctl`, from coloc R:
  409. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L543>`__.
  410. Arguments
  411. ---------
  412. f : pd.Series
  413. A vector of minor allele frequencies.
  414. Returns
  415. -------
  416. A pd.DF with the genotype frequencies.
  417. '''
  418. # @@@ check input
  419. is_type(f, pd.Series)
  420. if (0 > f.min()) or (f.max() > 0.50):
  421. raise ValueError('Supplied values should range between {} and {}'.
  422. format(0, 0.50))
  423. # @@@ calulcating the MAF in controls
  424. dt = pd.DataFrame(np.array([(1-f)**2,
  425. 2*f*(1-f),
  426. f**2 ])).T
  427. return dt
  428. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  429. # DONE
  430. def vestgeno_1_cse(G_0: pd.DataFrame, b: np.ndarray):
  431. '''
  432. Helper function to estimate the relative allele frequency of genotypes
  433. 0, 1, 2 in case subjects.
  434. Implements `vestgeno.1.cse`, from coloc R:
  435. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L559>`__.
  436. Arguments
  437. ---------
  438. G_0 : pd.DF
  439. The control group genotype frequencies;
  440. Results from `vestgeno_1_ctl`. With:
  441. -. Column 0 recording the frequency of genotype 0,
  442. -. Column 1 the frequency of genotype 1,
  443. -. Column 2 the frequency of genotype 2.
  444. b : pd.Series
  445. A column vector of effect estimates.
  446. Returns
  447. -------
  448. A pd.DF
  449. '''
  450. # @@@ check input
  451. is_type(G_0, pd.DataFrame)
  452. is_type(b, pd.Series)
  453. b = b.to_numpy()
  454. # @@@ Do the actual calculations
  455. g_0 = 1
  456. g_1 = np.exp(b - np.log(G_0.iloc[:,0]/G_0.iloc[:,1]))
  457. g_2 = np.exp(2*b - np.log(G_0.iloc[:,0]/G_0.iloc[:,2]))
  458. dt = pd.DataFrame({"g_0" : g_0,
  459. "g_1" : g_1,
  460. "g_2" : g_2})
  461. dt1 = dt.div(g_0+g_1+g_2, axis='rows')
  462. return dt1
  463. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  464. # DONE
  465. def VMAF_cc(f_0: pd.Series, b: pd.Series, N_0: float, N_1: float) -> np.ndarray:
  466. '''
  467. Estimate the MAF for a case-control analysis, based on MAF from controls,
  468. the log OR (`b`) and the number of controls `N_0` and cases `N_1`.
  469. Implements `VMAF.cc`, from coloc R:
  470. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L566>`__.
  471. Arguments
  472. ---------
  473. f_O : pd.Series
  474. The minor allele frequency estimated from control subjects.
  475. b : pd.Series
  476. A vector of log odds ratio.
  477. N_0 : float
  478. The number of control subjects.
  479. N_1 : float
  480. The number of subjects with an event (the cases).
  481. Returns
  482. -------
  483. An np.ndarray
  484. '''
  485. # @@@ check input
  486. is_type(f_0, pd.Series)
  487. is_type(b, pd.Series)
  488. is_type(N_0, float)
  489. is_type(N_1, float)
  490. # @@@ the calculations
  491. G_0 = vestgeno_1_ctl(f_0)*N_0
  492. G_1 = vestgeno_1_cse(G_0, b)*N_1
  493. # set same names
  494. G_0.columns = G_1.columns
  495. G = G_0.add(G_1)
  496. E_2 = np.divide((G @ np.array([0,1,4]).reshape(-1,1)), (N_0+N_1))
  497. E_1 = np.divide((G @ np.array([0,1,2]).reshape(-1,1)), (N_0+N_1))
  498. cc = np.divide((E_2 - E_1**2) * (N_0+N_1), (N_0+N_1-1))
  499. # to numpy
  500. # NOTE this may break stuff - was pd.DF before
  501. if cc.shape[1] == 1:
  502. cc = cc.iloc[:, 0].to_numpy()
  503. else:
  504. raise IndexError('`cc` has more than one column')
  505. return cc
  506. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  507. # DONE
  508. def map_cond(dt,
  509. corr_matrix,
  510. YY,
  511. sigsnps=[],
  512. r2_threshold = 0.80,
  513. effect_type='quant',
  514. variant_id=Coloc_Expected.VARIANT_ID,
  515. effect_size=c.EFFECT_SIZE.name,
  516. varbeta=c.COLOC_VARBETA,
  517. event_rate=c.COLOC_EVENT_RATE,
  518. sample_size=c.COLOC_SAMPLE_SIZE,
  519. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ,
  520. label=""):
  521. """
  522. Internal helper function for finemap.signals. Finds the next most significant
  523. variant, after conditioning on content of `sigsnps`.
  524. Implements `map_cond`, from coloc R:
  525. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L256>`__.
  526. Arguments
  527. ---------
  528. dt : pd.DF,
  529. A variant dataframe with columns ...
  530. corr_matrix : pd.DF,
  531. The correlation matrix of the varaints in `dt`.
  532. YY : float,
  533. The residual sum of squares.
  534. sigsnps : list,
  535. A list of row indices of the varaint(s) we want to condition on.
  536. effect_size : str,
  537. The name of the effect size column.
  538. r2_threshold : float, default 0.80,
  539. Pruning variants with a squared pairwise correlation above
  540. `r2_threshold`. This is done to improve model stability, nothing else.
  541. effect_type : str, default `quant`
  542. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  543. case-control/binary.
  544. varbeta : str,
  545. The name of the variance of the effect size column
  546. standard_deviation : str,
  547. The column name of the gwas trait variance (will be estimated if
  548. absent). Only relevant for quantitative trait GWAS.
  549. sample_size : str,
  550. The column name of the GWAS sample size.
  551. minor_allele_freq : str,
  552. The column name of the MAF.
  553. The name of the effect size column.
  554. label : str
  555. ...
  556. Returns
  557. -------
  558. A pd.DF with the conditionally significant variants.
  559. """
  560. # @@@ check input
  561. is_type(dt, pd.DataFrame)
  562. core_cols = [variant_id, effect_size, varbeta,
  563. sample_size, minor_allele_freq]
  564. are_columns_in_df(dt, core_cols)
  565. is_type(effect_type, str)
  566. # check effect_type
  567. if not effect_type in Coloc_Expected.TRAIT_TYPES:
  568. raise AttributeError("`effect_type` should be either {} or {}.".\
  569. format(*Coloc_Expected.TRAIT_TYPES))
  570. # Check if we have a correlation matrix
  571. if corr_matrix is None:
  572. raise AttributeError(" The `corr_matrix` is missing")
  573. if corr_matrix.empty == True:
  574. raise AttributeError(" The `corr_matrix` is missing")
  575. is_type(corr_matrix, pd.DataFrame)
  576. # check if corr_matrix and dt have the same entries
  577. # and align dt and corr_matrix entries
  578. _process_corr_mat(data=dt, corr_matrix=corr_matrix,
  579. index_col=Coloc_Expected.VARIANT_ID
  580. )
  581. # @@@ The calculations
  582. # if there are no pre-defined varaints
  583. # take max unconditional abs(z)
  584. if len(sigsnps) == 0:
  585. Z = np.divide(dt[effect_size], np.sqrt(dt[varbeta])).to_numpy()
  586. wh = np.argmax(np.abs(Z))
  587. sig_dt = pd.DataFrame([Z[wh]], columns=[dt[variant_id].iloc[wh]])
  588. elif len(sigsnps) > 0:
  589. # place holder
  590. if effect_size == 'quant':
  591. event_rate = c.COLOC_EVENT_RATE
  592. est = est_cond(dt=dt, corr_matrix=corr_matrix, YY=YY,
  593. sigsnps=sigsnps, effect_type=effect_type,
  594. r2_threshold=r2_threshold,
  595. variant_id=variant_id,
  596. effect_size=effect_size,
  597. varbeta=varbeta,
  598. event_rate=event_rate,
  599. sample_size=sample_size,
  600. minor_allele_freq=minor_allele_freq,
  601. label=label
  602. )
  603. Z = np.divide(est[c.EFFECT_SIZE.name+"_"+label],
  604. np.sqrt(est[c.COLOC_VARBETA+"_"+label]))
  605. wh = np.argmax(np.abs(Z))
  606. # NOTE the iloc is a dirty fix - should go back to this and actually
  607. # address the index
  608. sig_dt = pd.DataFrame([Z.iloc[wh]],
  609. columns=[est.iloc[wh][c.UNIVERSAL_ID.name]])
  610. # return
  611. return sig_dt
  612. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  613. # DONE
  614. def est_cond(dt,
  615. corr_matrix,
  616. YY,
  617. sigsnps,
  618. effect_type="quant",
  619. xtx=None,
  620. r2_threshold = 0.80,
  621. effect_size=c.EFFECT_SIZE.name,
  622. variant_id=Coloc_Expected.VARIANT_ID,
  623. varbeta=c.COLOC_VARBETA,
  624. event_rate=c.COLOC_EVENT_RATE,
  625. sample_size=c.COLOC_SAMPLE_SIZE,
  626. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ,
  627. label=""):
  628. """
  629. Internal helper function for est_all_cond. Estimates conditional GWAS summary
  630. statistics.
  631. Implements `est_cond`, from coloc R:
  632. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L115>`__.
  633. Arguments
  634. ---------
  635. dt : pd.DF,
  636. A variant dataframe with columns ...
  637. corr_matrix : pd.DF,
  638. The correlation matrix of the variants in `dt`.
  639. YY : float,
  640. The residual sum of squares.
  641. sigsnps : list,
  642. A list of row indices of the varaint(s) we want to condition on.
  643. effect_type : str, default `quant`
  644. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  645. case-control/binary.
  646. xtx : pd.DF?, default None,
  647. The "Gram matrix" matrix -- essentially the variance-covariance matrix
  648. obtained by X.transpose(). Will be internally calculated when set to
  649. `None`, based on the corr_matrix, MAF, beta and sample size (the last
  650. three are supplied through `dt`).
  651. r2_threshold : float, default 0.90,
  652. Pruning variants with a squared pairwise correlation above
  653. `r2_threshold`.
  654. effect_size : str,
  655. The name of the effect size column.
  656. varbeta : str,
  657. The name of the variance of the effect size column
  658. standard_deviation : str,
  659. The column name of the gwas trait variance (will be estimated if
  660. absent). Only relevant for quantitative trait GWAS.
  661. sample_size : str,
  662. The column name of the GWAS sample size.
  663. minor_allele_freq : str,
  664. The column name of the MAF.
  665. label : str
  666. ...
  667. Returns
  668. -------
  669. pd.DF with conditional GWAS estimates
  670. """
  671. # @@@ check input
  672. is_type(dt, pd.DataFrame)
  673. core_cols = [variant_id, effect_size, varbeta,
  674. sample_size, minor_allele_freq]
  675. are_columns_in_df(dt, core_cols)
  676. # Check if we have a correlation matrix
  677. if corr_matrix is None:
  678. raise AttributeError(" The `corr_matrix` is missing")
  679. if corr_matrix.empty == True:
  680. raise AttributeError(" The `corr_matrix` is missing")
  681. is_type(corr_matrix, pd.DataFrame)
  682. # check if corr_matrix and dt have the same entries
  683. # and align dt and corr_matrix entries
  684. _process_corr_mat(data=dt, corr_matrix=corr_matrix,
  685. index_col=Coloc_Expected.VARIANT_ID
  686. )
  687. # Guard the matrix-inverting path (np.linalg.solve, below) against
  688. # undefined-LD NaN rows and non-positive-definite matrices. This runs
  689. # AFTER _process_corr_mat has aligned dt/corr_matrix and BEFORE the XX /
  690. # solve computation. It is matrix-only (no instrument matching), so it is
  691. # safe for coloc's dt whose variant IDs live in a column with a RangeIndex.
  692. error_on_non_finite_pd_corr_matrix(corr_matrix)
  693. # further input
  694. is_type(YY, float)
  695. is_type(sigsnps, list)
  696. if not effect_type in Coloc_Expected.TRAIT_TYPES:
  697. raise AttributeError("`effect_type` should be either {} or {}.".\
  698. format(*Coloc_Expected.TRAIT_TYPES))
  699. # @@@ The actual calculations
  700. # Extracting dt.variants we want to condition one
  701. nuse = []
  702. for snp in sigsnps:
  703. nuse.append(dt[variant_id].tolist().index(snp))
  704. # to find new beta for, conditional on nuse, excluding variants in r-squared
  705. # > r2_threshold. With nuse because small inaccuracies can blow up, and
  706. # we generally don't expect a second detectable signal in
  707. # such high LD with a first signal
  708. ld_mask = np.max(np.power(corr_matrix.iloc[nuse], 2), axis=0) > r2_threshold
  709. nuse_ld = np.flatnonzero(np.asarray(ld_mask)).tolist()
  710. # adding high_ld variants
  711. nuse_comb = list(set(nuse + nuse_ld))
  712. nuse_comb_nam = dt[variant_id].iloc[nuse_comb].to_list()
  713. # position
  714. use_names = [x for x in dt[variant_id].tolist() if x not in nuse_comb_nam]
  715. # use_names = [x for x in dt[variant_id].tolist() if x not in sigsnps]
  716. use = []
  717. # get position
  718. for snp in use_names:
  719. use.append(dt[variant_id].tolist().index(snp))
  720. # getting the variance-covariance matrix
  721. if xtx is None:
  722. # depending on the trait, do
  723. if effect_type == "quant":
  724. # if quant, assume MAF is correct estimate for sample
  725. # expected variance of X, h_buf in GCTA, Dj/N in Yang et al
  726. VX = 2 * dt[minor_allele_freq] * (1 - dt[minor_allele_freq])
  727. A1 = np.array(VX).reshape(1,-1)
  728. A2 = np.array(VX).reshape(-1,1)
  729. # E(XtX/Ni)
  730. P = np.sqrt(A2 @ A1)
  731. elif effect_type == "bin":
  732. # Correcting the MAF
  733. # NOTE Taking the average event_rate and sample_size
  734. VW = VMAF_cc(f_0 = dt[minor_allele_freq],
  735. b = dt[effect_size],
  736. N_0 = (1-dt[event_rate].mean()) * dt[sample_size].mean(),
  737. N_1 = dt[event_rate].mean() * dt[sample_size].mean(),
  738. )
  739. A1 = np.array(VW).reshape(-1, 1)
  740. A2 = np.array(VW).reshape(1,-1)
  741. # np.sqrt(A1 @ A2)
  742. P = np.sqrt(A1 @ A2)
  743. else:
  744. raise AttributeError("effect_type does not exist, either supply \
  745. effect type as `quant` or `bin`, or supply a variance-covariance matrix through \
  746. xtx")
  747. else:
  748. XX = xtx
  749. # calculate XX
  750. XX = dt[sample_size].mean() * corr_matrix * P
  751. # The variance vectors
  752. D = np.diag(XX)
  753. D_1 = D[nuse]
  754. D_2 = D[use]
  755. # the covariance matrices (_1, _2, will be vectors)
  756. XX_1 = XX.iloc[nuse, nuse]
  757. XX_2 = XX.iloc[use, use]
  758. XX_12 = XX.iloc[nuse, use]
  759. XX_21 = XX.iloc[use, nuse]
  760. # accommodate nuse/use of length 1 or mor
  761. D_1 = np.array(D_1).reshape(1,1) if len(nuse) == 1 else np.diag(D_1)
  762. D_2 = np.array(D_2).reshape(1,1) if sum(use) == 1 else np.diag(D_2)
  763. # joint effects at nuse, if needed
  764. # (X'X)^-1 @ var @ beta
  765. b_1 = np.linalg.solve(XX_1, np.identity(len(XX_1))) @ D_1 @\
  766. dt[effect_size].iloc[nuse].to_numpy().reshape(-1,1) # single column vector
  767. ## conditional effects 2 | 1
  768. b_2 = np.subtract(
  769. # term 1
  770. dt[effect_size].iloc[use].to_numpy(),
  771. # subtract this from term 1
  772. np.divide(
  773. # numerator
  774. XX_21.to_numpy() @ np.linalg.solve(XX_1, np.identity(len(XX_1))) @ D_1 @\
  775. np.array(dt[effect_size].iloc[nuse]),
  776. # denominator
  777. np.diag(D_2)
  778. )
  779. ).reshape(1,-1)
  780. ## residual and conditional b2 variance
  781. # row vector
  782. # b_1 = b_1.reshape(1,-1)
  783. # column vector
  784. beta_mat = dt[effect_size].iloc[nuse].to_numpy().reshape(-1,1)
  785. # Sc
  786. # NOTE taking the mean -- this will always be a float
  787. Sc = YY - b_1.T @ D_1 @ beta_mat
  788. Sc = (
  789. # numerator
  790. (Sc - b_2 * np.diag(D_2) *
  791. dt[effect_size].iloc[use].to_numpy().reshape(1,-1)) /
  792. # denominator
  793. (dt[sample_size].mean() - len(sigsnps) - 1)
  794. )
  795. # variance b2
  796. vb_2 = np.divide(
  797. # numerator
  798. np.array(Sc) * (
  799. np.diag(D_2) -\
  800. np.diag(XX_21 @ np.linalg.solve(XX_1, np.identity(len(XX_1))) @\
  801. np.array(XX_12)
  802. )
  803. ),
  804. # denominator
  805. np.diag(D_2)**2)
  806. # abs here because very occasionally can get a small negative vb2 due to
  807. # approximation. In this case, replace by a positive vb2 of same small
  808. # magnitude
  809. vb_2 = np.abs(vb_2)
  810. # results table
  811. cond_dt = pd.concat(
  812. [
  813. # table 1
  814. pd.DataFrame({
  815. c.UNIVERSAL_ID.name: dt[variant_id].iloc[use].to_list(),
  816. c.EFFECT_SIZE.name+"_"+label: list(itertools.chain.from_iterable(b_2)),
  817. c.COLOC_VARBETA+"_"+label: list(itertools.chain.from_iterable(vb_2))
  818. }) ,
  819. # table 2
  820. # NOTE USING nuse_comb, here to ensure multicolinear variants
  821. # get returned as well
  822. pd.DataFrame({
  823. c.UNIVERSAL_ID.name: dt[variant_id].iloc[nuse_comb].to_list(),
  824. c.EFFECT_SIZE.name+"_"+label: 0*len(nuse_comb),
  825. c.COLOC_VARBETA+"_"+label: dt[varbeta].iloc[nuse_comb].to_list()
  826. })
  827. ]
  828. )
  829. # reset index
  830. cond_dt.reset_index(drop=True, inplace=True)
  831. # returns stuff
  832. return cond_dt
  833. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  834. # Done
  835. def find_best_signal(dt: pd.DataFrame,
  836. variant_id=Coloc_Expected.VARIANT_ID,
  837. effect_size=c.EFFECT_SIZE.name,
  838. varbeta=c.COLOC_VARBETA,
  839. ):
  840. """
  841. Internal helper function to select the most significant variant.
  842. Implements `find.best.signal`, from coloc R:
  843. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L285>`__.
  844. Arguments
  845. ---------
  846. dt : pd.DF,
  847. A variant dataframe with columns ...
  848. effect_size : str,
  849. The name of the effect size column.
  850. varbeta : str,
  851. The name of the variance of the effect size column
  852. Returns
  853. -------
  854. pd.DF with most significant variant
  855. """
  856. # @@@ Cehck input
  857. is_type(dt, pd.DataFrame)
  858. are_columns_in_df(dt, [effect_size, varbeta])
  859. # @@@ caclculate z-statistics
  860. # NOTE HAVE removed the YY part, as far as I can tell it does nothing.
  861. Z = dt[effect_size]/dt[varbeta].pow(1/2)
  862. # largest Z
  863. wh = np.argmax(np.abs(np.array(Z)))
  864. # return
  865. # NOTE perhaps this works better as a dictionary?
  866. # NOTE 2 - what if there are ties?
  867. Zs = Z.iloc[wh]
  868. nam = [dt[variant_id].iloc[wh]]
  869. return_dt = pd.DataFrame([Zs], columns=nam)
  870. return return_dt
  871. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  872. # DONE
  873. def est_all_cond(dt: pd.DataFrame,
  874. corr_matrix: Union[pd.DataFrame, type(None)],
  875. fm: pd.DataFrame,
  876. mode="iterative",
  877. effect_type="quant",
  878. r2_threshold = 0.80,
  879. variant_id=Coloc_Expected.VARIANT_ID,
  880. effect_size=c.EFFECT_SIZE.name,
  881. varbeta=c.COLOC_VARBETA,
  882. event_rate=c.COLOC_EVENT_RATE,
  883. sample_size=c.COLOC_SAMPLE_SIZE,
  884. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ,
  885. standard_deviation=c.COLOC_STANDARD_DEVIATION,
  886. label="") -> dict:
  887. """
  888. Implements `est_all_cond`, from coloc R:
  889. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L671>`__.
  890. Arguments
  891. ---------
  892. dt : pd.DF,
  893. A variant dataframe with columns ...
  894. corr_matrix : pd.DF,
  895. The correlation matrix of the variants in `dt`.
  896. fm : pd.DF,
  897. The best variants selected through `find_best_signal`.
  898. mode : float, default `iterative`,
  899. `iterative` or `allbutone`; to either successively condition on signals
  900. find all putative signals and condition on all but one of them in each
  901. analysis.
  902. effect_type : str, default `quant`
  903. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  904. case-control/binary.
  905. r2_threshold : float, default 0.80,
  906. Pruning variants with a squared pairwise correlation above
  907. `r2_threshold`.
  908. effect_size : str,
  909. The name of the effect size column.
  910. varbeta : str,
  911. The name of the variance of the effect size column
  912. event_rate : str,
  913. The column name of the proportion of cases in the source GWAS.
  914. sample_size : str,
  915. The column name of the GWAS sample size.
  916. minor_allele_freq : str,
  917. The column name of the MAF.
  918. standard_deviation : str,
  919. The column name of the gwas trait variance (will be estimated if
  920. absent). Only relevant for quantitative trait GWAS.
  921. label : str
  922. ...
  923. Returns
  924. -------
  925. A dictionary with one unconditional and x many conditional GWAS estimates
  926. """
  927. # @@@ Check input
  928. # Check if core data is present
  929. is_type(dt, pd.DataFrame)
  930. is_type(fm, pd.DataFrame)
  931. is_type(corr_matrix, (pd.DataFrame, type(None)))
  932. is_type(effect_type, str)
  933. is_type(mode, str)
  934. is_type(r2_threshold, float)
  935. # Check if effect types are correct
  936. if not effect_type in Coloc_Expected.TRAIT_TYPES:
  937. raise AttributeError("`effect_type` should be either {} or {}.".\
  938. format(*Coloc_Expected.TRAIT_TYPES))
  939. # check modes
  940. if not mode in Coloc_Expected.MODES:
  941. raise AttributeError(
  942. "`modes` should be a str: {}".format(Coloc_Expected.MODES)
  943. )
  944. # Checking columns content
  945. cor_cols = [variant_id, effect_size, varbeta]
  946. are_columns_in_df(dt, cor_cols)
  947. # @@@ the actual calculations
  948. if effect_type == 'bin':
  949. dt, _ = bin2lin(dt,
  950. variant_id=variant_id,
  951. effect_size=effect_size,
  952. varbeta=varbeta,
  953. event_rate=event_rate,
  954. sample_size=sample_size,
  955. minor_allele_freq=minor_allele_freq
  956. )
  957. # get z-statistics
  958. dt[c.COLOC_NORMAL_DEVIATE + label] = dt[effect_size]/dt[varbeta].pow(1/2)
  959. # calculate YY
  960. YY = est_YY(dt=dt, effect_type=effect_type,
  961. standard_deviation=standard_deviation,
  962. sample_size=sample_size,
  963. event_rate=event_rate)
  964. # replaced the `sigs` function with the following
  965. snps = fm.columns.tolist()
  966. # snps = ['snp_1', 'snp_2', 'snp_3']
  967. if mode == "allbutone":
  968. sigs = list(itertools.combinations(snps, len(snps)-1))
  969. sigs.reverse()
  970. elif mode == "iterative":
  971. sigs = [tuple(snps[:i]) for i in range(len(snps))]
  972. # getting the conditional dataframe
  973. # NOTE USING ORDERED_DICT
  974. cond_dt = OrderedDict()
  975. for sigsnps in sigs:
  976. # This will be True if sigsnps are not in sigs
  977. if not sigsnps:
  978. # making a tuple with the same structure as sigsnps
  979. temp = dt.copy()
  980. # making sure the index is correct
  981. temp.set_index(temp[variant_id], drop=False, inplace=True)
  982. temp.index.name = None
  983. cond_dt[(None, None)] = temp
  984. del temp
  985. else:
  986. # NOTE mapping sigsnps to a list in `est_cond`
  987. temp = est_cond(dt=dt, corr_matrix=corr_matrix, YY=YY,
  988. sigsnps=list(sigsnps),
  989. effect_type=effect_type,
  990. r2_threshold=r2_threshold,
  991. variant_id=variant_id,
  992. effect_size=effect_size,
  993. varbeta=varbeta,
  994. event_rate=event_rate,
  995. sample_size=sample_size,
  996. minor_allele_freq=minor_allele_freq,
  997. label=label
  998. )
  999. # ensuring the index is correct
  1000. temp.set_index(temp[variant_id], drop=False, inplace=True)
  1001. temp.index.name = None
  1002. cond_dt[sigsnps] = temp
  1003. del temp
  1004. # return
  1005. return cond_dt
  1006. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1007. # DONE
  1008. def map_mask(dt: pd.DataFrame,
  1009. corr_matrix: pd.DataFrame,
  1010. r2thr=0.01,
  1011. sigsnps=[],
  1012. variant_id=Coloc_Expected.VARIANT_ID,
  1013. effect_size=c.EFFECT_SIZE.name,
  1014. varbeta=c.COLOC_VARBETA,
  1015. zstatistic=c.COLOC_NORMAL_DEVIATE
  1016. ):
  1017. """
  1018. Internal helper function for finemap.signals. Finds the next most
  1019. significant variants, masking/removing variants in `sigsnps`.
  1020. Implements `map_mask`, from coloc R:
  1021. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L72>`__.
  1022. Arguments
  1023. ---------
  1024. dt : pd.DF,
  1025. A coloc dataframe with either the exposure or outcome data.
  1026. corr_matrix : pd.DF,
  1027. The correlation matrix of the variants in `dt`.
  1028. r2thr : float, default 0.01
  1029. Mask variants with a squared correlation with any of the `sigsnps`
  1030. larger than `r2thr`.
  1031. sigsnps : list,
  1032. A list of row indices of the varaint(s) we want to condition on.
  1033. effect_size : str,
  1034. The name of the effect size column.
  1035. varbeta : str,
  1036. The name of the variance of the effect size column.
  1037. zstatistic : str,
  1038. The name of the z-statistic column. Will be estimates if effect_size
  1039. and varbeta are supplied and the column is not present in `dt`.
  1040. Returns
  1041. -------
  1042. A pd.DF with the significant variants
  1043. """
  1044. # @@@ check input
  1045. is_type(dt, pd.DataFrame)
  1046. core_cols = [variant_id, varbeta ]
  1047. are_columns_in_df(dt, core_cols)
  1048. # Check if we have a correlation matrix
  1049. if corr_matrix is None:
  1050. raise AttributeError(" The `corr_matrix` is missing")
  1051. if corr_matrix.empty == True:
  1052. raise AttributeError(" The `corr_matrix` is missing")
  1053. is_type(corr_matrix, pd.DataFrame)
  1054. # check if corr_matrix and dt have the same entries
  1055. are_Series_equal(dt[variant_id], corr_matrix.index.to_series(),
  1056. objects_names=['dt', 'corr_matrix']
  1057. )
  1058. # Files have the same entries, now make sure they are in the same order
  1059. dt.sort_values(by=[variant_id], ascending=True, inplace=True)
  1060. corr_matrix.sort_index(axis=0, ascending=True, inplace=True)
  1061. corr_matrix.sort_index(axis=1, ascending=True, inplace=True)
  1062. # @@@ actual calculations
  1063. # calculate z-statistic
  1064. if not zstatistic in dt.columns:
  1065. Z = np.divide(dt[effect_size], np.sqrt(dt[varbeta])).to_numpy()
  1066. else:
  1067. Z = dt[zstatistic].to_numpy()
  1068. uni_id = np.array(dt[variant_id])
  1069. use = np.repeat(True, dt.shape[0])
  1070. # if there are sigsnps, do
  1071. if len(sigsnps) > 0:
  1072. expectedz = np.repeat(0, dt.shape[0])
  1073. # select just the column for the sigsnps
  1074. a = np.array(corr_matrix[sigsnps]).T
  1075. friends = np.any(np.abs(a) > np.sqrt(r2thr), axis=0)
  1076. use = np.logical_xor(use, friends)
  1077. # NOTE check if this needs to be an empty df with the same rows and columns
  1078. # It seems to only fill the column name by uni_id, so presumably you
  1079. # can keep it empty.
  1080. if np.any(use) == False:
  1081. sig_dt = pd.DataFrame()
  1082. else:
  1083. # otherwise go on
  1084. idx = np.flatnonzero(
  1085. dt[variant_id].isin(sigsnps).to_numpy()
  1086. )
  1087. expectedz = np.array(corr_matrix[sigsnps]) @ Z[idx]
  1088. zdiff = np.abs(Z.T[use]) - np.abs(expectedz.T[use])
  1089. wh = np.argmax(zdiff)
  1090. sig_dt = pd.DataFrame([Z.T[use][wh]],
  1091. columns=[uni_id[use][wh]])
  1092. elif len(sigsnps) == 0:
  1093. zdiff = np.abs(Z.T)
  1094. wh = np.argmax(zdiff)
  1095. sig_dt = pd.DataFrame([Z[wh]],
  1096. columns=[dt[variant_id].iloc[wh]])
  1097. # return
  1098. return sig_dt
  1099. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1100. # DONE
  1101. def wmean(estimates: Union[pd.DataFrame, pd.Series, np.ndarray],
  1102. weights: Union[pd.DataFrame, pd.Series, np.ndarray, float]
  1103. ) -> np.ndarray:
  1104. '''
  1105. Weighted mean -- fixed effect meta-analysis.
  1106. Arguments
  1107. ---------
  1108. estimates, weights : pd.DataFrame or pd.Series
  1109. Returns
  1110. -------
  1111. A np.array
  1112. '''
  1113. # @@@ check
  1114. is_type(estimates, (pd.DataFrame, pd.Series, np.ndarray))
  1115. is_type(weights, (pd.DataFrame, pd.Series, np.ndarray, float))
  1116. # @@@ estimates
  1117. # remove indices if needed
  1118. if isinstance(estimates, ( pd.DataFrame, pd.Series )):
  1119. estimates = estimates.to_numpy()
  1120. if isinstance(weights, ( pd.DataFrame, pd.Series )):
  1121. weights = weights.to_numpy()
  1122. # make 2d
  1123. if len(weights.shape) == 1:
  1124. weights = weights.reshape(-1,1)
  1125. try:
  1126. # colsums
  1127. res = np.sum(estimates * weights, axis=0)/np.sum(weights, axis=0)
  1128. except ValueError:
  1129. # print('testing here')
  1130. res = np.sum(estimates * weights, axis=0)/np.sum(weights)
  1131. # return
  1132. return res
  1133. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1134. # DONE
  1135. def bin2lin(dt,
  1136. variant_id=Coloc_Expected.VARIANT_ID,
  1137. effect_size=c.EFFECT_SIZE.name,
  1138. varbeta=c.COLOC_VARBETA,
  1139. event_rate=c.COLOC_EVENT_RATE,
  1140. sample_size=c.COLOC_SAMPLE_SIZE,
  1141. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ):
  1142. """
  1143. Estimate beta and varbeta if a linear regression had been run on a binary
  1144. outcome, given log odds ratio and their variance + MAF in controls
  1145. Implements `bin2lin` and `bin2lin.nometa`, from coloc R:
  1146. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L633>`__.
  1147. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L574>`__.
  1148. Arguments
  1149. ---------
  1150. dt : pd.DF,
  1151. A variant dataframe with columns ...
  1152. effect_size : str,
  1153. The name of the effect size column.
  1154. varbeta : str,
  1155. The name of the variance of the effect size column
  1156. event_rate : str,
  1157. The column name of the proportion of cases in the source GWAS.
  1158. sample_size : str,
  1159. The column name of the GWAS sample size.
  1160. minor_allele_freq : str,
  1161. The column name of the MAF.
  1162. Returns
  1163. Unpacks an updated `dt` and `linear_approx`. Here `dt` replaces the orignal
  1164. logOR and logOR variance with the estimated mean difference and its variance.
  1165. The `linear_approx` compares the z-statistics from both the mean difference
  1166. and the logOR, should be close to 1.
  1167. """
  1168. # @@@ check input
  1169. is_type(dt, pd.DataFrame)
  1170. core_cols = [variant_id, effect_size, varbeta,
  1171. minor_allele_freq, event_rate, sample_size]
  1172. are_columns_in_df(dt, core_cols)
  1173. # @@@ estimate the genotype frequencies
  1174. # control frequency
  1175. g0 = vestgeno_1_ctl(dt[minor_allele_freq])
  1176. # case frequency
  1177. g1 = vestgeno_1_cse(g0, dt[effect_size])
  1178. # expected genotypes
  1179. geno_mat = np.array([[0,1,2]])
  1180. # expected proportion of case and control subjects
  1181. ex1 = geno_mat @ g1.transpose()
  1182. ex0 = geno_mat @ g0.transpose()
  1183. # squared expression
  1184. vx1 = geno_mat**2 @ g1.transpose()
  1185. vx0 = geno_mat**2 @ g0.transpose()
  1186. # combined
  1187. ex = (np.array([1 - dt[event_rate].mean()]).T @ ex0 +
  1188. np.array([dt[event_rate].mean()]).T @ ex1)
  1189. vx = (np.array([1 - dt[event_rate].mean()]).T @ vx0 +
  1190. np.array([dt[event_rate].mean()]).T @ vx1 - ex**2)
  1191. # binomial variance
  1192. vy = np.array([dt[event_rate].mean() * (1 - dt[event_rate].mean())])
  1193. # centred
  1194. vxy = vy.transpose() @ ( ex1 - ex0 )
  1195. # @@@ estimating varbeta and beta on the linear scale
  1196. # essentially getting mean differences
  1197. varbeta_mat = (vy/vx - vxy**2/vx**2) / (dt[sample_size].mean() - 1)
  1198. # row vector
  1199. # varbeta_mat = varbeta_mat.to_numpy().reshape(-1, 1)
  1200. # storing original values
  1201. beta_org = dt[effect_size].copy()
  1202. varbeta_org = dt[varbeta].copy()
  1203. if len(vxy.shape) == 1:
  1204. dt[varbeta] = varbeta_mat.to_list()
  1205. dt[effect_size] = (vxy/vx).to_list()
  1206. else:
  1207. warnings.warn("Performing meta-analysis")
  1208. # meta-analysis, axis=1 is colsum
  1209. dt[varbeta] = 1/np.sum(1/varbeta_mat, axis=1).tolist()
  1210. dt[effect_size] = wmean(vxy/vx, 1/varbeta_mat).tolist()
  1211. # @@@ diagnostic results
  1212. zstat = dt[effect_size]/np.sqrt(dt[varbeta])
  1213. zstat_org = beta_org/np.sqrt(varbeta_org)
  1214. fit = smf.ols(formula='zstat ~ zstat_org - 1',
  1215. data = pd.DataFrame(
  1216. {'zstat': zstat, 'zstat_org': zstat_org}
  1217. ), missing = 'drop').fit()
  1218. linear_approx = fit.params['zstat_org']
  1219. if not (0.90 <= linear_approx <= 1.1):
  1220. warnings.warn("Poor linear approximation: {}".format(linear_approx))
  1221. # returns
  1222. return dt, linear_approx
  1223. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1224. # DONE
  1225. def est_YY(dt : pd.DataFrame,
  1226. effect_type: list,
  1227. variant_id=Coloc_Expected.VARIANT_ID,
  1228. standard_deviation=c.COLOC_STANDARD_DEVIATION,
  1229. sample_size=c.COLOC_SAMPLE_SIZE,
  1230. event_rate=c.COLOC_EVENT_RATE
  1231. ) -> float:
  1232. """
  1233. Calculates the squared residuals.
  1234. Implements code chunck from coloc R:
  1235. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L293>`__.
  1236. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L355>`__.
  1237. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L680>`__.
  1238. Arguments
  1239. ---------
  1240. dt : pd.DF,
  1241. A coloc dataframe.
  1242. effect_type : str, default `quant`,
  1243. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  1244. case-control/binary.
  1245. standard_deviation : str,
  1246. The column name of the gwas trait variance (will be estimated if
  1247. absent). Only relevant for quantitative trait GWAS.
  1248. sample_size : str,
  1249. The column name of the GWAS sample size.
  1250. event_rate : str,
  1251. The column name of the proportion of cases in the source GWAS. Only
  1252. applicable for a binary trait GWAS.
  1253. Returns
  1254. -------
  1255. A float
  1256. """
  1257. # @@@ Check if core data is present
  1258. is_type(dt, pd.DataFrame)
  1259. core_cols = [variant_id, sample_size ]
  1260. are_columns_in_df(dt, core_cols)
  1261. if not effect_type in Coloc_Expected.TRAIT_TYPES:
  1262. raise AttributeError("`effect_type` should be either {} or {}.".\
  1263. format(*Coloc_Expected.TRAIT_TYPES))
  1264. # @@@ performing the actual calulcations
  1265. if effect_type == "quant":
  1266. # check the correct columns are present
  1267. are_columns_in_df(dt, [sample_size, standard_deviation])
  1268. # do the actual calculations
  1269. # NOTE taking the mean
  1270. YY = dt[sample_size].mean() * dt[standard_deviation].mean()**2
  1271. elif effect_type == "bin":
  1272. # check input
  1273. are_columns_in_df(dt, [sample_size, event_rate])
  1274. # NOTE taking the mean here, reflecting R coloc
  1275. # NOTE 2, in R `find.best.signal` divides YY by N, however
  1276. # the value is never returned - dead code, not used here.
  1277. YY = (dt[sample_size].mean() * dt[event_rate].mean() *
  1278. (1 - dt[event_rate].mean()))
  1279. # return
  1280. return YY
  1281. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1282. # DONE
  1283. def finemap_signals(dt: pd.DataFrame,
  1284. corr_matrix=None,
  1285. method="single",
  1286. r2thr=0.01,
  1287. sigsnps=[],
  1288. pthr=1e-6,
  1289. maxhits=3,
  1290. effect_type="quant",
  1291. variant_id=Coloc_Expected.VARIANT_ID,
  1292. effect_size=c.EFFECT_SIZE.name,
  1293. varbeta=c.COLOC_VARBETA,
  1294. zstatistic=c.COLOC_NORMAL_DEVIATE,
  1295. standard_deviation=c.COLOC_STANDARD_DEVIATION,
  1296. sample_size=c.COLOC_SAMPLE_SIZE,
  1297. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ,
  1298. event_rate=c.COLOC_EVENT_RATE,
  1299. verbose=True
  1300. ):
  1301. """
  1302. This is an analogue to finemap.abf, adapted to find multiple signals where
  1303. they exist, via conditioning or masking - ie a stepwise procedure
  1304. Finemap multiple signals in a single dataset
  1305. Internal helper function for finemap.signals. Finds the next most
  1306. significant variants, masking/removing variants in `sigsnps`.
  1307. Implements `finemap.signals`, from coloc R:
  1308. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L328>`__.
  1309. Arguments
  1310. ---------
  1311. dt : pd.DF,
  1312. A coloc dataframe.
  1313. corr_matrix : pd.DF,
  1314. The correlation matrix of the variants in `dt`. Only relevant when
  1315. method == 'cond'.
  1316. r2thr : float, default 0.01
  1317. Mask variants with a squared correlation with any of the `sigsnps`
  1318. larger than `r2thr`.
  1319. method : str, default `single`,
  1320. Use `single` to perform coloc assuming there is a single causal variant,
  1321. Use `cond` to perform a stepwise conditional analysis,
  1322. Use `mask` to perform the stepwise selection algorithm using masking
  1323. instead.
  1324. sigsnps : list,
  1325. A list of row indices of the varaint(s) we want to condition on.
  1326. NOTE:-> this does not seem to do anything in the R-version.
  1327. pthr : float, default 1 \times 10^{-6},
  1328. Stops when p-values are larger than `pthr`, where input should be
  1329. defined between 0 and 1.
  1330. maxhits : int, default 3,
  1331. Stops when the procedures finds `maxhits`.
  1332. effect_type : str, default `quant`,
  1333. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  1334. case-control/binary.
  1335. effect_size : str,
  1336. The name of the effect size column.
  1337. varbeta : str,
  1338. The name of the variance of the effect size column
  1339. zstatistic : str,
  1340. The name of the z-statistic column. Will be estimates if effect_size
  1341. and varbeta are supplied and the column is not present in `dt`.
  1342. standard_deviation : str,
  1343. The column name of the gwas trait variance (will be estimated if
  1344. absent). Only relevant for quantitative trait GWAS.
  1345. sample_size : str,
  1346. The column name of the GWAS sample size.
  1347. minor_allele_freq : str,
  1348. The column name of the MAF.
  1349. event_rate : str,
  1350. The column name of the proportion of cases in the source GWAS. Only
  1351. applicable for a binary trait GWAS.
  1352. Returns
  1353. -------
  1354. a pd.df with the remaining significant variants after masking.
  1355. """
  1356. # @@@ Check if core data is present
  1357. is_type(dt, pd.DataFrame)
  1358. is_type(corr_matrix, (pd.DataFrame, type(None)))
  1359. core_cols = [variant_id, sample_size ]
  1360. are_columns_in_df(dt, core_cols)
  1361. # Checking methods
  1362. if not isinstance(method, str):
  1363. raise AttributeError( "The method type should be a string")
  1364. if not method in Coloc_Expected.METHODS:
  1365. raise AttributeError(
  1366. "`method` should be a string. Pick from: {}".\
  1367. format(Coloc_Expected.METHODS)
  1368. )
  1369. # Check input for `cond` or 'mask'
  1370. if method == "cond" or method =='mask':
  1371. # Check if we have an correlation matrix
  1372. if corr_matrix is None:
  1373. raise AttributeError(" The `corr_matrix` is missing")
  1374. # check if corr_matrix and dt have the same entries
  1375. # and align dt and corr_matrix entries
  1376. _process_corr_mat(data=dt, corr_matrix=corr_matrix,
  1377. index_col=Coloc_Expected.VARIANT_ID
  1378. )
  1379. # Check input for `cond`
  1380. if method == "cond":
  1381. # Check if MAF is present
  1382. are_columns_in_df(dt, minor_allele_freq)
  1383. # Is the trait standard deviation present or do we need to calculate it
  1384. if not standard_deviation in dt.columns:
  1385. dt[standard_deviation] = sd_pheno_approx(variancebeta=dt[varbeta],
  1386. maf=dt[minor_allele_freq],
  1387. n=dt[sample_size]
  1388. )
  1389. # Check if the sigsnps and dt have the same entries
  1390. # if len(sigsnps) > 0:
  1391. # are_Series_equal(dt[variant_id], pd.Series(sigsnps),
  1392. # objects_names=['dt', 'sigsnps']
  1393. # )
  1394. # map p-values to z-values
  1395. if 0 <= pthr <= 1:
  1396. zthr = scipy.stats.norm.ppf(1- pthr/2)
  1397. else:
  1398. raise ValueError("Please supply `pthr` as a value between 0 and 1")
  1399. # Calculate mean differences
  1400. if method == "cond" and effect_type == "bin":
  1401. if verbose is True:
  1402. warnings.warn("[info] Approximating linear analysis of binary trait")
  1403. # bin2lin will also check dt columns are correct.
  1404. dt, _ = bin2lin(dt,
  1405. variant_id=variant_id,
  1406. effect_size=effect_size,
  1407. varbeta=varbeta,
  1408. event_rate=event_rate,
  1409. sample_size=sample_size,
  1410. minor_allele_freq=minor_allele_freq
  1411. )
  1412. # get YY
  1413. YY = est_YY(dt=dt, effect_type=effect_type,
  1414. standard_deviation=standard_deviation,
  1415. sample_size=sample_size,
  1416. event_rate=event_rate)
  1417. # getting variants hits
  1418. hits = pd.DataFrame()
  1419. while hits.shape[1] < maxhits:
  1420. if method == "mask":
  1421. # Masking
  1422. newhit = map_mask(dt=dt, corr_matrix=corr_matrix, r2thr=r2thr,
  1423. sigsnps=hits.columns.unique().tolist(),
  1424. variant_id=variant_id, effect_size=effect_size,
  1425. zstatistic=zstatistic,
  1426. varbeta=varbeta
  1427. )
  1428. else:
  1429. # getting conditional hits
  1430. newhit = map_cond(dt=dt, corr_matrix=corr_matrix, YY=YY,
  1431. sigsnps=hits.columns.tolist(),
  1432. effect_type=effect_type,
  1433. variant_id=variant_id,
  1434. effect_size=effect_size,
  1435. varbeta=varbeta,
  1436. event_rate=event_rate,
  1437. sample_size=sample_size,
  1438. minor_allele_freq=minor_allele_freq
  1439. )
  1440. # DO we need to exit?
  1441. if newhit is None or newhit.empty:
  1442. break
  1443. # otherwise get value
  1444. # NOTE might change newhit to a dictionary - this is just painfull
  1445. value = np.array(newhit)[0][0]
  1446. # if not sufficiently significant stop
  1447. if np.abs(value) < zthr:
  1448. break
  1449. hits = pd.concat([hits, newhit], axis=1)
  1450. # stop after one hit
  1451. if method=="single":
  1452. break
  1453. return hits
  1454. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1455. # DONE
  1456. def gethits(hits, i, mode="iterative"):
  1457. """
  1458. Implements `gethits`, from coloc R:
  1459. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L378>`__.
  1460. Arguments
  1461. ---------
  1462. hits : list of strings
  1463. A list of strings of variant ids
  1464. i : float
  1465. mode : str, default iterative,
  1466. Mode is either `iterative` or `allbutone`.
  1467. Returns
  1468. -------
  1469. A numpy.array
  1470. """
  1471. # @@@ checking input
  1472. is_type(hits, list)
  1473. is_type(i, (np.int32,np.int16, np.int32, np.int64, int,float))
  1474. is_type(mode, str)
  1475. # check modes
  1476. if not mode in Coloc_Expected.MODES:
  1477. raise AttributeError(
  1478. "`modes` should be a str: {}".format(Coloc_Expected.MODES)
  1479. )
  1480. # @@@ algorithm
  1481. # copy, so we save the original
  1482. hits_c = hits.copy()
  1483. if mode == "allbutone":# mask everything else
  1484. # remove the ith element
  1485. del hits_c[i]
  1486. ret = hits_c
  1487. elif mode == "iterative": # mask everything earlier in list
  1488. if i == 0:
  1489. return np.empty(0, dtype='U5')
  1490. else:
  1491. # everything until i (excluding i)
  1492. ret = hits_c[:i]
  1493. # ret = hits[0:(i)]
  1494. # return
  1495. return np.setdiff1d(ret, "")
  1496. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1497. # DONE
  1498. def coloc_process(coloc_res,
  1499. hits1: list=None,
  1500. hits2: list=None,
  1501. corr_exp: pd.DataFrame=None,
  1502. corr_out: pd.DataFrame=None,
  1503. r2thr=0.01,
  1504. p1=10**-4,
  1505. p2=10**-4,
  1506. p12=5*10**-6,
  1507. mode="iterative",
  1508. verbose=True):
  1509. """Generates a coloc results table, used by coloc_signal.
  1510. Parameters
  1511. ----------
  1512. coloc_res : ColocResults,
  1513. ColocResults object output by `coloc_single`.
  1514. hits1, hits2 : list of strings,
  1515. strings from `gethits` representing the hits in dataset1 and
  1516. dataset2. If the array contain more than one lead variant -> masking.
  1517. corr_(exp|out) : pd.DF,
  1518. The correlation matrix of the variants in `dt`, for the exposure and
  1519. outcome data respectively.
  1520. r2thr : float, default 0.01
  1521. Mask variants with a squared correlation with any of the `sigsnps`
  1522. larger than `r2thr`.
  1523. p1 : `float`, optional, default: `1E-4`
  1524. Prior probability for H1 (variant only associated with trait 1)
  1525. p2 : `float`, optional, default: `1E-4`
  1526. Prior probability for H2 (variant only associated with trait 2)
  1527. p12 : `float`, optional, default: `5E-6`,
  1528. Prior probability for H4 (associated with both traits).
  1529. mode : float, default: `iterative`,
  1530. `iterative` or `allbutone`; to either successively condition on signals
  1531. find all putative signals and condition on all but one of them in each
  1532. analysis.
  1533. Returns
  1534. -------
  1535. results : `pandas.DataFrame`
  1536. The coloc results.
  1537. Notes
  1538. -----
  1539. Implements `finemap.signals`, from coloc R, see the `chris wallace
  1540. <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df8
  1541. 4d7eba39d92a8c1/R/split.R#L408>`_ implementation.
  1542. The H3 probability (causal variants for distinct traits) is calculated
  1543. internally from the product of p1 and p2. See supplemental of the
  1544. `original publication` <https://journals.plos.org/plosgenetics/article
  1545. ?id=10.1371/journal.pgen.1004383#s5>`_.
  1546. """
  1547. # @@@ Check
  1548. is_type(coloc_res, ColocResults)
  1549. is_type(corr_exp, (pd.DataFrame, type(None)))
  1550. is_type(corr_out, (pd.DataFrame, type(None)))
  1551. is_type(hits1, (list, type(None)))
  1552. is_type(hits2, (list, type(None)))
  1553. is_type(r2thr, float)
  1554. is_type(p1, float)
  1555. is_type(p2, float)
  1556. is_type(p12, float)
  1557. is_type(mode, str)
  1558. # check modes
  1559. if not mode in Coloc_Expected.MODES:
  1560. raise AttributeError(
  1561. "`modes` should be a str: {}".format(Coloc_Expected.MODES)
  1562. )
  1563. # check rsquared
  1564. if not (0 <= r2thr <= 1):
  1565. raise ValueError('Please define r2thr within the range [0, 1]')
  1566. # @@@ Actual calculations
  1567. # NOTE `.f` is moved to `approx_bf`
  1568. # see R coloc `.f`: https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L455
  1569. if hits1 is None:
  1570. hits1 = ['']
  1571. #
  1572. if hits2 is None:
  1573. hits2 = ['']
  1574. # one signal per trait
  1575. if len(hits1) <= 1 and len(hits2) <= 1:
  1576. res_dict = {
  1577. c.COLOC_SUMMARY: coloc_res.results_dataframe(hit1=hits1,
  1578. hit2=hits2),
  1579. c.COLOC_RESULTS: coloc_res.variant_dataframe(),
  1580. c.COLOC_PRIORS: pd.DataFrame(
  1581. {
  1582. c.COLOC_PRIOR1 : p1,
  1583. c.COLOC_PRIOR2 : p2,
  1584. c.COLOC_PRIOR12 : p12
  1585. }, index=[0]
  1586. )
  1587. }
  1588. # break function
  1589. return res_dict
  1590. # otherwise mask
  1591. # check corr_matrix
  1592. if (len(hits1) > 1 or len(hits2) > 1) and (corr_exp is None or corr_out is None):
  1593. raise AttributeError('Please supply `corr_matrix` as a pd.DataFrame')
  1594. # - allbutone : all but one signal from each trait with >1 signal
  1595. # - iterative : successively one more signal from each trait with >1 signal
  1596. ldfriends1 = np.power(corr_exp.loc[hits1, :], 2)
  1597. ldfriends2 = np.power(corr_out.loc[hits2, :], 2)
  1598. i = [idx for idx,value in enumerate(hits1)]
  1599. j = [idx for idx,value in enumerate(hits2)]
  1600. # procudes an i*j by 2 matrix
  1601. todo=pd.DataFrame(itertools.product(i,j))
  1602. # interchanges columns, followed by a transpose
  1603. todo=todo.iloc[:, [1,0]].T
  1604. # getting pd.Series with variant names
  1605. newresult = coloc_res.variant_dataframe()[[c.UNIVERSAL_ID.name]].copy()
  1606. res = pd.DataFrame()
  1607. for r in list(range(0,todo.shape[1])):
  1608. i = todo.iloc[1, r]
  1609. j = todo.iloc[0, r]
  1610. if verbose == True:
  1611. print("[info] {0}".format(r+1))
  1612. # collecting hits
  1613. drop1, drop2 = ([] for i in range(2))
  1614. # hit 1
  1615. if len(hits1) > 1:
  1616. # int(i) to get non-numpy ints
  1617. ihits1 = gethits(hits1, int(i), mode)
  1618. if len(ihits1) >= 1:
  1619. ld_out1 = ldfriends1.loc[ihits1, ]
  1620. drop1 = ldfriends1[ld_out1 > r2thr].\
  1621. dropna(thresh=1, axis=1).columns.tolist()
  1622. # hit 2
  1623. if len(hits2) > 1:
  1624. ihits2 = gethits(hits2, int(j), mode)
  1625. if len(ihits2) >= 1:
  1626. # if ihits2 != None:
  1627. ld_out2 = ldfriends2.loc[ihits2, ]
  1628. drop2 = ldfriends2[ld_out2 > r2thr].\
  1629. dropna(thresh=1, axis=1).columns.tolist()
  1630. # dropping variants
  1631. # finding the union
  1632. dropsnps = list(set(drop1+drop2))
  1633. if verbose == True:
  1634. # COLOR R uses message -> warnings
  1635. warnings.warn("[info] dropping {0}/{1}: {2} (hits1) + {3} (hits2)"
  1636. "".format(
  1637. len(dropsnps),
  1638. coloc_res.variant_dataframe().shape[0],
  1639. len(drop1),
  1640. len(drop2)
  1641. )
  1642. )
  1643. # NOTE returns an empty dict
  1644. if len(np.setdiff1d(coloc_res.variant_dataframe()[c.UNIVERSAL_ID.name],
  1645. dropsnps)) <=1:
  1646. res_dict = {
  1647. c.COLOC_SUMMARY: pd.DataFrame(),
  1648. c.COLOC_RESULTS: pd.DataFrame(),
  1649. c.COLOC_PRIORS: pd.DataFrame(
  1650. {
  1651. c.COLOC_PRIOR1 : p1,
  1652. c.COLOC_PRIOR2 : p2,
  1653. c.COLOC_PRIOR12 : p12
  1654. }, index=[0]
  1655. )
  1656. }
  1657. return res_dict
  1658. ### getting results object
  1659. # NOTE ADD some of these strings to merit.constant
  1660. df = coloc_res.variant_dataframe().copy()
  1661. # get ABF
  1662. df[Coloc_Expected.EXPOSURE_ABF] = np.where(
  1663. df[c.UNIVERSAL_ID.name].isin(drop1), -1.1,
  1664. df[Coloc_Expected.EXPOSURE_ABF])
  1665. df[Coloc_Expected.OUTCOME_ABF] = np.where(
  1666. df[c.UNIVERSAL_ID.name].isin(drop2), -1.1,
  1667. df[Coloc_Expected.OUTCOME_ABF])
  1668. # isum
  1669. df.loc[:,c.COLOC_INTERNAL_SUM] =\
  1670. np.where(df[c.UNIVERSAL_ID.name].isin(dropsnps), -1.1,
  1671. df[c.COLOC_INTERNAL_SUM].copy()).copy()
  1672. my_denom_log_abf = _logsum(df[c.COLOC_INTERNAL_SUM].values)
  1673. # new results
  1674. sum_temp1 = np.exp(
  1675. df[c.COLOC_INTERNAL_SUM].copy().values - my_denom_log_abf
  1676. ).tolist()
  1677. newresult["SNP.PP.H4.row"+str(r)] = sum_temp1
  1678. newresult.loc[:,"z.df1.row"+str(r)] = np.where(
  1679. df[c.UNIVERSAL_ID.name].isin(drop1), 0,
  1680. df[Coloc_Expected.EXPOSURE_Z_STATISTICS].copy()
  1681. ).copy()
  1682. newresult.loc[:, "z.df2.row"+str(r)] = np.where(
  1683. df[c.UNIVERSAL_ID.name].isin(drop2), 0,
  1684. df[Coloc_Expected.OUTCOME_ABF].copy()
  1685. ).copy()
  1686. # subsetting df3
  1687. df3 = pd.DataFrame(itertools.product(
  1688. coloc_res.variant_dataframe()[c.UNIVERSAL_ID.name],
  1689. coloc_res.variant_dataframe()[c.UNIVERSAL_ID.name]),
  1690. columns=["uni_id_exposure", "uni_id_outcome"]
  1691. )
  1692. df3 = df3.merge(
  1693. df[[Coloc_Expected.EXPOSURE_ABF, c.UNIVERSAL_ID.name]],
  1694. left_on = "uni_id_exposure", right_on = c.UNIVERSAL_ID.name
  1695. )
  1696. df3 = df3.merge(
  1697. df[[Coloc_Expected.OUTCOME_ABF, c.UNIVERSAL_ID.name]],
  1698. left_on = "uni_id_outcome", right_on = c.UNIVERSAL_ID.name
  1699. )
  1700. df3 = df3[["uni_id_exposure",
  1701. "uni_id_outcome",
  1702. Coloc_Expected.EXPOSURE_ABF,
  1703. Coloc_Expected.OUTCOME_ABF
  1704. ]]
  1705. # subsetting ln_abs_h3
  1706. df3["ln_abf_h3"] = df3[Coloc_Expected.EXPOSURE_ABF] +\
  1707. df3[Coloc_Expected.OUTCOME_ABF].copy()
  1708. df3["ln_abf_h3"] = np.where(
  1709. df3["uni_id_exposure"].isin(drop1), 0, df3["ln_abf_h3"])
  1710. df3["ln_abf_h3"] = np.where(
  1711. df3["uni_id_outcome"].isin(drop2), 0, df3["ln_abf_h3"])
  1712. # res object
  1713. # df3.ln_abf_h3 and df.isum are empty unless `method == mask`
  1714. ret = pd.DataFrame(approx_bf(df.ln_abf_exposure,
  1715. df.ln_abf_outcome,
  1716. masking_ln_abf3=df3.ln_abf_h3,
  1717. masking_ln_abf4=df.isum,
  1718. p1=p1,
  1719. p2=p2,
  1720. p12=p12,
  1721. extra_info=True),
  1722. index=[0])
  1723. # get best varaints
  1724. # ret[c.COLOC_BEST1] = hits1[todo.iloc[1,r]]
  1725. # ret[c.COLOC_BEST2] = hits2[todo.iloc[0,r]]
  1726. # hits
  1727. ret[c.COLOC_HIT1] = hits1[todo.iloc[1,r]]
  1728. ret[c.COLOC_HIT2] = hits2[todo.iloc[0,r]]
  1729. # number of variants
  1730. ret['nsnps'] = df.shape[0]
  1731. res = pd.concat([res, ret])
  1732. # adding hits
  1733. res_dict = {
  1734. c.COLOC_SUMMARY: res,
  1735. c.COLOC_RESULTS: newresult,
  1736. c.COLOC_PRIORS: pd.DataFrame(
  1737. {
  1738. c.COLOC_PRIOR1 : p1,
  1739. c.COLOC_PRIOR2 : p2,
  1740. c.COLOC_PRIOR12 : p12
  1741. }, index=[0]
  1742. )
  1743. }
  1744. return res_dict
  1745. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1746. # DONE
  1747. def coloc_single(inst: pd.DataFrame,
  1748. p1: float=10**-4,
  1749. p2: float=10**-4,
  1750. p12: float=5*10**-6,
  1751. effect_type: list=["quant","quant"],
  1752. variant_id: str=Coloc_Expected.VARIANT_ID,
  1753. exposure_effect_size: str=Coloc_Expected.EXPOSURE_EFFECT_SIZE,
  1754. outcome_effect_size: str=Coloc_Expected.OUTCOME_EFFECT_SIZE,
  1755. exposure_varbeta: str=Coloc_Expected.EXPOSURE_VARBETA,
  1756. outcome_varbeta: str=Coloc_Expected.OUTCOME_VARBETA,
  1757. exposure_zstatistic: str=Coloc_Expected.EXPOSURE_Z_STATISTICS,
  1758. outcome_zstatistic: str=Coloc_Expected.OUTCOME_Z_STATISTICS,
  1759. exposure_standard_deviation: str=Coloc_Expected.EXPOSURE_STANDARD_DEVIATION,
  1760. outcome_standard_deviation: str=Coloc_Expected.OUTCOME_STANDARD_DEVIATION,
  1761. exposure_sample_size: str=Coloc_Expected.EXPOSURE_SAMPLE_SIZE,
  1762. outcome_sample_size: str=Coloc_Expected.OUTCOME_SAMPLE_SIZE,
  1763. minor_allele_freq: str=Coloc_Expected.MINOR_ALLELE_FREQ,
  1764. extra_info: bool=False):
  1765. """
  1766. Coloc main function, is similar to coloc.details in R coloc. It started
  1767. out as a port of `coloc.abf` (assuming a single causal variant).
  1768. Will calculate `sdY`, the phenotypic standard deviation if this is not
  1769. provided by `inst` and type == `quant`.
  1770. Will compute the z-statistic if not provided from the effect_size and
  1771. varbeta.
  1772. Similar to `coloc.detail`, from coloc R:
  1773. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L21>`__.
  1774. And to `coloc.abf`:
  1775. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L21>`__.
  1776. Arguments
  1777. ---------
  1778. inst : pd.DF
  1779. A coloc.DataFrame.
  1780. p1, p2, p12 : float,
  1781. prior probabilities for H1 (variant only associated with trait 1),
  1782. for H2 (only with trait 2), or H4 (associated with both traits).
  1783. See supplemental of:
  1784. `Here <https://journals.plos.org/plosgenetics/article?id=10.1371/journal.pgen.1004383#s5>`__.
  1785. effect_type : list of str, default `quant`,
  1786. The effect_types or the `_exposure` and `_outcome` data.
  1787. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  1788. case-control/binary.
  1789. effect_size : str,
  1790. The effect size column names for the `exposure` and `outcome` data.
  1791. varbeta : str,
  1792. The variance of the effect size column names for the
  1793. `exposure` and `outcome` data.
  1794. zstatistics : str,
  1795. The z-statistics column names for the `exposure` and `outcome` data.
  1796. Note these can be readily obtained from the GWAS p-values. Can be
  1797. estimated from the effect_size and varbeta columns, if absent.
  1798. standard_deviation : str,
  1799. The column name of the gwas trait variance (will be estimated if
  1800. absent).
  1801. sample_size : str,
  1802. The column name of the GWAS sample size.
  1803. minor_allele_freq : str,
  1804. The column name of the MAF.
  1805. extra_info : boolean, default False
  1806. If additional information should be returned (mimicking coloc.details),
  1807. otherwise (defaults) output will be similar to coloc.abf.
  1808. Returns
  1809. -------
  1810. A `ColocResults` object.
  1811. """
  1812. # @@@ checks
  1813. # core data needed
  1814. core_cols = [variant_id, exposure_varbeta, outcome_varbeta,
  1815. exposure_zstatistic, outcome_zstatistic, minor_allele_freq ]
  1816. # are_columns_in_df(inst, core_cols)
  1817. # unpacking effect type
  1818. if np.all([ t in Coloc_Expected.TRAIT_TYPES for t in effect_type ]):
  1819. type_exposure, type_outcome = effect_type
  1820. # if needed estimate sdY
  1821. # check if standard deviation is provided
  1822. if (not exposure_standard_deviation in inst.columns) and\
  1823. (type_exposure == "quant"):
  1824. inst[exposure_standard_deviation] =\
  1825. sd_pheno_approx(
  1826. variancebeta=inst[exposure_varbeta],
  1827. maf=inst[minor_allele_freq],
  1828. n=inst[exposure_sample_size]
  1829. )
  1830. if (not outcome_standard_deviation in inst.columns) and\
  1831. (type_outcome == "quant"):
  1832. inst[outcome_standard_deviation] =\
  1833. sd_pheno_approx(
  1834. variancebeta=inst[outcome_varbeta],
  1835. maf=inst[minor_allele_freq],
  1836. n=inst[outcome_sample_size]
  1837. )
  1838. # set the variance prior
  1839. if type_exposure == "quant":
  1840. sd_prior_exp = 0.15 * inst[exposure_standard_deviation]
  1841. elif type_exposure == "bin":
  1842. # for case control (binary data)
  1843. sd_prior_exp = 0.20
  1844. else:
  1845. raise ValueError("Please specify `effect_type` two element list with {0} or {1}".\
  1846. format(*Coloc_Expected.TRAIT_TYPES))
  1847. if type_outcome == "quant":
  1848. sd_prior_out = 0.15 * inst[outcome_standard_deviation]
  1849. elif type_outcome == "bin":
  1850. # for case-control (binary data)
  1851. sd_prior_out = 0.20
  1852. else:
  1853. raise ValueError("Please specify `effect_type` two element list with {0} or {1}".\
  1854. format(*Coloc_Expected.TRAIT_TYPES))
  1855. # if needed calculate z normal deviate
  1856. if not exposure_zstatistic in inst.columns:
  1857. inst[exposure_zstatistic] =\
  1858. np.divide( inst[exposure_effect_size], np.sqrt(inst[exposure_varbeta])
  1859. )
  1860. if not outcome_zstatistic in inst.columns:
  1861. inst[outcome_zstatistic] =\
  1862. np.divide( inst[outcome_effect_size], np.sqrt(inst[outcome_varbeta])
  1863. )
  1864. # extract core columns
  1865. dt = inst[core_cols].copy()
  1866. # Shrinkage factor: ratio of the prior variance to the total variance
  1867. dt[Coloc_Expected.EXPOSURE_SHRINKAGE_FACTOR] = \
  1868. np.divide(
  1869. np.power(sd_prior_exp, 2),
  1870. (np.power(sd_prior_exp, 2) + dt[exposure_varbeta])
  1871. )
  1872. dt[Coloc_Expected.OUTCOME_SHRINKAGE_FACTOR] = \
  1873. np.divide(
  1874. np.power(sd_prior_out, 2),
  1875. (np.power(sd_prior_out, 2) + dt[outcome_varbeta])
  1876. )
  1877. # appending the log (approximate) Bayes Factor for colocalization
  1878. dt[Coloc_Expected.EXPOSURE_ABF] =\
  1879. (0.5 *
  1880. (
  1881. np.log(1 - dt[Coloc_Expected.EXPOSURE_SHRINKAGE_FACTOR]) +
  1882. dt[Coloc_Expected.EXPOSURE_SHRINKAGE_FACTOR] *
  1883. np.power( dt[Coloc_Expected.EXPOSURE_Z_STATISTICS], 2)
  1884. )
  1885. )
  1886. dt[Coloc_Expected.OUTCOME_ABF] =\
  1887. (0.5 *
  1888. (
  1889. np.log(1 - dt[Coloc_Expected.OUTCOME_SHRINKAGE_FACTOR]) +
  1890. dt[Coloc_Expected.OUTCOME_SHRINKAGE_FACTOR] *
  1891. np.power( dt[Coloc_Expected.OUTCOME_Z_STATISTICS], 2)
  1892. )
  1893. )
  1894. # INTERNAL SUM Just wrapping in tuple to get better code-alignment
  1895. dt[c.COLOC_INTERNAL_SUM] = (dt[Coloc_Expected.EXPOSURE_ABF] +
  1896. dt[Coloc_Expected.OUTCOME_ABF]
  1897. )
  1898. # posterior prob that each SNP is THE causal variant for a shared signal
  1899. dt[c.COLOC_PPH4] = np.exp(dt[c.COLOC_INTERNAL_SUM] -
  1900. _logsum(dt[c.COLOC_INTERNAL_SUM]))
  1901. # coloc summary following classic coloc
  1902. if extra_info is False:
  1903. summary = approx_bf(ln_abf1=dt[Coloc_Expected.EXPOSURE_ABF],
  1904. ln_abf2=dt[Coloc_Expected.OUTCOME_ABF],
  1905. p1=p1, p2=p2, p12=p12, extra_info=extra_info
  1906. )
  1907. # results
  1908. res = {c.COLOC_SUMMARY: summary, c.COLOC_VARIANT_RESULTS: dt}
  1909. elif extra_info is True:
  1910. summary = approx_bf(ln_abf1=dt[Coloc_Expected.EXPOSURE_ABF],
  1911. ln_abf2=dt[Coloc_Expected.OUTCOME_ABF],
  1912. p1=p1, p2=p2, p12=p12, extra_info=extra_info
  1913. )
  1914. summary[c.COLOC_BEST1] = dt.loc[ summary[c.COLOC_BEST1], variant_id]
  1915. summary[c.COLOC_BEST2] = dt.loc[ summary[c.COLOC_BEST2], variant_id]
  1916. summary[c.COLOC_BEST4] = dt.loc[ summary[c.COLOC_BEST4], variant_id]
  1917. # results
  1918. res = {c.COLOC_SUMMARY: summary, c.COLOC_VARIANT_RESULTS: dt}
  1919. else:
  1920. raise ValueError("`extra_info` is a boolean")
  1921. # returning
  1922. return ColocResults(**res)
  1923. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  1924. class ColocResults(object):
  1925. """Return Results object."""
  1926. ALL_ARGS = [c.COLOC_SUMMARY, c.COLOC_VARIANT_RESULTS]
  1927. # SET_ARGS = ['uni_id', 'b', 'se_fixed', 'pvalue_fixed', 'instrument']
  1928. IGNORE_ARGS = [c.COLOC_VARIANT_RESULTS]
  1929. # OUT_ARGS = [el for el in ALL_ARGS if el not in IGNORE_ARGS]
  1930. OUT_ARGS = [c.COLOC_SUMMARY]
  1931. def __init__(self, **kwargs):
  1932. """
  1933. Initialise
  1934. """
  1935. # Check that we have not supplied any arguments that can't be set
  1936. for k in kwargs.keys():
  1937. if k not in self.__class__.ALL_ARGS:
  1938. raise AttributeError("unrecognised argument '{0}'".format(k))
  1939. for s in self.__class__.ALL_ARGS:
  1940. try:
  1941. setattr(self, s, kwargs[s])
  1942. except KeyError:
  1943. warnings.warn("argument '{0}' is set to 'None'".format(s))
  1944. setattr(self, s, None)
  1945. def __repr__(self):
  1946. values = ",".join(["{0}={1}".format(s, getattr(self, s))
  1947. for s in self.__class__.ALL_ARGS if s not in
  1948. self.__class__.IGNORE_ARGS])
  1949. return "{0}({1})".format(self.__class__.__name__, values)
  1950. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1951. @property
  1952. def nsnps(self):
  1953. """Return the number of variants."""
  1954. return len(self.variant_results[c.UNIVERSAL_ID.name])
  1955. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1956. def results_dataframe(self, **kwargs):
  1957. """Return dataframe with summary results."""
  1958. pp_dict = [getattr(self, s) for s in self.__class__.OUT_ARGS][0]
  1959. # check if is a dict or a pd.DF
  1960. # if a pd.DF - assume it is already formatted.
  1961. if isinstance(pp_dict, dict):
  1962. dt = pd.DataFrame([[float(self.nsnps)] + list(pp_dict.values())],
  1963. columns=[c.MR_NSNPS] + list(pp_dict.keys()))
  1964. for k, v in kwargs.items():
  1965. dt.loc[:, k] = v
  1966. elif isinstance(pp_dict, pd.DataFrame):
  1967. dt = pp_dict
  1968. # return stuff
  1969. return dt
  1970. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1971. def variant_dataframe(self, **kwargs):
  1972. """Return dataframe with individual variant results."""
  1973. return self.variant_results
  1974. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1975. # def results_H3_dataframe(self, **kwargs):
  1976. # """Return dataframe with individual variant results."""
  1977. # return self.results_H3
  1978. # @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  1979. # MAIN ENTRY POINT
  1980. # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  1981. # DONE
  1982. def coloc_signals(input_data,
  1983. corr_matrix,
  1984. method=["single", "single"],
  1985. mode="iterative",
  1986. p1=1e-4,
  1987. p2=1e-4,
  1988. p12=5*10**-6,
  1989. maxhits=3,
  1990. r2thr=0.01,
  1991. pthr = 1e-06,
  1992. verbose=True,
  1993. effect_type=["quant","quant"],
  1994. variant_id=Coloc_Expected.VARIANT_ID,
  1995. exposure_effect_size=Coloc_Expected.EXPOSURE_EFFECT_SIZE,
  1996. outcome_effect_size=Coloc_Expected.OUTCOME_EFFECT_SIZE,
  1997. exposure_varbeta=Coloc_Expected.EXPOSURE_VARBETA,
  1998. outcome_varbeta=Coloc_Expected.OUTCOME_VARBETA,
  1999. exposure_zstatistic: str=Coloc_Expected.EXPOSURE_Z_STATISTICS,
  2000. outcome_zstatistic: str=Coloc_Expected.OUTCOME_Z_STATISTICS,
  2001. exposure_standard_deviation=Coloc_Expected.EXPOSURE_STANDARD_DEVIATION,
  2002. outcome_standard_deviation=Coloc_Expected.OUTCOME_STANDARD_DEVIATION,
  2003. exposure_sample_size=Coloc_Expected.EXPOSURE_SAMPLE_SIZE,
  2004. outcome_sample_size=Coloc_Expected.OUTCOME_SAMPLE_SIZE,
  2005. exposure_event_rate=Coloc_Expected.EXPOSURE_EVENT_RATE,
  2006. outcome_event_rate=Coloc_Expected.OUTCOME_EVENT_RATE,
  2007. minor_allele_freq=Coloc_Expected.MINOR_ALLELE_FREQ
  2008. ):
  2009. """
  2010. The main entry point for coloc to consider multiple causal signals.
  2011. Implements `coloc.signals`, from coloc R:
  2012. `Here <https://github.com/chr1swallace/coloc/blob/eca81cdd0e5cc9fc0c5dd4df84d7eba39d92a8c1/R/split.R#L760>`__.
  2013. Arguments
  2014. ---------
  2015. input_data : pd.DF,
  2016. A coloc pd.dataframe, with columns ... . The original coloc `data1` and
  2017. `data2` are combined in this single dataframe, with column suffixes
  2018. `_exposure` and `_outcome` respectivly.
  2019. corr_matrix : pd.DF,
  2020. The correlation matrix of the variants in `input_data`. If the expousre
  2021. and outcome GWAS are from distinct populations please supply a list of
  2022. two matrices [exposure_cor, outcome_cor]. Note is only required when
  2023. `method` is `cond` or 'mask'.
  2024. method : list of two strings, default `single`,
  2025. The method to apply to the `_exposure` and `_outcome` data.
  2026. Use `single` to perform coloc assuming there is a single causal
  2027. variant - equivalent to the original coloc implementation,
  2028. Use `cond` to perform a stepwise conditional analysis,
  2029. Use `mask` to perform the stepwise selection algorithm using masking
  2030. instead.
  2031. mode : float, default `iterative`,
  2032. `iterative` or `allbutone`; to either successively condition on signals
  2033. find all putative signals and condition on all but one of them in each
  2034. analysis.
  2035. p1, p2, p12 : float,
  2036. prior probabilities for H1 (any random variant only associated with
  2037. trait 1), for H2 (only with trait 2), or H4 (associated with both traits).
  2038. H3 (causal variants for distinct traits) is simply the product of
  2039. p1 and p2. See supplemental of:
  2040. `Here <https://journals.plos.org/plosgenetics/article?id=10.1371/journal.pgen.1004383#s5>`__.
  2041. Note that when the data is sufficiently large, p1 and p2 can often be
  2042. estimated from the emperical data under consideration.
  2043. pthr : float, default 1 \times 10^{-6},
  2044. Stops when p-values are larger than `pthr`, where input should be
  2045. defined between 0 and 1.
  2046. maxhits : int, default 3,
  2047. Stops when the procedures finds `maxhits`.
  2048. r2thr : float, default 0.01
  2049. Mask variants with a squared correlation with any of the `sigsnps`
  2050. larger than `r2thr`.
  2051. effect_type : list of str, default `quant`,
  2052. The effect_types or the `_exposure` and `_outcome` data.
  2053. If the GWAS trait was `quant`: continuous/quantative, or `bin`:
  2054. case-control/binary.
  2055. Returns
  2056. -------
  2057. Resutns a pd.DF of coloc results.
  2058. """
  2059. # @@@ CHECKING INPUT
  2060. # Check if core data is present
  2061. is_type(input_data, pd.DataFrame)
  2062. is_type(corr_matrix, (pd.DataFrame, type(None), list))
  2063. is_type(effect_type, list)
  2064. is_type(method, list)
  2065. is_type(mode, str)
  2066. # expected columns
  2067. Exposure_col = [variant_id, exposure_effect_size, exposure_varbeta,
  2068. exposure_zstatistic, exposure_standard_deviation,
  2069. exposure_sample_size, exposure_event_rate,
  2070. minor_allele_freq]
  2071. Outcome_col = [variant_id, outcome_effect_size, outcome_varbeta,
  2072. outcome_zstatistic, outcome_standard_deviation,
  2073. outcome_sample_size, outcome_event_rate,
  2074. minor_allele_freq]
  2075. # Check if effect types are correct
  2076. if np.all([ t in Coloc_Expected.TRAIT_TYPES for t in effect_type ]):
  2077. effect_type_exp = effect_type[0]
  2078. effect_type_out = effect_type[1]
  2079. else:
  2080. raise ValueError(
  2081. "`effect_type` should be a lis with two entries. Pick from: {}".\
  2082. format(Coloc_Expected.TRAIT_TYPES)
  2083. )
  2084. # Checking methods
  2085. if len(method) != 2:
  2086. raise AttributeError( "`method` should be a list with two entries")
  2087. if not np.all([ t in Coloc_Expected.METHODS for t in method ]):
  2088. raise ValueError(
  2089. "`method` should be a list with two entries. Pick from: {}".\
  2090. format(Coloc_Expected.METHODS)
  2091. )
  2092. else:
  2093. method_exp=method[0]
  2094. method_out=method[1]
  2095. # check modes
  2096. if not mode in Coloc_Expected.MODES:
  2097. raise ValueError(
  2098. "`modes` should be a str: {}".format(Coloc_Expected.MODES)
  2099. )
  2100. # Checking columns content
  2101. # are_columns_in_df(input_data, list(set(Coloc_Expected.exposure_columns +
  2102. # Coloc_Expected.outcome_columns)))
  2103. # Check if all SNPs are in the corr_matrix
  2104. if np.any(method_exp in ['cond', 'mask']) or np.any(method_out in ['cond', 'mask']):
  2105. if isinstance(corr_matrix, list):
  2106. if len(corr_matrix) == 2:
  2107. corr_exp = corr_matrix[0]
  2108. corr_out = corr_matrix[1]
  2109. else:
  2110. raise IndexError("`corr_matrix`, please supply a single pd.DataFrame, or a list of two pd.DataFrame's")
  2111. else:
  2112. corr_exp = corr_matrix.copy()
  2113. corr_out = corr_matrix.copy()
  2114. print('test1')
  2115. # align with dt
  2116. _process_corr_mat(data=input_data, corr_matrix=corr_exp,
  2117. index_col=Coloc_Expected.VARIANT_ID
  2118. )
  2119. _process_corr_mat(data=input_data, corr_matrix=corr_out,
  2120. index_col=Coloc_Expected.VARIANT_ID
  2121. )
  2122. else:
  2123. # if single just assing whatever is corr_matrix
  2124. corr_exp = corr_matrix
  2125. corr_out = corr_matrix
  2126. # add standard deviation of Y
  2127. if not Coloc_Expected.EXPOSURE_STANDARD_DEVIATION in input_data.columns:
  2128. input_data[exposure_standard_deviation] =\
  2129. sd_pheno_approx(
  2130. variancebeta=input_data[exposure_varbeta],
  2131. maf=input_data[minor_allele_freq],
  2132. n=input_data[exposure_sample_size]
  2133. )
  2134. if not Coloc_Expected.OUTCOME_STANDARD_DEVIATION in input_data.columns:
  2135. input_data[outcome_standard_deviation] =\
  2136. sd_pheno_approx(
  2137. variancebeta=input_data[outcome_varbeta],
  2138. maf=input_data[minor_allele_freq],
  2139. n=input_data[outcome_sample_size]
  2140. )
  2141. # map p-value to z-statistics
  2142. if 0 <= pthr <= 1:
  2143. zthr = scipy.stats.norm.ppf(1- pthr/2)
  2144. else:
  2145. raise ValueError("Please supply `pthr` as a value between 0 and 1")
  2146. # @@@ DOING ACTUAL ANALYSES
  2147. # Finemaping
  2148. if verbose is True:
  2149. print("[info] Fine-mapping exposure")
  2150. # NOTE the .copy() is essential here because it overwrites some columns
  2151. # when set to effect_type = 'bin'
  2152. fm1 = finemap_signals(
  2153. input_data.loc[:,input_data.columns.isin(Exposure_col)].copy(),
  2154. corr_matrix=corr_exp, method=method_exp, maxhits=maxhits,
  2155. r2thr=r2thr, pthr=pthr, effect_type=effect_type_exp,
  2156. variant_id=variant_id,
  2157. effect_size=exposure_effect_size,
  2158. varbeta=exposure_varbeta,
  2159. zstatistic=exposure_zstatistic,
  2160. standard_deviation=exposure_standard_deviation,
  2161. sample_size=exposure_sample_size,
  2162. event_rate=exposure_event_rate,
  2163. minor_allele_freq=minor_allele_freq
  2164. )
  2165. if verbose is True:
  2166. print("[info] Variants independently associated in the exposure dataset")
  2167. print(fm1)
  2168. print('')
  2169. print("[info] Fine-mapping outcome")
  2170. fm2 = finemap_signals(
  2171. input_data.loc[:,input_data.columns.isin(Outcome_col)].copy(),
  2172. corr_matrix=corr_out, method=method_out, maxhits=maxhits,
  2173. r2thr=r2thr, pthr=pthr, effect_type=effect_type_out,
  2174. variant_id=variant_id,
  2175. effect_size=outcome_effect_size,
  2176. varbeta=outcome_varbeta,
  2177. zstatistic=outcome_zstatistic,
  2178. standard_deviation=outcome_standard_deviation,
  2179. sample_size=outcome_sample_size,
  2180. event_rate=outcome_event_rate,
  2181. minor_allele_freq=minor_allele_freq
  2182. )
  2183. if verbose is True:
  2184. print("[info] Variants independently associated in the outcome dataset")
  2185. print(fm2)
  2186. if fm1.empty:
  2187. fm1 = find_best_signal(input_data,
  2188. variant_id=variant_id,
  2189. effect_size=exposure_effect_size,
  2190. varbeta=exposure_varbeta,
  2191. )
  2192. if fm2.empty:
  2193. fm2 = find_best_signal(input_data,
  2194. variant_id=variant_id,
  2195. effect_size=outcome_effect_size,
  2196. varbeta=outcome_varbeta,
  2197. )
  2198. # conditionals if needed
  2199. if (not fm1.empty) and (method_exp == "cond"):
  2200. # NOTE the copy() is intended, the function overwrites columns
  2201. cond1 = est_all_cond(
  2202. dt=input_data.loc[:,input_data.columns.isin(Exposure_col)].copy(),
  2203. corr_matrix=corr_exp,
  2204. fm=fm1, mode=mode, effect_type=effect_type_exp,
  2205. variant_id=variant_id,
  2206. effect_size=exposure_effect_size,
  2207. varbeta=exposure_varbeta,
  2208. event_rate=exposure_event_rate,
  2209. sample_size=exposure_sample_size,
  2210. standard_deviation=exposure_standard_deviation,
  2211. minor_allele_freq=minor_allele_freq,
  2212. label=Coloc_Expected.exp_name
  2213. )
  2214. if (not fm2.empty) and (method_out == "cond"):
  2215. cond2 = est_all_cond(
  2216. dt=input_data.loc[:,input_data.columns.isin(Outcome_col)].copy(),
  2217. corr_matrix=corr_out,
  2218. fm=fm2, mode=mode, effect_type=effect_type_out,
  2219. variant_id=variant_id,
  2220. effect_size=outcome_effect_size,
  2221. varbeta=outcome_varbeta,
  2222. event_rate=outcome_event_rate,
  2223. sample_size=outcome_sample_size,
  2224. standard_deviation=outcome_standard_deviation,
  2225. minor_allele_freq=minor_allele_freq,
  2226. label=Coloc_Expected.out_name
  2227. )
  2228. # doublemask
  2229. doublemask_method = ['single', 'mask']
  2230. if not np.all([ t in Coloc_Expected.METHODS for t in doublemask_method ]):
  2231. raise AttributeError(
  2232. "`doublemask_method` should be a list with two entries. Pick from: {}".\
  2233. format(Coloc_Expected.METHODS)
  2234. )
  2235. if (method_exp in doublemask_method) and (method_out in doublemask_method):
  2236. # coloc_single == coloc.detail
  2237. coloc_res = coloc_single(inst =input_data.copy(),
  2238. p1=p1, p2=p2, p12=p12,
  2239. effect_type=effect_type,
  2240. variant_id=variant_id,
  2241. exposure_effect_size=exposure_effect_size,
  2242. outcome_effect_size=outcome_effect_size,
  2243. exposure_varbeta=exposure_varbeta,
  2244. outcome_varbeta=outcome_varbeta,
  2245. exposure_zstatistic=exposure_zstatistic,
  2246. outcome_zstatistic=outcome_zstatistic,
  2247. exposure_standard_deviation=exposure_standard_deviation,
  2248. outcome_standard_deviation=outcome_standard_deviation,
  2249. exposure_sample_size=exposure_sample_size,
  2250. outcome_sample_size=outcome_sample_size,
  2251. minor_allele_freq=minor_allele_freq,
  2252. extra_info=True)
  2253. # get results
  2254. proc_res_1 = coloc_process(coloc_res,
  2255. hits1=fm1.columns.tolist(),
  2256. hits2=fm2.columns.tolist(),
  2257. corr_exp = corr_exp,
  2258. corr_out = corr_out,
  2259. r2thr=r2thr, mode=mode,
  2260. p1=p1, p2=p2, p12=p12,
  2261. verbose=verbose
  2262. )
  2263. # get summary
  2264. proc_res = proc_res_1[c.COLOC_SUMMARY]
  2265. # double cond
  2266. if (method_exp in ["cond"]) and (method_out in ["cond"]):
  2267. i = [idx for idx, value in enumerate(cond1)]
  2268. j = [idx for idx, value in enumerate(cond2)]
  2269. todo = pd.DataFrame(itertools.product(i,j), columns=["i", "j"])
  2270. # resuts pd.DF
  2271. proc_res = pd.DataFrame()
  2272. proc_res_variants = pd.DataFrame(
  2273. {c.UNIVERSAL_ID.name: input_data[variant_id]})
  2274. # looping over rows
  2275. for k in list(range(todo.shape[0])):
  2276. cond_dt = pd.merge( cond1[list(cond1.keys())[todo.loc[k,"i"]]],
  2277. cond2[list(cond2.keys())[todo.loc[k,"j"]]])
  2278. ### Check if all the columns are there
  2279. column_check = [exposure_sample_size, outcome_sample_size,
  2280. exposure_standard_deviation,
  2281. outcome_standard_deviation,
  2282. minor_allele_freq]
  2283. cond_dt = _add_missing_columns(cond_dt, input_data.copy(),
  2284. columns=column_check,
  2285. index_col=variant_id)
  2286. # Getting coloc results
  2287. coloc_res = coloc_single(inst=cond_dt,
  2288. p1=p1, p2=p2, p12=p12,
  2289. effect_type=effect_type,
  2290. variant_id=variant_id,
  2291. exposure_effect_size=exposure_effect_size,
  2292. outcome_effect_size=outcome_effect_size,
  2293. exposure_varbeta=exposure_varbeta,
  2294. outcome_varbeta=outcome_varbeta,
  2295. exposure_zstatistic=exposure_zstatistic,
  2296. outcome_zstatistic=outcome_zstatistic,
  2297. exposure_standard_deviation=exposure_standard_deviation,
  2298. outcome_standard_deviation=outcome_standard_deviation,
  2299. exposure_sample_size=exposure_sample_size,
  2300. outcome_sample_size=outcome_sample_size,
  2301. minor_allele_freq=minor_allele_freq,
  2302. extra_info=True)
  2303. # Processing
  2304. proc_res_1 = coloc_process(coloc_res,
  2305. hits1=[fm1.columns.tolist()[todo.loc[k,"i"]]],
  2306. hits2=[fm2.columns.tolist()[todo.loc[k,"j"]]],
  2307. corr_exp = corr_exp,
  2308. corr_out = corr_out,
  2309. r2thr=r2thr, mode=mode, p1=p1, p2=p2,
  2310. p12=p12,
  2311. verbose=verbose
  2312. )
  2313. # Getting Summary reults
  2314. proc_res= pd.concat([proc_res, proc_res_1[c.COLOC_SUMMARY]])
  2315. # merging — use iteration-specific suffixes to avoid
  2316. # duplicate column names across signal-pair iterations
  2317. proc_res_variants = pd.merge(
  2318. proc_res_variants, coloc_res.variant_dataframe(),
  2319. left_on=c.UNIVERSAL_ID.name, right_on=c.UNIVERSAL_ID.name,
  2320. suffixes=("", "_{0}".format(k))
  2321. )
  2322. # cond mask/-
  2323. if method_exp in ["cond"] and method_out in ["mask", "single"]:
  2324. proc_res = pd.DataFrame()
  2325. # Looping over the keys
  2326. for k in list(range(len(cond1))):
  2327. # this merges cond1 = exposure with outcome data
  2328. cond_dt = pd.merge(
  2329. cond1[list(cond1.keys())[k]],
  2330. input_data.loc[:,input_data.columns.isin(Outcome_col)].copy(),
  2331. )
  2332. ### adding back potentially missing data
  2333. column_check = [exposure_sample_size, outcome_sample_size,
  2334. exposure_standard_deviation,
  2335. outcome_standard_deviation,
  2336. minor_allele_freq]
  2337. cond_dt = _add_missing_columns(cond_dt, input_data,
  2338. columns=column_check,
  2339. index_col=variant_id)
  2340. # run coloc
  2341. coloc_res = coloc_single(inst=cond_dt,
  2342. p1=p1, p2=p2, p12=p12,
  2343. effect_type=effect_type,
  2344. variant_id=variant_id,
  2345. exposure_effect_size=exposure_effect_size,
  2346. outcome_effect_size=outcome_effect_size,
  2347. exposure_varbeta=exposure_varbeta,
  2348. outcome_varbeta=outcome_varbeta,
  2349. exposure_zstatistic=exposure_zstatistic,
  2350. outcome_zstatistic=outcome_zstatistic,
  2351. exposure_standard_deviation=exposure_standard_deviation,
  2352. outcome_standard_deviation=outcome_standard_deviation,
  2353. exposure_sample_size=exposure_sample_size,
  2354. outcome_sample_size=outcome_sample_size,
  2355. minor_allele_freq=minor_allele_freq,
  2356. extra_info=True)
  2357. # get summary
  2358. proc_res_1 = coloc_process(coloc_res,
  2359. hits1=[fm1.columns.tolist()[k]],
  2360. hits2=fm2.columns.tolist(),
  2361. corr_exp = corr_exp,
  2362. corr_out = corr_out,
  2363. r2thr=r2thr,
  2364. mode=mode, p1=p1, p2=p2, p12=p12,
  2365. verbose=verbose
  2366. )
  2367. proc_res = pd.concat([proc_res, proc_res_1[c.COLOC_SUMMARY]])
  2368. ## mask/- cond
  2369. if method_exp in ["mask", "single"] and method_out in ["cond"]:
  2370. proc_res = pd.DataFrame()
  2371. # NOTE change with `enumerate`?
  2372. for k in list(range(0,len(cond2))):
  2373. # this merges cond2 = outcomes with exposure data
  2374. cond_dt = pd.merge(
  2375. cond2[list(cond2.keys())[k]],
  2376. input_data.loc[:,input_data.columns.isin(Exposure_col)].copy()
  2377. )
  2378. ### adding back potentially missing data
  2379. column_check = [exposure_sample_size, outcome_sample_size,
  2380. exposure_standard_deviation,
  2381. outcome_standard_deviation,
  2382. minor_allele_freq]
  2383. cond_dt = _add_missing_columns(cond_dt, input_data,
  2384. columns=column_check,
  2385. index_col=variant_id)
  2386. # COLOC results
  2387. coloc_res = coloc_single(inst=cond_dt,
  2388. p1=p1, p2=p2, p12=p12,
  2389. effect_type=effect_type,
  2390. variant_id=variant_id,
  2391. exposure_effect_size=exposure_effect_size,
  2392. outcome_effect_size=outcome_effect_size,
  2393. exposure_varbeta=exposure_varbeta,
  2394. outcome_varbeta=outcome_varbeta,
  2395. exposure_zstatistic=exposure_zstatistic,
  2396. outcome_zstatistic=outcome_zstatistic,
  2397. exposure_standard_deviation=exposure_standard_deviation,
  2398. outcome_standard_deviation=outcome_standard_deviation,
  2399. exposure_sample_size=exposure_sample_size,
  2400. outcome_sample_size=outcome_sample_size,
  2401. minor_allele_freq=minor_allele_freq,
  2402. extra_info=True)
  2403. # get summary
  2404. proc_res_1 = coloc_process(coloc_res,
  2405. hits1=fm1.columns.tolist(),
  2406. hits2=[fm2.columns.tolist()[k]],
  2407. corr_exp = corr_exp,
  2408. corr_out = corr_out,
  2409. r2thr=r2thr,
  2410. mode=mode, p12=p12, p1=p1, p2=p2,
  2411. verbose=verbose
  2412. )
  2413. proc_res = pd.concat([proc_res,
  2414. proc_res_1[c.COLOC_SUMMARY]])
  2415. # returning results objects
  2416. proc_res.reset_index(inplace=True)
  2417. proc_res[c.MR_NSNPS] = proc_res[c.MR_NSNPS].astype(str)
  2418. results = proc_res[
  2419. [c.COLOC_HIT1, c.COLOC_HIT2,
  2420. c.MR_NSNPS, c.COLOC_PPH0, c.COLOC_PPH1,
  2421. c.COLOC_PPH2, c.COLOC_PPH3, c.COLOC_PPH4,
  2422. c.COLOC_BEST1, c.COLOC_BEST2, c.COLOC_BEST4]
  2423. ]
  2424. # add zstats
  2425. try:
  2426. results[c.COLOC_ZSTAT_HIT1] = fm1[results[c.COLOC_HIT1]].apply(pd.Series)
  2427. results[c.COLOC_ZSTAT_HIT2] = fm2[results[c.COLOC_HIT2]].apply(pd.Series)
  2428. except:
  2429. results[c.COLOC_ZSTAT_HIT1] = fm1[results[c.COLOC_HIT1]].iloc[0].to_list()
  2430. results[c.COLOC_ZSTAT_HIT2] = fm2[results[c.COLOC_HIT2]].iloc[0].to_list()
  2431. # returning ColocResults objects
  2432. res_obj = {c.COLOC_SUMMARY: results,
  2433. c.COLOC_VARIANT_RESULTS: proc_res_1[c.COLOC_RESULTS]}
  2434. # return stuff
  2435. # return results
  2436. return ColocResults(**res_obj)

coloc_core.py at commit 0e05762, under GPL-3.0 · at the source

Overview

  1. Department of Clinical, Educational and Health Psychology, University College London,London, UK
  2. Institute of Cardiovascular Science, University College London,London, UK
  3. The National Institute for Health Research University College London Hospitals Biomedical Research Centre, University College London,London, UK
  4. Department of Cardiology, Division of Heart and Lungs, University Medical Centre Utrecht, Utrecht University,Utrecht, the Netherlands
  5. Department of Cardiology, Amsterdam Cardiovascular Sciences, Amsterdam University Medical Centre, University of Amsterdam,Amsterdam, the Netherlands
  6. Institute of Health Informatics, University College London,London, UK
  7. Developmental Neurosciences, Zayed Centre for Research into Rare Disease in Children, UCL GOS Institute of Child Health, University College London,London, UK
  8. Division of Psychiatry, University College London,London, UK
  9. Social Genetic and Developmental Psychiatry, King’s College London,London, UK
Institutions: University College London (United Kingdom); Utrecht University (Netherlands); University of Amsterdam (Netherlands); King's College London (United Kingdom)
Journal: Translational psychiatry, volume 16, issue 1, article 392
Dates: received 26 September 2025; accepted 22 May 2026; published online 2 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1038/s41398-026-04137-9 · PMID 42230543 · PMCID PMC13443190 · OpenAlex W7163184988
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: genetics / omics (modality), human (organism), depression (population), clinical / translational (subfield)
Methods: Statistics
Keywords: Genomics, Depression, Clinical pharmacology
MeSH: Antidepressive Agents*, Drug Repositioning*, Major Depressive Disorder*, Mendelian Randomization Analysis*, Genome-Wide Association Study, Humans, Quantitative Trait Loci (* major topic)
Topic: Genetic Associations and Epidemiology (Genetics, Biochemistry, Genetics and Molecular Biology), according to OpenAlex
Funding: EC | EU Framework Programme for Research and Innovation H2020 | H2020 Priority Excellent Science | H2020 European Research Council (H2020 Excellent Science - European Research Council) (I-IRISK grant agreement No. 863981); Great Ormond Street Hospital Children’s Charity; British Heart Foundation (PG/18/5033837, PG/22/10989); National Institute for Health Research University College London Hospitals Biomedical Research Centre
Citations: not cited yet (Europe PMC); 101 references in the paper

Abstract

Major depression (MD) treatments have limited efficacy and target few mechanisms, highlighting the need for innovative drug discovery. Drugs targeting genetically supported proteins are 2.6 times more likely to succeed in drug development. Here, we use genetic methods to identify and prioritise MD drug targets, leveraging genome-wide association study (GWAS) summary statistics from >525,000 MD cases. We derived exposure data from 10 datasets measuring protein quantitative trait loci (pQTLs) and gene expression levels (eQTLs) in blood, cerebrospinal fluid, and brain tissues. We performed cis-Mendelian randomisation (MR) on 3469 druggable targets (genes encoding proteins targeted by existing compounds or experimentally predicted to be druggable). To strengthen causal inference, we implemented robust MR estimators, colocalisation, external replication, and assessed directional consistency across tissues. We integrated cis-MR effect directions with drug mechanisms and clinical annotations to infer potential therapeutic effects. Validation analyses showed that 82% of drugs approved for depression/anxiety had ≥1 significant MR target, compared to 51% for compounds in clinical trials. For repurposing, we prioritised 54 targets of compounds developed for other conditions with estimated beneficial effects on MD (e.g., an inhibitor for a risk-increasing target). Ten high-priority targets of brain-penetrating compounds included ACE and NISCH (cardiovascular drugs), NDUFA2, NDUFB6, and NDUFS1 (metformin), CDK4, NTRK3, and MET (oncology inhibitors), and GLS and NOS2 (enzyme inhibitors). We found genetic evidence for established and novel MD targets across the drug development pipeline. Novel targets point to mechanisms beyond monoaminergic systems, most with approved drugs for other conditions, offering immediate repurposing opportunities.

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

Repositories

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

cfinan/merit

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 0e05762f86400756a0152cb5aa645edb81d8d831, 4 September 2026
Languages: Python (398), R (28), Jupyter (26), Shell (22)
Size: 1,787 files, 474 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (pyproject.toml, requirements.txt, resources/conda/conda_build_config.yaml), tests, continuous integration, documentation, 27 notebooks
Not found: CITATION.cff
Tools: NumPy (194 files), pandas (164 files), SciPy (28 files), pysam (24 files), statsmodels (9 files), Matplotlib (4 files), seaborn (4 files), Numba (3 files), rpy2 (3 files), BCFtools (1 file), data.table (1 file), psych (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
476 files

cfinan/bio-misc

License: GPL-3.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 4060630a617911862a403c065d2cd0f5bebe0cc3, 15 February 2024
Languages: Python (87), Shell (14), Jupyter (1)
Size: 268 files, 102 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (requirements.txt, setup.py, resources/conda/envs/py310/conda_create.yml, resources/conda/envs/py310/conda_update.yml, resources/conda/envs/py38/conda_create.yml, resources/conda/envs/py38/conda_update.yml, resources/conda/envs/py39/conda_create.yml, resources/conda/envs/py39/conda_update.yml), tests, continuous integration, documentation, 1 notebook
Not found: CITATION.cff
Tools: NumPy (19 files), pandas (8 files), pysam (3 files), Biopython (2 files), Numba (2 files), BCFtools (1 file), SciPy (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
104 files

cfinan/gwas-norm

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 04b05b9eeb43a67266e4350a0e27f75ec0f2e0a3, 26 July 2026
Languages: Python (110), Shell (20), Jupyter (2)
Size: 659 files, 132 scripts
Software Heritage: not archived
Found in: “Code availability”
Holds: README, license file, environment (pixi.lock, pixi.toml, pyproject.toml, resources/conda/conda_build_config.yaml), tests, continuous integration, documentation, 2 notebooks
Not found: CITATION.cff
Tools: Biopython (10 files), NumPy (10 files), pysam (9 files), pandas (4 files), SciPy (4 files), BCFtools (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
134 files

chembl/tractability_pipeline_v2

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 84f53f49664455f89a802f0e2caf7927f7ff06c9, 9 February 2025
Languages: Python (14)
Size: 67 files, 14 scripts
Software Heritage: archived
Found in: “Code availability”
Holds: README, license file, environment (requirements.txt, setup.py, ot_tractability_pipeline_v2/SpaCy_NER_PROTAC_model_packaged/en_NER_PROTAC-0.2.5/setup.py)
Not found: CITATION.cff, tests, continuous integration, documentation
Tools: NumPy (5 files), pandas (5 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
16 files

Code availability

Cis-Mendelian randomisation and colocalisation analyses were performed using purpose-built Python (v3.9) packages (Mendelian Randomisation for Identifying Targets [MeRIT], v0.3.3a0; https://gitlab.com/cfinan/merit), which incorporates the coloc R package for colocalisation (https://github.com/chr1swallace/coloc). Estimated therapeutic relevance and known depression-related drug effects were derived using bio-misc (v0.2.0a0; https://gitlab.com/cfinan/bio-misc). GWAS summary statistics were processed and standardised using gwas-norm (https://gitlab.com/cfinan/gwas-norm). Druggable genome targets were defined using Open Targets tractability assessments (https://github.com/chembl/tractability_pipeline_v2; https://platform-docs.opentargets.org/target/tractability). Protein-protein interaction network analysis was conducted using STRING (v12.0; https://string-db.org).

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

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:

  • 4 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 722 scripts, each with its path and the digest of its content;
  • 5 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

Datasets cited

Data availability

GWAS summary statistics for MD are available from the Psychiatric Genomics Consortium at https://pgc.unc.edu/for-researchers/download-results/. GWAS summary statistics including 23andMe data require an approved application through 23andMe available to qualified researchers under an agreement with 23andMe that protects the privacy of the 23andMe participants (visit https://research.23andme.com/dataset-access/). UK Biobank data are available through application at https://www.ukbiobank.ac.uk/enable-your-research/apply-for-access/. Open Targets tractability data (version dated 2024-05-23) can be downloaded from http://ftp.ebi.ac.uk/pub/databases/opentargets/platform/latest/input/target/tractability/. QTL datasets are available from: Blood plasma pQTL data: deCODE (https://www.decode.com/summarydata/), UKB-PPP (https://www.synapse.org/#!Synapse:syn51364943/), INTERVAL (http://www.phpc.cam.ac.uk/ceu/proteins/), and Gudjonsson (https://www.ebi.ac.uk/gwas/publications/35078996). Brain pQTL data: ROSMAP and Banner (https://www.synapse.org/#!Synapse:syn24172458). CSF pQTL data: Yang (https://dss.niagads.org/datasets/ng00102/). Blood eQTL data: eQTLGen (https://www.eqtlgen.org/). Brain eQTL data: MetaBrain (https://www.metabrain.nl/). Compound mechanism of action and drug effect data were obtained from ChEMBL (v33; https://www.ebi.ac.uk/chembl/). Cis-MR results and genetic instruments for all targets tested, including non-significant results, are available on Zenodo (10.5281/zenodo.19135141).

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

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 10 authors, 3 keywords, 7 MeSH terms, 4 funders, 101 references.

Cite

This paper

ter Kuile, A. R., Finan, C., Chopade, S., van Vugt, M., Hukerikar, N., Barral, S., Stringaris, A., Schmidt, A. F., Kuchenbaecker, K., & Pingault, J.-B. (2026). An integrative mendelian randomisation and drug mechanism framework for target prioritisation and therapeutic repurposing in major depression. Translational psychiatry, 16(1), 392. https://doi.org/10.1038/s41398-026-04137-9

BibTeX

@article{terkuile2026integrative,
author = {ter Kuile, Abigail R. and Finan, Chris and Chopade, Sandesh and van Vugt, Marion and Hukerikar, Nikita and Barral, Serena and Stringaris, Argyris and Schmidt, Amand F. and Kuchenbaecker, Karoline and Pingault, Jean-Baptiste},
title = {{An integrative mendelian randomisation and drug mechanism framework for target prioritisation and therapeutic repurposing in major depression}},
journal = {Translational psychiatry},
year = {2026},
month = jun,
volume = {16},
number = {1},
pages = {392},
publisher = {Nature Publishing Group},
issn = {2158-3188},
doi = {10.1038/s41398-026-04137-9},
url = {https://doi.org/10.1038/s41398-026-04137-9},
pmid = {42230543},
pmcid = {PMC13443190}
}

RIS

TY - JOUR
AU - ter Kuile, Abigail R.
AU - Finan, Chris
AU - Chopade, Sandesh
AU - van Vugt, Marion
AU - Hukerikar, Nikita
AU - Barral, Serena
AU - Stringaris, Argyris
AU - Schmidt, Amand F.
AU - Kuchenbaecker, Karoline
AU - Pingault, Jean-Baptiste
TI - An integrative mendelian randomisation and drug mechanism framework for target prioritisation and therapeutic repurposing in major depression
T2 - Translational psychiatry
J2 - Transl Psychiatry
PY - 2026
DA - 2026/06/02
VL - 16
IS - 1
SP - 392
SN - 2158-3188
PB - Nature Publishing Group
DO - 10.1038/s41398-026-04137-9
UR - https://doi.org/10.1038/s41398-026-04137-9
LA - en
ER -

CSL-JSON

{
"id": "10.1038/s41398-026-04137-9",
"type": "article-journal",
"title": "An integrative mendelian randomisation and drug mechanism framework for target prioritisation and therapeutic repurposing in major depression",
"container-title": "Translational psychiatry",
"author": [
{
"family": "ter Kuile",
"given": "Abigail R."
},
{
"family": "Finan",
"given": "Chris"
},
{
"family": "Chopade",
"given": "Sandesh"
},
{
"family": "van Vugt",
"given": "Marion"
},
{
"family": "Hukerikar",
"given": "Nikita"
},
{
"family": "Barral",
"given": "Serena"
},
{
"family": "Stringaris",
"given": "Argyris"
},
{
"family": "Schmidt",
"given": "Amand F."
},
{
"family": "Kuchenbaecker",
"given": "Karoline"
},
{
"family": "Pingault",
"given": "Jean-Baptiste"
}
],
"container-title-short": "Transl Psychiatry",
"volume": "16",
"issue": "1",
"page": "392",
"DOI": "10.1038/s41398-026-04137-9",
"PMID": "42230543",
"PMCID": "PMC13443190",
"ISSN": "2158-3188",
"publisher": "Nature Publishing Group",
"URL": "https://doi.org/10.1038/s41398-026-04137-9",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
2
]
]
}
}

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/s41562-026-02476-7 [code]
Genome-wide meta-analysis of quantitatively measured generalized anxiety symptoms in individuals of European ancestry.
Journal: Nature human behaviour
In common: BCFtools, psych, data.table, clinical / translational, genetics / omics, 5 references, author Abigail R. ter Kuile
[2] doi:10.1038/s41467-026-76676-0 [code]
Determinants of functional burden pleiotropy and gene dosage responses across human traits.
Journal: Nature communications
In common: Numba, data.table, statsmodels, 5 other tools, ukbiobank.ac.uk/enable-your-research/apply-for-access, genetics / omics, 1 reference
[3] doi:10.1038/s41592-026-03211-w [code]
Spatial isoform sequencing at single-cell resolution reveals cell-type-specific spatial isoform variability in multiple brain cell types.
Journal: Nature methods
In common: pysam, Biopython, Numba, 7 other tools, genetics / omics
[4] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: BCFtools, Biopython, Numba, 7 other tools
[5] doi:10.1016/j.xcrm.2026.102766 [code]
A longitudinal single-cell and spatial multiomic atlas of pediatric high-grade glioma.
Journal: Cell reports. Medicine
In common: pysam, rpy2, Numba, 6 other tools, genetics / omics
[6] doi:10.1038/s41592-026-03057-2 [code]
CREsted: modeling genomic and synthetic cell-type-specific enhancers across tissues and species.
Journal: Nature methods
In common: pysam, Biopython, Numba, 6 other tools, genetics / omics
[7] doi:10.1016/j.celrep.2026.117110 [code]
Single-nucleus multiome analysis in the human prefrontal cortex identifies gene expression and cis-regulatory elements associated with aging.
Journal: Cell reports
In common: BCFtools, pysam, statsmodels, 5 other tools, genetics / omics, 1 reference
[8] doi:10.1038/s43587-026-01106-1 [code]
Repurposing drugs for the prevention of vascular dementia using evidence from drug target Mendelian randomization.
Journal: Nature aging
In common: genetics / omics, 7 references
[9] doi:10.1038/s41562-026-02486-5 [code]
Genome-wide association studies of infant and toddler temperament in European and multi-ancestry populations.
Journal: Nature human behaviour
In common: Biopython, psych, data.table, 5 other tools, genetics / omics, 1 reference
[10] doi:10.7554/elife.108109 [code]
Multimodal MRI marker of cognition explains the association between cognition and mental health in the UK Biobank.
Journal: eLife
In common: psych, data.table, seaborn, 4 other tools, ukbiobank.ac.uk/enable-your-research/apply-for-access

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.