We perform some check for the SuSiE result on region around ZBTB38.
Load pacakges:
library(readr)
library(dplyr)
library(gridExtra)
library(susieR)
Load plotting functions:
knitr::read_chunk("../scripts/plots.R")
library(ggplot2)
#' plot gene name annotations
#' @param dat a matrix of gene names with 'start' and 'end' base-pair position
#' @param xrange range of x axis, base-pair position
plot_geneName = function(dat, xrange, chr){
ngene = 2:nrow(dat)
line = 1
dat$lines = NA
dat$lines[1] = 1
gene.end = dat[1, 'end']
while(length(ngene) != 0){
id = which(dat[ngene, 'start'] > gene.end + 0.02)[1]
if(!is.na(id)){
dat$lines[ngene[id]] = line
gene.end = dat[ngene[id],'end']
ngene = ngene[-id]
}else{
line = line + 1
dat$lines[ngene[1]] = line
gene.end = dat[ngene[1],'end']
ngene = ngene[-1]
}
}
dat$start = pmax(dat$start, xrange[1])
dat$end = pmin(dat$end, xrange[2])
dat$mean = rowMeans(dat[,c('start', 'end')])
pl = ggplot(dat, aes(xmin = xrange[1], xmax = xrange[2])) + xlim(xrange[1], xrange[2]) + ylim(min(-dat$lines-0.6), -0.8) +
geom_rect(aes(xmin = start, xmax = end, ymin = -lines-0.05, ymax = -lines+0.05), fill='blue') +
geom_text(aes(x = mean, y=-lines-0.4, label=geneName), size=4) +
xlab(paste0('base-pair position (Mb) on chromosome ', chr)) + ylab('Gene') +
theme_bw() + theme(axis.text.x=element_blank(),
axis.ticks = element_blank(),
axis.text.y=element_blank(),
axis.title = element_text(size=15),
plot.title=element_text(size=11),
panel.grid.major = element_blank(),
panel.grid.minor = element_blank())
pl
}
discrete_gradient_pal <- function(colours, bins = 5) {
ramp <- scales::colour_ramp(colours)
function(x) {
if (length(x) == 0) return(character())
i <- floor(x * bins)
i <- ifelse(i > bins-1, bins-1, i)
ramp(i/(bins-1))
}
}
scale_colour_discrete_gradient <- function(..., colours, bins = 5, na.value = "grey50", guide = "colourbar", aesthetics = "colour", colors) {
colours <- if (missing(colours))
colors
else colours
continuous_scale(
aesthetics,
"discrete_gradient",
discrete_gradient_pal(colours, bins),
na.value = na.value,
guide = guide,
...
)
}
#' Locuszoom plot
#' @param z a vector of z scores with SNP names
#' @param pos base-pair positions
#' @param gene.pos.map a matrix of gene names with 'start' and 'end' base-pair position
#' @param z.ref.name the reference SNP
#' @param ld correlations between teh reference SNP and the rests
#' @param title title of the plot
#' @param title.size the size of the title
#' @param true the true value
#' @param y.height height of -log10(p) plot and height of the gene name annotation plot
#' @param y.lim range of y axis
locus.zoom = function(z, pos, chr, gene.pos.map=NULL, z.ref.name=NULL, ld=NULL,
title = NULL, title.size = 10, true = NULL,
y.height=c(5,1.5), y.lim=NULL, xrange=NULL){
if(is.null(xrange)){
xrange = c(min(pos), max(pos))
}
tmp = data.frame(POS = pos, p = -(pnorm(-abs(z), log.p = T) + log(2))/log(10))
pl_zoom = ggplot(tmp, aes(x = POS, y = p)) + geom_point(color = 'darkblue') +
ylab("-log10(p value)") + ggtitle(title) + xlim(xrange[1], xrange[2]) +
theme_bw() + theme(axis.title.x=element_blank(),
plot.title = element_text(size=title.size))
if(!is.null(ld) && !is.null(z.ref.name)){
tmp$ref = names(z) == z.ref.name
tmp$r2 = ld^2
pl_zoom = ggplot(tmp, aes(x = POS, y = p, shape = ref, size=ref, color=r2)) + geom_point() +
ylab("-log10(p value)") + ggtitle(title) + xlim(xrange[1], xrange[2]) +
scale_color_gradientn(colors = c("darkblue", "deepskyblue", "lightgreen", "orange", "red"),
values = seq(0,1,0.2), breaks=seq(0,1,0.2)) +
# scale_colour_discrete_gradient(
# colours = c("darkblue", "deepskyblue", "lightgreen", "orange", "red"),
# limits = c(0, 1.01),
# breaks = c(0,0.2,0.4,0.6,0.8,1),
# guide = guide_colourbar(nbin = 100, raster = FALSE, frame.colour = "black", ticks.colour = NA)
# ) +
scale_shape_manual(values=c(20, 18), guide=FALSE) + scale_size_manual(values=c(2,5), guide=FALSE) +
theme_bw() + theme(axis.title.x=element_blank(),
axis.title.y = element_text(size=15),axis.text = element_text(size=15),
plot.title = element_text(size=title.size))
}
if(!is.null(y.lim)){
pl_zoom = pl_zoom + ylim(y.lim[1], y.lim[2])
}
# pl_zoom = pl_zoom + geom_hline(yintercept=-log10(5e-08), linetype='dashed', color = 'red')
if(!is.null(true)){
tmp.true = data.frame(POS = which(true!=0), p = tmp$p[which(true!=0)],
ref = (names(z) == z.ref.name)[which(true!=0)],
label = paste0('SNP',1:length(which(true!=0))))
pl_zoom = pl_zoom + geom_point(data=tmp.true, aes(x=POS, y=p),
color='red', show.legend = FALSE, shape=1, stroke = 1) +
geom_text(data=tmp.true, aes(x = POS-30, y=p+1, label=label), size=3, color='red')
}
if(!is.null(gene.pos.map)){
pl_gene = plot_geneName(gene.pos.map, xrange = xrange, chr=chr)
g = egg::ggarrange(pl_zoom, pl_gene, nrow=2, heights = y.height, draw=FALSE)
}else{
g = pl_zoom
}
g
}
#' SuSiE plot with Locuszoom plot
#' @param z a vector of z scores with SNP names
#' @param model the fitted SuSiE model
#' @param pos base-pair positions
#' @param gene.pos.map a matrix of gene names with 'start' and 'end' base-pair position
#' @param z.ref.name the reference SNP
#' @param ld correlations between teh reference SNP and the rests
#' @param title title of the plot
#' @param title.size the size of the title
#' @param true the true value
#' @param plotz whether to plot locuszoom plot
#' @param y.lim range of y axis
#' @param y.susie the y axis of the SuSiE plot, 'PIP' or 'p', 'p' refers to -log10(p)
susie_plot_locuszoom = function(z, model, pos, chr, gene.pos.map = NULL, z.ref.name, ld,
title = NULL, title.size = 10, true = NULL,
plotz = TRUE, y.lim=NULL, y.susie='PIP', xrange=NULL){
if(is.null(xrange)){
xrange = c(min(pos), max(pos))
}
if(plotz){
pl_zoom = locus.zoom(z, pos = pos, chr = chr, ld=ld, z.ref.name = z.ref.name, title = title, title.size = title.size, y.lim=y.lim, xrange=xrange)
}
pip = model$pip
tmp = data.frame(POS = pos, PIP = pip, p = -(pnorm(-abs(z), log.p = T) + log(2))/log(10))
if(y.susie == 'PIP'){
if(plotz){
pl_susie = ggplot(tmp, aes(x = POS, y = PIP)) + geom_point(show.legend = FALSE, size=3) +
xlim(xrange[1], xrange[2]) +
theme_bw() + theme(axis.title.x=element_blank(), axis.text.x=element_blank())
}else{
pl_susie = ggplot(tmp, aes(x = POS, y = PIP)) + geom_point(show.legend = FALSE, size=3) +
xlim(xrange[1], xrange[2]) + ggtitle(title) +
theme_bw() + theme(axis.title.x=element_blank(),
axis.text = element_text(size=15),
axis.title.y = element_text(size=15),
plot.title = element_text(size=title.size))
}
}else if(y.susie == 'p'){
if(plotz){
pl_susie = ggplot(tmp, aes(x = POS, y = p)) + geom_point(show.legend = FALSE, size=3) +
ylab("-log10(p value)") + xlim(xrange[1], xrange[2]) +
theme_bw() + theme(axis.title.x=element_blank(), axis.text.x=element_blank())
# pl_susie = pl_susie + geom_hline(yintercept=-log10(5e-08), linetype='dashed', color = 'red')
}else{
pl_susie = ggplot(tmp, aes(x = POS, y = p)) + geom_point(show.legend = FALSE, size=3) +
ylab("-log10(p value)") + xlim(xrange[1], xrange[2]) + ggtitle(title) +
theme_bw() + theme(axis.title.x=element_blank(),
axis.text = element_text(size=15),
axis.title.y = element_text(size=15),
plot.title = element_text(size=title.size))
}
}
if(!is.null(true)){
tmp.true = data.frame(POS = pos[which(true!=0)], PIP = pip[which(true!=0)])
pl_susie = pl_susie + geom_point(data=tmp.true, aes(x=POS, y=PIP),
color='red', size=3, show.legend = FALSE)
}
model.cs = model$sets$cs
if(!is.null(model.cs)){
tmp$CS = numeric(length(z))
for(i in 1:length(model.cs)){
tmp$CS[model.cs[[i]]] = gsub('L', 'CS', names(model.cs)[i])
}
tmp.cs = tmp[unlist(model.cs),]
tmp.cs$CS = factor(tmp.cs$CS)
levels(tmp.cs$CS) = paste0('CS', 1:length(model.cs))
colors = c('red', 'cyan', 'green', 'orange', 'dodgerblue', 'violet', 'gold',
'#FF00FF', 'forestgreen', '#7A68A6')
if(y.susie == 'PIP'){
pl_susie = pl_susie + geom_point(data=tmp.cs, aes(x=POS, y=PIP, color=CS),
size=3, shape=1, stroke = 2) +
scale_color_manual(values=colors)
}else if(y.susie == 'p'){
pl_susie = pl_susie + geom_point(data=tmp.cs, aes(x=POS, y=p, color=CS),
shape=1, size=3, stroke=1.5) +
scale_color_manual(values=colors)
}
}
if(!is.null(gene.pos.map)){
pl_gene = plot_geneName(gene.pos.map, xrange = xrange, chr=chr)
if(plotz){
g = egg::ggarrange(pl_zoom, pl_susie, pl_gene, nrow=3, heights = c(4,4,1.5), draw=FALSE)
}else{
g = egg::ggarrange(pl_susie, pl_gene, nrow=2, heights = c(5.5,1.5), draw=FALSE)
}
}else{
if(plotz){
g = egg::ggarrange(pl_zoom, pl_susie, nrow=2, heights = c(4,4), draw=FALSE)
}else{
g = pl_susie
}
}
g
}
locus.zoom.cs = function(z, cs, pos, chr, gene.pos.map=NULL, z.ref.name=NULL, ld=NULL, title = NULL, title.size = 10, xrange = NULL, y.lab=NULL){
if(is.null(y.lab)){
y.lab = '-log10(p value)'
}
tmp = data.frame(POS = pos, log10p = -(pnorm(-abs(z), log.p = T) + log(2))/log(10))
tmp$ref = names(z) == z.ref.name
tmp$r2 = ld^2
tmp$CS = rep(4, length(z))
tmp$CS[cs] = 16
tmp$CS = as.factor(tmp$CS)
pl_zoom = ggplot(tmp, aes(x = POS, y = log10p, shape = CS, color = r2, size=CS)) +
geom_point() + ylab(y.lab) +
scale_color_gradientn(colors = c("darkblue", "deepskyblue", "lightgreen", "orange", "red"),
values = seq(0,1,0.2), breaks=seq(0,1,0.2)) +
# scale_colour_discrete_gradient(
# colours = c("darkblue", "deepskyblue", "lightgreen", "orange", "red"),
# limits = c(0, 1.01),
# breaks = c(0,0.2,0.4,0.6,0.8,1),
# guide = guide_colourbar(nbin = 100, raster = FALSE, frame.colour = "black", ticks.colour = NA)) +
scale_shape_manual(values = c(4, 19), guide=FALSE) +
scale_size_manual(values=c(1.5,4), guide=FALSE) +
ggtitle(title) +
theme_bw() + theme(axis.title.x=element_blank(), axis.text=element_text(size=15),
axis.title.y=element_text(size=12),
plot.title = element_text(size=title.size))
tmp.sub = tmp[cs,]
pl_zoom = pl_zoom + geom_point(data = tmp.sub, aes(x=POS, y=log10p), shape=1, size=4, color='black', stroke=0.1)
if(!is.null(xrange)){
pl_zoom = pl_zoom + xlim(xrange[1], xrange[2])
}
pl_gene = plot_geneName(gene.pos.map, xrange = xrange, chr=chr)
g = egg::ggarrange(pl_zoom, pl_gene, nrow=2, heights = c(5.5,1.5), draw=FALSE)
g
}
Get summary statistics:
ss.dat = readRDS('../data/height.ZBTB38.XtX.Xty.rds')
betas = as.vector(ss.dat$Xty/diag(ss.dat$XtX))
rss = c(ss.dat$yty) - betas * as.vector(ss.dat$Xty)
se = sqrt(rss/((ss.dat$n-1)*diag(ss.dat$XtX)))
z = betas/se
pval = 2*pnorm(-abs(z))
R = as.matrix(t(ss.dat$XtX * (1/sqrt(diag(ss.dat$XtX)))) * (1/ sqrt(diag(ss.dat$XtX))))
names(z) = rownames(R)
Get gene data:
genes <- read_delim("../data/seq_gene.md.gz",delim = "\t",quote = "")
class(genes) <- "data.frame"
genes <- subset(genes,
group_label == "GRCh37.p5-Primary Assembly" &
feature_type == "GENE")
start.pos <- min(ss.dat$pos$POS)
stop.pos <- max(ss.dat$pos$POS)
plot.genes <- subset(genes,
chromosome == 3 &
((chr_start > start.pos & chr_start < stop.pos) |
(chr_stop > start.pos & chr_start < stop.pos)) & feature_type == 'GENE')
gene.pos.map = plot.genes %>% select(feature_name, chr_start, chr_stop)
colnames(gene.pos.map) = c('geneName', 'start', 'end')
gene.pos.map = as.data.frame(gene.pos.map)
gene.pos.map = gene.pos.map %>% mutate(start = start/1e6, end = end/1e6)
gene.pos.map = gene.pos.map[-c(4, 9, 10, 15),]
SuSiE bhat result:
mod_bhat = susie_bhat(bhat = betas, shat = se, R = R, n = ss.dat$n, var_y = as.numeric(ss.dat$yty/(ss.dat$n-1)), track_fit=TRUE)
z.max = which.max(abs(z))
p1 = susie_plot_locuszoom(z, mod_bhat, pos = ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, ld = R[z.max,], z.ref.name = 'rs2871960_C')
p2 = susie_plot_locuszoom(z, mod_bhat, pos = ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, ld = R[z.max,], z.ref.name = 'rs2871960_C', y.susie ='p')
grid.arrange(p1, p2, ncol=2)
For 1Mb region about ZBTB38, SuSiE found 2 CSs. The SNP with the strongest marginal p value in CS1 is rs2871960 (p = 1.8670e-207). For CS2, the SNP with the strongest marginal p value is rs11919556 (p = 0.0211).
The correlation between rs2871960 and rs11919556 is 0.221051. The average correlation between SNPs in CS1 and CS2 is
round(mean(abs(R[mod_bhat$sets$cs$L1, mod_bhat$sets$cs$L2])), 4)
## [1] 0.1849
Zoom in plot:
p1 = susie_plot_locuszoom(z, mod_bhat, pos = ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = names(z), ld = R[z.max,], title='ZBTB38 Credible Sets', plotz = FALSE, y.susie = 'p', xrange=c(140.9,141.5), title.size = 20)
## Warning: Removed 1231 rows containing missing values (geom_point).
## Warning: Removed 5 rows containing missing values (geom_rect).
## Warning: Removed 5 rows containing missing values (geom_text).
p1
# pdf('GitHub/finemap-uk-biobank/ZBTB38_CSs.pdf', height = 5, width=8)
# p1
# dev.off()
library(data.table)
library(readr)
library(Matrix)
geno.file = 'height.ZBTB38.raw.gz'
cat("Reading genotype data.\n")
geno <- fread(geno.file,sep = "\t",header = TRUE,stringsAsFactors = FALSE)
class(geno) <- "data.frame"
# Extract the genotypes.
X <- as(as.matrix(geno[-(1:6)]),'dgCMatrix')
pheno.file <- "/gpfs/data/stephens-lab/finemap-uk-biobank/data/raw/height.csv.gz"
out.pheno.file <- "pheno.height.txt"
out.covar.file <- "covar.removeCS2.height.txt"
# LOAD PHENOTYPE and COVARIATES DATA
# -------------------
# Read the phenotype data from the CSV file.
cat("Reading phenotype data.\n")
pheno <- suppressMessages(read_csv(pheno.file))
class(pheno) <- "data.frame"
pheno$sex = factor(pheno$sex)
pheno$assessment_centre = factor(pheno$assessment_centre)
pheno$genotype_measurement_batch = factor(pheno$genotype_measurement_batch)
pheno$age2 = pheno$age^2
# match individual order with genotype file
ind = fread('height.ZBTB38.0.01.psam')
match.idx = match(ind$IID, pheno$id)
pheno = pheno[match.idx,]
Z = model.matrix(~ sex + age + age2 + assessment_centre + genotype_measurement_batch +
pc_genetic1 + pc_genetic2 + pc_genetic3 + pc_genetic4 + pc_genetic5 +
pc_genetic6 + pc_genetic7 + pc_genetic8 + pc_genetic9 + pc_genetic10 +
pc_genetic11 + pc_genetic12 + pc_genetic13 + pc_genetic14 + pc_genetic15 +
pc_genetic16 + pc_genetic17 + pc_genetic18 + pc_genetic19 + pc_genetic20 + X[,896], data = pheno)
# Remove intercept
Z = Z[,-1]
colnames(Z)[150] = 'X896'
Z = scale(Z, center=T, scale=F)
# standardize quantitative columns
cols = which(colnames(Z) %in% c("age","pc_genetic1","pc_genetic2","pc_genetic3","pc_genetic4",
"pc_genetic5","pc_genetic6","pc_genetic7","pc_genetic8","pc_genetic9",
"pc_genetic10","pc_genetic11","pc_genetic12","pc_genetic13","pc_genetic14",
"pc_genetic15","pc_genetic16","pc_genetic17","pc_genetic18","pc_genetic19","pc_genetic20", "X896"))
Z[,cols] = scale(Z[,cols])
Z[,'age2'] = Z[,'age']^2
# Compute XtX and Xty
y = pheno$height
names(y) = pheno$id
# Center y
y = y - mean(y)
# Center scale X
cm = Matrix::colMeans(X, na.rm = TRUE)
csd = susieR:::compute_colSds(X)
csd[csd == 0] = 1
X = as.matrix(t((t(X) - cm) / csd))
A <- crossprod(Z) # Z'Z
# chol decomposition for (Z'Z)^(-1)
R = chol(solve(A)) # R'R = (Z'Z)^(-1)
W = R %*% crossprod(Z, X) # RZ'X
S = R %*% crossprod(Z, y) # RZ'y
# Load LD matrix from raw genotype
ld.matrix = as.matrix(fread(paste0('height.ZBTB38.matrix')))
# X'X
XtX = ld.matrix*(nrow(X)-1) - crossprod(W) # W'W = X'ZR'RZ'X = X'Z(Z'Z)^{-1}Z'X
rownames(XtX) = colnames(XtX) = colnames(X)
# X'y
Xty = as.vector(y %*% X)
Xty = Xty - crossprod(W, S) # W'S = X'ZR'RZ'y = X'Z(Z'Z)^{-1}Z'y
saveRDS(list(XtX = XtX, Xty = Xty, yty = sum(y^2) - crossprod(S), n = length(y), pos=pos),
paste0('height.ZBTB38.removeCS2.XtX.Xty.rds'))
# # Save phenotype file
# pheno.table = data.frame(FID = pheno$id, IID = pheno$id, height = pheno$height)
# write.table(pheno.table, file = out.pheno.file, quote = FALSE, row.names = FALSE)
# # Save covariates file
# IID = pheno$id
# FID = pheno$id
# cov.table = cbind(FID, IID, Z)
# write.table(cov.table, file= out.covar.file, quote=FALSE, row.names = FALSE)
plink2 --linear hide-covar no-x-sex omit-ref --pfile height.ZBTB38.0.01 --covar covar.removeCS2.height.txt --pheno pheno.height.txt --vif 100 --out height.ZBTB38.0.01.removeCS2.plink2
After removing the effect of rs11919556 (p = 0.0213),
ss.dat = readRDS('../data/height.ZBTB38.removeCS2.XtX.Xty.rds')
betas = as.vector(ss.dat$Xty/diag(ss.dat$XtX))
rss = c(ss.dat$yty) - betas * ss.dat$Xty
se = as.vector(sqrt(rss/((ss.dat$n-1)*diag(ss.dat$XtX))))
z = betas/se
pval = 2*pnorm(-abs(z))
R = as.matrix(t(ss.dat$XtX * (1/sqrt(diag(ss.dat$XtX)))) * (1/ sqrt(diag(ss.dat$XtX))))
names(z) = rownames(R)
z.max = which.max(abs(z))
locus.zoom.cs(z, cs = mod_bhat$sets$cs$L1, pos=ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = names(z)[z.max], ld = R[z.max,], xrange=c(140.9,141.5), title='ZBTB38 CS1', y.lab = '-log10(p value condition on CS2)')
# lm.res = read.delim('../data/height.ZBTB38.0.01.removeCS2.plink2.height.glm.linear')
# lm.res.t = lm.res$T_STAT
# names(lm.res.t) = lm.res$ID
# z.max = which.max(abs(lm.res.t))
# locus.zoom.cs(lm.res.t, cs = mod_bhat$sets$cs$L1, pos=lm.res$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = lm.res$ID[z.max], ld = R[z.max,], xrange=c(140.9,141.5), title='ZBTB38 CS1', y.lab = '-log10(p value condition on CS2)')
XtXr = mod_bhat$XtXr - ss.dat$XtX %*% (mod_bhat$alpha[1,] * mod_bhat$mu[1,])
Xtr = ss.dat$Xty - XtXr
betas = Xtr/diag(ss.dat$XtX)
b_2 = colSums(mod_bhat$alpha[-1,]*mod_bhat$mu[-1,])/mod_bhat$X_column_scale_factors
rss = c(ss.dat$yty - 2*sum(ss.dat$Xty * b_2) + sum(XtXr * b_2)) - betas * Xtr
se = as.vector(sqrt(rss/((ss.dat$n-1)*diag(ss.dat$XtX))))
## Warning in sqrt(rss/((ss.dat$n - 1) * diag(ss.dat$XtX))): NaNs produced
z.CS1 = betas/se
names(z.CS1) = colnames(R)
z.cs1.max = which.max(abs(z.CS1))
p2 = locus.zoom.cs(z.CS1, mod_bhat$sets$cs$L1, pos=ss.dat$pos$POS/1e6, chr = 3, gene.pos.map = gene.pos.map, z.ref.name = names(z.cs1.max), ld = R[z.cs1.max,], xrange=c(140.9,141.5), title='ZBTB38 Credible Set 1', y.lab = '-log10(p value condition on other CSs)', title.size = 20)
## Warning: Removed 1232 rows containing missing values (geom_point).
## Warning: Removed 5 rows containing missing values (geom_rect).
## Warning: Removed 5 rows containing missing values (geom_text).
p2
# pdf('GitHub/finemap-uk-biobank/ZBTB38_CS1.pdf', height=5, width=8)
# p2
# dev.off()
tmp = data.frame(POS = ss.dat$pos$POS/1e6, logPO = log(mod_bhat$alpha[1,]/(1-mod_bhat$alpha[1,])))
tmp$CS = rep(4, length(z))
tmp$CS[mod_bhat$sets$cs$L1] = 16
tmp$CS = as.factor(tmp$CS)
pl_zoom = ggplot(tmp, aes(x = POS, y = logPO, shape = CS, size=CS)) +
geom_point() + ylab('log posterior odd') +
scale_shape_manual(values = c(4, 19), guide=FALSE) +
scale_size_manual(values=c(1.5,4), guide=FALSE) +
ggtitle('ZBTB38 Credible Set 1') +
theme_bw() + theme(axis.title.x=element_blank(), plot.title = element_text(size=12))
tmp.sub = tmp[mod_bhat$sets$cs$L1,]
pl_zoom = pl_zoom + geom_point(data = tmp.sub, aes(x=POS, y=logPO), shape=1, size=4, color='black', stroke=0.1)
pl_zoom = pl_zoom + xlim(140.9,141.5)
pl_gene = plot_geneName(gene.pos.map, xrange = c(140.9,141.5), chr=3)
g = egg::ggarrange(pl_zoom, pl_gene, nrow=2, heights = c(5,1), draw=FALSE)
## Warning: Removed 1231 rows containing missing values (geom_point).
## Warning: Removed 5 rows containing missing values (geom_rect).
## Warning: Removed 5 rows containing missing values (geom_text).
g
library(data.table)
library(readr)
library(Matrix)
geno.file = 'height.ZBTB38.raw.gz'
cat("Reading genotype data.\n")
geno <- fread(geno.file,sep = "\t",header = TRUE,stringsAsFactors = FALSE)
class(geno) <- "data.frame"
# Extract the genotypes.
X <- as(as.matrix(geno[-(1:6)]),'dgCMatrix')
pheno.file <- "/gpfs/data/stephens-lab/finemap-uk-biobank/data/raw/height.csv.gz"
out.pheno.file <- "pheno.height.txt"
out.covar.file <- "covar.removeCS1.height.txt"
# LOAD PHENOTYPE and COVARIATES DATA
# -------------------
# Read the phenotype data from the CSV file.
cat("Reading phenotype data.\n")
pheno <- suppressMessages(read_csv(pheno.file))
class(pheno) <- "data.frame"
pheno$sex = factor(pheno$sex)
pheno$assessment_centre = factor(pheno$assessment_centre)
pheno$genotype_measurement_batch = factor(pheno$genotype_measurement_batch)
pheno$age2 = pheno$age^2
# match individual order with genotype file
ind = fread('height.ZBTB38.psam')
match.idx = match(ind$IID, pheno$id)
pheno = pheno[match.idx,]
Z = model.matrix(~ sex + age + age2 + assessment_centre + genotype_measurement_batch +
pc_genetic1 + pc_genetic2 + pc_genetic3 + pc_genetic4 + pc_genetic5 +
pc_genetic6 + pc_genetic7 + pc_genetic8 + pc_genetic9 + pc_genetic10 +
pc_genetic11 + pc_genetic12 + pc_genetic13 + pc_genetic14 + pc_genetic15 +
pc_genetic16 + pc_genetic17 + pc_genetic18 + pc_genetic19 + pc_genetic20 + X[,865], data = pheno)
# Remove intercept
Z = Z[,-1]
colnames(Z)[150] = 'X865'
Z = scale(Z, center=T, scale = F)
# standardize quantitative columns
cols = which(colnames(Z) %in% c("age","pc_genetic1","pc_genetic2","pc_genetic3","pc_genetic4",
"pc_genetic5","pc_genetic6","pc_genetic7","pc_genetic8","pc_genetic9",
"pc_genetic10","pc_genetic11","pc_genetic12","pc_genetic13","pc_genetic14",
"pc_genetic15","pc_genetic16","pc_genetic17","pc_genetic18","pc_genetic19","pc_genetic20", "X865"))
Z[,cols] = scale(Z[,cols])
Z[,'age2'] = Z[,'age']^2
# Compute XtX and Xty
y = pheno$height
names(y) = pheno$id
# Center y
y = y - mean(y)
# Center scale X
cm = Matrix::colMeans(X, na.rm = TRUE)
csd = susieR:::compute_colSds(X)
csd[csd == 0] = 1
X = as.matrix(t((t(X) - cm) / csd))
A <- crossprod(Z) # Z'Z
# chol decomposition for (Z'Z)^(-1)
R = chol(solve(A)) # R'R = (Z'Z)^(-1)
W = R %*% crossprod(Z, X) # RZ'X
S = R %*% crossprod(Z, y) # RZ'y
# Load LD matrix from raw genotype
ld.matrix = as.matrix(fread(paste0('height.ZBTB38.matrix')))
# X'X
XtX = ld.matrix*(nrow(X)-1) - crossprod(W) # W'W = X'ZR'RZ'X = X'Z(Z'Z)^{-1}Z'X
rownames(XtX) = colnames(XtX) = colnames(X)
# X'y
Xty = as.vector(y %*% X)
Xty = Xty - crossprod(W, S) # W'S = X'ZR'RZ'y = X'Z(Z'Z)^{-1}Z'y
saveRDS(list(XtX = XtX, Xty = Xty, yty = sum(y^2) - crossprod(S), n = length(y), pos=pos),
paste0('height.ZBTB38.removeCS1.XtX.Xty.rds'))
# # Save phenotype file
# pheno.table = data.frame(FID = pheno$id, IID = pheno$id, height = pheno$height)
# write.table(pheno.table, file = out.pheno.file, quote = FALSE, row.names = FALSE)
# # Save covariates file
# IID = pheno$id
# FID = pheno$id
# cov.table = cbind(FID, IID, Z)
# write.table(cov.table, file= out.covar.file, quote=FALSE, row.names = FALSE)
plink2 --linear hide-covar no-x-sex omit-ref --pfile height.ZBTB38.0.01 --covar covar.removeCS1.height.txt --pheno pheno.height.txt --vif 100 --out height.ZBTB38.0.01.removeCS1.plink2
After removing the effect of rs2871960 (p = 7.200e-207), the conditional p value for rs11919556 becomes 4.279e-06, and it becomes the strongest one among all SNPs!
ss.dat = readRDS('../data/height.ZBTB38.removeCS1.XtX.Xty.rds')
betas = as.vector(ss.dat$Xty/diag(ss.dat$XtX))
rss = c(ss.dat$yty) - betas * ss.dat$Xty
se = as.vector(sqrt(rss/((ss.dat$n-1)*diag(ss.dat$XtX))))
z = betas/se
pval = 2*pnorm(-abs(z))
R = as.matrix(t(ss.dat$XtX * (1/sqrt(diag(ss.dat$XtX)))) * (1/ sqrt(diag(ss.dat$XtX))))
names(z) = rownames(R)
z.max = which.max(abs(z))
locus.zoom.cs(z, cs = mod_bhat$sets$cs$L2, pos=ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = names(z)[z.max], ld = R[z.max,], xrange=c(140.9,141.5), title='ZBTB38 CS2', y.lab = '-log10(p value condition on CS1)')
# lm.res = read.delim('../data/height.ZBTB38.removeCS1.plink2.height.glm.linear')
# lm.res.t = lm.res$T_STAT
# names(lm.res.t) = lm.res$ID
# z.max = which.max(abs(lm.res.t))
# locus.zoom.cs(lm.res.t, cs = mod_bhat$sets$cs$L2, pos=lm.res$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = lm.res$ID[z.max], ld = R[z.max,], xrange=c(140.9,141.5), title='ZBTB38 CS2', y.lab = '-log10(p value condition on CS1)')
XtXr = mod_bhat$XtXr - ss.dat$XtX %*% (mod_bhat$alpha[2,] * mod_bhat$mu[2,])
Xtr = ss.dat$Xty - XtXr
betas = Xtr/diag(ss.dat$XtX)
b_2 = colSums(mod_bhat$alpha[-2,]*mod_bhat$mu[-2,])/mod_bhat$X_column_scale_factors
rss = c(ss.dat$yty - 2*sum(ss.dat$Xty * b_2) + sum(XtXr * b_2)) - betas * Xtr
se = as.vector(sqrt(rss/((ss.dat$n-1)*diag(ss.dat$XtX))))
## Warning in sqrt(rss/((ss.dat$n - 1) * diag(ss.dat$XtX))): NaNs produced
z.CS2 = betas/se
names(z.CS2) = colnames(R)
z.cs2.max = which.max(abs(z.CS2))
p3 = locus.zoom.cs(z.CS2, mod_bhat$sets$cs$L2, pos=ss.dat$pos$POS/1e6, chr=3, gene.pos.map = gene.pos.map, z.ref.name = names(z.cs2.max), ld = R[z.cs2.max,], xrange=c(140.9,141.5), title='ZBTB38 Credible Set 2', y.lab = '-log10(p value condition on other CSs)', title.size = 20)
## Warning: Removed 1232 rows containing missing values (geom_point).
## Warning: Removed 5 rows containing missing values (geom_rect).
## Warning: Removed 5 rows containing missing values (geom_text).
p3
# pdf('GitHub/finemap-uk-biobank/ZBTB38_CS2.pdf', height=5, width=8)
# p3
# dev.off()
tmp = data.frame(POS = ss.dat$pos$POS/1e6, logPO = log(mod_bhat$alpha[2,]/(1-mod_bhat$alpha[2,])))
tmp$CS = rep(4, length(z))
tmp$CS[mod_bhat$sets$cs$L2] = 16
tmp$CS = as.factor(tmp$CS)
pl_zoom = ggplot(tmp, aes(x = POS, y = logPO, shape = CS, size=CS)) +
geom_point() + ylab('log posterior odd') +
scale_shape_manual(values = c(4, 19), guide=FALSE) +
scale_size_manual(values=c(1.5,4), guide=FALSE) +
ggtitle('ZBTB38 Credible Set 2') +
theme_bw() + theme(axis.title.x=element_blank(), plot.title = element_text(size=12))
tmp.sub = tmp[mod_bhat$sets$cs$L2,]
pl_zoom = pl_zoom + geom_point(data = tmp.sub, aes(x=POS, y=logPO), shape=1, size=4, color='black', stroke=0.1)
pl_zoom = pl_zoom + xlim(140.9,141.5)
pl_gene = plot_geneName(gene.pos.map, xrange = c(140.9,141.5), chr=3)
g = egg::ggarrange(pl_zoom, pl_gene, nrow=2, heights = c(5,1), draw=FALSE)
## Warning: Removed 1231 rows containing missing values (geom_point).
## Warning: Removed 5 rows containing missing values (geom_rect).
## Warning: Removed 5 rows containing missing values (geom_text).
g
In the following simulation, we treat rs2871960 and rs11919556 as true signals. The effect sizes are from estimated model. We use the fitted residual variance in the simulation. The response y is simulated from \[ y \sim N_n(Xb, \sigma^2 I) \] , where X the genotype matrix that column centered, scaled, and removed the effect of covariates.
# ON CRI
library(data.table)
library(readr)
library(Matrix)
library(susieR)
geno.file = 'height.ZBTB38.0.01.raw.gz'
cat("Reading genotype data.\n")
geno <- fread(geno.file,sep = "\t",header = TRUE,stringsAsFactors = FALSE)
class(geno) <- "data.frame"
# Extract the genotypes.
X <- as(as.matrix(geno[-(1:6)]),'dgCMatrix')
pheno.file <- "/gpfs/data/stephens-lab/finemap-uk-biobank/data/raw/height.csv.gz"
# LOAD PHENOTYPE and COVARIATES DATA
# -------------------
# Read the phenotype data from the CSV file.
cat("Reading phenotype data.\n")
pheno <- suppressMessages(read_csv(pheno.file))
class(pheno) <- "data.frame"
pheno$sex = factor(pheno$sex)
pheno$assessment_centre = factor(pheno$assessment_centre)
pheno$genotype_measurement_batch = factor(pheno$genotype_measurement_batch)
pheno$age2 = pheno$age^2
# match individual order with genotype file
ind = fread('height.ZBTB38.0.01.psam')
match.idx = match(ind$IID, pheno$id)
pheno = pheno[match.idx,]
Z = model.matrix(~ sex + age + age2 + assessment_centre + genotype_measurement_batch +
pc_genetic1 + pc_genetic2 + pc_genetic3 + pc_genetic4 + pc_genetic5 +
pc_genetic6 + pc_genetic7 + pc_genetic8 + pc_genetic9 + pc_genetic10 +
pc_genetic11 + pc_genetic12 + pc_genetic13 + pc_genetic14 + pc_genetic15 +
pc_genetic16 + pc_genetic17 + pc_genetic18 + pc_genetic19 + pc_genetic20, data = pheno)
# Remove intercept
Z = Z[,-1]
# standardize quantitative columns
cols = which(colnames(Z) %in% c("age","pc_genetic1","pc_genetic2","pc_genetic3","pc_genetic4",
"pc_genetic5","pc_genetic6","pc_genetic7","pc_genetic8","pc_genetic9",
"pc_genetic10","pc_genetic11","pc_genetic12","pc_genetic13","pc_genetic14",
"pc_genetic15","pc_genetic16","pc_genetic17","pc_genetic18","pc_genetic19","pc_genetic20"))
Z[,cols] = scale(Z[,cols])
Z[,'age2'] = Z[,'age']^2
# Center scale X
cm = Matrix::colMeans(X, na.rm = TRUE)
csd = susieR:::compute_colSds(X)
csd[csd == 0] = 1
X = t((t(X) - cm) / csd)
A <- crossprod(Z)
# chol decomposition for (Z'Z)^(-1)
R = chol(solve(A))
W = R %*% t(Z) %*% X
# Remove Covariates from X
X <- as.matrix(X - Z %*% crossprod(R,W))
# Get estimated parameters
mod = readRDS('height.chr3.140800000.141800000.0.01.susie.model.rds')
ss.dat = readRDS('height.ZBTB38.0.01.XtX.Xty.rds')
R = as.matrix(t(ss.dat$XtX * (1/sqrt(diag(ss.dat$XtX)))) * (1/ sqrt(diag(ss.dat$XtX))))
sigma2 = mod$sigma2
V = mod$V[1:2]
n = nrow(X)
b2 = c(mod$mu[1, 945], mod$mu[2, 978])
Xb = as.vector(X[, c(945,978)] %*% b2)
result = vector("list", 1000)
set.seed(201910)
for(i in 1:1000){
## generate y
y = Xb + rnorm(n, 0, sqrt(sigma2))
## compute Xty, yty
Xty = as.vector(y %*% X)
yty = sum(y^2)
## compute summary stats
betas = as.vector(Xty/diag(ss.dat$XtX))
rss = yty - betas * Xty
se = as.vector(sqrt(rss/((n-1)*diag(ss.dat$XtX))))
z = betas/se
result[[i]] = susie_bhat(betas, se, R=R, n=n, var_y = as.numeric(yty/(n-1)))
}
saveRDS(result, 'height.ZBTB38.0.01.simulation1000.rds')
Load simulation results:
result = readRDS('../data/height.ZBTB38.0.01.simulation1000.rds')
In 1000 simulations, 655 runs have only 1 CS, 344 runs have 2 CSs.
table(sapply(result, function(mod) length(mod$sets$cs)))
##
## 1 2 3
## 655 344 1
323 runs contain both true signals.
contain.true = sapply(result, function(mod) all(c(945, 978) %in% unlist(mod$sets$cs)))
sum(contain.true)
## [1] 323
959 runs contain rs2871960.
contain.true.1 = sapply(result, function(mod) all( 945%in% unlist(mod$sets$cs)))
sum(contain.true.1)
## [1] 959
333 runs contain rs11919556.
contain.true.2 = sapply(result, function(mod) all( 978 %in% unlist(mod$sets$cs)))
sum(contain.true.2)
## [1] 333