Cox Mixed-Effects Models for Genome-Wide Association Studies"

# library(knitcitations)
# cleanbib()
# options("citation_format" = "pandoc")
# r<-citep("10.1534/genetics.119.302940")
# write.bibtex(file="references.bib")

coxmeg 1.1.4

Overview

Time-to-event is one of the most important phenotypes in genetic epidemiology. The R-package, "coxmeg", provides a set of utilities to fit a Cox mixed-effects model and to efficiently perform genome-wide association analysis of time-to-event phenotypes using a Cox mixed-effects model. More details can be found in [@He_2020].

Installation

The R package can be installed from CRAN

install.packages("coxmeg")

Most recent version

install.packages("devtools")
library(devtools)
install_github("lhe17/coxmeg")

Functions

The current version provides five functions.

Fit a Cox mixed-effects model with a sparse relatedness matrix

We illustrate how to use coxmeg to fit a Cox mixed-effects model with a sparse relatedness matrix. We first simulate a block-diagonal relatedness matrix for a cohort consisting of 200 families, each of which has five members. We use 'dgCMatrix' to save memory.

library(coxmeg)
library(MASS)
library(Matrix)
n_f <- 200
mat_list <- list()
size <- rep(5,n_f)
offd <- 0.5
for(i in 1:n_f)
{
  mat_list[[i]] <- matrix(offd,size[i],size[i])
  diag(mat_list[[i]]) <- 1
}
sigma = as(bdiag(mat_list),'dgCMatrix')
sigma[1:5,1:5]

Next, we simulate random effects, censoring variables, and time-to-event outcomes assuming a constant baseline hazard function. We assume that the variance component is 0.2 and simulate a continuous variable with the effect of log(HR)=0.1.

n = nrow(sigma)
tau_var <- 0.2
x <- mvrnorm(1, rep(0,n), tau_var*sigma)
pred = rnorm(n,0,1)
myrates <- exp(x+0.1*pred-1)
y <- rexp(n, rate = myrates)
cen <- rexp(n, rate = 0.02 )
ycen <- pmin(y, cen)
outcome <- cbind(ycen,as.numeric(y <= cen))
head(outcome)

We fit a Cox mixed-effects model using the function coxmeg.

re = coxmeg(outcome,sigma,type='bd',X=pred,order=1,detap='diagonal')

Here, we set type='bd' because the relatedness matrix is a block-diagonal matrix. Note that type='bd' should be used only for a block-diagonal matrix or a sparse matrix of which the inverse matrix is also highly sparse. A sparse kinship matrix can be converted to a block-diagonal matrix using kingToMatrix. For a general sparse relatedness matrix of which the inverse is not sparse, it is recommended that type='sparse' be used. However, if there are more than 50% non-zero elements in the matrix, coxmeg will ignore this argument and automatically treat the relatedness matrix as dense. The argument X is a design matrix of the predictors, which can be produced by e.g., the function model.matrix. The design matrix for the Cox model does not include the intercept term. The columns in X should be linearly independent; otherwise the function will stop with an error indicating sigularity.

re

In the above result, tau is the estimated variance component, and int_ll is -2*log(lik) of the integrated/marginal likelihood for estimating tau.

We give more details about specifying order and detap. The argument order=1 (by default) uses the first-order approximation of the inverse Hessian matrix in the optimization, which works well in most general situations (See [@He_2020] for more details). By detap='diagonal', we tell coxmeg to use a diagonal approximation to compute the determinant, which is much faster under this setting, when estimating the variance component. By default (detap='NULL'), coxmeg will automatically select a method for computing the determinant based on type, the sample size, and whether the relatedness matrix is symmetric positive definite (SPD).

We compare the results with coxme, which are slightly different due to different approximation of the log-determinant used in the estimation of the variance component. Also, the integrated log-likelihoods cannot be compared directly because different approximation of log-determinant is used.

library(coxme)
bls <- c(1)
for(i in (size[1]-1):1)
{bls <- c(bls, c(rep(offd,i),1))}
tmat <- bdsmatrix(blocksize=size, blocks=rep(bls,n_f),dimnames=list(as.character(1:n),as.character(1:n)))
re_coxme = coxme(Surv(outcome[,1],outcome[,2])~as.matrix(pred)+(1|as.character(1:n)), varlist=list(tmat),ties='breslow')
re_coxme

