This document describes several simulation studies to show the performance and robustness of BACON under various scenario’s and in comparison with other approaches that estimate the empirical null distribution or the amount of inflation.
Global settings for the number of simulations and features used in all examples. In the paper the number of simulations was set to 100 and the number of features at 2000. Here we have extended this. Furthermore, since BACON version 1.1.7 the priors have been adjusted to cover a wider range of simulation scenario’s.
nsim <- 100
p <- 2000
priors = list(sigma = list(alpha = 1.28, beta = 0.36), mu = list(lambda = c(0,
3, -3), tau = c(100, 100, 100)), epsilon = list(gamma = c(1, 1, 1)))
This simulation shows that the genomic inflation factor(Devlin and Roeder 1999), \(\lambda_{\chi^2_1}\), overestimates the amount of inflation when a moderated proportion of true associations is present.
f <- function(x) {
sqrt(qchisq(1/(2 * x), 1)/qchisq(1/2, 1))
}
set.seed(12345)
theta1 <- c(0.8, 0, 1, 0, 3, 1)
theta2 <- c(0.9, 0, 1, 0, 3, 1)
theta3 <- c(0.95, 0, 1, 0, 3, 1)
sim.data1 <- replicate(nsim, rnormmix(p, theta1)) ##simulate
sim.data2 <- replicate(nsim, rnormmix(p, theta2))
sim.data3 <- replicate(nsim, rnormmix(p, theta3))
gif1 <- apply(sim.data1, 2, function(x) sqrt(median(x^2)/qchisq(1/2, 1))) ##estimate
gif2 <- apply(sim.data2, 2, function(x) sqrt(median(x^2)/qchisq(1/2, 1)))
gif3 <- apply(sim.data3, 2, function(x) sqrt(median(x^2)/qchisq(1/2, 1)))
data <- data.frame(inflation = c(gif1, gif2, gif3), proportion = rep(c(0.8,
0.9, 0.95), each = nsim), group = factor(rep(c(0.8, 0.9, 0.95), each = nsim)))
gp <- ggplot(data, aes(x = proportion, y = inflation, group = group)) ##plot
gp <- gp + geom_boxplot()
gp <- gp + geom_hline(yintercept = 1, linetype = 2)
gp <- gp + stat_function(fun = f, colour = "red")
gp <- gp + ylab("Genomic Inflation Factor Estimates")
gp <- gp + theme_set(theme_bw(base_size = 20, base_family = "Helvetica")) +
theme_update(plot.margin = unit(rep(2, 4), units = "points"))
gp
This figure should be identical to Fig. 2 of the manuscript. The genomic inflation factor overestimates the amount of inflation even for moderated amount of true associations and in the absence of true inflation.
Other methods have been proposed for the estimation of the empirical null distribution e.g., (B Efron 2004) Estimates for the mean and standard deviation of the empirical null distribution are estimates of bias and inflation. Furthermore, robust estimators are proposed for the estimation of bias and inflation e.g., using the median and mad (median absolute deviation)(Wang et al. 2015).
set.seed(12345)
theta1 <- c(0.8, 0, 1, 0, 3, 1)
theta2 <- c(0.9, 0, 1, 0, 3, 1)
theta3 <- c(0.95, 0, 1, 0, 3, 1)
sim.data1 <- replicate(nsim, rnormmix(p, theta1)) ##simulate
sim.data2 <- replicate(nsim, rnormmix(p, theta2))
sim.data3 <- replicate(nsim, rnormmix(p, theta3))
gif1 <- sqrt(apply(sim.data1^2, 2, median)/0.456) ##genomic inflation factor Devlin and Roeder
gif2 <- sqrt(apply(sim.data2^2, 2, median)/0.456)
gif3 <- sqrt(apply(sim.data3^2, 2, median)/0.456)
mad1 <- apply(sim.data1, 2, mad) ##mad propose by Wang et al.
mad2 <- apply(sim.data2, 2, mad)
mad3 <- apply(sim.data3, 2, mad)
mlecm1 <- apply(sim.data1, 2, function(x) locfdr(x, plot = 0)$fp0[c(3, 5), 3]) ##locfdr Efron et al.
mlecm2 <- apply(sim.data2, 2, function(x) locfdr(x, plot = 0)$fp0[c(3, 5), 3])
mlecm3 <- apply(sim.data3, 2, function(x) locfdr(x, plot = 0)$fp0[c(3, 5), 3])
bac1 <- apply(sim.data1, 2, function(x) inflation(bacon(x))) ##bacon
bac2 <- apply(sim.data2, 2, function(x) inflation(bacon(x)))
bac3 <- apply(sim.data3, 2, function(x) inflation(bacon(x)))
data <- data.frame(inflation = c(gif1, gif2, gif3, mad1, mad2, mad3, mlecm1[1,
], mlecm2[1, ], mlecm3[1, ], mlecm1[2, ], mlecm2[2, ], mlecm3[2, ], bac1,
bac2, bac3), proportion = factor(rep(rep(c(0.8, 0.9, 0.95), each = nsim),
5)), method = factor(rep(c("gif", "mad", "mle", "cm", "bif"), each = 3 *
nsim)))
gp <- ggplot(data, aes(x = proportion, y = inflation, fill = method)) ##create boxplots
gp <- gp + geom_boxplot()
gp <- gp + geom_hline(yintercept = 1, linetype = 2)
gp <- gp + xlab("null associations (%)") + ylab("Inflation Estimates")
gp <- gp + theme_set(theme_bw(base_size = 20, base_family = "Helvetica")) +
scale_fill_brewer(palette = "Dark2") + scale_color_brewer(palette = "Dark2")
gp
Here bif and gif represent the Bayesian inflation factor as estimated by bacon and the genomic inflation factor as proposed by (Devlin and Roeder 1999).
The figure should be identical to Additional Fig. 4 of the manuscript.
Here we compare estimation of the empirical null distribution under different scenario’s using different approach:
median and mad as proposed by (Wang et al. 2015)simulate <- function(nsim, p, theta, name) {
require(bacon)
require(locfdr)
require(matrixStats)
sim.data <- replicate(nsim, rnormmix(p, theta))
bc <- bacon(sim.data)
bay <- estimates(bc)[, c(1, 4, 7)]
rob <- cbind(NA, colMedians(sim.data), colMads(sim.data))
mle <- t(apply(sim.data, 2, function(x) locfdr(x, plot = 0)$fp0[3, c(3,
1, 2)]))
cm <- t(apply(sim.data, 2, function(x) locfdr(x, plot = 0)$fp0[5, c(3, 1,
2)]))
data.frame(values = c(as.vector(bay), as.vector(mle), as.vector(cm), as.vector(rob)),
estimate = rep(rep(c("prop0", "mu0", "sigma0"), each = nsim), 4), method = rep(c("Bayesian",
"MLE", "CM", "Robust"), each = 3 * nsim), scenario = rep(name, 3 *
4 * nsim))
}
set.seed(12345)
thetas <- list(equal = c(0.9, 0, 1, 0, 3, 1), skewed = c(0.9, 0, 1, 3, 1, 1),
small = c(0.98, 0, 1, 0, 1, 1), close = c(0.9, 0, 1, 1, 1, 1))
data <- c()
for (name in names(thetas)) data <- rbind(data, simulate(nsim, p, thetas[[name]],
name))
## Detected 2 workers!
## Running in parallel!
## Detected 2 workers!
## Running in parallel!
## Detected 2 workers!
## Running in parallel!
## Detected 2 workers!
## Running in parallel!
gp <- ggplot(data, aes(x = method, y = values, fill = method)) ##plotting
gp <- gp + geom_boxplot()
gp <- gp + facet_grid(estimate ~ scenario, scales = "free_y")
gp <- gp + scale_fill_brewer(palette = "Dark2") + scale_color_brewer(palette = "Dark2")
gp <- gp + xlab(label = "") + ylab(label = "")
gp <- gp + theme_set(theme_bw(base_size = 20, base_family = "Helvetica")) +
theme_update(plot.margin = unit(rep(2, 4), units = "points"))
gp <- gp + theme(legend.position = "none") + theme(axis.text.x = element_text(angle = 45,
hjust = 1))
gp
This figure is identical to Additional Fig. 5 of the manuscript.
Here we simulate test-statistic with a strong positive or negative bias to investigate how flexible the Bayesian estimation of the empirical null is.
set.seed(12345)
thetapos <- c(0.9, 1, 1, 0, 3, 1)
thetaneg <- c(0.9, -1, 1, 0, 3, 1)
estpos <- estneg <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, thetapos)
estpos[i, ] <- estimates(bacon(z, priors = priors))
z <- rnormmix(p, thetaneg)
estneg[i, ] <- estimates(bacon(z, priors = priors))
}
results <- rbind(c(thetapos[1], NA, NA, thetapos[2], NA, NA, thetapos[3], NA,
NA), apply(estpos, 2, mean), apply(estpos, 2, sd), apply(estneg, 2, mean),
apply(estneg, 2, sd))
colnames(results) <- colnames(estimates(bacon(z, priors = priors)))
rownames(results) <- c("true", "mean (pos.)", "stdev (pos.)", "mean (neg.)",
"stdev (neg.)")
results <- signif(results, 3)
results[1, 4] <- paste0("+/-", results[1, 4])
kable(results)
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.9 | NA | NA | +/-1 | NA | NA | 1 | NA | NA |
| mean (pos.) | 0.934 | 0.0334 | 0.0325 | 0.913 | 2.99 | -3.03 | 1.05 | 1.83 | 1.77 |
| stdev (pos.) | 0.00994 | 0.00826 | 0.00574 | 0.0264 | 0.0175 | 0.0369 | 0.0231 | 0.329 | 0.296 |
| mean (neg.) | 0.933 | 0.0329 | 0.0339 | -0.911 | 3.02 | -2.99 | 1.05 | 1.82 | 1.83 |
| stdev (neg.) | 0.00985 | 0.00474 | 0.00774 | 0.0276 | 0.0337 | 0.0129 | 0.0201 | 0.269 | 0.319 |
fit(bacon(z, priors = priors), n = 25, main = "neg. bias fit (colored) and true (black)",
xlim = c(-6, 6))
curve(0.9 * dnorm(x, -1, 1), add = TRUE, col = 1, lwd = 2, lty = 2)
Although the null is not exactly identical it is very close even with the strong bias.
Here we simulate test-statistic with a strong positive or negative bias to investigate how flexible the Bayesian estimation of the empirical null is.
set.seed(12345)
thetainfl <- c(0.9, 0, 1.2, 0, 3, 1)
thetadefl <- c(0.9, 0, 0.8, 0, 3, 1)
estinfl <- estdefl <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, thetainfl)
estinfl[i, ] <- estimates(bacon(z, priors = priors))
z <- rnormmix(p, thetadefl)
estdefl[i, ] <- estimates(bacon(z, priors = priors))
}
results <- rbind(c(thetainfl[1], NA, NA, thetainfl[2], NA, NA, thetainfl[3],
NA, NA), apply(estinfl, 2, mean), apply(estinfl, 2, sd), apply(estdefl,
2, mean), apply(estdefl, 2, sd))
colnames(results) <- colnames(estimates(bacon(z, priors = priors)))
rownames(results) <- c("true", "mean (infl.)", "stdev (infl.)", "mean (defl.)",
"stdev (defl.)")
results <- signif(results, 3)
results[1, 7] <- paste(thetainfl[3], thetadefl[3], sep = "/")
kable(results)
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.9 | NA | NA | 0 | NA | NA | 1.2/0.8 | NA | NA |
| mean (infl.) | 0.933 | 0.0328 | 0.0337 | -0.000831 | 2.99 | -3 | 1.2 | 1.84 | 1.83 |
| stdev (infl.) | 0.00973 | 0.00729 | 0.00644 | 0.0303 | 0.0161 | 0.0172 | 0.0264 | 0.306 | 0.316 |
| mean (defl.) | 0.931 | 0.0348 | 0.0345 | 0.00257 | 2.98 | -2.98 | 0.82 | 1.85 | 1.8 |
| stdev (defl.) | 0.00799 | 0.00528 | 0.00585 | 0.0207 | 0.0316 | 0.0366 | 0.0157 | 0.264 | 0.249 |
fit(bacon(z, priors = priors), n = 25, main = "defl. fit (colored) and true (black)",
xlim = c(-6, 6))
curve(0.9 * dnorm(x, 0, 0.8), add = TRUE, col = 1, lwd = 2, lty = 2)
Although the null is not exactly identical it is very close even with the moderated inflation or deflation.
Here we use the approach of Efron to introduce correlation among genes(B. Efron 2010).
The following function generates two sets of test-statistics one with block-correlation structure one without correlation. Both sets have added for a proportion of the genes an effect according to our mixture simulation.
load(url("http://statweb.stanford.edu/~ckirby/brad/LSI/datasets-and-programs/programs/simz.RData"))
gendat <- function(N, theta, alpha) {
U <- runif(N, min = 0, max = 1)
N1 <- sum(U < theta[1])
N2 <- sum(U >= theta[1])
index <- rep(FALSE, N)
index[sample(1:N, N2)] <- TRUE
mu <- rep(0, N)
mu[index] <- rnorm(N2, mean = theta[4], sd = theta[5])
## uncorrelated matrix genes
z <- simz(alpha = 0, N, 1, stand = 1)
zu <- mu + z
## correlated matrix genes
z <- simz(alpha = alpha, N, 1, stand = 1)
zc <- mu + z
return(list(zu = zu, zc = zc, index = index))
}
Here we generate 100 times two sets of test-statistics one with a correlation structure among genes and one without.
set.seed(12345)
theta <- c(0.98, 0, 1, 3, 1, 1)
alpha <- 0.4
cor <- uncor <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
ce <- gendat(p, theta, alpha)
uncor[i, ] <- estimates(bacon(ce$zu))
cor[i, ] <- estimates(bacon(ce$zc))
}
names <- rep(colnames(estimates(bacon(ce$zu))), each = nsim)
data <- data.frame(values = as.vector(uncor), estimates = gsub("\\..*$", "",
names), component = gsub("^.*\\.", "", names), group = "uncorrelated")
data <- rbind(data, data.frame(values = as.vector(cor), estimates = gsub("\\..*$",
"", names), component = gsub("^.*\\.", "", names), group = "correlated"))
For example, plot the estimate proportion of null genes, the mean and standard deviation of the null distribution for both the correlated and uncorrelated test-statistics.
gp <- ggplot(subset(data, component == 0), aes(component, values, colour = group))
gp <- gp + geom_boxplot() + geom_point(position = position_jitterdodge())
gp <- gp + facet_wrap(~estimates, scales = "free_y")
gp
Or tabulate the results.
results <- rbind(c(theta[1], NA, NA, theta[2], NA, NA, theta[3], NA, NA), apply(uncor,
2, mean), apply(uncor, 2, sd), apply(cor, 2, mean), apply(cor, 2, sd))
colnames(results) <- colnames(estimates(bacon(ce$zu)))
rownames(results) <- c("true", "mean (uncor)", "stdev (uncor)", "mean (cor)",
"stdev (cor)")
kable(round(results, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.900 | NA | NA | 0.000 | NA | NA | 1.000 | NA | NA |
| mean (uncor) | 0.926 | 0.051 | 0.022 | 0.002 | 3.012 | -2.972 | 1.009 | 2.167 | 1.514 |
| stdev (uncor) | 0.008 | 0.006 | 0.004 | 0.008 | 0.036 | 0.017 | 0.022 | 0.261 | 0.312 |
| mean (cor) | 0.928 | 0.050 | 0.022 | 0.002 | 3.023 | -2.958 | 0.957 | 2.145 | 1.485 |
| stdev (cor) | 0.014 | 0.014 | 0.011 | 0.049 | 0.072 | 0.121 | 0.141 | 0.340 | 0.497 |
Even with moderated correlation the estimates variance increases almost 10x for the mean and standard deviation. Although on average the estimated mean and proportion nulls is in good agreement with the expected values there is a observed deflation for the estimated standard deviation.
fpr <- tpr <- matrix(nrow = nsim, ncol = 6)
for (i in 1:nsim) {
ce <- gendat(p, theta, alpha)
Pvals <- matrix(nrow = p, ncol = 6)
bc <- bacon(ce$zu, priors = priors)
Pvals[, 1] <- pval(bc, corrected = FALSE)
Pvals[, 2] <- pval(bc)
t <- tstat(bc, corrected = FALSE)
Pvals[, 3] <- 2 * pnorm(-abs(t/sqrt(median(t^2)/qchisq(1/2, 1))))
bc <- bacon(ce$zc, priors = priors)
Pvals[, 4] <- pval(bc, corrected = FALSE)
Pvals[, 5] <- pval(bc)
t <- tstat(bc, corrected = FALSE)
Pvals[, 6] <- 2 * pnorm(-abs(t/sqrt(median(t^2)/qchisq(1/2, 1))))
fpr[i, ] <- apply(Pvals[!ce$index, ], 2, function(x) mean(x < 0.05)) ##Type I error
tpr[i, ] <- apply(Pvals[ce$index, ], 2, function(x) mean(x < 0.05)) ##Power
}
colnames(fpr) <- colnames(tpr) <- c("uncor (naive)", "uncor (emp)", "uncor (N(0,1))",
"cor(naive)", "cor (emp)", "cor (N(0,1))")
results <- rbind(signif(apply(fpr, 2, mean), 2), signif(apply(fpr, 2, sd), 1),
signif(apply(tpr, 2, mean), 2), signif(apply(tpr, 2, sd), 1))
rownames(results) <- c("Type I error (mean)", "Type I error (stdev)", "Power (mean)",
"Power (stdev)")
kable(results)
| uncor (naive) | uncor (emp) | uncor (N(0,1)) | cor(naive) | cor (emp) | cor (N(0,1)) | |
|---|---|---|---|---|---|---|
| Type I error (mean) | 0.050 | 0.053 | 0.029 | 0.038 | 0.050 | 0.023 |
| Type I error (stdev) | 0.005 | 0.005 | 0.005 | 0.020 | 0.008 | 0.007 |
| Power (mean) | 0.770 | 0.770 | 0.720 | 0.770 | 0.800 | 0.740 |
| Power (stdev) | 0.030 | 0.030 | 0.030 | 0.040 | 0.070 | 0.090 |
More or less the same trend is observed as with estimation of the empirical null. For correlated data the standard deviations are much larger while the expected Type I error is similar as estimated from uncorrelated data.
Here we simulated from a three component normal mixture, i.e, same model that is used for fitting the data.
set.seed(12345)
theta <- c(0.8, 0.1, 0.1, 0, -3, 4, 1, 3, 2) ##extended parameters compared to rnormmix
z <- c(rnorm(p * theta[1], theta[4], theta[7]), rnorm(p * theta[2], theta[5],
theta[8]), rnorm(p * theta[3], theta[6], theta[9]))
h <- hist(z, n = 25, freq = FALSE)
curve(theta[1] * dnorm(x, theta[4], theta[7]), add = TRUE)
curve(theta[2] * dnorm(x, theta[5], theta[8]), add = TRUE)
curve(theta[3] * dnorm(x, theta[6], theta[9]), add = TRUE)
set.seed(12345)
est <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- c(rnorm(p * theta[1], theta[4], theta[7]), rnorm(p * theta[2], theta[5],
theta[8]), rnorm(p * theta[3], theta[6], theta[9]))
est[i, ] <- estimates(bacon(z, priors = priors))
}
names <- rep(colnames(estimates(bacon(z, priors = priors))), each = nsim)
data <- data.frame(values = as.vector(est), estimates = gsub("\\..*$", "", names),
component = gsub("^.*\\.", "", names))
data <- rbind(theta[c(1, 3, 2, 4, 6, 5, 7, 9, 8)], apply(est, 2, mean), apply(est,
2, sd))
colnames(data) <- colnames(estimates(bacon(z, priors = priors)))
rownames(data) <- c("true", "mn", "sd")
kable(signif(data, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.80000 | 0.10000 | 0.10000 | 0.0000 | 4.0000 | -3.0000 | 1.0000 | 2.000 | 3.000 |
| mn | 0.77100 | 0.13100 | 0.09820 | -0.0123 | 3.1300 | -3.0500 | 0.9710 | 2.320 | 2.750 |
| sd | 0.00774 | 0.00572 | 0.00598 | 0.0253 | 0.0547 | 0.0525 | 0.0253 | 0.116 | 0.197 |
Estimated parameters are in good agreement with the expected values.
Computer Age Statistical InferenceSection 15.5 (B Efron and Hastie 2016) discusses briefly estimation of the empirical null distribution and describes a small simulation studies to give some ideas on the biases and variabilities inherent in the locfdr estimation process.
Here I use the same simulation setup but added bacon as competitive estimator of the empirical null.
set.seed(12345)
nsim <- 250
p <- 5000
sim.data1 <- replicate(nsim, c(rnorm(0.95 * p, 0, 1), rnorm(0.05 * p, 3, 1))) ##simulate
sim.data2 <- replicate(nsim, c(rnorm(0.95 * p, 0, 1), rnorm(0.05 * p, 4.2, 1)))
mlecm1 <- apply(sim.data1, 2, function(x) locfdr(x, plot = 0)$fp0[c(3, 5), ]) ##locfdr Efron et al.
mlecm2 <- apply(sim.data2[, 1:5], 2, function(x) locfdr(x, plot = 0)$fp0[c(3,
5), ])
bac1 <- apply(sim.data1, 2, function(x) estimates(bacon(x))[c(1, 4, 7)]) ##bacon
bac2 <- apply(sim.data2, 2, function(x) estimates(bacon(x))[c(1, 4, 7)])
results1 <- rbind(c(theta1[2], theta1[3], theta1[1]), matrix(apply(mlecm1, 1,
mean), 2, 3), apply(bac1, 1, mean)[c(2, 3, 1)], matrix(apply(mlecm1, 1,
sd), 2, 3), apply(bac1, 1, sd)[c(2, 3, 1)])
colnames(results1) <- c("delta0", "sigma0", "pi0")
rownames(results1) <- c("true", paste(rep(c("mean", "stdev"), each = 3), rep(c("(mle)",
"(ce)", "(bacon)"), 2)))
results2 <- rbind(c(theta2[2], theta2[3], theta2[1]), matrix(apply(mlecm2, 1,
mean), 2, 3), apply(bac2, 1, mean)[c(2, 3, 1)], matrix(apply(mlecm2, 1,
sd), 2, 3), apply(bac2, 1, sd)[c(2, 3, 1)])
colnames(results2) <- c("delta0", "sigma0", "pi0")
rownames(results2) <- c("true", paste(rep(c("mean", "stdev"), each = 3), rep(c("(mle)",
"(ce)", "(bacon)"), 2)))
results <- cbind(results1, results2)
round(results, 3)
## delta0 sigma0 pi0 delta0 sigma0 pi0
## true 0.000 1.000 0.800 0.000 1.000 0.900
## mean (mle) 0.010 1.013 0.960 0.009 0.987 0.947
## mean (ce) 0.002 0.974 0.934 0.057 0.948 0.923
## mean (bacon) 0.006 0.990 0.950 -0.022 0.964 0.931
## stdev (mle) 0.019 0.022 0.010 0.018 0.015 0.008
## stdev (ce) 0.034 0.040 0.024 0.026 0.029 0.017
## stdev (bacon) 0.012 0.012 0.003 0.012 0.011 0.002
A few results are interesting bacon seems to have the smallest standard deviation, while the central moment matching approach the largest. The mean estimates are as close or closer to the expected values as the maximum likelihood approach.
We could add some other examples from Efron since all data sets are available???
Run simulation with large, say, 40% proportion of non-nulls.
theta <- c(0.6, 0, 1, 1, 3, 1)
est <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, theta)
est[i, ] <- estimates(bacon(z, priors = priors))
}
names <- rep(colnames(estimates(bacon(z, priors = priors))), each = nsim)
data <- data.frame(values = as.vector(est), estimates = gsub("\\..*$", "", names),
component = gsub("^.*\\.", "", names))
theta <- c(0.6, NA, NA, 0, NA, NA, 1, NA, NA)
data <- rbind(theta, apply(est, 2, mean), apply(est, 2, sd))
colnames(data) <- colnames(estimates(bacon(z, priors = priors)))
rownames(data) <- c("true", "mean", "stdev")
kable(signif(data, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.6000 | NA | NA | 0.00000 | NA | NA | 1.0000 | NA | NA |
| mean | 0.7020 | 0.2220 | 0.07600 | 0.00624 | 2.830 | -2.9600 | 1.0700 | 2.4400 | 1.850 |
| stdev | 0.0135 | 0.0127 | 0.00693 | 0.02440 | 0.106 | 0.0523 | 0.0258 | 0.0914 | 0.121 |
Of course we can always adjust the prior parameters e.g., here we assign more weight to the alternative Dirichlet prior parameters.
priors = list(sigma = list(alpha = 1.28, beta = 0.36), mu = list(lambda = c(0,
3, -3), tau = c(100, 100, 100)), epsilon = list(gamma = c(1, 25, 25)))
estimates(bacon(z, priors = priors))
## p.0 p.1 p.2 mu.0 mu.1 mu.2
## [1,] 0.6545217 0.2483022 0.09717605 -0.03387671 2.636836 -2.480639
## sigma.0 sigma.1 sigma.2
## [1,] 0.9984402 2.505345 2.112102
bc <- bacon(z, priors = priors)
fit(bc, n = 50)
curve(0.6 * dnorm(x, 0, 1), lty = 2, add = TRUE, lwd = 2)
Bacon seems to under estimate the proportion of non-nulls in this situation. As a cost of increase in stdev of the null. But this will also depend on effect-sizes.
theta <- c(0.6, 0, 1, 2, 3, 1)
est <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, theta)
est[i, ] <- estimates(bacon(z, priors = priors))
}
names <- rep(colnames(estimates(bacon(z, priors = priors))), each = nsim)
data <- data.frame(values = as.vector(est), estimates = gsub("\\..*$", "", names),
component = gsub("^.*\\.", "", names))
theta <- c(0.6, NA, NA, 0, NA, NA, 1, NA, NA)
data <- rbind(theta, apply(est, 2, mean), apply(est, 2, sd))
colnames(data) <- colnames(estimates(bacon(z, priors = priors)))
rownames(data) <- c("true", "mean", "stdev")
kable(signif(data, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.600 | NA | NA | 0.0000 | NA | NA | 1.0000 | NA | NA |
| mean | 0.635 | 0.3090 | 0.05560 | 0.0176 | 2.9700 | -2.6200 | 1.0100 | 2.6600 | 1.710 |
| stdev | 0.012 | 0.0112 | 0.00492 | 0.0233 | 0.0796 | 0.0558 | 0.0202 | 0.0637 | 0.149 |
Now simulate with also large effect-sizes.
theta <- c(0.6, 0, 1, 2, 4, 1)
est <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, theta)
est[i, ] <- estimates(bacon(z, priors = priors))
}
names <- rep(colnames(estimates(bacon(z, priors = priors))), each = nsim)
data <- data.frame(values = as.vector(est), estimates = gsub("\\..*$", "", names),
component = gsub("^.*\\.", "", names))
theta <- c(0.6, NA, NA, 0, NA, NA, 1, NA, NA)
data <- rbind(theta, apply(est, 2, mean), apply(est, 2, sd))
colnames(data) <- colnames(estimates(bacon(z, priors = priors)))
rownames(data) <- c("true", "mean", "stdev")
kable(signif(data, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.600 | NA | NA | 0.0000 | NA | NA | 1.0000 | NA | NA |
| mean | 0.604 | 0.3030 | 0.09310 | 0.0116 | 3.290 | -2.4200 | 1.0000 | 3.4800 | 2.740 |
| stdev | 0.012 | 0.0102 | 0.00608 | 0.0218 | 0.093 | 0.0789 | 0.0229 | 0.0857 | 0.158 |
The estimated mixture distribution propely fits the data in this scenario.
BACON to non-normal dataedgeR quasi-likelihood exampleOnly possible via a transformation to z-values, e.g., Efron’s z-transformation.
Here is an example using count data from the edgeR vignette section 4.3 Profiles of Yoruba HapMap individuals. Test-statistic used here a quasi-likelihood ratio-test that takes genewise variances in account (empirical Bayes approach) which results that under the null hypothesis the test-statistic follow a F-distribution.
data(pickrell1)
Counts <- exprs(pickrell1.eset)
Gender <- pickrell1.eset$gender
rm(pickrell1.eset)
data(annotEnsembl63)
annot <- annotEnsembl63[, c("Symbol", "Chr")]
rm(annotEnsembl63)
y <- DGEList(counts = Counts, genes = annot[rownames(Counts), ])
isexpr <- rowSums(cpm(y) > 1) >= 20
hasannot <- rowSums(is.na(y$genes)) == 0
y <- y[isexpr & hasannot, , keep.lib.sizes = FALSE]
y <- calcNormFactors(y)
design <- model.matrix(~Gender)
y <- estimateDisp(y, design, robust = TRUE)
fit <- glmQLFit(y, design, robust = TRUE)
qlf <- glmQLFTest(fit)
res <- as.data.frame(topTags(qlf, n = nrow(counts)))
head(res)
## Symbol Chr logFC logCPM F PValue
## ENSG00000229807 XIST X -9.485109 7.248644 1213.2655 1.030708e-46
## ENSG00000099749 CYorf15A Y 4.279499 1.756924 856.6674 1.186779e-41
## ENSG00000131002 CYorf15B Y 5.625815 2.055374 587.3207 2.660854e-36
## ENSG00000157828 RPS4Y2 Y 3.175600 4.207146 585.0200 3.017466e-36
## ENSG00000233864 TTTY15 Y 4.839482 1.253822 537.7832 4.421561e-35
## ENSG00000198692 EIF1AY Y 2.359992 3.246548 375.5065 3.041696e-30
## FDR
## ENSG00000229807 1.784156e-42
## ENSG00000099749 1.027157e-37
## ENSG00000131002 1.305809e-32
## ENSG00000157828 1.305809e-32
## ENSG00000233864 1.530744e-31
## ENSG00000198692 8.775292e-27
Now we transform the F-statistics to z-score via P values, i.e., \(z = \Phi^{-1}(1 - F_{F_{\nu, 1}}(f))\). (Actually, P values are calculated with genewise differences in the degrees of freedom as a result of the empirical Bayes analysis).
hist(res$PValue)
z <- qnorm(1 - res$PValue)
hist(z, n = 50)
z[!is.finite(z)] <- 16
estimates(bacon(z, priors = priors))
## p.0 p.1 p.2 mu.0 mu.1 mu.2
## [1,] 0.9947817 0.005087697 0.0001306095 0.1496109 3.093815 -2.992524
## sigma.0 sigma.1 sigma.2
## [1,] 1.034135 2.17404 0.7901363
Is there a difference with using the linear model, e.g., when using voom.
v <- voom(y, design)
fit <- lmFit(v, design)
z <- fit$coef[, 2]/fit$stdev.unscaled[, 2]/fit$sigma
hist(z, n = 100, xlim = c(-10, 10))
estimates(bacon(z, priors = priors))
## p.0 p.1 p.2 mu.0 mu.1 mu.2
## [1,] 0.9958433 0.0002456772 0.00391101 0.02409905 2.993919 -3.028117
## sigma.0 sigma.1 sigma.2
## [1,] 1.082282 0.6567649 1.788893
Estimates are quite close the linear model approach is sligtly inflated, however, it also has a smaller bias.
Squaring the original simulated z-statistics from the mixture distribution?
theta <- c(0.9, 0, 1, 0, 3, 1)
est <- estchi <- matrix(nrow = nsim, ncol = 9)
for (i in 1:nsim) {
z <- rnormmix(p, theta)
est[i, ] <- estimates(bacon(z, priors = priors))
pval <- 1 - pchisq(z^2, 1) ##p-values
z <- qnorm(1 - pval) ##z-values
z[!is.finite(z)] <- 16
estchi[i, ] <- estimates(bacon(z, priors = priors))
}
results <- rbind(c(theta[1], NA, NA, theta[2], NA, NA, theta[3], NA, NA), apply(est,
2, mean), apply(est, 2, sd), apply(estchi, 2, mean), apply(estchi, 2, sd))
colnames(results) <- colnames(estimates(bacon(z, priors = priors)))
rownames(results) <- c("true", "mean (z)", "stdev (z)", "mean (z^2)", "stdev (z^2)")
kable(signif(results, 3))
| p.0 | p.1 | p.2 | mu.0 | mu.1 | mu.2 | sigma.0 | sigma.1 | sigma.2 | |
|---|---|---|---|---|---|---|---|---|---|
| true | 0.90000 | NA | NA | 0.000000 | NA | NA | 1.0000 | NA | NA |
| mean (z) | 0.90000 | 0.05010 | 0.050400 | -0.000612 | 2.6000 | -2.5900 | 0.9870 | 1.9500 | 1.970 |
| stdev (z) | 0.00549 | 0.00423 | 0.004070 | 0.016100 | 0.0424 | 0.0426 | 0.0124 | 0.1650 | 0.165 |
| mean (z^2) | 0.90800 | 0.08060 | 0.011400 | 0.017400 | 2.6900 | -2.6900 | 0.9770 | 1.9900 | 1.360 |
| stdev (z^2) | 0.00500 | 0.00502 | 0.000435 | 0.015700 | 0.0556 | 0.0745 | 0.0119 | 0.0921 | 0.360 |
Interesting all effects are now positive but still the null is estimated quite well!!!
n <- 100 #number of samples
p <- 2000 #number of features
r <- 5 #number of unobserved covariates
nsim <- 100 #number of simulations
p0 <- 0.9 #proportion null
set.seed(12345)
sim.data <- simulate(n, p, r, nsim, p0)
table3 <- cbind(`Type I error` = summarize(sim.data[[1]]), Power = summarize(sim.data[[2]]))
rownames(table3) <- colnames(sim.data[[1]])
kable(table3)
| Type I error | Power | |
|---|---|---|
| naive | 0.72 (0.035) | 0.72 (0.046) |
| naive-gc | 0.0019 (0.0036) | 0.007 (0.0092) |
| naive-bc | 0.77 (0.05) | 0.78 (0.057) |
| cate | 0.061 (0.0061) | 0.85 (0.038) |
| cate.calibrate | 0.03 (0.0054) | 0.76 (0.053) |
| cate.bacon | 0.072 (0.0074) | 0.87 (0.039) |
| oracle | 0.053 (0.0056) | 0.84 (0.04) |
Using for the mean prior distribution a variance prior of 1/10 results in unstable Gibbs traces and elliptical posterior distribution.
set.seed(12345)
theta <- c(0.9, 0, 1, 1, 3, 1)
z <- rnormmix(p, theta)
bc <- bacon(z, priors = priors)
estimates(bc)
## p.0 p.1 p.2 mu.0 mu.1 mu.2
## [1,] 0.863173 0.07846989 0.05835708 -0.0003810109 2.615812 -2.627495
## sigma.0 sigma.1 sigma.2
## [1,] 0.940657 2.128642 1.605065
posteriors(bc)
traces(bc)
priors = list(sigma = list(alpha = 1.28, beta = 0.36), mu = list(lambda = c(0,
3, -3), tau = c(10, 10, 10)), epsilon = list(gamma = c(1, 1, 1)))
bc <- bacon(z, priors = priors)
posteriors(bc)
traces(bc)
estimates(bc)
## p.0 p.1 p.2 mu.0 mu.1 mu.2
## [1,] 0.9196971 0.05231659 0.02798635 -0.006903698 2.782342 -2.832273
## sigma.0 sigma.1 sigma.2
## [1,] 0.9835463 2.123704 1.442606
This document is generated using rmarkdown/ knitr with the BioConductor-stylesheet from BiocStyle.
sessionInfo()
## R Under development (unstable) (2016-08-25 r71150)
## Platform: x86_64-pc-linux-gnu (64-bit)
## Running under: Ubuntu 14.04.5 LTS
##
## locale:
## [1] LC_CTYPE=en_US.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_US.UTF-8 LC_COLLATE=en_US.UTF-8
## [5] LC_MONETARY=en_US.UTF-8 LC_MESSAGES=en_US.UTF-8
## [7] LC_PAPER=en_US.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_US.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] parallel stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] RColorBrewer_1.1-2 knitr_1.14
## [3] matrixStats_0.51.0 edgeR_3.16.1
## [5] limma_3.28.20 tweeDEseqCountData_1.12.0
## [7] Biobase_2.34.0 BiocGenerics_0.20.0
## [9] locfdr_1.1-8 bacon_1.2.0
## [11] ellipse_0.3-8 BiocParallel_1.8.0
## [13] ggplot2_2.1.0 BiocStyle_2.2.0
##
## loaded via a namespace (and not attached):
## [1] Rcpp_0.12.7 magrittr_1.5 splines_3.4.0 munsell_0.4.3
## [5] lattice_0.20-33 colorspace_1.2-7 stringr_1.1.0 plyr_1.8.4
## [9] tools_3.4.0 grid_3.4.0 gtable_0.2.0 htmltools_0.3.5
## [13] yaml_2.1.13 digest_0.6.10 formatR_1.4 evaluate_0.10
## [17] rmarkdown_1.0 stringi_1.1.2 scales_0.4.0 locfit_1.5-9.1
Devlin, B, and K Roeder. 1999. “Genomic control for association studies.” Biometrics 55 (4): 997–1004.
Efron, B. 2004. “Large-Scale Simultaneous Hypothesis Testing: The Choice of a Null Hypothesis.” JASA 99 (465).
Efron, B, and T Hastie. 2016. “Large-Scale Hypothesis Testing and FDRs.” In Computer Age Statistical Inference, edited by Holmes Cox Hambly, 271–94. New York: Cambrige university press.
Efron, B. 2010. “Correlation Questions.” In Large-Scale Inference, edited by Holmes Cox Hambly, 141–62. New York: Cambrige university press.
Wang, J, Q Zhao, T Hastie, and A B Owen. 2015. “Confounder Adjustment in Multiple Hypothesis Testing.” ArXiv E-Prints. http://arxiv.org/abs/1508.04178.