Extract genome-wide data for multivariate analysis#
Extracts strong, random and null sets of effects from genome-wide summary statistics and writes them as a single MASH-format input.
Overview#
The workflow produces three z-score matrices used to fit a multivariate prior:
Strong effects (
Z_s) — extracted from genome-wide cis fine-mapping results. The top locus per condition is taken (credible-set coverage threshold 0.7) and the z-scores are merged into one data frame.Null effects (
Z_n) — up toMcandidate SNPs per region satisfying |z| ≤ 2 are overlapped with the independent-SNP list to keep only independent variants; the union is taken.Random effects (
Z_r) — variants randomly sampled from the supplied independent-variant list.
These matrices feed Ultimate Deconvolution / MASH to learn patterns of effect sharing across conditions (e.g. cell types or tissues).
Requires genome-wide SuSiE fine-mapping outputs. See the Important Note below for toy-data limitations.
When to run it. After per-region fine-mapping or tensorQTL association scans have produced results across conditions, and before mash_fit.
Input#
susie_to_mash - builds the MASH input from fine-mapped SuSiE results:
--fine_mapping_metainput/finemapping/protocol_example.fine_mapping_meta.tsv(index of the fine-mapped RDS files, one row per region and context. Columnschrom,start,end,region_id,gene_id,context,susie_path,sumstats_path,conditions.)
chrom start end region_id gene_id context susie_path sumstats_path ...
chr22 15528191 15529138 chr22:15528191-15529138 ENSG00000130538 De_Jager protocol_example.susie_fit.rds protocol_example.sumstats_db.rds ...
--finemapping_column susie_path(which column of the meta table holds the fine-mapping RDS path.)--sig_p_cutoff 0.1(p-value threshold for calling a variant strong. The toy signal is weak, so loosen this or every strong variant is filtered out.)
random_null_tensorqtl - extracts random and null effects directly from tensorQTL summary statistics:
--region_fileinput/finemapping/protocol_example.region(genome region partition defining the analysis units, columnschrom,start,end,gene_id.)
chrom start end gene_id
chr11 187771 3551668 ENSG00000142089
--sum_filesinput/protocol_example.sumstats_list.txt(list of the per-trait tensorQTL summary-statistics files. This file does not ship with the repository, so no preview is shown.)--traits bulk_rnaseq(trait or context labels matching the summary-statistics files.)--independent_variant_list(optional; LD-pruned independent SNPs, used to keep only independent null and random variants.)
Note on susie_to_mash inputs#
susie_to_mash consumes one FineMappingResult RDS per region (the S4 output of fine_mapping.R), listed in --fine_mapping_meta under --finemapping_column. For each region it takes the lead variant (max PIP) of every credible set in each condition as the strong set, and samples the random / null background from the region’s full variant universe (pecotmr::mashInput).
Toy MWE. The legacy per-region SuSiE fixtures can be converted once to a QtlFineMappingResult — see tests/fixtures/mash/build_qtl_fine_mapping_result.R, which builds protocol_example.QtlFineMappingResult.rds from the committed toy fine-mapping tables. Point --fine_mapping_meta at that RDS to run the toy demo. The toy signal is weak, so loosen --sig_p_cutoff (e.g. 0.1) or every strong variant is filtered out.
For a genome-wide run, provide a --fine_mapping_meta listing every region’s FineMappingResult RDS.
--cwd output/mash_preprocessing(working directory all outputs are written under. Defaults tooutput.)--name protocol_example(tag used in the output filename and in the per-gene cache directory)
Output#
<cwd>/<name>.mash_input.rds(susie_to_mash, orrandom_null_tensorqtl_2) (a list of 10 elements containing thestrong.b,random.bandnull.bdata frames of z-scores across all conditions, ready for Ultimate Deconvolution or MASH. Both entry points write this same file.)
List of 10
$ strong.b:'data.frame': 62 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:62] 0.0402 0.0177 0.1852 -0.2188 0.1352 ...
..$ Ast_De_Jager_eQTL : num [1:62] -0.74 -0.215 0.165 -0.263 0.113 ...
..$ Oli_De_Jager_eQTL : num [1:62] -0.0564 0.195 0.1379 -0.1245 -0.061 ...
..$ OPC_De_Jager_eQTL : num [1:62] 0.1748 0.2064 0.1182 -0.0751 0.1023 ...
..$ Exc_De_Jager_eQTL : num [1:62] -0.56004 0.04682 0.2279 -0.13765 0.00667 ...
..$ Inh_De_Jager_eQTL : num [1:62] -0.6306 0.1502 0.1777 -0.2118 -0.0562 ...
..$ DLPFC_De_Jager_eQTL: num [1:62] -0.0379 0.1315 0.0361 -0.315 -0.1005 ...
..$ PCC_De_Jager_eQTL : num [1:62] -0.0403 0.0209 0.0614 -0.1755 0.0422 ...
..$ AC_De_Jager_eQTL : num [1:62] -0.13649 -0.00365 0.08409 -0.19717 0.08202 ...
..$ Mic_Kellis_eQTL : num [1:62] -0.396 0 0 0 0 ...
..$ Ast_Kellis_eQTL : num [1:62] -0.9948 0.1155 0.0732 -0.2074 -0.044 ...
..$ Oli_Kellis_eQTL : num [1:62] -0.559 0.387 0.1339 -0.3099 -0.0334 ...
..$ OPC_Kellis_eQTL : num [1:62] -0.11556 0.17907 0.06332 0.06868 0.00266 ...
..$ Exc_Kellis_eQTL : num [1:62] -0.5978 0.0797 0.1463 -0.1659 0.049 ...
..$ Inh_Kellis_eQTL : num [1:62] -0.40719 0.04581 0.12052 -0.2968 0.00677 ...
..$ Ast.10_Kellis_eQTL : num [1:62] 0 0 0 0 0 0 0 0 0 0 ...
$ random.b:'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] -0.014 -0.0691 -0.0426 -0.1391 0.1639 ...
..$ Ast_De_Jager_eQTL : num [1:200] -0.006636 -0.051634 0.064082 0.000642 -0.040912 ...
..$ Oli_De_Jager_eQTL : num [1:200] -0.0702 -0.0912 0.0985 -0.0492 0.0375 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.0517 0.0577 -0.0443 0.0677 0.019 ...
..$ Exc_De_Jager_eQTL : num [1:200] 0.0404 -0.02711 0.02452 -0.01076 0.00808 ...
..$ Inh_De_Jager_eQTL : num [1:200] -0.09783 -0.0174 0.01247 0.00424 0.0561 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] -0.005966 0.006705 0.000253 -0.004673 0.001346 ...
..$ PCC_De_Jager_eQTL : num [1:200] -0.0113 -0.0248 -0.0239 0.0113 0.0307 ...
..$ AC_De_Jager_eQTL : num [1:200] -0.01516 0.00196 -0.05586 0.01852 0.0188 ...
..$ Mic_Kellis_eQTL : num [1:200] 0.0641 -0.1708 -0.0346 -0.0661 0.0293 ...
..$ Ast_Kellis_eQTL : num [1:200] 0.05854 -0.03466 -0.03777 -0.00221 -0.01986 ...
..$ Oli_Kellis_eQTL : num [1:200] -0.01408 0.02196 0.02674 0.00619 -0.03527 ...
..$ OPC_Kellis_eQTL : num [1:200] -0.018068 -0.026661 -0.051184 0.000499 -0.018648 ...
..$ Exc_Kellis_eQTL : num [1:200] 0.00046 -0.08625 0.02572 0.00127 -0.07891 ...
..$ Inh_Kellis_eQTL : num [1:200] -0.07466 -0.03319 -0.00879 0.06033 -0.02189 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 0 0 0 0 0 0 0 0 0 0 ...
$ null.b :'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] -0.0252 -0.0296 -0.1086 -0.0394 -0.2567 ...
..$ Ast_De_Jager_eQTL : num [1:200] -0.0597 0.0402 -0.03 0.0394 -0.1582 ...
..$ Oli_De_Jager_eQTL : num [1:200] -0.0376 0.0668 0.1072 0.0819 -0.3407 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.058 0.0907 -0.0941 0.0193 -0.0585 ...
..$ Exc_De_Jager_eQTL : num [1:200] 0.01518 -0.00338 0.01137 -0.03883 -0.02836 ...
..$ Inh_De_Jager_eQTL : num [1:200] 0.00196 -0.05773 0.06773 -0.08281 0.29775 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] 0.000521 -0.004139 -0.010968 -0.019917 0.081309 ...
..$ PCC_De_Jager_eQTL : num [1:200] -0.0193 0.0273 -0.0237 0.0212 -0.0884 ...
..$ AC_De_Jager_eQTL : num [1:200] 0.001409 0.016122 -0.049309 -0.000438 -0.045112 ...
..$ Mic_Kellis_eQTL : num [1:200] 0.0506 0.0105 0.0444 0.0287 0.4847 ...
..$ Ast_Kellis_eQTL : num [1:200] 1.18e-02 1.34e-06 1.17e-01 1.28e-01 -5.49e-01 ...
..$ Oli_Kellis_eQTL : num [1:200] 0.0742 0.0675 -0.025 -0.0363 0.2675 ...
..$ OPC_Kellis_eQTL : num [1:200] -0.0333 -0.0226 -0.0231 0.0717 -0.0915 ...
..$ Exc_Kellis_eQTL : num [1:200] 0.03593 0.00316 0.01524 -0.0089 0.08042 ...
..$ Inh_Kellis_eQTL : num [1:200] -0.043 -0.0471 0.0418 0.0791 -0.0831 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 0 0 0 0 0 0 0 0 0 0 ...
$ strong.s:'data.frame': 62 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:62] 0.3036 0.0925 0.0569 0.095 0.0626 ...
..$ Ast_De_Jager_eQTL : num [1:62] 0.2298 0.0777 0.0476 0.08 0.053 ...
..$ Oli_De_Jager_eQTL : num [1:62] 0.2397 0.0912 0.0556 0.0944 0.0623 ...
..$ OPC_De_Jager_eQTL : num [1:62] 0.3184 0.1084 0.0672 0.1122 0.0741 ...
..$ Exc_De_Jager_eQTL : num [1:62] 0.1051 0.0577 0.0336 0.0586 0.039 ...
..$ Inh_De_Jager_eQTL : num [1:62] 0.198 0.0861 0.0513 0.0872 0.0581 ...
..$ DLPFC_De_Jager_eQTL: num [1:62] 0.0511 0.0366 0.0225 0.0349 0.0249 ...
..$ PCC_De_Jager_eQTL : num [1:62] 0.0844 0.0551 0.0326 0.0543 0.0367 ...
..$ AC_De_Jager_eQTL : num [1:62] 0.0729 0.0483 0.0302 0.0484 0.0343 ...
..$ Mic_Kellis_eQTL : num [1:62] 0.319 1000 1000 1000 1000 ...
..$ Ast_Kellis_eQTL : num [1:62] 0.3174 0.0941 0.054 0.0926 0.0604 ...
..$ Oli_Kellis_eQTL : num [1:62] 0.2342 0.0776 0.0471 0.0783 0.0527 ...
..$ OPC_Kellis_eQTL : num [1:62] 0.3137 0.0972 0.0568 0.0953 0.0631 ...
..$ Exc_Kellis_eQTL : num [1:62] 0.1788 0.0754 0.0442 0.0756 0.0495 ...
..$ Inh_Kellis_eQTL : num [1:62] 0.2481 0.0783 0.0454 0.0762 0.0506 ...
..$ Ast.10_Kellis_eQTL : num [1:62] 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 ...
$ null.s :'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] 0.0633 0.0667 0.0866 0.0795 0.3139 ...
..$ Ast_De_Jager_eQTL : num [1:200] 0.0486 0.0502 0.0665 0.0603 0.2429 ...
..$ Oli_De_Jager_eQTL : num [1:200] 0.0498 0.0522 0.0671 0.0621 0.2443 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.0663 0.0688 0.0905 0.0842 0.3302 ...
..$ Exc_De_Jager_eQTL : num [1:200] 0.0225 0.0234 0.0308 0.0282 0.1132 ...
..$ Inh_De_Jager_eQTL : num [1:200] 0.0421 0.044 0.0577 0.0529 0.2107 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] 0.0127 0.0133 0.0181 0.0163 0.0726 ...
..$ PCC_De_Jager_eQTL : num [1:200] 0.0201 0.0213 0.0289 0.0259 0.104 ...
..$ AC_De_Jager_eQTL : num [1:200] 0.0183 0.0191 0.0269 0.0237 0.1017 ...
..$ Mic_Kellis_eQTL : num [1:200] 0.0602 0.0619 0.0864 0.078 0.305 ...
..$ Ast_Kellis_eQTL : num [1:200] 0.0543 0.0565 0.0765 0.0707 0.2917 ...
..$ Oli_Kellis_eQTL : num [1:200] 0.0421 0.0444 0.0597 0.0545 0.2213 ...
..$ OPC_Kellis_eQTL : num [1:200] 0.0539 0.0566 0.077 0.0708 0.2842 ...
..$ Exc_Kellis_eQTL : num [1:200] 0.0329 0.0344 0.0457 0.0427 0.1859 ...
..$ Inh_Kellis_eQTL : num [1:200] 0.0461 0.047 0.0634 0.0589 0.2358 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 ...
$ random.s:'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] 0.0603 0.096 0.0694 0.0657 0.0769 ...
..$ Ast_De_Jager_eQTL : num [1:200] 0.0466 0.074 0.0521 0.0507 0.0578 ...
..$ Oli_De_Jager_eQTL : num [1:200] 0.048 0.0752 0.0526 0.0517 0.0585 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.0635 0.1011 0.0711 0.0693 0.0789 ...
..$ Exc_De_Jager_eQTL : num [1:200] 0.0219 0.0345 0.0243 0.0237 0.0266 ...
..$ Inh_De_Jager_eQTL : num [1:200] 0.0405 0.0648 0.0457 0.0439 0.0501 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] 0.0125 0.0196 0.0145 0.0139 0.016 ...
..$ PCC_De_Jager_eQTL : num [1:200] 0.0198 0.0305 0.0234 0.021 0.0251 ...
..$ AC_De_Jager_eQTL : num [1:200] 0.0176 0.0284 0.0209 0.0196 0.0229 ...
..$ Mic_Kellis_eQTL : num [1:200] 0.0598 0.0894 0.065 0.0641 0.0709 ...
..$ Ast_Kellis_eQTL : num [1:200] 0.0523 0.0838 0.0587 0.0559 0.0625 ...
..$ Oli_Kellis_eQTL : num [1:200] 0.041 0.0651 0.0453 0.0444 0.0509 ...
..$ OPC_Kellis_eQTL : num [1:200] 0.053 0.0829 0.0582 0.0571 0.0627 ...
..$ Exc_Kellis_eQTL : num [1:200] 0.033 0.0506 0.0348 0.0343 0.0387 ...
..$ Inh_Kellis_eQTL : num [1:200] 0.045 0.0694 0.0492 0.0481 0.0545 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 ...
$ strong.z:'data.frame': 62 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:62] 0.132 0.191 3.253 -2.304 2.161 ...
..$ Ast_De_Jager_eQTL : num [1:62] -3.22 -2.77 3.46 -3.28 2.13 ...
..$ Oli_De_Jager_eQTL : num [1:62] -0.235 2.139 2.48 -1.319 -0.98 ...
..$ OPC_De_Jager_eQTL : num [1:62] 0.549 1.905 1.759 -0.67 1.38 ...
..$ Exc_De_Jager_eQTL : num [1:62] -5.33 0.811 6.79 -2.35 0.171 ...
..$ Inh_De_Jager_eQTL : num [1:62] -3.185 1.744 3.466 -2.428 -0.967 ...
..$ DLPFC_De_Jager_eQTL: num [1:62] -0.741 3.598 1.601 -9.025 -4.042 ...
..$ PCC_De_Jager_eQTL : num [1:62] -0.477 0.379 1.885 -3.231 1.151 ...
..$ AC_De_Jager_eQTL : num [1:62] -1.8726 -0.0757 2.7865 -4.0712 2.3891 ...
..$ Mic_Kellis_eQTL : num [1:62] -1.24 0 0 0 0 ...
..$ Ast_Kellis_eQTL : num [1:62] -3.134 1.227 1.355 -2.239 -0.729 ...
..$ Oli_Kellis_eQTL : num [1:62] -2.386 4.987 2.843 -3.956 -0.634 ...
..$ OPC_Kellis_eQTL : num [1:62] -0.3683 1.8424 1.1147 0.7209 0.0422 ...
..$ Exc_Kellis_eQTL : num [1:62] -3.343 1.056 3.308 -2.194 0.991 ...
..$ Inh_Kellis_eQTL : num [1:62] -1.641 0.585 2.653 -3.893 0.134 ...
..$ Ast.10_Kellis_eQTL : num [1:62] 0 0 0 0 0 0 0 0 0 0 ...
$ random.z:'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] -0.232 -0.72 -0.614 -2.116 2.132 ...
..$ Ast_De_Jager_eQTL : num [1:200] -0.1425 -0.6982 1.2298 0.0127 -0.7073 ...
..$ Oli_De_Jager_eQTL : num [1:200] -1.463 -1.214 1.873 -0.953 0.641 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.813 0.571 -0.624 0.976 0.24 ...
..$ Exc_De_Jager_eQTL : num [1:200] 1.844 -0.786 1.009 -0.455 0.304 ...
..$ Inh_De_Jager_eQTL : num [1:200] -2.4158 -0.2683 0.273 0.0964 1.1196 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] -0.4757 0.3416 0.0175 -0.3357 0.084 ...
..$ PCC_De_Jager_eQTL : num [1:200] -0.572 -0.813 -1.023 0.536 1.221 ...
..$ AC_De_Jager_eQTL : num [1:200] -0.8634 0.0689 -2.6709 0.9463 0.8218 ...
..$ Mic_Kellis_eQTL : num [1:200] 1.073 -1.91 -0.533 -1.03 0.413 ...
..$ Ast_Kellis_eQTL : num [1:200] 1.1182 -0.4138 -0.6432 -0.0395 -0.3177 ...
..$ Oli_Kellis_eQTL : num [1:200] -0.343 0.337 0.59 0.139 -0.693 ...
..$ OPC_Kellis_eQTL : num [1:200] -0.34097 -0.32177 -0.8792 0.00874 -0.29735 ...
..$ Exc_Kellis_eQTL : num [1:200] 0.0139 -1.705 0.7393 0.0371 -2.0365 ...
..$ Inh_Kellis_eQTL : num [1:200] -1.658 -0.478 -0.179 1.255 -0.402 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 0 0 0 0 0 0 0 0 0 0 ...
$ null.z :'data.frame': 200 obs. of 16 variables:
..$ Mic_De_Jager_eQTL : num [1:200] -0.398 -0.444 -1.254 -0.495 -0.818 ...
..$ Ast_De_Jager_eQTL : num [1:200] -1.229 0.8 -0.452 0.654 -0.651 ...
..$ Oli_De_Jager_eQTL : num [1:200] -0.755 1.28 1.597 1.319 -1.394 ...
..$ OPC_De_Jager_eQTL : num [1:200] 0.876 1.318 -1.039 0.229 -0.177 ...
..$ Exc_De_Jager_eQTL : num [1:200] 0.675 -0.145 0.37 -1.379 -0.251 ...
..$ Inh_De_Jager_eQTL : num [1:200] 0.0466 -1.3118 1.1748 -1.5654 1.4129 ...
..$ DLPFC_De_Jager_eQTL: num [1:200] 0.041 -0.31 -0.605 -1.221 1.121 ...
..$ PCC_De_Jager_eQTL : num [1:200] -0.962 1.283 -0.819 0.82 -0.85 ...
..$ AC_De_Jager_eQTL : num [1:200] 0.077 0.8421 -1.835 -0.0184 -0.4436 ...
..$ Mic_Kellis_eQTL : num [1:200] 0.841 0.17 0.514 0.368 1.589 ...
..$ Ast_Kellis_eQTL : num [1:200] 2.17e-01 2.37e-05 1.53 1.81 -1.88 ...
..$ Oli_Kellis_eQTL : num [1:200] 1.762 1.519 -0.418 -0.665 1.209 ...
..$ OPC_Kellis_eQTL : num [1:200] -0.619 -0.4 -0.3 1.013 -0.322 ...
..$ Exc_Kellis_eQTL : num [1:200] 1.091 0.092 0.333 -0.208 0.433 ...
..$ Inh_Kellis_eQTL : num [1:200] -0.932 -1.002 0.659 1.344 -0.353 ...
..$ Ast.10_Kellis_eQTL : num [1:200] 0 0 0 0 0 0 0 0 0 0 ...
$ XtX : num [1:16, 1:16] 2.182 1.09 1.481 0.759 2.405 ...
..- attr(*, "dimnames")=List of 2
.. ..$ : chr [1:16] "Mic_De_Jager_eQTL" "Ast_De_Jager_eQTL" "Oli_De_Jager_eQTL" "OPC_De_Jager_eQTL" ...
.. ..$ : chr [1:16] "Mic_De_Jager_eQTL" "Ast_De_Jager_eQTL" "Oli_De_Jager_eQTL" "OPC_De_Jager_eQTL" ...
<cwd>/<name>_cache/<name>.<gene_id>.qss.rds(random_null_tensorqtl_1) (per-gene intermediate written by the tensorQTL entry point before the effects are pooled; not produced bysusie_to_mash)
Minimal Working Example#
This pipeline has two entry-point workflows. Run whichever matches your input; both write the strong / null / random z-score matrices for downstream multivariate analysis. Both write {name}.mash_input.rds, so run one or the other rather than both in sequence.
Build the MASH input from fine-mapped SuSiE results#
The susie_to_mash workflow requires --name, --fine_mapping_meta (the index of fine-mapped RDS files) and --independent_variant_list; credible-set coverage and per-chunk size are tunable.
Timing: ~2 min (on toy dataset)
sos run pipeline/mash_preprocessing.ipynb susie_to_mash \
--name protocol_example_mash \
--fine_mapping_meta input/finemapping/protocol_example.fine_mapping_meta.tsv \
--finemapping_column susie_path \
--sig_p_cutoff 0.1 \
--cwd output/mash_preprocessing
Extract random and null effects from tensorQTL summary statistics#
The random_null_tensorqtl workflow requires --name, --region_file, --independent_variant_list, and the list of summary-statistics files and traits.
Timing: TBD (on toy dataset)
sos run pipeline/mash_preprocessing.ipynb random_null_tensorqtl \
--name protocol_example_mash \
--region_file input/finemapping/protocol_example.region \
--sum_files input/protocol_example.sumstats_list.txt \
--traits bulk_rnaseq \
--cwd output/mash_preprocessing
Command Interface#
sos run pipeline/mash_preprocessing.ipynb -h
usage: sos run pipeline/mash_preprocessing.ipynb
[workflow_name | -t targets] [options] [workflow_options]
workflow_name: Single or combined workflows defined in this script
targets: One or more targets to generate
options: Single-hyphen sos parameters (see "sos run -h" for details)
workflow_options: Double-hyphen workflow-specific parameters
Workflows:
susie_to_mash
random_null_tensorqtl
Global Workflow Options:
--name VAL (as str, required)
--cwd output (as path)
Path to work directory where output locates
--modular-script-dir code/script (as path)
--seed 999 (as int)
--n-random 10 (as int)
Random / null background rows sampled per region
--n-null 10 (as int)
--exclude-condition (as list)
Conditions (columns) to drop from every partition
--coverage 0.95 (as float)
Credible-set coverage for FineMappingResult strong
selection (susie_to_mash)
--sig-p-cutoff 1e-06 (as float)
Strong-partition significance cutoff. Production default
1E-6; loosen (e.g. 0.1) for weak-signal toy data or
every strong variant is filtered out.
--[no-]z-only (default to False)
Emit only the z-score matrices (drop the effect-size .b
/ .s matrices)
--container ''
Containers that contains the necessary packages
--job-size 1 (as int)
For cluster jobs, number commands to run per job
--walltime 1h
Wall clock time expected
--mem 16G
Memory expected
--numThreads 1 (as int)
Number of threads
--independent-variant-list . (as path)
LD-pruned independent SNP list (optional). When
provided, restricts the random/null background to
matching variants (allele-aware via matchVariants);
strong is unaffected. Default path(".") means not
provided.
Sections
susie_to_mash: Build the MASH input from per-region FineMappingResult
RDS (e.g. fine_mapping.R output). Strong = the lead
variant (max PIP) of each credible set per condition;
random / null are sampled from each region's full
variant universe. mashInput merges across regions,
cleans via filterInvalidSummaryStat, and appends the
strong XtX cross-product.
Workflow Options:
--fine-mapping-meta VAL (as path, required)
Index of per-region fine-mapping results (one row per
region)
--finemapping-column 'susie_path'
Column in --fine_mapping_meta holding each region's
FineMappingResult RDS path
--region-column 'region_id'
Column holding the region id (labels rows across
regions)
random_null_tensorqtl_1:
Workflow Options:
--sum-files paths
--region-file VAL (as path, required)
--traits paths
random_null_tensorqtl_2:
Workflow implementation#
The pipeline defines two workflows. susie_to_mash (steps susie_to_mash_1 and susie_to_mash_2) builds the MASH input from SuSiE fine-mapping outputs. random_null_tensorqtl extracts the random and null effects directly from tensorQTL summary statistics. The shared [global] block below declares the common parameters.
[global]
parameter: name = str
# Path to work directory where output locates
parameter: cwd = path("./output")
parameter: modular_script_dir = path('code/script')
parameter: seed = 999
# Random / null background rows sampled per region
parameter: n_random = 10
parameter: n_null = 10
# Conditions (columns) to drop from every partition
parameter: exclude_condition = []
# Credible-set coverage for FineMappingResult strong selection (susie_to_mash)
parameter: coverage = 0.95
# Strong-partition significance cutoff. Production default 1E-6; loosen (e.g.
# 0.1) for weak-signal toy data or every strong variant is filtered out.
parameter: sig_p_cutoff = 1E-6
# Emit only the z-score matrices (drop the effect-size .b / .s matrices)
parameter: z_only = False
# Containers that contains the necessary packages
parameter: container = ""
# For cluster jobs, number commands to run per job
parameter: job_size = 1
# Wall clock time expected
parameter: walltime = "1h"
# Memory expected
parameter: mem = "16G"
# Number of threads
parameter: numThreads = 1
# LD-pruned independent SNP list (optional). When provided, restricts the
# random/null background to matching variants (allele-aware via matchVariants);
# strong is unaffected. Default path(".") means not provided.
parameter: independent_variant_list = path(".")
# Build the MASH input from per-region FineMappingResult RDS (e.g. fine_mapping.R
# output). Strong = the lead variant (max PIP) of each credible set per
# condition; random / null are sampled from each region's full variant universe.
# mashInput merges across regions, cleans via filterInvalidSummaryStat, and
# appends the strong XtX cross-product.
[susie_to_mash]
# Index of per-region fine-mapping results (one row per region)
parameter: fine_mapping_meta = path
# Column in --fine_mapping_meta holding each region's FineMappingResult RDS path
parameter: finemapping_column = 'susie_path'
# Column holding the region id (labels rows across regions)
parameter: region_column = 'region_id'
import csv
with open(fine_mapping_meta) as _f:
_rows = list(csv.DictReader(_f, delimiter = '\t'))
_fmr_paths = [r[finemapping_column] for r in _rows]
_region_ids = ",".join(str(r[region_column]) for r in _rows)
input: _fmr_paths
output: f"{cwd}/{name}.mash_input.rds"
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = "${ }", stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', container = container
Rscript ${modular_script_dir}/pecotmr_integration/mash_preprocessing.R \
--objects ${_input} \
--region-ids ${_region_ids} \
--coverage ${coverage} \
--n-random ${n_random} \
--n-null ${n_null} \
--exclude-condition "${','.join(exclude_condition)}" \
--sig-p-cutoff ${sig_p_cutoff} \
${'--z-only' if z_only else ''} \
${('--independent-variant-list ' + str(independent_variant_list)) if independent_variant_list.is_file() else ''} \
--seed ${seed} \
--output ${_output}
Get the random and null effects per analysis unit#
[random_null_tensorqtl_1]
parameter: sum_files = paths
parameter: region_file = path
parameter: traits = paths
import re
meta = []
with open(region_file) as regions:
regions.readline() # header
for line in regions:
parts = line.strip().split("\t")
chr_id, start, end, gene_id = parts[0], parts[1], parts[2], parts[3]
pattern = re.compile(r"\.%s\." % chr_id[3:])
matched = []
for sf in sum_files:
with open(str(sf)) as af:
for aline in af:
if pattern.search(aline):
matched.append(aline.strip())
meta.append(dict(gene_id = gene_id, paths = ",".join(matched),
region = f"{chr_id}:{start}-{end}"))
input: for_each = 'meta'
output: f'{cwd:a}/{name}_cache/{name}.{_meta["gene_id"]}.qss.rds'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, tags = f'{step_name}_{_output:bn}'
bash: expand = "${ }", stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', container = container
Rscript ${modular_script_dir}/pecotmr_integration/mash_sumstats_construct.R \
--tensorqtl-paths ${_meta['paths'].replace(',', ' ')} \
--conditions ${','.join([str(t) for t in traits])} \
--region ${_meta['region']} \
--study ${name} \
--output ${_output}
[random_null_tensorqtl_2]
input: group_by = 'all'
output: f"{cwd}/{name}.mash_input.rds"
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{_output:bn}'
bash: expand = "${ }", stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', container = container
Rscript ${modular_script_dir}/pecotmr_integration/mash_preprocessing.R \
--objects ${_input} \
--n-random ${n_random} \
--n-null ${n_null} \
--exclude-condition "${','.join(exclude_condition)}" \
--sig-p-cutoff ${sig_p_cutoff} \
${'--z-only' if z_only else ''} \
${('--independent-variant-list ' + str(independent_variant_list)) if independent_variant_list.is_file() else ''} \
--seed ${seed} \
--output ${_output}