Mixture prior estimation for MASH#
Estimates the data-driven mixture of covariance matrices that a MASH model uses as its prior, from factor analyses and canonical patterns through residual-variance estimation to the fitted mixture.
Overview#
MASH borrows strength across conditions by putting a prior on effect-size vectors: a mixture of covariance matrices, each describing one pattern of sharing, such as an effect present in a single condition, shared equally everywhere, or shared with condition-specific magnitudes. This module estimates that mixture from the data before any MASH model is fitted, so one prior can serve several downstream analyses.
The work proceeds in four steps, in order.
Covariance components.
flashandflash_nonnegderive candidate patterns by empirical Bayes factor analysis, the second constraining the factors to be non-negative;pcaderives them from the leading principal components of the strong effects;canonicalwrites the fixed patterns that need no fitting, namely an identity matrix, one matrix per condition, equal effects, and a few simple heterogeneity models. All four are produced by default, which is what--mixture-componentslists.Residual variance. Correlated noise across conditions would otherwise be read as shared signal, so a residual covariance Vhat is estimated from the null effects. Five estimators are available:
identityassumes no correlation,simpletakes the empirical correlation of the null z-scores,simple_specifictakes their covariance with a nearest-positive-definite correction,corshrinkapplies adaptive shrinkage to that correlation, andmlerefines an estimate by EM on a random subset of the data.Mixture prior. One of three engines fits the mixture weights and refines the data-driven components.
ed_bovyis the extreme deconvolution exported by mashr;udandud_unconstrainedare the ED and TED updates from udr, and the code marks both as opt-in,udfor known numerical issues andud_unconstrainedbecause it expects independent, identically distributed data on the z-score scale. The engine reads the component files from step 1 together with the Vhat estimate from step 2.Plotting.
plot_Udraws the estimated covariance matrices as heatmaps ordered by their mixture weight.
Two settings tie the steps together. --effect-model decides whether effects are exchangeable on the effect-size scale (EE) or the z-score scale (EZ), and it appears in every file name from step 2 onwards. --vhat names which residual-variance estimate the engine should read, so it has to match the estimator that was actually run; the default is simple. mle is the exception in both directions: it needs a prior to exist already, and when --vhat mle is set the engines fall back to reading the simple estimate instead.
When to run it. After the per-condition summary statistics have been assembled into a single RDS by the effect-extraction step, and before fitting the MASH model, which consumes the prior produced here.
Input#
--data– the assembled summary statistics, an RDS holding effect sizes (.b), standard errors (.s) and z-scores (.z) for the strong, random and null subsets, plusXtX, each with one column per condition:List of 10 $ random.z: num [1:68, 1:8] -0.286 0.909 1.362 -0.807 -0.185 ... $ strong.z: num [1:17, 1:8] 8.77 1.16 9.16 -4.99 -1.02 ... $ null.z : num [1:68, 1:8] -0.508 -1.149 -0.422 -0.206 0.351 ... $ random.b: num [1:68, 1:8] -0.00799 0.0248 0.03869 -0.02005 -0.00388 ... $ strong.b: num [1:17, 1:8] 0.2491 0.0927 0.3318 -0.1553 -0.1398 ... $ null.b : num [1:68, 1:8] -0.03792 -0.02839 -0.03397 -0.00571 0.03438 ... $ null.s : num [1:68, 1:8] 0.0746 0.0247 0.0805 0.0278 0.0979 ... $ random.s: num [1:68, 1:8] 0.028 0.0273 0.0284 0.0249 0.021 ... $ strong.s: num [1:17, 1:8] 0.0284 0.0798 0.0362 0.0311 0.1374 ... $ XtX : num [1:8, 1:8] 31.06 6.09 1.02 27.12 9.89 ...
--output-prefix– prefix for every output file; derived from the--databasename when omitted.--cwd– output directory, defaultmashr_workflow_output.--effect-model–EEorEZ, defaultEE.--vhat– which residual-variance estimate the mixture engines read:identity,simple,mle,vhat_corshrink_xconditionorvhat_simple_specific. Defaultsimple.--mixture-components– which component files the engines combine, defaultflash flash_nonneg pca canonical.--output-suffix– suffix carried on output names, defaultall.
Step-specific:
--npc(pca) – number of principal components, default2. The comment beside it in the code says it is set to 3 as in the mash paper, which no longer matches the value.--n-subsetand--max-iter(vhat_mle) – rows sampled for the EM estimate, default6000, and the iteration cap, default6.--max-comp,--to-cor,--tol,--remove-label,--name(plot_U) – how many components to draw (-1for all), whether to convert covariances to correlations, the tolerance, whether to drop labels, and a suffix added to the plot file name. Note thatplot_Uredefines--datato mean the prior RDS rather than the summary statistics.
Runtime:
--container,--job-size,--walltime,--mem,--numThreads– software environment and cluster resources.--modular-script-dir– location of the R drivers, defaultcode/script.
Output#
<prefix>.flash.rds,<prefix>.flash_nonneg.rds,<prefix>.pca.rds,<prefix>.canonical.rds– the covariance components from step 1, each a named list of condition-by-condition matrices. On the toy datapcayieldsPCA_1,PCA_2andtPCAandcanonicalyields 13 matrices, while both FLASH files are empty (NULL), so FLASH contributes nothing to the toy prior.<prefix>.<effect_model>.V_<method>.rds– the residual variance estimate from step 2, one condition-by-condition matrix. The method is part of the name, so estimates from different estimators coexist.<prefix>.<effect_model>.prior.rds– the prior itself: the list of covariance matricesU, their mixture weightsw, and the log-likelihood trace of the fit. Components that received zero weight are kept in the list:List of 3 $ U :List of 17 $ w : Named num [1:17] 0.4714 0 0 0.0616 0.0949 ... $ loglik: num [1:1447] 0.705 3.455 3.719 4.142 5.068 ... names(U): XtX, PCA_1, PCA_2, tPCA, identity, ALL, Ast, End, Exc, Inh, Mic, OPC, Oli, equal_effects, simple_het_1, simple_het_2, simple_het_3
<prefix>.<effect_model>.prior_c.logand<prefix>.<effect_model>.prior_loglike.log– engine diagnostics.<plot_prefix>.pdf– heatmaps of the covariance matrices, ordered by mixture weight.
Each step also writes .stdout and .stderr beside its output. Example results for the toy data are under output/mixture_prior/.
Minimal Working Example#
Step 1. Covariance components#
All four run independently of each other, and all four are expected by the engines in step 3, since --mixture-components defaults to the full set.
FLASH factors#
Empirical Bayes factor analysis of the strong effects.
Timing: ~5-15 min (on toy dataset)
sos run pipeline/mixture_prior.ipynb flash \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
FLASH factors, non-negative#
The same analysis with the factors constrained to be non-negative.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb flash_nonneg \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Principal components#
Covariances built from the leading principal components; --npc sets how many.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb pca \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Canonical patterns#
The fixed patterns that need no fitting: identity, one matrix per condition, equal effects, and simple heterogeneity models.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb canonical \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Step 2. Residual variance#
Pick one estimator. Whichever is chosen has to be named to step 3 through --vhat, which defaults to simple.
identity#
Assume the conditions carry uncorrelated noise.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb vhat_identity \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
simple#
Empirical correlation of the null z-scores. This is the estimate the engines look for by default.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb vhat_simple \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
mle#
EM refinement on a random subset. It reads an existing prior, so it can only run after a first pass through step 3.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb vhat_mle \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
corshrink per condition#
Adaptive shrinkage applied to the null correlation.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb vhat_corshrink_xcondition \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
simple per condition#
Covariance of the null z-scores with a nearest-positive-definite correction.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb vhat_simple_specific \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Step 3. Mixture prior#
Pick one engine. It reads the four component files from step 1 together with the Vhat estimate from step 2, and writes <prefix>.<effect_model>.prior.rds.
Ultimate Deconvolution (ud)#
The ED update from udr. The code marks it opt-in because of known numerical issues.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb ud \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Ultimate Deconvolution, unconstrained (ud_unconstrained)#
The TED update from udr, which expects independent, identically distributed data on the z-score scale.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb ud_unconstrained \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Extreme Deconvolution (ed_bovy)#
The extreme deconvolution exported by mashr.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb ed_bovy \
--output-prefix protocol_example \
--data input/mash/protocol_example.mashr_input.rds \
--cwd output/mixture_prior
Step 4. Plot the prior#
Draw the fitted covariance matrices as heatmaps, ordered by mixture weight.
Plot the prior#
Here --data is the prior produced in step 3, not the summary statistics, and --output-prefix names the plot.
Timing: TBD (on toy dataset)
sos run pipeline/mixture_prior.ipynb plot_U \
--output-prefix protocol_example_plots \
--data output/mixture_prior/protocol_example.EE.prior.rds \
--cwd output/mixture_prior
Command Interface#
sos run pipeline/mixture_prior.ipynb -h
usage: sos run pipeline/mixture_prior.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:
flash
flash_nonneg
pca
canonical
vhat_identity
vhat_simple
vhat_mle
vhat_corshrink_xcondition
vhat_simple_specific
ud
ud_unconstrained
ed_bovy
plot_U
Global Workflow Options:
--cwd mashr_workflow_output (as path)
--modular-script-dir code/script (as path)
--data VAL (as path, required)
Input summary statistics data
--output-prefix ''
Prefix of output files. If not specified, it will derive
it from data. If it is specified, for example,
`--output-prefix AnalysisResults` It will save output
files as `{cwd}/AnalysisResults*`.
--output-suffix all
--effect-model EE
Exchangable effect (EE) or exchangable z-scores (EZ)
--vhat simple
Identifier of $\hat{V}$ estimate file Options are
"identity", "simple", "mle",
"vhat_corshrink_xcondition", "vhat_simple_specific"
--mixture-components flash flash_nonneg pca canonical (as list)
--container ''
--job-size 1 (as int)
For cluster jobs, number commands to run per job
--walltime 5h
Wall clock time expected
--mem 16G
Memory expected
--numThreads 1 (as int)
Number of threads
Sections
flash:
flash_nonneg:
pca:
Workflow Options:
--npc 2 (as int)
Number of components in PCA analysis for prior set to 3
as in mash paper
canonical:
vhat_identity: V estimate: "identity" method
vhat_simple: V estimate: "simple" method (null z-scores)
vhat_mle: V estimate: "mle" method (mash_estimate_corr_em on a
random subset; needs the prior U)
Workflow Options:
--n-subset 6000 (as int)
number of samples to use
--max-iter 6 (as int)
maximum number of iterations
vhat_corshrink_xcondition: V estimate: "corshrink" adaptive-shrinkage null
correlation (run globally on the null set; the legacy
per-gene GTEx SumstatQuery path is not portable)
vhat_simple_specific: V estimate: "simple_specific" nearPD(cov(nullZ)) (run
globally on the null set; legacy per-gene GTEx path not
portable)
ud: Prior engine: udr ED update (opt-in; known numerical
issues)
ud_unconstrained: Prior engine: udr TED update (opt-in; needs
i.i.d./z-scale data)
ed_bovy: Prior engine: mashr extreme deconvolution (the exported
bovy ED)
plot_U:
Workflow Options:
--max-comp -1 (as int)
number of components to show
--[no-]to-cor (default to False)
whether or not to convert to correlation
--tol '1E-6'
--[no-]remove-label (default to False)
--name ''
Workflow implementation#
Global parameters#
[global]
parameter: cwd = path('./mashr_workflow_output')
parameter: modular_script_dir = path('code/script')
# Input summary statistics data
parameter: data = path
# Prefix of output files. If not specified, it will derive it from data.
# If it is specified, for example, `--output-prefix AnalysisResults`
# It will save output files as `{cwd}/AnalysisResults*`.
parameter: output_prefix = ''
parameter: output_suffix = 'all'
# Exchangable effect (EE) or exchangable z-scores (EZ)
parameter: effect_model = 'EE'
# Identifier of $\hat{V}$ estimate file
# Options are "identity", "simple", "mle", "vhat_corshrink_xcondition", "vhat_simple_specific"
parameter: vhat = 'simple'
parameter: mixture_components = ['flash', 'flash_nonneg', 'pca',"canonical"]
parameter: container = ""
# For cluster jobs, number commands to run per job
parameter: job_size = 1
# Wall clock time expected
parameter: walltime = "5h"
# Memory expected
parameter: mem = "16G"
# Number of threads
parameter: numThreads = 1
data = data.absolute()
cwd = cwd.absolute()
if len(output_prefix) == 0:
output_prefix = f"{data:bn}"
prior_data = file_target(f"{cwd:a}/{output_prefix}.{effect_model}.prior.rds")
vhat_data = file_target(f"{cwd:a}/{output_prefix}.{effect_model}.V_{vhat}.rds")
Factor analyses#
[flash]
input: data
output: f"{cwd}/{output_prefix}.flash.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_covariance.R \
--data ${_input} \
--component flash \
--effect-model ${effect_model} \
--output ${_output}
[flash_nonneg]
input: data
output: f"{cwd}/{output_prefix}.flash_nonneg.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_covariance.R \
--data ${_input} \
--component flash_nonneg \
--effect-model ${effect_model} \
--output ${_output}
[pca]
# Number of components in PCA analysis for prior
# set to 3 as in mash paper
parameter: npc = 2
input: data
output: f"{cwd}/{output_prefix}.pca.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_covariance.R \
--data ${_input} \
--component pca \
--npc ${npc} \
--effect-model ${effect_model} \
--output ${_output}
[canonical]
input: data
output: f"{cwd}/{output_prefix}.canonical.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_covariance.R \
--data ${_input} \
--component canonical \
--effect-model ${effect_model} \
--output ${_output}
Residual variance estimation#
# V estimate: "identity" method
[vhat_identity]
input: data
output: f'{vhat_data:nn}.V_identity.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_vhat.R \
--data ${_input} \
--method identity \
--effect-model ${effect_model} \
--output ${_output}
# V estimate: "simple" method (null z-scores)
[vhat_simple]
input: data
output: f'{vhat_data:nn}.V_simple.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_vhat.R \
--data ${_input} \
--method simple \
--effect-model ${effect_model} \
--output ${_output}
# V estimate: "mle" method (mash_estimate_corr_em on a random subset; needs the prior U)
[vhat_mle]
# number of samples to use
parameter: n_subset = 6000
# maximum number of iterations
parameter: max_iter = 6
input: data, prior_data
output: f'{vhat_data:nn}.V_mle.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_vhat.R \
--data ${_input[0]} \
--method mle \
--prior-data ${_input[1]} \
--n-subset ${n_subset} \
--max-iter ${max_iter} \
--effect-model ${effect_model} \
--output ${_output}
# V estimate: "corshrink" adaptive-shrinkage null correlation (run globally on the null set; the legacy per-gene GTEx SumstatQuery path is not portable)
[vhat_corshrink_xcondition]
input: data
output: f'{vhat_data:nn}.V_vhat_corshrink_xcondition.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_vhat.R \
--data ${_input} \
--method corshrink \
--effect-model ${effect_model} \
--output ${_output}
# V estimate: "simple_specific" nearPD(cov(nullZ)) (run globally on the null set; legacy per-gene GTEx path not portable)
[vhat_simple_specific]
input: data
output: f'{vhat_data:nn}.V_vhat_simple_specific.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_vhat.R \
--data ${_input} \
--method simple_specific \
--effect-model ${effect_model} \
--output ${_output}
Mixture prior engines#
# Prior engine: udr ED update (opt-in; known numerical issues)
[ud]
input: [data, vhat_data if vhat != "mle" else f'{vhat_data:nn}.V_simple.rds'] + [f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components]
output: prior_data
comp_files = ",".join([f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components])
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_prior.R \
--data ${_input[0]} \
--engine ud \
--vhat-data ${_input[1]} \
--component-files ${comp_files} \
--effect-model ${effect_model} \
--output ${_output}
# Prior engine: udr TED update (opt-in; needs i.i.d./z-scale data)
[ud_unconstrained]
input: [data, vhat_data if vhat != "mle" else f'{vhat_data:nn}.V_simple.rds'] + [f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components]
output: prior_data
comp_files = ",".join([f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components])
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_prior.R \
--data ${_input[0]} \
--engine ud_ted \
--vhat-data ${_input[1]} \
--component-files ${comp_files} \
--effect-model ${effect_model} \
--output ${_output}
# Prior engine: mashr extreme deconvolution (the exported bovy ED)
[ed_bovy]
input: [data, vhat_data if vhat != "mle" else f'{vhat_data:nn}.V_simple.rds'] + [f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components]
output: prior_data
comp_files = ",".join([f"{cwd}/{output_prefix}.{m}.rds" for m in mixture_components])
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_prior.R \
--data ${_input[0]} \
--engine cov_ed \
--vhat-data ${_input[1]} \
--component-files ${comp_files} \
--effect-model ${effect_model} \
--output ${_output}
Plotting#
A utility that reads the prior produced above and draws heatmaps of its covariance matrices, ordered by mixture weight.
[plot_U]
parameter: data = path
# number of components to show
parameter: max_comp = -1
# whether or not to convert to correlation
parameter: to_cor = False
parameter: tol = "1E-6"
parameter: remove_label = False
parameter: name = ""
input: data
output: f'{cwd:a}/{_input:bn}{("_" + name.replace("$", "_")) if name != "" else ""}.pdf'
bash: expand = "${ }", stderr = f'{_output:n}.stderr', stdout = f'{_output:n}.stdout', container = container
Rscript ${modular_script_dir}/pecotmr_integration/mash_plot_prior.R \
--data ${_input} \
--max-comp ${max_comp} \
--tol ${tol} \
--name "${name}" \
${'--to-cor' if to_cor else ''} \
${'--remove-label' if remove_label else ''} \
--output ${_output}