#knitr::opts_chunk$set(eval=FALSE)

Introduction

This document will walk through a real genome-sized example of how to use CAUSE. Some of the steps will take 5-10 minutes. For steps that require long computation we also provide output files that can be downloaded to make it easier to run through the example.

We will be analyzing GWAS data for LDL cholesterol and for coronary artery disease to test for a causal relationship of LDL on CAD. The analysis will have the following steps:

  1. Format the data for use with CAUSE
  2. Calculate nuisance parameters
  3. LD pruning
  4. Fit CAUSE
  5. Look at results

There are two ways to do the LD pruning in step 3. The easiest way is to use Plink which is the method we use here. There are also built-in functions in the cause package that allow you to do LD pruning with pre-computed pairwise LD files. This could let you use an alternate LD calculation. The last section of this document shows how to do that using LD information estimated from the 1000 Genomes CEU population using LDshrink here. These data are about 11 Gb.

The GWAS data we will use are about 320 Gb. However, in this tutorial you will be able to skip the large data steps and simply download the results.

Step 0: Install CAUSE

Follow installation instructions here

Step 1: Format Data for CAUSE

This section describes formatting for data that are available in a flat file. If you have vcf files downloaded from the IEU Open GWAS project see here for formatting instructions.

We will use read_tsv to read in summary statistics for a GWAS of LDL cholesterol and a GWAS of coronary artery disease from the internet. We will then combine these and format them for use with CAUSE. First read in the data. For LDL Cholesterol, we use summary statistics from Willer et al (2013) [PMID: 24097068]. For CAD we use summary statistics from van der Harst et al. (2017) [PMID: 29212778]. Downloading and formatting the data takes several minutes. If you want to skip this step, we provide a formatted data file that you can download below.

library(readr)
library(dplyr)
library(cause)
X1 <- read_tsv("http://csg.sph.umich.edu/abecasis/public/lipids2013/jointGwasMc_LDL.txt.gz")
X2 <- read_tsv("ftp://ftp.ebi.ac.uk/pub/databases/gwas/summary_statistics/vanderHarstP_29212778_GCST005194/CAD_META.gz")

CAUSE needs the following information from each data set: SNP or variant ID, effect size, and standard error, effect allele and other allele. We provide a simple function that will merge data sets and produce a cause_data object that can be used with later functions. This step and the rest of the analysis are done in R.

The function gwas_merge will merge two data sets and and align effect sizes to correspond to the same allele. It will remove variants with ambiguous alleles (G/C or A/T) or with alleles that do not match between data sets (e.g A/G in one data set and A/C in the other). It will also remove variants that are duplicated in either data set. It will not remove variants that are simply strand flipped between the two data sets (e. g. A/C in one data set, T/G in the other). If p-values are available it will accept those but they are not required. If p-values are missing, be default they will be computed using a normal approximation but this can be bypassed by setting compute_._pval = FALSE. p-values are only used in the LD pruning step.

LDL column headers:

CAD column headers:

If the p-value column in either data set is missing, the pval_cols argument can be omitted or one of the elements can be NA.

X <- gwas_merge(X1, X2, snp_name_cols = c("rsid", "oldID"), 
                       beta_hat_cols = c("beta", "Effect"), 
                       se_cols = c("se", "StdErr"), 
                       A1_cols = c("A1", "Allele1"), 
                       A2_cols = c("A2", "Allele2"), 
                       pval_cols = c("P-value", "P-value"))

Alternatively, you can download already formatted data here and read it in using readRDS.

system("mkdir example_data/")
download.file("https://github.com/jean997/cause/blob/master/example_data/LDL_CAD_merged.RDS", destfile = "example_data/LDL_CAD_merged.RDS")
X <- readRDS("example_data/LDL_CAD_merged.RDS")
head(X)

There are likely more efficient ways to do this merge. If you would like to process the data yourself, you can construct a cause_data object from a data frame using the constructor new_cause_data(X) where X is any data frame that includes the columns snp, beta_hat_1, seb1, beta_hat_2, and seb2.

Step 2: Calculate nuisance parameters

The next step is to estimate the parameters that define the prior distribution of $\beta_{M}$ and $\theta$ and to estimate $\rho$, the correlation between summary statistics that is due to sample overlap or population structure. We will do this with a random subset of 1,000,000 variants since our data set is large. est_cause_params estimates the nuisance parameters by finding the maximum a posteriori estimate of $\rho$ and the mixing parameters when $\gamma = \eta = 0$. This step takes a several minutes.

