Turns per-region fine-mapping objects into flat TSV tables, diagnostic plots, VCFs and QTL-GWAS overlap summaries.
Overview¶
Fine-mapping leaves you with per-region R objects, which are the wrong shape for almost
everything that comes next - inspecting results, sharing them, or intersecting them with
a GWAS. This module reads those objects and re-expresses them as text: susie_to_tsv
writes a per-variant table (PIP, credible-set membership, posterior mean and sd), a
wide per-variant by per-effect log-Bayes-factor table, and a per-credible-set effect
table. The remaining workflows build on those tables - collapsing them across regions,
drawing PIP landscape and UpSet plots, annotating variants, exporting VCFs, and
overlapping QTL credible sets with GWAS signals.
The export step consumes a FineMappingResult object rather than a raw susieR fit,
so the three tables are views onto that object: variant.tsv is its topLoci view,
lbf.tsv its lbf view and effect.tsv its cs_summary view. Because the object
carries its own region and trait, --region-list is accepted for CLI compatibility but
is no longer used.
When to run it. After fine-mapping, on the outputs of susie_twas or fsusie, and
before any downstream reporting or GWAS integration. Most of the workflows beyond
susie_to_tsv additionally need GWAS summary statistics, plot-recipe files or a
singularity container.
Input¶
--rds_pathtests/fixtures/mnm_postprocessing/protocol_example.ENSG00000283047.fine_mapping.rds(one or moreFineMappingResultRDS files written bymnm_regression; each carries its own region and trait, and the step runs once per file)
QtlFineMappingResult: S4 DataFrame, 2 rows x 7 columns
$ study: character [1:2] test_study test_study
$ context: character [1:2] context1 context2
$ trait: character [1:2] ENSG00000283047 ENSG00000283047
$ method: character [1:2] susie susie
$ entry: S4 object <SimpleList>
..$ listData: List of 2
.. ..$ : S4 object <FineMappingEntry>
.. .. ..$ variantIds: ...
.. .. ..$ susieFit: ...
.. .. ..$ topLoci: ...
.. .. ..$ cvResult: ...
.. ..$ : S4 object <FineMappingEntry>
.. .. ..$ variantIds: ...
.. .. ..$ susieFit: ...
.. .. ..$ topLoci: ...
.. .. ..$ cvResult: ...
..$ elementType: character [1:1] ANY
..$ elementMetadata: name [1:1] NULL
..$ metadata: List of 0
... (truncated)--study toy(tag used asnamein output filenames and in intermediate RDS names. Required.)--cwd output_postproc(working directory all outputs are written under. Defaults tooutput.)--container(an empty string runs the step natively in the project environment instead of through a singularity image)--region-list(accepted for CLI compatibility and ignored. TheFineMappingResultcarries its own region and trait, so no external region list is needed.)
The same directory also holds protocol_example.fsusie.fine_mapping.rds and
protocol_example.mvsusie.fine_mapping.rds, for the fsusie_to_tsv and mv_susie_2
workflows.
The export_top_loci workflow consolidates results across regions and takes a different
set of inputs, none of which the example data ships:
--file_path(directory holding the per-region fine-mapping RDS files)--prefix,--suffix(the filename parts before and after the region id, so each region resolves to<file_path>/<prefix>.<region>.<suffix>- for exampleROSMAP_AC_eQTL.ENSG00000012779.univariate_bvsr.rds)--qtl_type cis(one ofcis,transortrans_<program>; appears in the combined output filename)--region_file(BED file listing the regions to consolidate)
The remaining workflows take these additional flags:
--tsv_path output_postproc/*.lbf.tsv(per-region LBF tables to concatenate;susie_tsv_collapse)--annot_tibble <annotation_tibble.tsv>(annotation tibble used to label variants;susie_pip_landscape_plotandfsusie_extract_effect)--trait_to_select 1(how many traits the credible-set UpSet diagram shows;susie_upsetR_cs_plot)--SNP_list <variant_list.txt>(variants to annotate;tmp_annotatation_of_snps)--annotation_rds <annotation.rds>(annotation reference the variants are looked up against;tmp_annotatation_of_snps)--geno_ref <genotype_reference.bim.gz>(genotype reference used to resolve variant identifiers;export_top_loci)--min_corr 0.8(minimum correlation for a variant to join a top-loci block;export_top_loci)--condition_meta <gwas_meta.tsv>(GWAS meta file naming the conditions to overlap;overlap_qtl_gwas)
Output¶
Per-region TSV export (susie_to_tsv, sQTL_susie_to_tsv, fsusie_to_tsv), one set per input SuSiE object:
<region>.variant.tsv-- one row per variant: PIP, credible-set order and id, posterior mean and sd.<region>.lbf.tsv-- per-variant log Bayes factors for each single-effect.<region>.effect.tsv-- per-effect summary (susie_to_tsvandsQTL_susie_to_tsvonly).<name>.all_variants.tsv-- variants pooled across regions (fsusie_to_tsvonly).
Collapsed tables (susie_tsv_collapse):
<prefix>.chr<chromosome>.unisusie_rss.lbf.tsv-- per-region lbf tables concatenated by chromosome.
Diagnostic plots:
<region>.pip_landscape_plot.pdf(susie_pip_landscape_plot)<name>.UpSetR.pdfand<name>.UpSetR_cs.pdf(susie_upsetR_plot,susie_upsetR_cs_plot)
Variant annotation (tmp_annotatation_of_snps):
<name>.annotated.rdsand<name>.annotated_rev.rds
fSuSiE effect extraction:
<region>.estimated_effect.tsv(fsusie_extract_effect)<region>.affected_region.tsv(fsusie_affected_region)
VCF export (mv_susie_2):
<region>.<context>.vcf.bgz
cis and GWAS consolidation:
<name>_cache/<name>_<meta_info>.tsv, collapsed into<name>.cis_results_db.tsv(or<name>.block_results_db.tsvfor GWAS input) --cis_results_export<name>.union_export.tsv.gz--gwas_results_exportsummary/<region>.top_loci.bed.gz, collapsed intosummary/<name>.<qtl_type>.top_loci.bed.gz--export_top_loci
QTL and GWAS overlap (overlap_qtl_gwas):
gwas_qtl/cache/<name>_gwas_batch_meta_<meta_info>.tsv<name>.overlapped.gwas.tsv
No example output ships with the repository: output_postproc/ is empty and the pipeline cannot be re-run here, so no structure dumps are shown. The filenames above come from each step output: statement.
Minimal Working Example¶
Export fine-mapping results to per-variant, LBF and effect tables¶
susie_to_tsv reads each FineMappingResult object and writes its three text views.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb susie_to_tsv \
--cwd output_postproc --study toy \
--rds_path tests/fixtures/mnm_postprocessing/protocol_example.ENSG00000283047.fine_mapping.rds \
--container '' -j1fSuSiE and sQTL exports¶
fsusie_to_tsv and sQTL_susie_to_tsv perform the same export for fSuSiE and sQTL objects.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb fsusie_to_tsv \
--cwd output_postproc --study <study_name> \
--rds_path tests/fixtures/mnm_postprocessing/protocol_example.fsusie.fine_mapping.rds \
--container '' -j1Collapse per-region tables¶
susie_tsv_collapse concatenates the per-region LBF tables into one table per chromosome.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb susie_tsv_collapse \
--cwd output_postproc --study <study_name> \
--tsv_path output_postproc/*.lbf.tsv \
--container '' -j1Diagnostic plots¶
susie_pip_landscape_plot draws the PIP landscape; susie_upsetR_plot and susie_upsetR_cs_plot draw UpSet diagrams of credible-set overlap. The landscape plot needs an annotation tibble; --trait_to_select picks how many traits the credible-set diagram shows.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb susie_pip_landscape_plot \
--cwd output_postproc --study <study_name> \
--rds_path <fine_mapping_dir>/*.fine_mapping.rds \
--annot_tibble <annotation_tibble.tsv> \
--container '' -j1
sos run pipeline/mnm_postprocessing.ipynb susie_upsetR_cs_plot \
--cwd output_postproc --study <study_name> \
--rds_path <fine_mapping_dir>/*.fine_mapping.rds \
--trait_to_select 1 \
--container '' -j1Variant annotation¶
tmp_annotatation_of_snps annotates a variant list against a reference RDS, then writes the reverse mapping.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb tmp_annotatation_of_snps \
--cwd output_postproc --study <study_name> \
--SNP_list <variant_list.txt> \
--annotation_rds <annotation.rds> \
--container '' -j1fSuSiE effect extraction¶
fsusie_extract_effect writes the estimated effect curve per region; fsusie_affected_region writes the intervals a signal spans.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb fsusie_extract_effect \
--cwd output_postproc --study <study_name> \
--rds_path tests/fixtures/mnm_postprocessing/protocol_example.fsusie.fine_mapping.rds \
--annot_tibble <annotation_tibble.tsv> \
--container '' -j1VCF export¶
mv_susie_2 writes multivariate results as bgzipped VCF, one file per region and context.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb mv_susie \
--cwd output_postproc --study <study_name> \
--rds_path tests/fixtures/mnm_postprocessing/protocol_example.mvsusie.fine_mapping.rds \
--container '' -j1cis and GWAS consolidation¶
cis_results_export collects per-region results into a results database, gwas_results_export does the same for GWAS blocks, combine_export_meta merges the per-chunk caches, and export_top_loci writes the combined top-loci BED. All four resolve each region as <file_path>/<prefix>.<region>.<suffix> and need a GWAS meta file.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb export_top_loci \
--cwd output_postproc --study <study_name> \
--region_file <regions.bed> \
--file_path <fine_mapping_dir> \
--prefix <fm_rds_prefix> --suffix <fm_rds_suffix> \
--qtl_type cis --min_corr 0.8 \
--geno_ref <genotype_reference.bim.gz> \
--container '' -j1QTL and GWAS overlap¶
overlap_qtl_gwas intersects QTL credible sets with GWAS results, caching a batch meta file per chunk before writing the overlap table.
Timing: TBD (on toy dataset)
sos run pipeline/mnm_postprocessing.ipynb overlap_qtl_gwas \
--cwd output_postproc --study <study_name> \
--region_file <regions.bed> \
--file_path <fine_mapping_dir> \
--prefix <fm_rds_prefix> --suffix <fm_rds_suffix> \
--condition_meta <gwas_meta.tsv> \
--container '' -j1The meta file consumed by the consolidation workflows is produced by:
get_condition <- function(conditions, Author, qtl_type){
strings <- c()
for(condition in conditions){
string = paste(condition, Author, qtl_type, sep = "_")
string = paste(unique(unlist(strsplit(string, "_"))), collapse = "_")
strings <- c(strings, string)
}
return(strings)
}
raw_name<- c("Mic","Ast","Oli","OPC","Exc","Inh","DLPFC","PCC","AC")
raw_name_kellis<- c("Mic_Kellis","Ast_Kellis","Oli_Kellis","OPC_Kellis","Exc_Kellis","Inh_Kellis","Ast.10","Mic.12","Mic.13")
dejager_name <- get_condition(raw_name, "De_Jager","eQTL")
kellis_name <- get_condition(raw_name_kellis, "Kellis","eQTL")
eQTL_meta <- data.frame(raw_name = c(raw_name, raw_name_kellis), new_name = c(dejager_name, kellis_name))
write_delim(eQTL_meta, "output/mnm_postprocessing/eQTL_meta.tsv")Command Interface¶
sos run pipeline/mnm_postprocessing.ipynb -husage: sos run pipeline/mnm_postprocessing.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_tsv
sQTL_susie_to_tsv
fsusie_to_tsv
susie_tsv_collapse
susie_pip_landscape_plot
susie_upsetR_plot
susie_upsetR_cs_plot
tmp_annotatation_of_snps
fsusie_extract_effect
fsusie_affected_region
mv_susie
cis_results_export
gwas_results_export
export_top_loci
combine_export_meta
overlap_qtl_gwas
Global Workflow Options:
--cwd output (as path)
A region list file documenting the chr_pos_ref_alt of a
susie_object
--study VAL (as str, required)
--container ''
Path to work directory where output locates Containers
that contains the necessary packages
--modular-script-dir code/script (as path)
--entrypoint ('micromamba run -a "" -n' + ' ' + re.sub(r'(_apptainer:latest|_docker:latest|\.sif)$', '', container.split('/')[-1])) if container else ""
--job-size 1 (as int)
For cluster jobs, number commands to run per job
--walltime 96h
Wall clock time expected
--mem 6G
Memory expected
--numThreads 2 (as int)
Number of threads
--windows 1000000 (as int)
Sections
susie_to_tsv_1:
Workflow Options:
--region-list . (as path)
Migrated: consumes a FineMappingResult RDS
(fine_mapping.R output) instead of a raw susieR object.
The three legacy outputs map to fine_mapping_export.R
views: variant.tsv = topLoci (per-variant: pip,
posterior mean/sd, logBF, cs, region) lbf.tsv = lbf
(wide per-variant x per-effect log Bayes factor matrix)
effect.tsv = cs_summary (per credible set: size,
purity, V, logBF, lead variant) The FineMappingResult
carries its own region/trait, so `region_list` is
accepted for CLI compatibility but is no longer used.
--rds-path paths
sQTL_susie_to_tsv_1:
Workflow Options:
--region-list . (as path)
Migrated: identical to susie_to_tsv but preserves the
sQTL / leafcutter2 filename sanitization ("*" -> "N";
":"/"+" scrubbed from the task tag), since intron ids
contain shell-hostile characters. Consumes a
FineMappingResult RDS and writes the topLoci / lbf /
cs_summary views.
--rds-path paths
fsusie_to_tsv_1:
Workflow Options:
--region-list . (as path)
Migrated: consumes an fSuSiE FineMappingResult RDS.
variant.tsv = topLoci (per-variant: pip, cs,
cs_95_purity) lbf.tsv = lbf (fSuSiE per-variant
x per-effect lBF matrix) (Fixes the legacy bug where
lbf.tsv was written the variant table, not the lbf
matrix.) is_dummy = cs_95_purity < 0.5; the functional
effect curves + peak positions are exported by
fsusie_extract_effect / fsusie_affected_region
(credible_band / affected_regions views), so they are
not duplicated here.
--rds-path paths
*_to_tsv_2:
Workflow Options:
--name f'{_input[0]:b}'.split(".")[0]
Reduce: concatenate the per-region variant tables (the
*variant.tsv outputs of the parallel _1 map step) into
one file, in R via data.table::rbindlist (fill=TRUE
aligns columns). _input carries all of _1's outputs, so
keep only the variant tables (not the lbf / effect
ones).
susie_tsv_collapse:
Workflow Options:
--tsv-path paths
Concatenate the per-region lbf tables (named
*.chr<N>_<start>_<end>...lbf.tsv) by chromosome.
Chromosome split is plain python (no pandas); the concat
is R data.table::rbindlist (headers handled by fread;
fill=TRUE aligns columns).
susie_pip_landscape_plot:
Workflow Options:
--rds-path paths
Per-region PIP landscape from a FineMappingResult
(fine_mapping output). Modernized from the legacy multi-
TSV plot: pecotmr does no plotting, so the ggplot lives
in fine_mapping_pip_plot.R. (The regulatory/gene
annotation overlay is a deferred enhancement;
annot_tibble is kept for CLI compat.)
susie_upsetR_plot:
Workflow Options:
--rds-path paths
UpSet plot of credible-set variant overlap across the
FineMappingResult(s)' (study, context, trait, method)
tuples. pecotmr does no plotting; the UpSet lives in
fine_mapping_upset.R.
susie_upsetR_cs_plot:
Workflow Options:
--rds-path paths
Credible-set overlap UpSet plot across the
FineMappingResult(s), via fine_mapping_upset.R (pecotmr
does no plotting). trait_to_select is retained for CLI
compatibility; the single-trait "CS shared with other
phenotypes" sub-view is a deferred wrapper option.
--trait-to-select 1 (as int)
tmp_annotatation_of_snps_1:
Workflow Options:
--SNP-list VAL (as path, required)
--annotation-rds VAL (as path, required)
tmp_annotatation_of_snps_2:
Workflow Options:
--SNP-list VAL (as path, required)
--annotation-rds VAL (as path, required)
fsusie_extract_effect:
Workflow Options:
--rds-path paths
Fitted effect curve + credible band per fSuSiE credible
set, via fsusieCredibleBand (fine_mapping_export.R
`credible_band` view). pecotmr does the wavelet credible
band; it needs an UNtrimmed fSuSiE fit (fine_mapping
trim=FALSE). The effect-curve PDF is a deferred plot
wrapper (pecotmr does no plotting); annot_tibble is kept
for CLI compatibility.
fsusie_affected_region:
Workflow Options:
--rds-path paths
GRanges of intervals where the fSuSiE credible band
excludes 0 (with a direction mcol), via
fsusieAffectedRegions (fine_mapping_export.R
`affected_regions` view). Needs an UNtrimmed fSuSiE fit
(fine_mapping trim=FALSE). The region PDF is a deferred
plot wrapper.
mv_susie_2:
Workflow Options:
--rds-path paths
Write per-context fine-mapping VCFs (ES / SE / LP / AF +
PIP / CS / within_cs_pip in the sample column) from a
FineMappingResult via fine_mapping_vcf.R ->
pecotmr::writeSumstatsVcf, replacing the inline
create_vcf. writeSumstatsVcf splits a multi-context
collection into one .vcf.bgz per context; the runtime
set is tracked with output: dynamic() (so {cwd} must be
created up front, since a dynamic output -- unlike a
concrete target -- does not pre-create its dir).
cis_results_export_1, gwas_results_export_1, export_top_loci_1: Exporting cis
susie_twas results
Workflow Options:
--per-chunk 200 (as int)
per chunk we process at most 200 datasets
--region-file . (as path)
Region list should have last column being region name.
--file-path ''
the path stored original output files
--prefix (as list)
assuming orinal files are named as prefix.id.suffix
prefix of the original files (before id) to identify
file.
--suffix VAL (as str, required)
suffix of the original files (after id) to identify
file.
--condition-meta . (as path)
if need to rename context, please load condition meta
file
--pip-thres 0.05 (as float)
if the pip all variants in a cs < this threshold, then
remove this cs
--cs-size 3 (as int)
only keep top cs_size variants in one cs
--exported-file . (as path)
provide exported meta to filter the exported genes
--region-list . (as path)
Optional: if a region list is provide the analysis will
be focused on provided region. The LAST column of this
list will contain the ID of regions to focus on
--region-name (as list)
Optional: if a region name is provided the analysis
would be focused on the union of provides region list
and region names
--geno-ref . (as path)
the (aligned) geno bim file to check allele flipping.
Retained for CLI compatibility; allele alignment is now
done upstream by fine_mapping.R (matchVariants), so it
is unused here.
--context-meta . (as path)
context meta file to map renamed context backs
--min-corr (as list)
CS purity (min.abs.corr) cutoff(s); the first value is
used for conditions_top_loci.
--[no-]gwas (default to False)
set this parameter as `True` when exporting gwas data.
--[no-]fsusie (default to False)
set this parameter as `True` when exporting fsusie data.
--[no-]metaQTL (default to False)
set this parameter as `True` when exporting metaQTL
data.
--[no-]mnm (default to False)
set this parameter as `True` when exporting mnm data.
cis_results_export_2, gwas_results_export_2:
Workflow Options:
--[no-]step1-only (default to False)
set it as True if you only want run first step (running
parallel) and combine them manually after all finished
--exported-file . (as path)
provide exported meta to filter the exported genes
--[no-]gwas (default to False)
optional: qtl or gwas, there is slightly different in
qtl and gwas rds file
cis_results_export_3:
Workflow Options:
--[no-]step1-only (default to False)
set it as True if you only want run first step (running
parallel) and combine them manually after all finished
gwas_results_export_3: get union of step1 1200 blocks costed ~5mins with in
one for loop
Workflow Options:
--[no-]step1-only (default to False)
combine_export_meta: simply combine seperate meta files, not works for having
an exsiting exported file for now.
Workflow Options:
--cache-path VAL (as path, required)
--output-file VAL (as str, required)
--[no-]remove-cache (default to False)
export_top_loci_2:
Workflow Options:
--export-path cwd
Export the per-region top-loci BED from each
cis_results_db (an FMR in the modern pipeline) via the
pecotmr-backed fine_mapping_top_loci_bed.R wrapper.
getTopLoci does the PIP/CS + independent purity
filtering; lfsr / conditional_effect are per-(variant,
context) numeric columns, so the legacy filter_lfsr
semicolon split/rejoin is gone. `export_suffix`
deliberately differs from step 1's `--suffix` (input
rds) to avoid CLI collision.
--export-prefix ''
--export-suffix 'cis_results_db.rds'
--fsusie-prefix ''
--[no-]preset-top-loci (default to False)
--signal-cutoff 0.025 (as float)
PIP cutoff for getTopLoci; optional independent CS
purity (min.abs.corr) cutoff (<0 => none).
--min-purity -1.0 (as float)
--lfsr-thres 0.01 (as float)
Retained for CLI compatibility; lfsr is now a per-
variant column (no split/rejoin).
export_top_loci_3:
Workflow Options:
--qtl-type VAL (as str, required)
Combine per-region .top_loci.bed.gz files into one
study-level file. Public-facing name:
{name}.{qtl_type}[.{variant_tag}].top_loci.bed.gz
--variant-tag ''
--combine auto
'auto' (default): skip step 3 if region_name or
region_list is set; 'yes': force run; 'no': force skip.
--region-list . (as path)
Re-declared so this step can detect step-1 subset
filters.
--region-name (as list)
overlap_qtl_gwas_1:
Workflow Options:
--per-chunk 100 (as int)
--gwas-meta-path . (as path)
--qtl-meta-path . (as path)
--gwas-file-path ''
--qtl-file-path ''
--region-list . (as path)
Optional: focus on a region list (last column = region
id) and/or region names.
--region-name (as list)
--signal-cutoff 0.0 (as float)
PIP cutoff for the top-loci overlap. 0 = intersect all
top-loci variants (the legacy behaviour, which had its
CS filter commented out); the coarse block pairing that
feeds coloc wants breadth, not just the strongest
signal.
overlap_qtl_gwas_2:
Workflow Options:
--[no-]step1-only (default to False)
set it as True if you only want run first step (running
parallel) and combine them manually after all finishedWorkflow implementation¶
The global parameters and the workflow step definitions.
[global]
# A region list file documenting the chr_pos_ref_alt of a susie_object
parameter: cwd = path("output")
parameter: study = str
name = study
## Path to work directory where output locates
## Containers that contains the necessary packages
parameter: container = ""
import re
parameter: modular_script_dir = path('code/script') # override with --modular-script-dir
parameter: entrypoint= ('micromamba run -a "" -n' + ' ' + re.sub(r'(_apptainer:latest|_docker:latest|\.sif)$', '', container.split('/')[-1])) if container else ""
# For cluster jobs, number commands to run per job
parameter: job_size = 1
# Wall clock time expected
parameter: walltime = "96h"
# Memory expected
parameter: mem = "6G"
# Number of threads
parameter: numThreads = 2
parameter: windows = 1000000
# use this function to edit memory string for PLINK input
from sos.utils import expand_size[susie_to_tsv_1]
# Migrated: consumes a FineMappingResult RDS (fine_mapping.R output) instead of a
# raw susieR object. The three legacy outputs map to fine_mapping_export.R views:
# variant.tsv = topLoci (per-variant: pip, posterior mean/sd, logBF, cs, region)
# lbf.tsv = lbf (wide per-variant x per-effect log Bayes factor matrix)
# effect.tsv = cs_summary (per credible set: size, purity, V, logBF, lead variant)
# The FineMappingResult carries its own region/trait, so `region_list` is accepted
# for CLI compatibility but is no longer used.
parameter: region_list = path()
parameter: rds_path = paths
input: rds_path, group_by = 1
output: f"{cwd}/{_input:bn}.variant.tsv", f"{cwd}/{_input:bn}.lbf.tsv", f"{cwd}/{_input:bn}.effect.tsv"
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output[0]:bn}'
bash: expand = '${ }', stdout = f"{_output[0]:nn}.stdout", stderr = f"{_output[0]:nn}.stderr", container = container, entrypoint = entrypoint
export_script=${modular_script_dir}/pecotmr_integration/fine_mapping_export.R
Rscript $export_script --input ${_input} --view topLoci --signal-cutoff 0 --output ${_output[0]}
Rscript $export_script --input ${_input} --view lbf --output ${_output[1]}
Rscript $export_script --input ${_input} --view cs_summary --output ${_output[2]}[sQTL_susie_to_tsv_1]
# Migrated: identical to susie_to_tsv but preserves the sQTL / leafcutter2
# filename sanitization ("*" -> "N"; ":"/"+" scrubbed from the task tag), since
# intron ids contain shell-hostile characters. Consumes a FineMappingResult RDS
# and writes the topLoci / lbf / cs_summary views.
parameter: region_list = path()
parameter: rds_path = paths
input: rds_path, group_by = 1
input_name = f"{_input:bn}".replace('*', 'N')
output: f"{cwd}/{input_name}.variant.tsv", f"{cwd}/{input_name}.lbf.tsv", f"{cwd}/{input_name}.effect.tsv"
tags = f'{step_name}_{_output[0]:bn}'.replace(':', '_').replace('+', 'ps')
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = tags
bash: expand = '${ }', stdout = f"{_output[0]:nn}.stdout", stderr = f"{_output[0]:nn}.stderr", container = container, entrypoint = entrypoint
export_script=${modular_script_dir}/pecotmr_integration/fine_mapping_export.R
Rscript $export_script --input ${_input} --view topLoci --signal-cutoff 0 --output ${_output[0]}
Rscript $export_script --input ${_input} --view lbf --output ${_output[1]}
Rscript $export_script --input ${_input} --view cs_summary --output ${_output[2]}[fsusie_to_tsv_1]
# Migrated: consumes an fSuSiE FineMappingResult RDS.
# variant.tsv = topLoci (per-variant: pip, cs, cs_95_purity)
# lbf.tsv = lbf (fSuSiE per-variant x per-effect lBF matrix)
# (Fixes the legacy bug where lbf.tsv was written the variant table, not the lbf
# matrix.) is_dummy = cs_95_purity < 0.5; the functional effect curves + peak
# positions are exported by fsusie_extract_effect / fsusie_affected_region
# (credible_band / affected_regions views), so they are not duplicated here.
parameter: region_list = path()
parameter: rds_path = paths
input: rds_path, group_by = 1
output: f"{cwd}/{_input:bn}.variant.tsv", f"{cwd}/{_input:bn}.lbf.tsv"
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f"{step_name}_{_output[0]:bn}"
bash: expand = "${ }", stdout = f"{_output[0]:nn}.stdout", stderr = f"{_output[0]:nn}.stderr", container = container, entrypoint = entrypoint
export_script=${modular_script_dir}/pecotmr_integration/fine_mapping_export.R
Rscript $export_script --input ${_input} --view topLoci --signal-cutoff 0 --output ${_output[0]}
Rscript $export_script --input ${_input} --view lbf --output ${_output[1]}[*_to_tsv_2]
# Reduce: concatenate the per-region variant tables (the *variant.tsv outputs of
# the parallel _1 map step) into one file, in R via data.table::rbindlist
# (fill=TRUE aligns columns). _input carries all of _1's outputs, so keep only
# the variant tables (not the lbf / effect ones).
parameter: name = f'{_input[0]:b}'.split(".")[0]
input: group_by = "all"
output: f"{cwd}/{name}.all_variants.tsv"
R: expand = "${ }", stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
library(data.table)
files <- Filter(function(f) endsWith(f, "variant.tsv"), c(${_input:r,}))
fwrite(rbindlist(lapply(files, fread), fill = TRUE), ${_output:r}, sep = "\t")[susie_tsv_collapse]
# Concatenate the per-region lbf tables (named *.chr<N>_<start>_<end>...lbf.tsv)
# by chromosome. Chromosome split is plain python (no pandas); the concat is R
# data.table::rbindlist (headers handled by fread; fill=TRUE aligns columns).
parameter: tsv_path = paths
chromosome = list(set(str(x).split(".")[-5].split("_")[0].replace("chr", "") for x in tsv_path))
input: tsv_path, for_each = "chromosome"
output: f'{cwd}/{_input[0]:bnnnnnnn}.chr{_chromosome}.unisusie_rss.lbf.tsv'
R: expand = "${ }", stdout = f"{_output}.stdout", stderr = f"{_output}.stderr", container = container, entrypoint = entrypoint
library(data.table)
files <- list.files(dirname("${_input[0]}"),
pattern = "\\.chr${_chromosome}_.*lbf\\.tsv$", full.names = TRUE)
fwrite(rbindlist(lapply(files, fread), fill = TRUE), "${_output}", sep = "\t")[susie_pip_landscape_plot]
# Per-region PIP landscape from a FineMappingResult (fine_mapping output).
# Modernized from the legacy multi-TSV plot: pecotmr does no plotting, so the
# ggplot lives in fine_mapping_pip_plot.R. (The regulatory/gene annotation
# overlay is a deferred enhancement; annot_tibble is kept for CLI compat.)
parameter: rds_path = paths
parameter: annot_tibble = path("~/Annotatr_builtin_annotation_tibble.tsv")
input: rds_path, group_by = 1
output: f'{cwd}/{_input:bn}.pip_landscape_plot.pdf'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = '${ }', stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_pip_plot.R \
--input ${_input} \
--output ${_output}
[susie_upsetR_plot]
# UpSet plot of credible-set variant overlap across the FineMappingResult(s)'
# (study, context, trait, method) tuples. pecotmr does no plotting; the UpSet
# lives in fine_mapping_upset.R.
parameter: rds_path = paths
input: rds_path
output: f'{cwd}/{name}.UpSetR.pdf'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = '${ }', stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_upset.R \
--input ${" ".join([str(x) for x in _input])} \
--output ${_output}
[susie_upsetR_cs_plot]
# Credible-set overlap UpSet plot across the FineMappingResult(s), via
# fine_mapping_upset.R (pecotmr does no plotting). trait_to_select is retained
# for CLI compatibility; the single-trait "CS shared with other phenotypes"
# sub-view is a deferred wrapper option.
parameter: rds_path = paths
parameter: trait_to_select = 1
input: rds_path
output: f'{cwd}/{name}.UpSetR_cs.pdf'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = '${ }', stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_upset.R \
--input ${" ".join([str(x) for x in _input])} \
--output ${_output}
[tmp_annotatation_of_snps_1]
parameter: SNP_list = path
parameter: annotation_rds = path
input: SNP_list
output: f'{cwd}/{_input:b}.annotated.rds'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
R: expand = '${ }', stdout = f"{_output}.stdout", stderr = f"{_output}.stderr", container = container
library("dplyr")
library("readr")
library("purrr")
library("stringr")
sharing_snp = readRDS("${_input}")
sharing_snp_fsusie = sharing_snp[[1]]%>%filter(haQTL == 1 | mQTL == 1)
sharing_snp_fsusie = sharing_snp_fsusie%>%mutate(X1 = read.table(text = sharing_snp_fsusie$variants, sep = ":")$V1, X2 = read.table(text = read.table(text = sharing_snp_fsusie$variants, sep = ":")$V2 , sep = "_")$V1 )
sharing_snp_fsusie = sharing_snp_fsusie%>%select(variants,chr = X1, pos = X2)
annotation = readRDS("${annotation_rds}")
print("data loaded")
result = sharing_snp_fsusie%>%mutate(annot = map2( chr,pos , ~ annotation%>%filter(X1 == .x, X2 <= .y, X3 >= .y)%>%pull(X5)))%>%mutate(annot = map_chr(annot, ~paste0(.x ,collapse = ",")) )
print("snp annotated")
result%>%saveRDS("${_output}")[tmp_annotatation_of_snps_2]
parameter: SNP_list = path
parameter: annotation_rds = path
output: f'{cwd}/{_input:b}.annotated_rev.rds'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
R: expand = '${ }', stdout = f"{_output}.stdout", stderr = f"{_output}.stderr", container = container
library("dplyr")
library("readr")
library("purrr")
library("stringr")
result = readRDS(${_input:r})
result_rev = tibble(annot = unique(annotation$X5))%>%mutate(variants = map(annot, ~ result%>%filter( str_detect(annot,.x))%>%pull(variants)) )%>%mutate( variants = map_chr(variants,~paste0(.x ,collapse = ",")) )
result_rev%>%saveRDS("${_output}")[fsusie_extract_effect]
# Fitted effect curve + credible band per fSuSiE credible set, via
# fsusieCredibleBand (fine_mapping_export.R `credible_band` view). pecotmr does
# the wavelet credible band; it needs an UNtrimmed fSuSiE fit (fine_mapping
# trim=FALSE). The effect-curve PDF is a deferred plot wrapper (pecotmr does no
# plotting); annot_tibble is kept for CLI compatibility.
parameter: rds_path = paths
parameter: annot_tibble = path("~/Annotatr_builtin_annotation_tibble.tsv")
input: rds_path, group_by = 1
output: f'{cwd}/{_input:bn}.estimated_effect.tsv'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = '${ }', stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_export.R \
--input ${_input} \
--view credible_band \
--output ${_output}
[fsusie_affected_region]
# GRanges of intervals where the fSuSiE credible band excludes 0 (with a
# direction mcol), via fsusieAffectedRegions (fine_mapping_export.R
# `affected_regions` view). Needs an UNtrimmed fSuSiE fit (fine_mapping
# trim=FALSE). The region PDF is a deferred plot wrapper.
parameter: rds_path = paths
input: rds_path, group_by = 1
output: f'{cwd}/{_input:bn}.affected_region.tsv'
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output:bn}'
bash: expand = '${ }', stdout = f"{_output:n}.stdout", stderr = f"{_output:n}.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_export.R \
--input ${_input} \
--view affected_regions \
--output ${_output}
#[uni_susie_2]
input: group_with = "genoFile"
output: f"{_input:n}.vcf.bgz"
task: trunk_workers = 1, trunk_size = job_size, walltime = walltime, mem = mem, cores = numThreads, tags = f'{step_name}_{_output[0]:bn}'
R: expand = '${ }', stdout = f"{_output:nn}.stdout", stderr = f"{_output:nn}.stderr", container = container
## Define create_vcf function
create_vcf = function (chrom, pos, nea, ea, snp = NULL, ea_af = NULL, effect = NULL,
se = NULL, pval = NULL, name = NULL,cs = NULL, pip = NULL)
{
stopifnot(length(chrom) == length(pos))
if (is.null(snp)) {
snp <- paste0(chrom, ":", pos)
}
snp <- paste0(chrom, ":", pos)
nsnp <- length(chrom)
gen <- list()
## Setupt data content for each sample column
if (!is.null(ea_af))
gen[["AF"]] <- matrix(ea_af, nsnp)
if (!is.null(effect))
gen[["ES"]] <- matrix(effect, nsnp)
if (!is.null(se))
gen[["SE"]] <- matrix(se, nsnp)
if (!is.null(pval))
gen[["LP"]] <- matrix(-log10(pval), nsnp)
if (!is.null(cs))
gen[["CS"]] <- matrix(cs, nsnp)
if (!is.null(pip))
gen[["PIP"]] <- matrix(pip, nsnp)
gen <- S4Vectors::SimpleList(gen)
## Setup snps info for the fix columns
gr <- GenomicRanges::GRanges(chrom, IRanges::IRanges(start = pos,
end = pos + pmax(nchar(nea), nchar(ea)) - 1, names = snp))
coldata <- S4Vectors::DataFrame(Studies = name, row.names = name)
## Setup header informations
hdr <- VariantAnnotation::VCFHeader(header = IRanges::DataFrameList(fileformat = S4Vectors::DataFrame(Value = "VCFv4.2",
row.names = "fileformat")), sample = name)
VariantAnnotation::geno(hdr) <- S4Vectors::DataFrame(Number = c("A",
"A", "A", "A", "A", "A"), Type = c("Float", "Float",
"Float", "Float", "Float", "Float"), Description = c("Effect size estimate relative to the alternative allele",
"Standard error of effect size estimate", "-log10 p-value for effect estimate",
"Alternate allele frequency in the association study",
"The CS this variate are captured, 0 indicates not in any cs", "The posterior inclusion probability to a CS"),
row.names = c("ES", "SE", "LP", "AF", "CS", "PIP"))
## Save only the meta information in the sample columns
VariantAnnotation::geno(hdr) <- subset(VariantAnnotation::geno(hdr),
rownames(VariantAnnotation::geno(hdr)) %in% names(gen))
## Save VCF
vcf <- VariantAnnotation::VCF(rowRanges = gr, colData = coldata,
exptData = list(header = hdr), geno = gen)
VariantAnnotation::alt(vcf) <- Biostrings::DNAStringSetList(as.list(ea))
VariantAnnotation::ref(vcf) <- Biostrings::DNAStringSet(nea)
## Add fixed values
VariantAnnotation::fixed(vcf)$FILTER <- "PASS"
return(sort(vcf))
}
library("susieR")
library("dplyr")
library("tibble")
library("purrr")
library("readr")
library("tidyr")
library("stringr")
# Get list of cs snps
susie_list = readRDS(${_input:r})
susie_tb_ls = list()
for (i in 1:length(susie_list)){
susie_tb = tibble( snps = names(susie_list[[1]]$pip)[which( susie_list[[i]]$pip >= 0)], snps_index = which(( susie_list[[i]]$pip >= 0)) )
susie_tb_ls[[i]]= susie_tb%>%mutate( cs = map(snps_index,~which( susie_list[[i]]$sets$cs %in% .x))%>%as.numeric%>%replace_na(0),
pip = map_dbl(snps_index,~( susie_list[[i]]$pip[.x])),
coef = map_dbl(snps_index,~(coef.susie( susie_list[[i]])[.x+1])))
}
if(length(susie_tb_ls) >= 2){
for(i in 2:length(susie_tb_ls)){
susie_tb_ls[[i]] = full_join(susie_tb_ls[[i-1]],susie_tb_ls[[i]], by = "snps")
}
}
m = c("cs","pip","coef")
output = list()
for(i in m){
output[[i]] = susie_tb_ls[[length(susie_tb_ls)]]%>%select(contains(i))%>%as.matrix
}
snps_tb = susie_tb_ls[[length(susie_tb_ls)]]%>%mutate(
chr = map_chr(snps,~read.table(text = .x,sep = ":",as.is = T)$V1),
pos_alt_ref = map_chr(snps,~read.table(text = .x,sep = ":",as.is = TRUE)$V2),
pos = map_dbl(pos_alt_ref,~read.table(text = .x,sep = "_",as.is = TRUE)$V1),
alt = map_chr(pos_alt_ref,~read.table(text = .x,sep = "_",as.is = TRUE, colClass = "character")$V2),
ref = map_chr(pos_alt_ref,~read.table(text = .x,sep = "_",as.is = TRUE, colClass = "character")$V3))
snps_tb = snps_tb%>%filter(str_detect(ref, "[ACTG]") & str_detect(alt, "[ACTG]"))
output_vcf = create_vcf(
chrom = snps_tb$chr,
pos = snps_tb$pos,
ea = snps_tb$alt,
nea = snps_tb$ref,
effect = snps_tb%>%select(contains("coef"))%>%as.matrix ,
pip = snps_tb%>%select(contains("pip"))%>%as.matrix,
cs = snps_tb%>%select(contains("cs"))%>%as.matrix,
name = names(susie_list))
VariantAnnotation::writeVcf(output_vcf,${_output:nr},index = TRUE)[mv_susie_2]
# Write per-context fine-mapping VCFs (ES / SE / LP / AF + PIP / CS / within_cs_pip
# in the sample column) from a FineMappingResult via fine_mapping_vcf.R ->
# pecotmr::writeSumstatsVcf, replacing the inline create_vcf. writeSumstatsVcf
# splits a multi-context collection into one .vcf.bgz per context; the runtime
# set is tracked with output: dynamic() (so {cwd} must be created up front, since
# a dynamic output -- unlike a concrete target -- does not pre-create its dir).
parameter: rds_path = paths
import os
os.makedirs(str(cwd), exist_ok=True)
input: rds_path, group_by = 1
output: dynamic(f"{cwd}/{_input:bn}.*.vcf.bgz")
task: trunk_workers = 1, trunk_size = 1, walltime = '2h', mem = mem, cores = 1, tags = f'{step_name}_{_input:bn}'
bash: expand = '${ }', stdout = f"{cwd}/{_input:bn}.vcf.stdout", stderr = f"{cwd}/{_input:bn}.vcf.stderr", container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_vcf.R \
--input ${_input} \
--output ${cwd}/${_input:bn}.vcf.bgz \
--split-by-context
# Exporting cis susie_twas results
[cis_results_export_1, gwas_results_export_1, export_top_loci_1]
# per chunk we process at most 200 datasets
parameter: per_chunk = 200
# Region list should have last column being region name.
parameter:region_file=path()
# the path stored original output files
parameter:file_path=''
# assuming orinal files are named as prefix.id.suffix
# prefix of the original files (before id) to identify file.
parameter:prefix=[]
# suffix of the original files (after id) to identify file.
parameter:suffix=str
# if need to rename context, please load condition meta file
parameter: condition_meta = path()
# if the pip all variants in a cs < this threshold, then remove this cs
parameter: pip_thres = 0.05
# only keep top cs_size variants in one cs
parameter: cs_size = 3
# provide exported meta to filter the exported genes
parameter: exported_file = path()
# Optional: if a region list is provide the analysis will be focused on provided region.
# The LAST column of this list will contain the ID of regions to focus on
parameter: region_list = path()
# Optional: if a region name is provided
# the analysis would be focused on the union of provides region list and region names
parameter: region_name = []
# the (aligned) geno bim file to check allele flipping. Retained for CLI
# compatibility; allele alignment is now done upstream by fine_mapping.R
# (matchVariants), so it is unused here.
parameter: geno_ref = path()
# context meta file to map renamed context backs
parameter: context_meta = path()
# CS purity (min.abs.corr) cutoff(s); the first value is used for conditions_top_loci.
parameter: min_corr = []
# set this parameter as `True` when exporting gwas data.
parameter: gwas = False
# set this parameter as `True` when exporting fsusie data.
parameter: fsusie = False
# set this parameter as `True` when exporting metaQTL data.
parameter: metaQTL = False
# set this parameter as `True` when exporting mnm data.
parameter: mnm = False
# Orchestration only (no pandas / no helper defs): resolve, per region, the
# existing per-study/context FineMappingResult input file(s). The heavy legacy
# work (allele alignment, top-loci processing, combined-db assembly) now lives
# in fine_mapping.R + the fine_mapping_cis_db_export.R wrapper below.
import os
_region_rows = [tuple((_l.rstrip('\n').split('\t') + ['', '', '', ''])[:4])
for _l in open(region_file)
if _l.strip() and not _l.startswith('#')]
_focus = set(region_name)
if region_list.is_file():
_focus |= {_l.rstrip('\n').split('\t')[-1] for _l in open(region_list)
if _l.strip() and not _l.startswith('#')}
if len(_focus) > 0:
_region_rows = [r for r in _region_rows if r[3] in _focus or f"{r[0]}_{r[3]}" in _focus]
# candidate names: prefix.id.suffix (plain id first, then chr_id), pick existing.
_pfxs = list(prefix) if len(prefix) > 0 else ['']
_meta = []
_data = []
for _chr, _start, _end, _id in _region_rows:
_plain = [f"{file_path}/{(p + '.') if p else ''}{_id}.{suffix}" for p in _pfxs]
_plain = [c for c in _plain if os.path.exists(c)]
_chrid = [f"{file_path}/{(p + '.') if p else ''}{_chr}_{_id}.{suffix}" for p in _pfxs]
_chrid = [c for c in _chrid if os.path.exists(c)]
_files = _plain if _plain else _chrid
if _files:
_meta.append((_chr, _start, _end, _id))
_data.append(_files)
regional_data = {'meta': _meta, 'data': _data}
meta_info = regional_data['meta']
stop_if(len(regional_data['data']) == 0, 'No input FineMappingResult files found for any region.')
# db suffix mirrors cis_results_export_2's block_results_db / cis_results_db split
_db_kind = 'block_results_db' if gwas else 'cis_results_db'
_purity_arg = f"--min-purity {min_corr[0]}" if len(min_corr) > 0 else ""
input: regional_data["data"], group_by = lambda x: regional_data["data"], group_with = "meta_info"
output: f"{cwd}/{name}_cache/{name}_{_meta_info[3]}.tsv"
task: trunk_workers = job_size, walltime = walltime, trunk_size = job_size, mem = mem, cores = numThreads, tags = f'{_output:bn}'
bash: expand = "${ }", stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', container = container, entrypoint = entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_cis_db_export.R \
--input ${_input} \
--combined-data-output ${cwd}/${name}.${_meta_info[3]}.${_db_kind}.rds \
${('--combined-data-sumstats-output %s/%s.%s.%s.sumstats.rds' % (cwd, name, _meta_info[3], _db_kind)) if not (fsusie or mnm) else ''} \
--region-id ${_meta_info[3]} --chr ${_meta_info[0]} --start ${_meta_info[1]} --end ${_meta_info[2]} \
--signal-cutoff ${pip_thres} ${_purity_arg} \
--meta-output ${_output} \
--pip-sum-output ${cwd}/${name}.${_meta_info[3]}.pip_sum
[cis_results_export_2, gwas_results_export_2]
# set it as True if you only want run first step (running parallel) and combine them manually after all finished
parameter: step1_only = False
skip_if(step1_only)
# provide exported meta to filter the exported genes
parameter: exported_file = path()
# optional: qtl or gwas, there is slightly different in qtl and gwas rds file
parameter: gwas = False
input: group_by = 'all'
output: f"{cwd}/{name}.{'block_results_db' if gwas else 'cis_results_db'}.tsv"
# stop_if(_input[0] not in locals().keys(), 'All files have been exported already') #FIXME should we remove to a separate file. sothat we can stop globally as above
task: trunk_workers = 1, walltime = '1h', trunk_size = 1, mem = '16G', cores = 1, tags = f'{_output:bn}'
bash: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
if [ -e "${_output:ad}/${name}_cache/" ]; then
sed 's/^chr/#chr/' `ls ${_output:ad}/${name}_cache/*tsv |head -n1` | head -n 1 > ${_output:an}.temp
tail -n +2 -q ${_output:ad}/${name}_cache/*.tsv >> ${_output:an}.temp
error_files=$(find "${_output:ad}/${name}_cache/" -type f -name "*_error")
if [[ -n $error_files ]]; then
cat $error_files >> ${_output:an}.error_genes
else
echo "No truncated files detected"
fi
else
echo "All files have been exported already"
fi
R: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
if (file.exists(paste0(${_output:anr},".temp"))) {
library(tidyverse)
meta <- read_delim(paste0(${_output:anr},".temp"), delim = '\t')
if (${"TRUE" if exported_file.is_file() else "FALSE"}){
exp_meta <- read_delim(${exported_file:r}, delim = '\t')
meta <- bind_rows(meta, exp_meta) %>%
group_by(`#chr`, start, end, region_id, TSS) %>%
summarise(across(c(original_data, combined_data, combined_data_sumstats, conditions, conditions_top_loci),
~paste(unique(.), collapse = ",")),
.groups = 'drop')
}
write_delim(meta, ${_output:r}, delim = '\t')
}[cis_results_export_3]
# set it as True if you only want run first step (running parallel) and combine them manually after all finished
parameter: step1_only = False
skip_if(step1_only)
bash: expand = "${ }", container = container, stderr = f'{_input:n}.stderr', stdout = f'{_input:n}.stdout'
rm -rf "${_input:ad}/${name}_cache/"
rm -rf ${_input:an}.temp#get union of step1
#1200 blocks costed ~5mins with in one for loop
[gwas_results_export_3]
parameter: step1_only = False
skip_if(step1_only)
output: f"{cwd}/{name}.union_export.tsv.gz"
task: trunk_workers = 1, walltime = '1h', trunk_size = 1, mem = '16G', cores = 1, tags = f'{_output:bn}'
bash: expand = "${ }", container = container, stderr = f'{_input:n}.stderr', stdout = f'{_input:n}.stdout'
rm -rf "${_input:ad}/${name}_cache/"
rm -rf ${_input:an}.temp
R: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
library(tidyverse)
library(data.table)
mtx <- read_delim(${_input:r})
files <- mtx %>% filter(!is.na(conditions_top_loci)) %>% pull(combined_data) %>% paste0(${_input[0]:dr},'/',.)
all_top_loci <- data.frame()
for (i in seq_along(files)) {
file <- files[i]
res <- readRDS(file)
file_top_loci <- lapply(names(res), function(block) {
lapply(names(res[[block]]), function(study) {
lapply(names(res[[block]][[study]]), function(method) {
if (!is.null(res[[block]][[study]][[method]]$top_loci)) {
temp_df <- res[[block]][[study]][[method]]$top_loci
mutate(temp_df, study = study, method = method, block = block)
} else {
NULL
}
})
}) %>% bind_rows()
}) %>% bind_rows()
all_top_loci <- bind_rows(all_top_loci, file_top_loci)
if (i %% 100 == 0) {
message(sprintf("Have processed %d files.", i))
}
}
fwrite(all_top_loci, ${_output:r})# simply combine seperate meta files, not works for having an exsiting exported file for now.
[combine_export_meta]
parameter: cache_path=path
parameter: output_file = str
parameter: remove_cache = False
output: f"{cache_path:d}/{output_file}"
bash: expand = "${ }", container = container, stderr = f'{_input:n}.stderr', stdout = f'{_input:n}.stdout'
head -n 1 -q ${cache_path}/*.tsv | sed 's/^chr/#chr/' | head -n 1 > ${_output}
tail -n +2 -q ${cache_path}/*.tsv >> ${_output}
if ${"true" if remove_cache else "false"}; then
rm -rf ${cache_path}
fi[export_top_loci_2]
# Export the per-region top-loci BED from each cis_results_db (an FMR in the
# modern pipeline) via the pecotmr-backed fine_mapping_top_loci_bed.R wrapper.
# getTopLoci does the PIP/CS + independent purity filtering; lfsr /
# conditional_effect are per-(variant, context) numeric columns, so the legacy
# filter_lfsr semicolon split/rejoin is gone.
# `export_suffix` deliberately differs from step 1's `--suffix` (input rds) to avoid CLI collision.
parameter: export_path = cwd
parameter: export_prefix = '' # falls back to step-1 `name` if empty
parameter: export_suffix = '' # empty => derived from `gwas` below
# Re-declared so the suffix can default by mode: step 1 writes
# block_results_db.rds under --gwas True, else cis_results_db.rds.
parameter: gwas = False
parameter: fsusie_prefix = ''
parameter: preset_top_loci = False
# PIP cutoff for getTopLoci; optional independent CS purity (min.abs.corr) cutoff (<0 => none).
parameter: signal_cutoff = 0.025
parameter: min_purity = -1.0
# Retained for CLI compatibility; lfsr is now a per-variant column (no split/rejoin).
parameter: lfsr_thres = 0.01
_pfx = export_prefix if export_prefix else name
import glob as _glob
_suffix = export_suffix if export_suffix else ('block_results_db.rds' if gwas else 'cis_results_db.rds')
_rds_files = sorted(_glob.glob(f"{export_path}/{_pfx}.{fsusie_prefix}*.{_suffix}"))
stop_if(len(_rds_files) == 0, f"No RDS files matched: {export_path}/{_pfx}.{fsusie_prefix}*.{_suffix}")
_purity_arg = f"--min-purity {min_purity}" if float(min_purity) >= 0 else ""
input: _rds_files, group_by = 1
output: f"{cwd}/summary/{_input:bnn}.top_loci.bed.gz"
task: trunk_workers = 1, walltime = '1h', trunk_size = job_size, mem = '16G', cores = 1, tags = f'{_output:bn}'
bash: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', entrypoint=entrypoint
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_top_loci_bed.R \
--input ${_input} \
--signal-cutoff ${signal_cutoff} ${_purity_arg} \
--output ${_output}
[export_top_loci_3]
# Combine per-region .top_loci.bed.gz files into one study-level file.
# Public-facing name: {name}.{qtl_type}[.{variant_tag}].top_loci.bed.gz
parameter: qtl_type = str
parameter: variant_tag = ''
# 'auto' (default): skip step 3 if region_name or region_list is set; 'yes': force run; 'no': force skip.
parameter: combine = 'auto'
# Re-declared so this step can detect step-1 subset filters.
parameter: region_list = path()
parameter: region_name = []
stop_if(combine not in ('auto', 'yes', 'no'), f"--combine must be auto|yes|no, got {combine}")
_region_filter_set = (region_list.is_file() or len(region_name) > 0)
_do_combine = (combine == 'yes') or (combine == 'auto' and not _region_filter_set)
skip_if(not _do_combine, f"Skipping combine: combine={combine}, region_filter_set={_region_filter_set}")
_tag = f".{variant_tag}" if variant_tag else ""
input: group_by = 'all'
output: f"{cwd}/summary/{name}.{qtl_type}{_tag}.top_loci.bed.gz"
task: trunk_workers = 1, walltime = '2h', trunk_size = 1, mem = '8G', cores = 1, tags = f'{_output:bn}'
bash: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', entrypoint=entrypoint
set -euo pipefail
mkdir -p "$(dirname "${_output}")"
GZ=$(command -v bgzip || echo gzip)
{
# header from first non-empty file
for f in ${_input}; do
if [ "$(zcat -f "$f" 2>/dev/null | wc -l)" -ge 2 ]; then
zcat -f "$f" | head -1
break
fi
done
# data rows from every non-empty file
for f in ${_input}; do
if [ "$(zcat -f "$f" 2>/dev/null | wc -l)" -ge 2 ]; then
zcat -f "$f" | tail -n +2
fi
done
} | $GZ > ${_output}
[overlap_qtl_gwas_1]
parameter: per_chunk = 100
parameter: gwas_meta_path = path()
parameter: qtl_meta_path = path()
parameter: gwas_file_path = ''
parameter: qtl_file_path = ''
# Optional: focus on a region list (last column = region id) and/or region names.
parameter: region_list = path()
parameter: region_name = []
# PIP cutoff for the top-loci overlap. 0 = intersect all top-loci variants (the
# legacy behaviour, which had its CS filter commented out); the coarse block
# pairing that feeds coloc wants breadth, not just the strongest signal.
parameter: signal_cutoff = 0.0
# Orchestration only (no pandas / no helper defs): pair each QTL region with the
# GWAS blocks it overlaps by coordinate, resolving both sides' original_data
# (per-study/per-block S4 FineMappingResult files). fine_mapping_overlap.R does
# the allele-aware variant overlap + stamps block_top_loci onto the meta row.
import os, csv
if qtl_file_path == '': qtl_file_path = str(qtl_meta_path.parent)
if gwas_file_path == '': gwas_file_path = str(gwas_meta_path.parent)
_hasTop = lambda r: (r.get('conditions_top_loci') or '').strip() not in ('', 'NA')
_g = [r for r in csv.DictReader(open(gwas_meta_path), delimiter='\t') if _hasTop(r)]
_q = [r for r in csv.DictReader(open(qtl_meta_path), delimiter='\t') if _hasTop(r)]
_focus = set(region_name)
if region_list.is_file():
_focus |= {l.rstrip('\n').split('\t')[-1] for l in open(region_list) if l.strip() and not l.startswith('#')}
if len(_focus) > 0:
_q = [r for r in _q if r['region_id'] in _focus or f"{r['#chr']}_{r['region_id']}" in _focus]
_meta = []
_qtl_data = []
for q in _q:
qchr, qs, qe = q['#chr'], int(q['start']), int(q['end'])
blocks = [g for g in _g if g['#chr'] == qchr and int(g['start']) <= qe and int(g['end']) >= qs]
gfiles = [f"{gwas_file_path}/{x.strip()}" for g in blocks for x in g['original_data'].split(',') if x.strip()]
gfiles = [f for f in gfiles if os.path.exists(f)]
qfiles = [f"{qtl_file_path}/{x.strip()}" for x in q['original_data'].split(',') if x.strip()]
qfiles = [f for f in qfiles if os.path.exists(f)]
if gfiles and qfiles:
_meta.append((qchr, q['start'], q['end'], q['region_id'], gfiles))
_qtl_data.append(qfiles)
stop_if(len(_qtl_data) == 0, 'No overlapping QTL x GWAS region pairs with existing files.')
meta_info = _meta
input: [f for grp in _qtl_data for f in grp], group_by = lambda x: _qtl_data, group_with = "meta_info"
output: f"{cwd}/gwas_qtl/cache/{name}_gwas_batch_meta_{_meta_info[3]}.tsv"
task: trunk_workers = 1, walltime = '1h', trunk_size = job_size, mem = '16G', cores = 1, tags = f'{_output:bn}'
bash: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
Rscript ${modular_script_dir}/pecotmr_integration/fine_mapping_overlap.R \
--qtl ${" ".join([str(x) for x in _input])} \
--gwas ${" ".join(_meta_info[4])} \
--signal-cutoff ${signal_cutoff} \
--qtl-meta ${qtl_meta_path} --region-id ${_meta_info[3]} \
--meta-output ${_output} \
--output ${cwd}/gwas_qtl/cache/${name}_gwas_batch_export_${_meta_info[3]}.tsv
[overlap_qtl_gwas_2]
# set it as True if you only want run first step (running parallel) and combine them manually after all finished
parameter: step1_only = False
skip_if(step1_only)
input: group_by = 'all'
output: f"{cwd}/{name}.overlapped.gwas.tsv"
task: trunk_workers = 1, walltime = '1h', trunk_size = job_size, mem = '16G', cores = 1, tags = f'{_output:bn}'
R: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
library(data.table)
exp_path <- ${_input[0]:adr}
meta_files <- c(${",".join(['"%s"' % x.absolute() for x in _input])})
exp_files <- list.files(exp_path, "_gwas_batch_export", full.names = T)
meta_list <- exp_list <- list()
meta_combined <- rbindlist(lapply(meta_files, fread), fill = TRUE)
exp_combined <- rbindlist(lapply(exp_files, fread), fill = TRUE)
fwrite(exp_combined, gsub("tsv","export.csv.gz","${_output}"))
fwrite(meta_combined, "${_output}", sep = '\t')
#bash: expand = "${ }", container = container, stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout'
# rm -rf ${_input[0]:adr}