In GWAS, we may split the procedure into two separate steps, (1) estimate the variance component under the null model, and (2) estimate the coefficients for the predictors using the estimated variance component. This can be carried out in the following way.

re = coxmeg(outcome,sigma,type='bd',order=1,detap='diagonal')
tau = re$tau
print(tau)
re2 = fit_ppl(pred,outcome,sigma,type='bd',tau=tau,order=1)
re2

Perform GWAS of an age-at-onset phenotype with a sparse relatedness matrix

We illustrate how to perform a GWAS using the coxmeg_plink function. This function supports plink bed files. We provide example files in the package. The example plink files include 20 SNPs and 3000 subjects from 600 families. The following code performs a GWAS for all SNPs in the example bed files. The coxmeg_plink function will write a temporary .gds file for the SNPs in the folder specified by tmp_dir. The user needs to specify a tmp_dir to store the temporary file when bed is provided. The temporary file is removed after the analysis is done.

library(coxmeg)
bed = system.file("extdata", "example_null.bed", package = "coxmeg")
bed = substr(bed,1,nchar(bed)-4)
pheno = system.file("extdata", "ex_pheno.txt", package = "coxmeg")
cov = system.file("extdata", "ex_cov.txt", package = "coxmeg")

## building a relatedness matrix
n_f <- 600
mat_list <- list()
size <- rep(5,n_f)
offd <- 0.5
for(i in 1:n_f)
{
  mat_list[[i]] <- matrix(offd,size[i],size[i])
  diag(mat_list[[i]]) <- 1
}
sigma <- as.matrix(bdiag(mat_list))

re = coxmeg_plink(pheno,sigma,type='bd',bed=bed,tmp_dir=tempdir(),cov_file=cov,verbose=FALSE)
re

The above code first retrieves the full path of the files. If the full path is not given, coxmeg_plink will search the current working directory. The file name of the bed file should not include the suffix (.bed). The phenotype and covariate files have the same format as used in plink, and the IDs must be consistent with the bed files. Specifically, the phenotype file should include four columns including family ID, individual ID, time, and status. The covariate file always starts with two columns, family ID and individual ID. Missing values in the phenotype and covariate files are denoted by -9 and NA, respectively. Note that coxmeg_plink does not impute genotypes itself, and only SNPs without missing values will be analyzed. Therefore, it will be better to use imputed genotype data for coxmeg_plink.

The coxmeg_plink function first estimates the variance component(s) with only the covariates, and then uses it to analyze each SNP after filtering. These two steps can be done separately as follows. The first command without bed only esitmates the variance component tau, and the second command uses the estimated tau to analyze the SNPs.

re = coxmeg_plink(pheno,sigma,type='bd',cov_file=cov,verbose=FALSE)
re = coxmeg_plink(pheno,sigma,type='bd',bed=bed,tmp_dir=tempdir(),tau=re$tau,cov_file=cov,verbose=FALSE)

When the genotypes of a group of SNPs are stored in a matrix object, the function coxmeg_m instead can be used to perform GWAS for each of these SNPs. Similarly, coxmeg_m first estimates the variance component with only the covariates. In the following example, we simulate 10 independent SNPs, and use coxmeg_m to perform an association analysis. By default, coxmeg_m and coxmeg_plink will choose an optimal order between 1 and 10 for analyzing the SNPs when order is not specified.

geno = matrix(rbinom(nrow(sigma)*10,2,runif(nrow(sigma)*10,0.05,0.5)),nrow(sigma),10)
pheno_m = read.table(pheno)
re = coxmeg_m(geno,pheno_m[,3:4],sigma,type='bd',verbose=FALSE)
re

Perform GWAS of an age-at-onset phenotype with a dense relatedness matrix

When the relatedness matrix is dense, type='dense' should be used. In this case, it will be more efficient to use preconditioned conjugate gradient (PCG) (solver=2) and stochastic Lanczos quadrature (SLQ) (detap='slq' or detap='gkb') in the optimization if the sample size is large (>5000). These can be specified as follows.

re = coxmeg_plink(pheno,sigma,type='dense',bed=bed,tmp_dir=tempdir(),cov_file=cov,detap='slq',verbose=FALSE,solver=2)