set.seed(100)
varlist <- with(X, sample(snp, size=1000000, replace=FALSE))
params <- est_cause_params(X, varlist)

The object params is of class cause_params and contains information about the fit as well as the maximum a posteriori estimates of the mixing parameters ($\pi$) and $\rho$. The object params$mix_grid is a data frame defining the distribution of summary statistics. The column S1 is the variance for trait 1 ($M$), the column S2 is the variance for trait 2 ($Y$) and the column pi is the mixture proportion assigned to that variance combination.

class(params)
names(params)
params$rho
head(params$mix_grid)

In this case, we have estimated that r round(params$mix_grid$pi[1]*100)\% of variants have trait 1 variance and trait 2 equal to 0 meaning that they have no association with either trait.

Tip: Do not try to estimate the nuisance parameters with substantially fewer than 100,000 variants. This can lead to poor estimates of the mixing parameters whih leads to bad model comparisons.

If you don't want to wait for this step, the parameters object can be downloaded from here using

download.file("https://github.com/jean997/cause/blob/master/example_data/LDL_CAD_params.RDS", destfile = "example_data/LDL_CAD_params.RDS")
params <- readRDS("example_data/LDL_CAD_params.RDS")

Step 3: LD Pruning

We estimate CAUSE posterior distributions using an LD pruned set of variants, prioritizing variants with low trait $M$ (LDL) $p$-values.

The easiest way is to use Plink to perform LD clumping. The ieugwasr package provides a convenient R interface to Plink. This method is fast but requires a reference sample which can be accessed through an API using ieugwasr::ld_clump (see help for that function). You can download also download reference data from here. That file will need to be unzipped.

An alternative is to use a built in function in the CAUSE R pacakge and precomputed pairwise estimates of $r^2$. This is the method we used in our paper, coupled with LD estimates 1000 Genomes European samples computed via LDshrink. This method is slow but lets you use any LD estimates you want. We show how to do this in a section at the end of this document.

In either case, we prioritize variants based on their trait 1 p-value. We can limit ourselves to SNPs with trait 1 p-value less than 0.001 since we will use that threshold for estimating the CAUSE posteriors in the next step. If you use a higher threshold in the next step, you should also use a higher threshold in the pruning step. It is ok to have a lower threshold in the posterior estimation step than in the pruning step. In this case, the original GWAS data for LDL contains p-values se we use these. However, if these are missing you can compute approximate p-values using a normal approximation.

LD pruning using Plink

The following code performs LD pruning using the ieugwasr wrapper to Plink LD clumping function. If you have Plink already installed, you can replace the path in plink_bin with a path to your local installation. To use the code below, you will need the genetics.binaRies package which can be installed with devtools::install_github("explodecomputer/genetics.binaRies"). If you want to use local reference data, you can set the bfile option of ld_clump (see ?ld_clump).

r2_thresh = 0.01
pval_thresh = 1e-3

X_clump <- X %>%
           rename(rsid = snp,
                  pval = p1) %>%
           ieugwasr::ld_clump(dat = .,
                     clump_r2 = r2_thresh,
                     clump_p = pval_thresh,
                     plink_bin = genetics.binaRies::get_plink_binary(), 
                     pop = "EUR")
top_vars <- X_clump$rsid

Here r2_thresh is the $r^2$ threshold you would like to use and pval_thresh is the p-value threshold you will use to compute the CAUSE posteriors. We recommend using a lower $r^2$ threshold for plink LD clumping than for LD clumping based on estimates from LDshrink, since these are not shrunken.

Step 4: Fit CAUSE

Now that we have formatted data, an LD pruned set of variants, and nuisance parameters estimated, we can fit CAUSE. The function cause estimates posterior distributions under the sharing and causal models and calculates the ELPD for both models as well as for the null model in which there is neither a causal or a shared factor effect. This might take 5-10 minutes.

res <- cause(X=X, variants = top_vars, param_ests = params)

Pareto k diagnostics warning

Occasionally we see a warining about Pareto k diagnostics. This comes from the estimate of the elpd from the loo package. Usually we do not worry about it if there are few problematic samples for more details see help('pareto-k-diagnostic'). The loo objects are stored in a three element list res$loos. The first element is empty, the second element corresponds to the sharing model and the third element corresponds to the causal model. To print the Pareto k tables for the CAUSE models use

