Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

TWAS, cTWAS and MR

Runs transcriptome-wide association analysis, quantile TWAS, and cTWAS fine-mapping to identify which gene and SNP signals are likely to be causal.

Overview

A TWAS scan tests each gene’s genetically predicted expression, or another molecular trait, against a trait, but a significant gene is not necessarily a causal one: nearby variants with direct effects on the trait, and the predicted expression of neighbouring genes, are correlated with the gene’s own xQTLs and act as confounders. cTWAS addresses this by fine-mapping genes and variants jointly within a region, so a gene is credited only for signal that its expression explains beyond the surrounding variants and genes, and reports a posterior inclusion probability rather than a p-value (Zhao et al., 2024). This module follows the multi-group cTWAS formulation of Qian et al. (2024+), in which each molecular context forms its own group. Quantile TWAS extends the scan by testing genetic effects at different quantiles of the trait distribution. Mendelian randomization uses fine-mapped xQTL variants as instrumental variables (Zhang et al., 2020) to estimate a causal effect for candidate genes: it runs inside the same pecotmr pipeline when an xQTL fine-mapping result is supplied, aggregates multiple instruments by fixed-effect meta-analysis, and excludes results with severe exclusion-restriction violations. The unit of analysis is a single gene-trait pair.

TWAS and cTWAS read prediction weights stored as pecotmr TwasWeights objects. Weights from the legacy FunGen-xQTL release on Synapse use an older list layout and are converted first by the optional twas_weights_conversion workflow; weights already stored as TwasWeights skip this step. The conversion copies the weights and cross-validation metrics unchanged and writes the xQTL meta and data-type tables that the other workflows read. It is optional: skip it when the weights are already TwasWeights. MR in twas also needs the QTL fine-mapping results of the same genes; the optional fine_mapping_conversion workflow converts legacy fine-mapping files the same way.

The module offers five workflows, in the order they run: twas_weights_conversion and fine_mapping_conversion (both optional) convert legacy weights and fine-mapping results, twas tests each gene and molecular context for association using the models that pass the cross-validation cutoffs, ctwas fine-maps genes and variants jointly in three chained steps, and quantile_twas is an alternative scan across trait quantiles.

When to run it. After xQTL fine-mapping and TWAS weight training (mnm_regression), with GWAS summary statistics and an LD reference in hand. Weights from the legacy FunGen-xQTL release need twas_weights_conversion first; weights already in pecotmr TwasWeights format do not. Cross-validated weights are strongly recommended. cTWAS consumes weights; it does not produce them.

Input

  • --gwas_meta_data tests/fixtures/twas/protocol_example.twas.gwas_meta.tsv (one row per GWAS study. Columns study_id, chrom, file_path, sample_size; file_path points at the summary statistics for that chromosome.)