If solver is not specified, coxmeg_plink will by default choose PCG as a solver when type='dense'. If detap is not specified, coxmeg_plink will by default use detap='gkb' for a dense matrix when the sample size exceeds 5000. The number of Monte Carlo samples in the SLQ can be specified by mc (by default mc=100). The difference between detap='slq' and detap='gkb' is that the former might be inaccurate when the relatedness matrix is almost singular (e.g., a kinship matrix including many monozygotic (MZ) twins) and the latter is robust against the singularity. However, detap='slq' is faster by ~50% than detap='gkb' when type='dense'. In the above example, the relatedness matrix is well conditioned, so detap='slq' works properly.

The above command estimates HRs and reports p-values. Instead, a score test, which is computationally much more efficient, can be used by specifying score=TRUE.

re = coxmeg_plink(pheno,sigma,type='dense',bed=bed,tmp_dir=tempdir(),tau=re$tau,cov_file=cov,detap='slq',verbose=FALSE,solver=2,score=TRUE)
re

In this result, the column score_test is the score test statistic, which follows a $\chi^2$ distribution with 1 d.f. The column score is the score function divided by its variance. Note that score is not the estimate of log(HR) under the full model. It is actually a one-step update of the Newton-Raphson algorithm starting from the values estimated under the null model. Comparing score with log(HR) estimated in the previous section, we see that they are close to each other in this example. However, the difference can be large if the genotype is highly correlated with the covariates or random effects.

Handle positive semidefinite relatedness matrices

We now assume that the first two subjects in the sample are MZ twins. In this case, the relatedness matrix becomes positive semidefinite. Specifying spd=FALSE will let coxmeg_plink handle a positive semidefinite relatedness matrix.

sigma[2,1] = sigma[1,2] = 1
re = coxmeg_plink(pheno,sigma,type='bd',cov_file=cov,verbose=FALSE,spd=FALSE)
re

If the user is not sure whether the relatedness matrix is positive definite or positive semidefinite, it is better to use spd=FALSE although this might be slower because coxmeg_plink will check the smallest eigenvalue. In the current version, instead of using the previously proposed GPPL in [@He_2020], coxmeg performs an eigenvalue decomposition if type='dense' and uses a modified PPL by turning all zero eigenvalues of the relatedness matrix to a small value (1e-6). This modification makes coxmeg suitable for twin cohorts. If type='sparse', coxmeg will add a small value (1e-6) to the diagonal to make it positive definite.

Use multiple relatedness matrices

Multiple correlation matrices might be needed in some situations, e.g., twin studies. In a twin study, the dependence between twins can be further decomposed into the additive genetic component and the shared environmental component, and thus requires two correlation matrices. The coxmeg R package can handle multiple correlation matrices. As an example, we first construct the second correlation matrix, for which we want to estimate its variance component. We then build a List object containing these two correlation matrices.

## building two relatedness matrices and put them in a List
n_f <- 200
mat_list <- list()
size <- rep(5,n_f)
offd <- 0.5
for(i in 1:n_f)
{
  mat_list[[i]] <- matrix(offd,size[i],size[i])
  diag(mat_list[[i]]) <- 1
}
sigma = as(bdiag(mat_list),'dgCMatrix')

n_f <- 500
mat_list <- list()
size <- rep(2,n_f)
offd <- 0.9
for(i in 1:n_f)
{
  mat_list[[i]] <- matrix(offd,size[i],size[i])
  diag(mat_list[[i]]) <- 1
}
sigma2 = as(bdiag(mat_list),'dgCMatrix')
sigmas <- list(sigma,sigma2)

## run coxmeg
re = coxmeg(outcome,sigmas,type='bd',X=pred,order=1,detap='diagonal')
re

The sum of these correlation matrices determines which value should be specified for type. As shown in the above example, because the sum of the matrices is still block diagonal, type='bd' is appropriate. If all of these matrices are sparse but not all of them are block diagonal, then type='sparse' is a good option. On the other hand, if one of these matrices is dense, then type='dense' should be used. In the current version, spd=FALSE is not supported for multiple matrices, which means that the sum of the matrices must be positive definite. A sufficient condition is that one of the matrices is positive definite.

References



Try the coxmeg package in your browser

Any scripts or data that you put into this service are public.

coxmeg documentation built on Jan. 13, 2023, 5:07 p.m.