Nothing
# ==============================================================================
# MR-RAPS (vendored) - internal implementation for this package
#
# The functions below are adapted/copied from the mr.raps package (MR-RAPS)
# Original Authors: Qingyuan Zhao, Jingshu Wang, et al.
# License: GPL-3
# Source: https://github.com/qingyuanzhao/mr.raps
#
# Notes:
# - Copied/adapted on: 2026-03-05
# ==============================================================================
# ---- internal helpers ----
mr_raps_check_numeric_vec <- function(x, name) {
if (!is.numeric(x) || !is.vector(x)) stop(sprintf("%s must be a numeric vector.", name), call. = FALSE)
if (any(!is.finite(x))) stop(sprintf("%s contains non-finite values.", name), call. = FALSE)
}
mr_raps_check_lengths <- function(...) {
xs <- list(...)
n <- length(xs[[1]])
if (!all(vapply(xs, length, integer(1)) == n)) stop("All inputs must have the same length.", call. = FALSE)
if (n < 2) stop("Need at least 2 variants (SNPs).", call. = FALSE)
}
# ---- robust loss functions ----
mr_raps_rho_huber <- function(r, k = 1.345, deriv = 0) {
if (deriv == 0) {
ifelse(abs(r) <= k, r^2 / 2, k * (abs(r) - k / 2))
} else if (deriv == 1) {
ifelse(abs(r) <= k, r, k * sign(r))
} else if (deriv == 2) {
ifelse(abs(r) <= k, 1, 0)
} else {
stop("deriv must be 0, 1, or 2.", call. = FALSE)
}
}
mr_raps_rho_tukey <- function(r, k = 4.685, deriv = 0) {
if (deriv == 0) {
pmin(1 - (1 - (r / k)^2)^3, 1)
} else if (deriv == 1) {
r * (1 - (r / k)^2)^2 * (abs(r) <= k)
} else if (deriv == 2) {
t <- (r / k)^2
ifelse(t < 1, (1 - t) * (1 - 5 * t), 0)
} else {
stop("deriv must be 0, 1, or 2.", call. = FALSE)
}
}
# ---- main exported-by-name (but keep it INTERNAL: do NOT @export) ----
# IMPORTANT: do NOT add roxygen @export tag; then it won't be exported to users.
mr.raps <- function(b_exp, b_out, se_exp, se_out,
over.dispersion = FALSE,
loss.function = c("l2", "huber", "tukey"),
diagnosis = FALSE,
se.method = c("sandwich", "bootstrap"),
k = switch(loss.function[1], l2 = NULL, huber = 1.345, tukey = 4.685),
B = 1000,
suppress.warning = FALSE) {
loss.function <- match.arg(loss.function, c("l2", "huber", "tukey"))
se.method <- match.arg(se.method, c("sandwich", "bootstrap"))
mr_raps_check_numeric_vec(b_exp, "b_exp")
mr_raps_check_numeric_vec(b_out, "b_out")
mr_raps_check_numeric_vec(se_exp, "se_exp")
mr_raps_check_numeric_vec(se_out, "se_out")
mr_raps_check_lengths(b_exp, b_out, se_exp, se_out)
if (any(se_exp <= 0) || any(se_out <= 0)) stop("Standard errors must be > 0.", call. = FALSE)
if (loss.function == "l2") {
fit <- if (!over.dispersion) {
mr_raps_simple(b_exp, b_out, se_exp, se_out, diagnosis = diagnosis)
} else {
mr_raps_overdispersed(
b_exp, b_out, se_exp, se_out,
diagnosis = diagnosis,
suppress.warning = suppress.warning
)
}
} else {
fit <- if (!over.dispersion) {
mr_raps_simple_robust(
b_exp, b_out, se_exp, se_out,
loss.function = loss.function,
k = k,
diagnosis = diagnosis
)
} else {
mr_raps_overdispersed_robust(
b_exp, b_out, se_exp, se_out,
loss.function = loss.function,
k = k,
suppress.warning = suppress.warning,
diagnosis = diagnosis
)
}
}
if (se.method == "bootstrap") {
fit.bootstrap <- vector("list", B)
for (b in seq_len(B)) {
s <- sample.int(length(b_exp), replace = TRUE)
fit.bootstrap[[b]] <- tryCatch(
unlist(mr.raps(
b_exp[s], b_out[s], se_exp[s], se_out[s],
over.dispersion = over.dispersion,
loss.function = loss.function,
diagnosis = FALSE,
se.method = "sandwich",
k = k,
suppress.warning = TRUE
)),
error = function(e) NA
)
}
fit.bootstrap <- data.frame(do.call(rbind, fit.bootstrap))
fit <- c(
fit,
list(
beta.hat.bootstrap = stats::median(fit.bootstrap$beta.hat),
beta.se.bootstrap = stats::mad(fit.bootstrap$beta.hat)
)
)
}
fit
}
# ---- l2, no overdispersion ----
mr_raps_simple <- function(b_exp, b_out, se_exp, se_out, diagnosis = FALSE) {
profile.loglike <- function(beta) {
-0.5 * sum((b_out - b_exp * beta)^2 / (se_out^2 + se_exp^2 * beta^2))
}
bound <- stats::quantile(abs(b_out / b_exp), 0.95) * 2
beta.hat <- stats::optimize(profile.loglike, bound * c(-1, 1), maximum = TRUE,
tol = .Machine$double.eps^0.5)$maximum
while (abs(beta.hat) > 0.95 * bound) {
bound <- bound * 2
beta.hat <- stats::optimize(profile.loglike, bound * c(-1, 1), maximum = TRUE,
tol = .Machine$double.eps^0.5)$maximum
}
score.var <- sum(((b_exp^2 - se_exp^2) * se_out^2 +
(b_out^2 - se_out^2) * se_exp^2 +
se_exp^2 * se_out^2) / (se_out^2 + beta.hat^2 * se_exp^2)^2)
I <- sum(((b_exp^2 - se_exp^2) * se_out^2 +
(b_out^2 - se_out^2) * se_exp^2) / (se_out^2 + beta.hat^2 * se_exp^2)^2)
dif <- b_out - beta.hat * b_exp
dif.var <- se_out^2 + beta.hat^2 * se_exp^2
chi.sq.test <- sum((dif / sqrt(dif.var))^2)
if (diagnosis) {
std.resid <- (b_out - b_exp * beta.hat) / sqrt(se_out^2 + beta.hat^2 * se_exp^2)
oldpar <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(oldpar), add = TRUE)
graphics::par(mfrow = c(1, 2))
stats::qqnorm(std.resid); graphics::abline(0, 1)
if (requireNamespace("nortest", quietly = TRUE)) {
ad <- nortest::ad.test(std.resid)
message("Anderson-Darling test: statistic = ", round(ad$statistic, 4),
", p-value = ", round(ad$p.value, 4))
}
sw <- stats::shapiro.test(std.resid)
message("Shapiro-Wilk test: statistic = ", round(sw$statistic, 4),
", p-value = ", round(sw$p.value, 4))
}
list(
beta.hat = beta.hat,
beta.se = sqrt(score.var / I^2),
beta.p.value = min(1, 2 * (1 - stats::pnorm(abs(beta.hat) / sqrt(score.var / I^2)))),
naive.se = sqrt(1 / I),
chi.sq.test = chi.sq.test
)
}
# ---- l2, with overdispersion ----
mr_raps_overdispersed <- function(b_exp, b_out, se_exp, se_out,
initialization = c("simple", "mode"),
suppress.warning = FALSE,
diagnosis = FALSE,
niter = 20,
tol = .Machine$double.eps^0.5) {
initialization <- match.arg(initialization, c("simple", "mode"))
profile.loglike.fixbeta <- function(beta, tau2) {
alpha.hat <- 0
-0.5 * sum(se_exp^2 * (log(tau2 + se_out^2 + se_exp^2 * beta^2))) -
0.5 * sum(se_exp^2 * (b_out - alpha.hat - b_exp * beta)^2 / (tau2 + se_out^2 + se_exp^2 * beta^2))
}
profile.loglike.fixtau <- function(beta, tau2) {
alpha.hat <- 0
-0.5 * sum((b_out - alpha.hat - b_exp * beta)^2 / (tau2 + se_out^2 + se_exp^2 * beta^2))
}
bound.beta <- stats::quantile(abs(b_out / b_exp), 0.95) * 10
bound.tau2 <- stats::quantile(se_out^2, 0.95) * 10
if (initialization == "mode") {
stop("Initialization by mode estimator is currently not supported.", call. = FALSE)
} else {
fit <- mr_raps_simple(b_exp, b_out, se_exp, se_out)
beta.hat <- fit$beta.hat
tau2.hat <- 0
}
for (iter in 1:niter) {
beta.hat.old <- beta.hat
tau2.hat.old <- tau2.hat
tau2.hat <- stats::optimize(
function(tau2) profile.loglike.fixbeta(beta.hat, tau2),
bound.tau2 * c(0, 1),
maximum = TRUE,
tol = .Machine$double.eps^0.5
)$maximum
if (tau2.hat < 0) tau2.hat <- 0
if (tau2.hat > 0.95 * bound.tau2) warning("Estimated overdispersion seems abnormally large.", call. = FALSE)
beta.hat <- stats::optimize(
function(beta) profile.loglike.fixtau(beta, tau2.hat),
bound.beta * c(-1, 1),
maximum = TRUE,
tol = .Machine$double.eps^0.5
)$maximum
if (abs(beta.hat.old - beta.hat) / abs(beta.hat + 1e-10) +
abs(tau2.hat.old - tau2.hat) / abs(tau2.hat + 1e-10) <= tol) {
break
}
}
if ((tau2.hat <= min(se_out^2) / 5) && (!suppress.warning)) {
warning("The estimated overdispersion parameter is very small. Consider using the simple model.", call. = FALSE)
}
score.var <- diag(c(
sum(((b_exp^2 - se_exp^2) * (tau2.hat + se_out^2) +
(b_out^2 - tau2.hat - se_out^2) * se_exp^2 +
se_exp^2 * (tau2.hat + se_out^2)) / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
sum(2 * se_exp^4 / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2)
))
I <- matrix(c(
-sum(((b_exp^2 - se_exp^2) * (tau2.hat + se_out^2) +
(b_out^2 - tau2.hat - se_out^2) * se_exp^2) / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
0,
-sum(se_exp^2 * beta.hat / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
-sum(se_exp^2 / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2)
), 2, 2)
asymp.var <- solve(I) %*% score.var %*% t(solve(I))
out <- list(
beta.hat = beta.hat,
tau2.hat = tau2.hat,
beta.se = sqrt(asymp.var[1, 1]),
tau2.se = sqrt(asymp.var[2, 2]),
beta.p.value = min(1, 2 * (1 - stats::pnorm(abs(beta.hat) / sqrt(asymp.var[1, 1]))))
)
if (diagnosis) {
std.resid <- (b_out - b_exp * beta.hat) / sqrt(tau2.hat + se_out^2 + beta.hat^2 * se_exp^2)
oldpar <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(oldpar), add = TRUE)
graphics::par(mfrow = c(1, 2))
stats::qqnorm(std.resid); graphics::abline(0, 1)
if (requireNamespace("nortest", quietly = TRUE)) {
ad <- nortest::ad.test(std.resid)
message("Anderson-Darling test: statistic = ", round(ad$statistic, 4),
", p-value = ", round(ad$p.value, 4))
}
sw <- stats::shapiro.test(std.resid)
message("Shapiro-Wilk test: statistic = ", round(sw$statistic, 4),
", p-value = ", round(sw$p.value, 4))
out$std.resid <- std.resid
}
out
}
# ---- robust, no overdispersion ----
mr_raps_simple_robust <- function(b_exp, b_out, se_exp, se_out,
loss.function = c("huber", "tukey"),
k = switch(loss.function[1], huber = 1.345, tukey = 4.685),
diagnosis = FALSE) {
loss.function <- match.arg(loss.function, c("huber", "tukey"))
rho <- switch(
loss.function,
huber = function(r, ...) mr_raps_rho_huber(r, k, ...),
tukey = function(r, ...) mr_raps_rho_tukey(r, k, ...)
)
delta <- stats::integrate(function(x) x * rho(x, deriv = 1) * stats::dnorm(x), -Inf, Inf)$value
c1 <- stats::integrate(function(x) rho(x, deriv = 1)^2 * stats::dnorm(x), -Inf, Inf)$value
robust.loglike <- function(beta) {
-sum(rho((b_out - b_exp * beta) / sqrt(se_out^2 + se_exp^2 * beta^2)))
}
bound <- stats::quantile(abs(b_out / b_exp), 0.95) * 2
beta.hat <- stats::optimize(robust.loglike, bound * c(-1, 1), maximum = TRUE,
tol = .Machine$double.eps^0.5)$maximum
while (abs(beta.hat) > 0.95 * bound) {
bound <- bound * 2
beta.hat <- stats::optimize(robust.loglike, bound * c(-1, 1), maximum = TRUE,
tol = .Machine$double.eps^0.5)$maximum
}
score.var <- c1 * sum(((b_exp^2 - se_exp^2) * se_out^2 +
(b_out^2 - se_out^2) * se_exp^2 +
se_exp^2 * se_out^2) / (se_out^2 + beta.hat^2 * se_exp^2)^2)
I <- delta * sum(((b_exp^2 - se_exp^2) * se_out^2 +
(b_out^2 - se_out^2) * se_exp^2) / (se_out^2 + beta.hat^2 * se_exp^2)^2)
dif <- b_out - beta.hat * b_exp
dif.var <- se_out^2 + beta.hat^2 * se_exp^2
chi.sq.test <- sum((dif / sqrt(dif.var))^2)
list(
beta.hat = beta.hat,
beta.se = sqrt(score.var / I^2),
naive.se = sqrt(1 / I),
chi.sq.test = chi.sq.test,
beta.p.value = min(1, 2 * (1 - stats::pnorm(abs(beta.hat) / sqrt(score.var / I^2))))
)
}
# ---- robust, with overdispersion ----
mr_raps_overdispersed_robust <- function(b_exp, b_out, se_exp, se_out,
loss.function = c("huber", "tukey"),
k = switch(loss.function[1], huber = 1.345, tukey = 4.685),
initialization = c("l2", "mode"),
suppress.warning = FALSE,
diagnosis = FALSE,
niter = 20,
tol = .Machine$double.eps^0.5) {
loss.function <- match.arg(loss.function, c("huber", "tukey"))
initialization <- match.arg(initialization, c("l2", "mode"))
rho <- switch(
loss.function,
huber = function(r, ...) mr_raps_rho_huber(r, k, ...),
tukey = function(r, ...) mr_raps_rho_tukey(r, k, ...)
)
delta <- stats::integrate(function(x) x * rho(x, deriv = 1) * stats::dnorm(x), -Inf, Inf)$value
c1 <- stats::integrate(function(x) rho(x, deriv = 1)^2 * stats::dnorm(x), -Inf, Inf)$value
c2 <- stats::integrate(function(x) x^2 * rho(x, deriv = 1)^2 * stats::dnorm(x), -Inf, Inf)$value - delta^2
c3 <- stats::integrate(function(x) x^2 * rho(x, deriv = 2) * stats::dnorm(x), -Inf, Inf)$value
robust.E <- function(beta, tau2) {
t <- (b_out - beta * b_exp) / sqrt(tau2 + se_out^2 + se_exp^2 * beta^2)
se_exp^2 * (t * rho(t, deriv = 1) - delta) / (tau2 + se_out^2 + se_exp^2 * beta^2)
}
robust.loglike.fixtau <- function(beta, tau2) {
alpha.hat <- 0
-0.5 * sum(rho((b_out - alpha.hat - b_exp * beta) / sqrt(tau2 + se_out^2 + se_exp^2 * beta^2)))
}
bound.beta <- stats::quantile(abs(b_out / b_exp), 0.95) * 10
bound.tau2 <- stats::quantile(se_out^2, 0.95) * 10
if (initialization == "mode") {
stop("Initialization by mode estimator is currently not supported.", call. = FALSE)
} else {
fit <- mr_raps_overdispersed(b_exp, b_out, se_exp, se_out, suppress.warning = TRUE)
beta.hat <- fit$beta.hat
tau2.hat <- fit$tau2.hat
}
for (iter in 1:niter) {
beta.hat.old <- beta.hat
tau2.hat.old <- tau2.hat
tau2.hat <- tryCatch(
stats::uniroot(
function(tau2) sum(robust.E(beta.hat, tau2)),
bound.tau2 * c(0, 1),
extendInt = "yes",
tol = bound.tau2 * .Machine$double.eps^0.25
)$root,
error = function(e) {
warning("Did not find a solution for tau2; setting tau2=0.", call. = FALSE)
0
}
)
if (tau2.hat < 0) tau2.hat <- 0
if (tau2.hat > bound.tau2 * 0.95) warning("Estimated overdispersion seems abnormally large.", call. = FALSE)
beta.hat <- stats::optim(
beta.hat,
function(beta) robust.loglike.fixtau(beta, tau2.hat),
method = "L-BFGS-B",
lower = -bound.beta,
upper = bound.beta,
control = list(fnscale = -1)
)$par
if (abs(beta.hat.old - beta.hat) / abs(beta.hat + 1e-10) +
abs(tau2.hat.old - tau2.hat) / abs(tau2.hat + 1e-10) <= tol) {
break
}
}
if ((tau2.hat <= min(se_out^2) / 5) && (!suppress.warning)) {
warning("Estimated overdispersion is very small; consider no-overdispersion model.", call. = FALSE)
}
score.var <- diag(c(
c1 * sum(((b_exp^2 - se_exp^2) * (tau2.hat + se_out^2) +
(b_out^2 - tau2.hat - se_out^2) * se_exp^2 +
se_exp^2 * (tau2.hat + se_out^2)) / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
(c2 / 2) * sum(2 * se_exp^4 / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2)
))
I <- matrix(c(
-delta * sum(((b_exp^2 - se_exp^2) * (tau2.hat + se_out^2) +
(b_out^2 - tau2.hat - se_out^2) * se_exp^2) / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
0,
-delta * sum(se_exp^2 * beta.hat / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2),
-(delta + c3) / 2 * sum(se_exp^2 / (tau2.hat + se_out^2 + se_exp^2 * beta.hat^2)^2)
), 2, 2)
asymp.var <- solve(I) %*% score.var %*% t(solve(I))
out <- list(
beta.hat = beta.hat,
tau2.hat = tau2.hat,
beta.se = sqrt(asymp.var[1, 1]),
tau2.se = sqrt(asymp.var[2, 2]),
beta.p.value = min(1, 2 * (1 - stats::pnorm(abs(beta.hat) / sqrt(asymp.var[1, 1]))))
)
if (diagnosis) {
std.resid <- (b_out - b_exp * beta.hat) / sqrt(tau2.hat + se_out^2 + beta.hat^2 * se_exp^2)
oldpar <- graphics::par(no.readonly = TRUE)
on.exit(graphics::par(oldpar), add = TRUE)
graphics::par(mfrow = c(1, 2))
stats::qqnorm(std.resid); graphics::abline(0, 1)
if (requireNamespace("nortest", quietly = TRUE)) {
ad <- nortest::ad.test(std.resid)
message("Anderson-Darling test: statistic = ", round(ad$statistic, 4),
", p-value = ", round(ad$p.value, 4))
}
sw <- stats::shapiro.test(std.resid)
message("Shapiro-Wilk test: statistic = ", round(sw$statistic, 4),
", p-value = ", round(sw$p.value, 4))
out$std.resid <- std.resid
}
out
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.