res$loos[[2]]
loo::pareto_k_table(res$loos[[2]])
res$loos[[3]]
loo::pareto_k_table(res$loos[[3]])

In some cases the problem can be resolved by fitting with more variants. If you are fitting with fewer than 1000 variants you could consider raising the p-value or $r^2$ thresholds and verifying that both studies have genome-wide coverage.

Step 5: Look at Results

The resulting cause object contains an object for the partial sharing model fit (sharing), and object for the causal model fit (causal) and a table of ELPD results.

class(res)
names(res)
res$elpd

class(res$sharing)
class(res$causal)

The elpd table has the following columns:

In this case we see that the causal model is significantly better than the sharing model from the thrid line of the table. The $z$-score is r round(res$elpd$z[3], digits=2) corresponding to a p-value of r signif(pnorm(res$elpd$z[3]), digits=2).

For each model (partial sharing and full) we can plot the posterior distributions of the parameters. Dotted lines show the prior distributions.

plot(res$sharing)
plot(res$causal)

The summary method will summarize the posterior medians and credible intervals.

summary(res, ci_size=0.95)

The plot method applied to a cause object will arrange all of this information on one spread.

plot(res)

The plot method can also produce scatter plots of the data showing for each model, the probability that each variant is acting through the shared factor and the contribution of each variant to the ELPD test statistic.

plot(res, type="data")

LD pruning using built in function

The function ld_prune uses a greedy algorithm that selects the variant with the lowest LDL p-value and removes all variants in LD with the selected variant and then repeats until no variants are left. This step requires LD estimates. You can download estimates made in the 1000 Genomes CEU population here. We first demonstrate use of the function for one chromosome and then show an example of how to parallelize the analysis over all 22 autosomes.

download.file("https://zenodo.org/record/1464357/files/chr22_AF0.05_0.1.RDS?download=1", destfile = "example_data/chr22_AF0.05_0.1.RDS")
download.file("https://zenodo.org/record/1464357/files/chr22_AF0.05_snpdata.RDS?download=1", destfile="example_data/chr22_AF0.05_snpdata.RDS")
ld <- readRDS("example_data/chr22_AF0.05_0.1.RDS")
snp_info <- readRDS("example_data/chr22_AF0.05_snpdata.RDS")

head(ld)
head(snp_info)

The ld data frame should contain the column names rowsnp, colsnp, and r2. The snp_info data frame contains information about all of the chromosome 22 variants with allele frequency greater than 0.05. The only piece of information we need from this data frame is the list of variants snp_info$SNP which provides the total list of variants used in the LD calculations.

LD pruning for one chromosome

The ld_prune function is somewhat flexible in its arguments, see help(ld_prune).

variants <- X %>% mutate(pval1 = 2*pnorm(abs(beta_hat_1/seb1), lower.tail=FALSE))
pruned <- ld_prune(variants = variants, 
                            ld = ld, total_ld_variants = snp_info$SNP, 
                            pval_cols = c("pval1"), 
                            pval_thresh = c(1e-3))
length(pruned)

If length(pval_cols) =1, ld_prune returns a vector of selected variants. If There are multipld $p$-value columns provided, ld_prune will return a list of vectors, one for each column. The length of pval_thresh should be equal to the length of pval_cols and provides a threshold for each column. Excluding variants with high $p$-values speeds up the pruning step. We can fit CAUSE without high $p$-value variants because these variants have almost no influence on the posterior distributions or the ELPD test statistic. The exact value of the threshold isn't important as long as it is fairly lenient. Including additional variants may slow down computation but shouldn't change the results

Parallelizing over chromosomes

We highly recommend parallelizing for whole genome LD pruning. One way to do this is with the parallel pacakge, though this option uses a lot of memory. A better option is to submit separate jobs to nodes of a compute cluster and then combine results.

If you are analyzing many phenotypes, the most efficient way to complete this step is to first obtain a list of variants present in all data sets and use only these variants in your analysis. You can then obtain an LD-pruned set of variants prioritized for low $p$-values for each traits ($N$ lists if there are $N$ phenotypes). In any analysis, you will use the list of variants prioritized for the trait $M$ phenotype.

Download the LD-pruned variant list for our example analysis: top LDL list

download.file("https://github.com/jean997/cause/blob/master/example_data/top_ldl_pruned_vars.RDS", destfile = "example_data/top_ldl_pruned_vars.RDS")
sessionInfo()


jean997/cause documentation built on Dec. 25, 2021, 10 p.m.