study_id	chrom	file_path	sample_size
protocol_example_twas_chr22	22	protocol_example.twas.gwas_sumstats.chr22.tsv.gz	200000
  • --xqtl_meta_data output/twas_weights_conversion/protocol_example.protocol_example.xqtl_meta.tsv (one row per gene with its TwasWeights file, as written by twas_weights_conversion; for weights already in that format, a table of the same layout pointing at them. Columns #chr, start, end, region_id, TSS, original_data, contexts.)

#chr	start	end	region_id	TSS	original_data	contexts
chr22	0	18960000	ENSG00000130538	10685239	<path>/output/twas_weights_conversion/protocol_example/protocol_example.ENSG00000130538.twas_weights.s4.rds	bulk_rnaseq
  • --ld_meta_data tests/fixtures/ld_reference/ld_meta_file.tsv (LD blocks and where the matrices live. Columns #chr, start, end, path. Produced by ld_reference_generation.)

#chr	start	end	path
chr1	16103	2888443	chr1/protocol_example.LD.chr1
chr1	2888443	4320284	chr1/protocol_example.LD.chr1
  • --regions tests/fixtures/twas/protocol_example.twas.LD_blocks.chr22.bed (the LD blocks to analyze. Columns chr, start, stop.)

chr	start	stop
chr22	10000000	19000000
  • --xqtl_type_table output/twas_weights_conversion/protocol_example.xqtl_type_table.tsv (maps each xQTL context to its modality, used when grouping contexts for cTWAS; written by twas_weights_conversion, or supplied directly)

context	type
bulk_rnaseq	eQTL
  • --legacy_weights tests/fixtures/twas/protocol_example.twas.legacy_weights.tsv (twas_weights_conversion only) (legacy FunGen-xQTL weight files to convert, one per row. Columns study, data_type, path; rows that share a study and a gene are merged into one weight file.)

  • --legacy_fine_mapping legacy_fine_mapping.tsv (fine_mapping_conversion only) (legacy FunGen-xQTL fine-mapping files to convert, one per gene. Columns study, path; study must match the study of the converted weights.)

  • --fine_mapping_dir output/fine_mapping_conversion (twas, optional) (folder written by fine_mapping_conversion. When set, twas also runs MR for each gene that has a fine-mapping file there; --mr_method (ivwPerVariant or csAware) and --mr_pip_cutoff (default 0.5) control it. Without it, twas reports TWAS only.)

study	data_type	path
protocol_example	eQTL	tests/fixtures/twas/protocol_example.twas.reshaped_toy.chr22_ENSG00000130538.univariate_twas_weights.rds
  • --cwd output/twas (working directory all outputs are written under)

  • --name protocol_example (tag used in every output filename)

  • --region-name chr22_10000000_19000000 (optional; restrict to named regions)

Model and filtering options:

  • --ld_reference_sample_size (sample size of the LD reference panel, used to scale the LD matrices)

  • --prior_var_structure (how the cTWAS prior variance is shared across groups)

  • --gwas_study <study_id> (ctwas only: the one GWAS study to fine-map, required when --gwas_meta_data lists several; run each study with its own --cwd)

  • --rsq_cutoff (minimum cross-validation r-squared for a weight model to be used)

  • --rsq_pval_cutoff (maximum cross-validation p-value for a weight model to be used)

  • --twas_weight_cutoff (drop weights below this magnitude before assembling cTWAS inputs)

  • --thin (fraction of SNPs retained when thinning for parameter estimation)

  • --run_param_est (estimate cTWAS group priors)

  • --run_finemapping (run cTWAS fine-mapping after the priors are estimated)

  • --skip_assembly (reuse an existing ctwas_inputs.rds instead of rebuilding it)

File formats:

  • GWAS summary statistics are tab-delimited and tabix-indexed by chrom/pos; the first four columns are chrom, pos, A1, A2. For MR, effect_allele_frequency and a sample-size column are also required.

  • The GWAS meta-file may carry an optional column_mapping_file column pointing at a YAML that renames columns: required chrom, pos, A1, A2, z (or betahat/sebetahat); optional n, var_y. chrom 0 means genome-wide.

  • The LD reference meta-file gives the LD-matrix path as a comma-separated matrix,bim pair, plus the genome build. See ld_reference_generation.

  • xQTL weights are an RDS database keyed region to context to weight matrix. Weights from LASSO, Elastic Net and mr.ash are taken as-is for QTL variants overlapping GWAS variants; SuSiE weights are adjusted to match GWAS variants exactly.

  • Use --comment_string "#" if the summary statistics carry comment lines; no comment character is assumed by default.

  • --cs_min_cor (minimum correlation for a cTWAS credible set; default 0)

  • --min_pip_cutoff (minimum PIP for a variant to be reported; default 0)

  • --max_num_variants (cap on variants considered per region; default unlimited)

  • --multi_group (use the multi-group cTWAS model instead of a single shared prior)

Output

  • output/twas_weights_conversion/<study>/<study>.<gene>.twas_weights.s4.rds (twas_weights_conversion) (one pecotmr TwasWeights object per study and gene; one row per context and method, weights copied unchanged from the legacy file)

  • output/twas_weights_conversion/<name>.<study>.xqtl_meta.tsv (twas_weights_conversion) (--xqtl_meta_data input of twas and ctwas, one table per study)

#chr	start	end	region_id	TSS	original_data	contexts
chr22	0	18960000	ENSG00000130538	10685239	<path>/output/twas_weights_conversion/protocol_example/protocol_example.ENSG00000130538.twas_weights.s4.rds	bulk_rnaseq
  • output/twas_weights_conversion/<name>.xqtl_type_table.tsv (twas_weights_conversion) (--xqtl_type_table input: the data type of every converted context)

context	type
bulk_rnaseq	eQTL
  • output/fine_mapping_conversion/<study>/<study>.<gene>.fine_mapping.s4.rds (fine_mapping_conversion) (one pecotmr QtlFineMappingResult per study and gene, one row per context; the SuSiE fit and marginal QTL statistics are copied unchanged from the legacy file. Read by twas through --fine_mapping_dir.)

  • output/twas/twas/<name>.<region_id>.<gene>.twas.rds (twas) (per-gene TWAS result, one file per gene in the region)

  • output/twas/twas/<name>.<region_id>.twas.tsv.gz (twas, side file) (the TWAS table for the whole region. Columns gwas_study, chrom, start, end, block, gene, TSS, context, is_imputable, method, is_selected_method, rsq_adj_cv, pval_cv, twas_z, twas_pval. is_selected_method marks the best-performing model per gene-context pair -- the one with the highest cross-validation r-square.)

chr	molecular_id	TSS	start	end	context	gwas_study	method	is_imputable
22	ENSG00000130538	15528191	10000000	19000000	bulk_rnaseq	protocol_example_twas_chr22	bayes_c	TRUE
22	ENSG00000130538	15528191	10000000	19000000	bulk_rnaseq	protocol_example_twas_chr22	bayes_r	TRUE
  • output/twas/twas/<name>.<region_id>.mr_result.tsv.gz (twas, side file) (Mendelian Randomization results for the same region)

gene_name	num_CS	num_IV	cpip	meta_eff	se_meta_eff	meta_pval	Q
ENSG00000130538							
  • <cwd>/ctwas/<name>.ctwas_inputs.rds (ctwas_1) (assembled cTWAS inputs: weights, LD and GWAS z-scores for the regions to be fine-mapped)

  • <cwd>/ctwas/<name>.ctwas_est.rds (ctwas_2) (estimated cTWAS group prior parameters)

  • <cwd>/ctwas/<name>.ctwas_finemap.rds (ctwas_3) (cTWAS fine-mapping result: posterior inclusion probabilities for genes and SNPs in each LD block)

Only the .twas.rds files are declared in the twas step output: statement. The .twas.tsv.gz, .mr_result.tsv.gz, .twas_data.rds and .gwas_sumstats.rds files, and the cor_cache/ directory, are written alongside but not tracked by SoS.

Minimal Working Example

The three methods below are independent: run whichever you need. All use the chr22 example data in this repository.

twas_weights_conversion, below, is only needed for weights in the legacy FunGen-xQTL format, as the example weights here are; its tables are the --xqtl_meta_data and --xqtl_type_table inputs of the three methods. Weights already stored as pecotmr TwasWeights skip it and go straight to the three methods with a table of the same layout pointing at them.

Convert legacy TWAS weights

twas_weights_conversion converts legacy FunGen-xQTL weight files into pecotmr TwasWeights, one per study and gene, and writes the xQTL meta and data-type tables that the other workflows read.

Timing: ~20 sec (on toy dataset)

Convert legacy fine-mapping results

fine_mapping_conversion converts legacy FunGen-xQTL fine-mapping files (*.univariate_bvsr.rds) into pecotmr QtlFineMappingResult, one per study and gene, for MR in twas. No toy legacy fine-mapping file ships with this repository, so point --legacy_fine_mapping at a table of your own files (columns study, path).

Timing: ~7 sec per gene

Convert GWAS summary statistics

gwas_sumstats_to_tsv is optional. It turns per-LD-block pecotmr GwasSumStats files of one study (for example the QC’d output of the RSS GWAS pipeline, <study>.<chr>_<start>_<end>.gwas_sumstats.rds) into one TSV per chromosome (<study>.chr<N>.tsv.gz) and writes the matching <study>.gwas_meta.tsv to pass as --gwas_meta_data in twas. Point --gwas_sumstats at a text file that lists the RDS files, one per line. No toy GwasSumStats files ship with this repository. A variant present in two blocks is kept once: from a block listed in the optional --prefer_blocks file first, then the measured copy over a RAISS-imputed one, then the block that starts first.

Timing: ~2 min for 1,358 LD blocks (one study)

Standard TWAS scan

twas extracts the GWAS z-scores and LD matrix for each region, processes the SuSiE-TWAS weights, and runs the association test for each molecular context, keeping the best cross-validated model per gene. Genes that clear neither --rsq_cutoff nor --rsq_pval_cutoff in any method are dropped as non-imputable. Add --fine_mapping_dir output/fine_mapping_conversion to also run MR, and --rsq_cutoff 0 --rsq_pval_cutoff inf to keep every method per gene instead of only the best one.

For a genome-wide scan, split the work across the cluster: either submit through SoS with -q and a host configuration (job_size, walltime, mem), or split --regions into one file per chromosome and run one job per chromosome, each with its own --cwd.

Timing: ~25 sec (on toy dataset)

cTWAS fine-mapping

ctwas combines the selected TWAS models, gene and SNP z-scores and the LD reference into a per-region input, estimates the global group prior across regions, then fine-maps each region for posterior inclusion probabilities over genes and SNPs. It is one workflow run three times, switched by --run_param_est and --run_finemapping; --skip_assembly reuses the assembled inputs rather than rebuilding them.

Requires the cTWAS R package: remotes::install_github("xinhe-lab/ctwas", ref = "multigroup").

-n cannot preview this chain: ctwas_1 reads a manifest that get_analysis_regions only writes at run time, so a dry run stops at the first step.

Step 1.: Assemble region data

Timing: TBD (on toy dataset)

Step 2.: Estimate global parameters

Timing: TBD (on toy dataset)

Step 3.: Fine-map causal genes and SNPs

Timing: TBD (on toy dataset)

Quantile TWAS

Use pre-computed TWAS weights for quantile-specific testing: for each quantile level, cluster and integrate by fixed and dynamic region groups, extract the relevant GWAS z-scores and LD matrix for the region, and perform quantile region-specific association tests, identifying variants whose effects vary across different quantile regions of the phenotype distribution. Use --region-name (formatted as chr_start_stop) to focus on specific blocks, or --region to analyze a selected list of regions.

Timing: TBD (on toy dataset)

Command Interface

usage: sos run twas_ctwas.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:
  twas_weights_conversion
  fine_mapping_conversion
  get_analysis_regions
  twas
  ctwas
  quantile_twas

Global Workflow Options:
  --cwd output (as path)
  --modular-script-dir code/script (as path)
  --gwas-meta-data . (as path)
  --xqtl-meta-data . (as path)
  --ld-meta-data . (as path)
  --ld-reference-sample-size 17000 (as int)
  --xqtl-type-table ''
  --gwas-name  (as list)
  --gwas-data  (as list)
  --column-mapping  (as list)
  --regions . (as path)
  --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
  --name  f"{xqtl_meta_data:bn}.{gwas_meta_data:bn}"

  --container ''
  --job-size 100 (as int)
  --walltime 5m
  --mem 8G
  --numThreads 1 (as int)
  --name-suffix ''
                        name suffix add to end of the name variable for the
                        output files
  --gwas-study  (as list)
                        optional parameter in ctwas to only perform gwas
                        specific analysis

Sections
  twas_weights_conversion_1:
    Workflow Options:
      --legacy-weights . (as path)
                        Convert legacy FunGen-xQTL TWAS weight RDS files (the
                        list layout released on Synapse: gene ->
                        "<context>_<gene>" -> twas_weights / twas_cv_result /
                        region_info) into pecotmr TwasWeights, one RDS per
                        (study, gene), with twas_weights_conversion.R. Weights
                        and CV metrics are copied unchanged; no LD sketch is
                        attached. --legacy-weights is a TSV with columns study,
                        data_type, path (one legacy RDS per row). Rows sharing a
                        study and a gene (e.g. the univariate and multicontext
                        files of one gene) are merged into one TwasWeights,
                        because the twas step reads one weight file per gene.
                        The gene is the ENSG ID in the legacy file name.
  twas_weights_conversion_2:
  fine_mapping_conversion:
    Workflow Options:
      --legacy-fine-mapping . (as path)
                        Optional. Convert legacy FunGen-xQTL QTL fine-mapping
                        RDS files (*.univariate_bvsr.rds: gene ->
                        "<context>_<gene>" -> susie_result_trimmed / sumstats /
                        variant_names / region_info) into pecotmr
                        QtlFineMappingResult, one RDS per (study, gene), with
                        fine_mapping_conversion.R. The SuSiE fit, marginal QTL
                        effects and PIPs are copied unchanged; no LD sketch is
                        attached. Study and context labels match
                        twas_weights_conversion, so the twas step can pair each
                        fine-mapping row with its weights for MR: pass this
                        step's output folder as --fine_mapping_dir.
                        --legacy_fine_mapping is a TSV with columns study, path
                        (one legacy RDS per gene; the gene is the ENSG ID in the
                        name).
  get_analysis_regions:
  twas:
    Workflow Options:
      --coverage 'cs_coverage_0.95'
                        Legacy CLI retained. Wired to the pecotmr selection
                        knobs: rsq_cutoff, rsq_pval_cutoff, rsq_option,
                        rsq_pval_option (CV weight selection; set --rsq_cutoff 0
                        to keep every method) and the MR knobs mr_pval_cutoff,
                        mr_method, mr_pip_cutoff. The remaining legacy params
                        are kept declared for CLI stability but are not consumed
                        by the S4 path.
      --rsq-cutoff 0.01 (as float)
                        Thresholds for rsq and CV p-value for imputability/best-
                        model selection
      --rsq-pval-cutoff 0.05 (as float)
      --mr-pval-cutoff 0.05 (as float)
      --fine-mapping-dir ''
                        Folder of converted QTL fine-mapping results
                        (fine_mapping_conversion output); empty = TWAS only, no
                        MR
      --mr-method ivwPerVariant
                        MR method (ivwPerVariant or csAware) and the PIP cutoff
                        for MR instruments
      --mr-pip-cutoff 0.5 (as float)
      --[no-]save-ctwas-data (default to True)
      --[no-]save-mr-result (default to True)
      --rsq-option rsq
      --rsq-pval-option adj_rsq_pval pval (as list)
      --batch-load-memory 500 (as int)
                        load by batches if memory resource is limited, default
                        to load all at once
      --event-filter-rules . (as path)
      --comment-string NULL
      --[no-]rename-column (default to False)
  ctwas_1:
    Workflow Options:
      --chrom ''
                        chromosome to assemble (integer or 'chrN'); default: the
                        lone gene-bearing chrom
      --twas-weight-cutoff 0.0 (as float)
                        weight pre-filters passed to assembleCtwasInputs
      --max-num-variants Inf
      --cs-min-cor 0.0 (as float)
      --min-pip-cutoff 0.0 (as float)
      --ctwas-method ''
                        TWAS method fed to cTWAS (assembleCtwasInputs
                        weightSelectionParam=). Empty: each gene's best method
                        by cross-validated r-squared; a gene whose methods
                        carry no r-squared needs one, e.g. --ctwas-method susie.
      --thin 1.0 (as float)
                        declared for CLI stability; consumed downstream / not by
                        assembleCtwasInputs
      --maxSNP 20000 (as int)
      --[no-]multi-group (default to True)
      --[no-]skip-assembly (default to False)
      --twas-weights  paths()

                        TwasWeights source (option c): empty -> the upstream
                        twas step's per-gene weights (the per-gene TwasWeights
                        files in the TWAS manifest); override with prebuilt
                        TwasWeights RDS paths (e.g. the blessed S4 ctwas
                        weights) when the toy twas weights carry no signal. A
                        quoted wildcard is expanded.
      --twas-weights-list . (as path)
                        Or a file listing those RDS paths, one per line: a
                        genome-wide list is too long for the command line.
  ctwas_2:
    Workflow Options:
      --[no-]run-param-est (default to False)
                        cTWAS step 2 (estimate priors): estCtwasParam via
                        ctwas_est.R. Reads the assembled inputs from [ctwas_1]
                        on disk so it can be run independently. --fallback-to-
                        prefit recovers the prefit estimate when the accurate EM
                        cannot be estimated (e.g. the single-gene MWE),
                        mirroring the legacy ctwas_2 workaround.
      --[no-]skip-assembly (default to False)
      --thin 1.0 (as float)
      --prior-var-structure 'shared_all'
      --niter 50 (as int)
      --[no-]multi-group (default to True)
                        declared for CLI stability; not consumed by the S4 path
  ctwas_3:
    Workflow Options:
      --[no-]run-finemapping (default to False)
                        cTWAS step 3 (screen + finemap): screenCtwasRegions +
                        finemapCtwasRegions via ctwas_finemap.R. Reads the
                        estimated params from [ctwas_2] on disk so it can be run
                        independently. Emits the per-gene cTWAS fine-mapping
                        result.
      --L 5 (as int)
      --min-nonSNP-PIP 0.5 (as float)
      --[no-]merge-regions (default to False)
                        Boundary-gene region merging (legacy ctwas_3
                        merge_regions, default-off): when True, after fine-
                        mapping, each high-PIP boundary gene's adjacent LD
                        blocks are merged and re-fine-mapped via
                        mergeCtwasBoundaryRegions (require-in-CS, like the
                        legacy susie_pip>0.5 & !is.na(cs) selection); maxSNP
                        caps SNPs per merged region.
      --maxSNP 20000 (as int)
      --[no-]keep-snps (default to True)
                        retain the SNP background as a dedicated
                        study=context="SNP" CtwasResult row; default True
                        matches the legacy pipeline, which kept SNP rows in the
                        finemap output
      --thin 1.0 (as float)
                        declared for CLI stability; not consumed by the S4 path
      --max-iter 0 (as int)
      --prior-var-structure 'shared_all'
      --subset-context  (as list)
      --[no-]multi-group (default to True)
      --p-diff-thresh 5e-08 (as float)
      --alias NULL
  quantile_twas:
    Workflow Options:
      --[no-]save-ctwas-data (default to True)
      --[no-]save-mr-result (default to False)

Workflow implementation

The following cells define the SoS workflow steps ([global], [get_analysis_regions], [twas], [ctwas_1], [ctwas_2], [ctwas_3], [quantile_twas]) invoked by the commands in the steps above.

References
  1. Zhao, S., Crouse, W., Qian, S., Luo, K., Stephens, M., & He, X. (2024). Adjusting for genetic confounders in transcriptome-wide association studies improves discovery of risk genes of complex traits. Nature Genetics, 56(2), 336–347. 10.1038/s41588-023-01648-9