From 00d17115f578766e0fbd8b509be533e255ebbcaa Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 05:28:28 +0100 Subject: [PATCH 01/13] Amend gwas.mrcieu.ac.uk to opengwas.io --- DESCRIPTION | 2 +- README.md | 2 +- index.md | 2 +- vignettes/exposure.Rmd | 2 +- vignettes/gwas2020.Rmd | 6 +++--- vignettes/introduction.Rmd | 2 +- vignettes/outcome.Rmd | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index b0feef5e1..bb09f5ba3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -20,7 +20,7 @@ Authors@R: c( ) Description: A package for performing Mendelian randomization using GWAS summary data. It uses the IEU OpenGWAS database - to automatically obtain data, and a wide + to automatically obtain data, and a wide range of methods to run the analysis. License: MIT + file LICENSE URL: https://github.com/MRCIEU/TwoSampleMR, diff --git a/README.md b/README.md index c91a78882..024065355 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ badge](https://mrcieu.r-universe.dev/badges/TwoSampleMR)](https://mrcieu.r-unive A package for performing Mendelian randomization using GWAS summary -data. It uses the [IEU OpenGWAS database](https://gwas.mrcieu.ac.uk/) to +data. It uses the [IEU OpenGWAS database](https://opengwas.io) to obtain data automatically, and a wide range of methods to run the analysis. diff --git a/index.md b/index.md index 940e9e480..85c4fef85 100644 --- a/index.md +++ b/index.md @@ -7,7 +7,7 @@ [![TwoSampleMR status badge](https://mrcieu.r-universe.dev/badges/TwoSampleMR)](https://mrcieu.r-universe.dev/TwoSampleMR) -A package for performing Mendelian randomization using GWAS summary data. It uses the [IEU OpenGWAS database](https://gwas.mrcieu.ac.uk/) to obtain data automatically, and a wide range of methods to run the analysis. +A package for performing Mendelian randomization using GWAS summary data. It uses the [IEU OpenGWAS database](https://opengwas.io) to obtain data automatically, and a wide range of methods to run the analysis. ## January 2020 major update diff --git a/vignettes/exposure.Rmd b/vignettes/exposure.Rmd index 1d59a0897..e33af9697 100644 --- a/vignettes/exposure.Rmd +++ b/vignettes/exposure.Rmd @@ -281,7 +281,7 @@ cg25212131_exp_dat <- ### IEU OpenGWAS database -The IEU OpenGWAS database contains the entire summary statistics for thousands of GWASs. You can browse them here: https://gwas.mrcieu.ac.uk/ +The IEU OpenGWAS database contains the entire summary statistics for thousands of GWASs. You can browse them here: https://opengwas.io You can use this database to define the instruments for a particular exposure. You can also use this database to obtain the effects for constructing polygenic risk scores using different p-value thresholds. diff --git a/vignettes/gwas2020.Rmd b/vignettes/gwas2020.Rmd index f936bed84..0533e0ff9 100644 --- a/vignettes/gwas2020.Rmd +++ b/vignettes/gwas2020.Rmd @@ -53,7 +53,7 @@ We are using Elasticsearch and Neo4j on an Oracle Cloud Infrastructure to serve ### Browse available datasets online -We have a new home for the GWAS summary data: . +We have a new home for the GWAS summary data: . ### Chromosome and position @@ -89,7 +89,7 @@ It is now possible to perform clumping, or create LD matrices, using your own lo ### Access the data directly -Previously the data was only accessible through the database. Now the data can be downloaded in "GWAS VCF" format from here https://gwas.mrcieu.ac.uk/. (IEU members can access all the data on RDSF or bluecrystal4 directly). This means that if you want to perform very large or numerous operations, you can do it on HPC or locally in a more performant manner by using the data files directly. Please see the [gwasvcf R package](https://github.com/mrcieu/gwasvcf) on how to work with these data. +Previously the data was only accessible through the database. Now the data can be downloaded in "GWAS VCF" format from here https://opengwas.io. (IEU members can access all the data on RDSF or bluecrystal4 directly). This means that if you want to perform very large or numerous operations, you can do it on HPC or locally in a more performant manner by using the data files directly. Please see the [gwasvcf R package](https://github.com/mrcieu/gwasvcf) on how to work with these data. ### Connect the data to different analytical tools @@ -97,7 +97,7 @@ Either the data in the database, or the GWAS VCF files, can be queried and the r ## Key links -- The IEU OpenGWAS database: https://gwas.mrcieu.ac.uk +- The IEU OpenGWAS database: https://opengwas.io - API to the IEU OpenGWAS database: https://api.opengwas.io/api/ - ieugwasr package, for R access to the API: https://mrcieu.github.io/ieugwasr/ - ieugwaspy package, for python access to the API: https://github.com/MRCIEU/ieugwaspy/ diff --git a/vignettes/introduction.Rmd b/vignettes/introduction.Rmd index de4604d35..d2bf72593 100644 --- a/vignettes/introduction.Rmd +++ b/vignettes/introduction.Rmd @@ -82,7 +82,7 @@ install.packages('TwoSampleMR', The workflow for performing MR is as follows: 1. Select instruments for the exposure (perform LD clumping if necessary) -2. Extract the instruments from the [IEU GWAS database](https://gwas.mrcieu.ac.uk/) for the outcomes of interest +2. Extract the instruments from the [IEU GWAS database](https://opengwas.io) for the outcomes of interest 3. Harmonise the effect sizes for the instruments on the exposures and the outcomes to be each for the same reference allele 4. Perform MR analysis, sensitivity analyses, create plots, compile reports diff --git a/vignettes/outcome.Rmd b/vignettes/outcome.Rmd index 8905d8ada..057f53637 100644 --- a/vignettes/outcome.Rmd +++ b/vignettes/outcome.Rmd @@ -49,7 +49,7 @@ Once instruments for the exposure trait have been specified, those variants need The IEU GWAS database (IGD) contains complete GWAS summary statistics from a large number of studies. You can browse them here: -https://gwas.mrcieu.ac.uk/ +https://opengwas.io To obtain details about the available GWASs programmatically do the following: From 140685edbab30710caf0622a687cb0cc3d7b0371 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 05:36:53 +0100 Subject: [PATCH 02/13] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 2f7eb1eee..2ea57cf4b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,4 @@ vignettes/mr_report.md vignettes/figure/*.png vignettes/*.html rf.rdata +.positai From d3ddf1743cb798920de8c99feeb98e61e513d963 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 05:36:56 +0100 Subject: [PATCH 03/13] Update .Rbuildignore --- .Rbuildignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.Rbuildignore b/.Rbuildignore index 43ff7d8af..f5c6edf52 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -21,3 +21,5 @@ cache$ ^inst/sandpit$ ^air\.toml$ ^vignettes/rf\.rdata$ +^\.positai$ +^\.claude$ From da10e8cf65b2c0cdf6991fea30f7c04d9b13df07 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 06:16:24 +0100 Subject: [PATCH 04/13] Bump version --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index bb09f5ba3..6cdce005c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: TwoSampleMR Title: Two Sample MR Functions and Interface to MRC Integrative Epidemiology Unit OpenGWAS Database -Version: 0.7.4 +Version: 0.7.5 Authors@R: c( person("Gibran", "Hemani", , "g.hemani@bristol.ac.uk", role = c("aut", "cre"), comment = c(ORCID = "0000-0003-0920-1055")), From 70e8c78cd02e5bf624d18f7dab4f048e6b8581e9 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Sun, 3 May 2026 05:59:16 +0100 Subject: [PATCH 05/13] Use roxygen2 8.0.0 --- DESCRIPTION | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 6cdce005c..81857575a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -68,6 +68,6 @@ Remotes: MRPRESSO=rondolab/MR-PRESSO, qingyuanzhao/mr.raps, WSpiller/RadialMR +Config/roxygen2/markdown: TRUE +Config/roxygen2/version: 8.0.0 Encoding: UTF-8 -Roxygen: list(markdown = TRUE) -RoxygenNote: 7.3.3 From 49fd6ff87215574229150c2294d97ba923832db5 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 05:30:06 +0100 Subject: [PATCH 06/13] Document --- man/TwoSampleMR-package.Rd | 3 ++- man/mr_steiger.Rd | 2 +- man/mr_steiger2.Rd | 2 +- man/steiger_sensitivity.Rd | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/man/TwoSampleMR-package.Rd b/man/TwoSampleMR-package.Rd index c1e4647c0..9d3a28bc1 100644 --- a/man/TwoSampleMR-package.Rd +++ b/man/TwoSampleMR-package.Rd @@ -6,7 +6,7 @@ \alias{TwoSampleMR-package} \title{TwoSampleMR: Two Sample MR Functions and Interface to MRC Integrative Epidemiology Unit OpenGWAS Database} \description{ -A package for performing Mendelian randomization using GWAS summary data. It uses the IEU OpenGWAS database \url{https://gwas.mrcieu.ac.uk/} to automatically obtain data, and a wide range of methods to run the analysis. +A package for performing Mendelian randomization using GWAS summary data. It uses the IEU OpenGWAS database \url{https://opengwas.io} to automatically obtain data, and a wide range of methods to run the analysis. } \seealso{ Useful links: @@ -22,6 +22,7 @@ Useful links: Authors: \itemize{ + \item Gibran Hemani \email{g.hemani@bristol.ac.uk} (\href{https://orcid.org/0000-0003-0920-1055}{ORCID}) \item Philip Haycock \email{philip.haycock@bristol.ac.uk} (\href{https://orcid.org/0000-0001-5001-3350}{ORCID}) \item Jie Zheng \email{Jie.Zheng@bristol.ac.uk} (\href{https://orcid.org/0000-0002-6623-6839}{ORCID}) \item Tom Gaunt \email{Tom.Gaunt@bristol.ac.uk} (\href{https://orcid.org/0000-0003-0924-3247}{ORCID}) diff --git a/man/mr_steiger.Rd b/man/mr_steiger.Rd index 7024738cd..dce0904aa 100644 --- a/man/mr_steiger.Rd +++ b/man/mr_steiger.Rd @@ -23,7 +23,7 @@ mr_steiger(p_exp, p_out, n_exp, n_out, r_exp, r_out, r_xxo = 1, r_yyo = 1, ...) \item{r_yyo}{Measurement precision of outcome} -\item{...}{Further arguments to be passed to \code{\link[lattice:cloud]{lattice::wireframe()}}} +\item{...}{Further arguments to be passed to \code{\link[lattice:wireframe]{lattice::wireframe()}}} } \value{ List with the following elements: diff --git a/man/mr_steiger2.Rd b/man/mr_steiger2.Rd index 504270c93..8bf4b395d 100644 --- a/man/mr_steiger2.Rd +++ b/man/mr_steiger2.Rd @@ -19,7 +19,7 @@ mr_steiger2(r_exp, r_out, n_exp, n_out, r_xxo = 1, r_yyo = 1, ...) \item{r_yyo}{Measurement precision of outcome} -\item{...}{Further arguments to be passed to \code{\link[lattice:cloud]{lattice::wireframe()}}} +\item{...}{Further arguments to be passed to \code{\link[lattice:wireframe]{lattice::wireframe()}}} } \value{ List with the following elements: diff --git a/man/steiger_sensitivity.Rd b/man/steiger_sensitivity.Rd index 89b426369..22fd6870f 100644 --- a/man/steiger_sensitivity.Rd +++ b/man/steiger_sensitivity.Rd @@ -11,7 +11,7 @@ steiger_sensitivity(rgx_o, rgy_o, ...) \item{rgy_o}{Observed variance of outcome explained by SNPs} -\item{...}{Further arguments to be passed to \code{\link[lattice:cloud]{lattice::wireframe()}}} +\item{...}{Further arguments to be passed to \code{\link[lattice:wireframe]{lattice::wireframe()}}} } \value{ List with the following elements: From 68117a8250bcef62cc9f68b79aea7884ab2b1da7 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:17:03 +0100 Subject: [PATCH 07/13] Replace array(1:nexp) placeholder allocations with numeric(nexp)/integer(nexp) in multivariable MR functions --- R/multivariable_mr.R | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/R/multivariable_mr.R b/R/multivariable_mr.R index 37cee5398..06b8ed566 100644 --- a/R/multivariable_mr.R +++ b/R/multivariable_mr.R @@ -498,10 +498,10 @@ mv_residual <- function( pval.exposure <- mvdat$exposure_pval nexp <- ncol(beta.exposure) - effs <- array(1:nexp) - se <- array(1:nexp) - pval <- array(1:nexp) - nsnp <- array(1:nexp) + effs <- numeric(nexp) + se <- numeric(nexp) + pval <- numeric(nexp) + nsnp <- integer(nexp) marginal_outcome <- matrix(0, nrow(beta.exposure), ncol(beta.exposure)) p <- list() nom <- colnames(beta.exposure) @@ -607,10 +607,10 @@ mv_multiple <- function( w <- 1 / mvdat$outcome_se^2 nexp <- ncol(beta.exposure) - effs <- array(1:nexp) - se <- array(1:nexp) - pval <- array(1:nexp) - nsnp <- array(1:nexp) + effs <- numeric(nexp) + se <- numeric(nexp) + pval <- numeric(nexp) + nsnp <- integer(nexp) # marginal_outcome <- matrix(0, nrow(beta.exposure), ncol(beta.exposure)) p <- list() nom <- colnames(beta.exposure) @@ -704,10 +704,10 @@ mv_basic <- function(mvdat, pval_threshold = 5e-8) { pval.exposure <- mvdat$exposure_pval nexp <- ncol(beta.exposure) - effs <- array(1:nexp) - se <- array(1:nexp) - pval <- array(1:nexp) - nsnp <- array(1:nexp) + effs <- numeric(nexp) + se <- numeric(nexp) + pval <- numeric(nexp) + nsnp <- integer(nexp) marginal_outcome <- matrix(0, nrow(beta.exposure), ncol(beta.exposure)) p <- list() nom <- colnames(beta.exposure) @@ -772,10 +772,10 @@ mv_ivw <- function(mvdat, pval_threshold = 5e-8) { w <- 1 / mvdat$outcome_se^2 nexp <- ncol(beta.exposure) - effs <- array(1:nexp) - se <- array(1:nexp) - pval <- array(1:nexp) - nsnp <- array(1:nexp) + effs <- numeric(nexp) + se <- numeric(nexp) + pval <- numeric(nexp) + nsnp <- integer(nexp) # marginal_outcome <- matrix(0, nrow(beta.exposure), ncol(beta.exposure)) p <- list() nom <- colnames(beta.exposure) From b17b3c74210ee358c083a17eda8a78be1d77d6a6 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:19:03 +0100 Subject: [PATCH 08/13] Replace 1:n with seq_len(n) in loop indices across singlesnp, leaveoneout, rucker, mr_mode, and multivariable_mr for empty-vector safety --- R/leaveoneout.R | 2 +- R/mr_mode.R | 2 +- R/multivariable_mr.R | 8 ++++---- R/rucker.R | 6 +++--- R/singlesnp.R | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/R/leaveoneout.R b/R/leaveoneout.R index bd724204d..706a048b5 100644 --- a/R/leaveoneout.R +++ b/R/leaveoneout.R @@ -44,7 +44,7 @@ mr_leaveoneout <- function(dat, parameters = default_parameters(), method = mr_i return(d) } if (nsnp > 2) { - l <- lapply(1:nsnp, function(i) { + l <- lapply(seq_len(nsnp), function(i) { with( x, method(beta.exposure[-i], beta.outcome[-i], se.exposure[-i], se.outcome[-i], parameters) diff --git a/R/mr_mode.R b/R/mr_mode.R index f4de06ab5..0247792a4 100644 --- a/R/mr_mode.R +++ b/R/mr_mode.R @@ -78,7 +78,7 @@ mr_mode <- function(dat, parameters = default_parameters(), mode_method = "all") #Set up a matrix to store the results from each bootstrap iteration beta.boot <- matrix(nrow = nboot, ncol = length(beta_Mode.in)) - for (i in 1:nboot) { + for (i in seq_len(nboot)) { BetaIV.boot <- BetaIV.boot_mat[i, ] BetaIV.boot_NOME <- BetaIV.boot_NOME_mat[i, ] diff --git a/R/multivariable_mr.R b/R/multivariable_mr.R index 06b8ed566..b4e566012 100644 --- a/R/multivariable_mr.R +++ b/R/multivariable_mr.R @@ -506,7 +506,7 @@ mv_residual <- function( p <- list() nom <- colnames(beta.exposure) nom2 <- mvdat$expname$exposure[match(nom, mvdat$expname$id.exposure)] - for (i in 1:nexp) { + for (i in seq_len(nexp)) { # For this exposure, only keep SNPs that meet some p-value threshold index <- pval.exposure[, i] < pval_threshold @@ -615,7 +615,7 @@ mv_multiple <- function( p <- list() nom <- colnames(beta.exposure) nom2 <- mvdat$expname$exposure[match(nom, mvdat$expname$id.exposure)] - for (i in 1:nexp) { + for (i in seq_len(nexp)) { # For this exposure, only keep SNPs that meet some p-value threshold index <- pval.exposure[, i] < pval_threshold @@ -712,7 +712,7 @@ mv_basic <- function(mvdat, pval_threshold = 5e-8) { p <- list() nom <- colnames(beta.exposure) nom2 <- mvdat$expname$exposure[match(nom, mvdat$expname$id.exposure)] - for (i in 1:nexp) { + for (i in seq_len(nexp)) { # For this exposure, only keep SNPs that meet some p-value threshold index <- pval.exposure[, i] < pval_threshold @@ -780,7 +780,7 @@ mv_ivw <- function(mvdat, pval_threshold = 5e-8) { p <- list() nom <- colnames(beta.exposure) nom2 <- mvdat$expname$exposure[match(nom, mvdat$expname$id.exposure)] - for (i in 1:nexp) { + for (i in seq_len(nexp)) { # For this exposure, only keep SNPs that meet some p-value threshold index <- pval.exposure[, i] < pval_threshold diff --git a/R/rucker.R b/R/rucker.R index c207abfeb..ce3eca1d3 100644 --- a/R/rucker.R +++ b/R/rucker.R @@ -37,7 +37,7 @@ PM <- function(y = y, s = s, Alpha = 0.1) { v <- 1 / s^2 sum.v <- sum(v) typS <- sum(v * (k - 1)) / (sum.v^2 - sum(v^2)) - for (j in 1:L) { + for (j in seq_len(L)) { tausq <- 0 Fstat <- 1 TAUsq <- NULL @@ -302,7 +302,7 @@ mr_rucker_bootstrap <- function(dat, parameters = default_parameters()) { dat2 <- dat l <- list() - for (i in 1:nboot) { + for (i in seq_len(nboot)) { dat2$beta.exposure <- boot_exp[i, ] dat2$beta.outcome <- boot_out[i, ] l[[i]] <- mr_rucker(dat2, parameters) @@ -452,7 +452,7 @@ mr_rucker_jackknife_internal <- function(dat, parameters = default_parameters()) )) } else { l <- list() - for (i in 1:nboot) { + for (i in seq_len(nboot)) { # dat2$beta.exposure <- rnorm(nsnp, mean=dat$beta.exposure, sd=dat$se.exposure) # dat2$beta.outcome <- rnorm(nsnp, mean=dat$beta.outcome, sd=dat$se.outcome) dat2 <- dat[sample(seq_len(nrow(dat)), nrow(dat), replace = TRUE), ] diff --git a/R/singlesnp.R b/R/singlesnp.R index 85585d360..23ea96227 100644 --- a/R/singlesnp.R +++ b/R/singlesnp.R @@ -59,7 +59,7 @@ mr_singlesnp <- function( ) return(d) } - l <- lapply(1:nsnp, function(j) { + l <- lapply(seq_len(nsnp), function(j) { with( x, get(single_method)( From aa31f2bbea48ec5dc3ee1b81f1c6757dace83639 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:20:39 +0100 Subject: [PATCH 09/13] Pre-generate jackknife resample indices in mr_rucker_jackknife_internal and switch to lapply, matching the bootstrap function pattern --- R/rucker.R | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/R/rucker.R b/R/rucker.R index ce3eca1d3..f2e510ed1 100644 --- a/R/rucker.R +++ b/R/rucker.R @@ -451,13 +451,15 @@ mr_rucker_jackknife_internal <- function(dat, parameters = default_parameters()) e_plot = NULL )) } else { - l <- list() - for (i in seq_len(nboot)) { - # dat2$beta.exposure <- rnorm(nsnp, mean=dat$beta.exposure, sd=dat$se.exposure) - # dat2$beta.outcome <- rnorm(nsnp, mean=dat$beta.outcome, sd=dat$se.outcome) - dat2 <- dat[sample(seq_len(nrow(dat)), nrow(dat), replace = TRUE), ] - l[[i]] <- mr_rucker_internal(dat2, parameters) - } + n <- nrow(dat) + boot_idx <- matrix( + sample.int(n, n * nboot, replace = TRUE), + nrow = nboot, + ncol = n + ) + l <- lapply(seq_len(nboot), function(i) { + mr_rucker_internal(dat[boot_idx[i, ], ], parameters) + }) modsel <- data.table::rbindlist( lapply(l, function(x) x$selected), @@ -505,7 +507,7 @@ mr_rucker_jackknife_internal <- function(dat, parameters = default_parameters()) p1 <- ggplot2::ggplot(bootstrap, ggplot2::aes_string(x = "Q", y = "Qdash")) + ggplot2::geom_point(ggplot2::aes_string(colour = "model")) + - ggplot2::geom_point(data = subset(bootstrap, i == "Full")) + + ggplot2::geom_point(data = bootstrap[bootstrap$i == "Full", ]) + ggplot2::scale_colour_brewer(type = "qual") + ggplot2::xlim(0, max(bootstrap$Q, bootstrap$Qdash)) + ggplot2::ylim(0, max(bootstrap$Q, bootstrap$Qdash)) + From 0b7825019274f525dadd7fcf8070fab9d112be2a Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:30:46 +0100 Subject: [PATCH 10/13] Vectorise get_population_allele_frequency by inlining the quadratic-root logic from contingency() and picking the valid root with vectorised constraint checks --- R/add_rsq.r | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/R/add_rsq.r b/R/add_rsq.r index 8c6c018b7..ec3338218 100644 --- a/R/add_rsq.r +++ b/R/add_rsq.r @@ -347,13 +347,35 @@ allele_frequency <- function(g) { get_population_allele_frequency <- function(af, prop, odds_ratio, prevalence) { stopifnot(length(af) == length(odds_ratio)) stopifnot(length(prop) == length(odds_ratio)) - for (i in seq_along(odds_ratio)) { - co <- contingency(af[i], prop[i], odds_ratio[i]) - af_controls <- co[1, 2] / (co[1, 2] + co[2, 2]) - af_cases <- co[1, 1] / (co[1, 1] + co[2, 1]) - af[i] <- af_controls * (1 - prevalence[i]) + af_cases * prevalence[i] + eps <- 1e-15 + a <- odds_ratio - 1 + b <- (af + prop) * (1 - odds_ratio) - 1 + c_ <- odds_ratio * af * prop + + z <- numeric(length(odds_ratio)) + linear <- abs(a) < eps + z[linear] <- -c_[linear] / b[linear] + + quad <- !linear + if (any(quad)) { + d <- pmax(0, b[quad]^2 - 4 * a[quad] * c_[quad]) + sqrt_d <- sqrt(d) + two_a <- 2 * a[quad] + z_pos <- (-b[quad] + sqrt_d) / two_a + z_neg <- (-b[quad] - sqrt_d) / two_a + af_q <- af[quad] + prop_q <- prop[quad] + tol <- -1e-7 + valid_pos <- z_pos >= tol & + (prop_q - z_pos) >= tol & + (af_q - z_pos) >= tol & + (1 + z_pos - af_q - prop_q) >= tol + z[quad] <- ifelse(valid_pos, z_pos, z_neg) } - return(af) + + af_controls <- (af - z) / (1 - prop) + af_cases <- z / prop + return(af_controls * (1 - prevalence) + af_cases * prevalence) } From 45d53722f66484d9c0ae357698b584879e3a5d8e Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:34:45 +0100 Subject: [PATCH 11/13] Check on oldrel-5 --- .github/workflows/check-full.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/check-full.yaml b/.github/workflows/check-full.yaml index 4162fc7dc..02f7067c9 100644 --- a/.github/workflows/check-full.yaml +++ b/.github/workflows/check-full.yaml @@ -38,6 +38,7 @@ jobs: - {os: ubuntu-latest, r: '4.3.2'} - {os: ubuntu-latest, r: 'oldrel-3'} - {os: ubuntu-latest, r: 'oldrel-4'} + - {os: ubuntu-latest, r: 'oldrel-5'} - {os: macos-14, r: 'release'} # - {os: ubuntu-24.04-arm, r: 'release', rspm: 'no' } From fe574eb73ed4a48a4d94eed9c3f3c559239e33c3 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Mon, 4 May 2026 10:51:00 +0100 Subject: [PATCH 12/13] Skip mr_wald_ratio inside mr() when more than one SNP is provided and other methods are also requested, suppressing the multi-SNP warning for default method runs while preserving the warning when mr_wald_ratio is requested on its own --- R/mr.R | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/R/mr.R b/R/mr.R index 3f1b88a79..7148d70ce 100644 --- a/R/mr.R +++ b/R/mr.R @@ -72,7 +72,13 @@ mr <- function( } else { message("Analysing '", exp_id, "' on '", out_id, "'") } - res <- lapply(method_list, function(meth) { + keep <- if (nrow(x) > 1 && length(method_list) > 1) { + method_list != "mr_wald_ratio" + } else { + rep(TRUE, length(method_list)) + } + methods_to_run <- method_list[keep] + res <- lapply(methods_to_run, function(meth) { get(meth)(x$beta.exposure, x$beta.outcome, x$se.exposure, x$se.outcome, parameters) }) mr_tab <- data.frame( @@ -80,7 +86,7 @@ mr <- function( id.outcome = out_id, outcome = x$outcome[1], exposure = x$exposure[1], - method = method_names, + method = method_names[keep], nsnp = vapply(res, function(x) x$nsnp, numeric(1)), b = vapply(res, function(x) x$b, numeric(1)), se = vapply(res, function(x) x$se, numeric(1)), From f155c758e3a6d9b93157a5418bba6dd8b7932057 Mon Sep 17 00:00:00 2001 From: Tom Palmer Date: Thu, 30 Apr 2026 06:16:28 +0100 Subject: [PATCH 13/13] Update NEWS.md --- NEWS.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/NEWS.md b/NEWS.md index 60a97ddec..1105bdb01 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,15 @@ +# TwoSampleMR v0.7.5 + +(Release date 2026-05-04) + +* Amend URLs to +* Bump version of roxygen2 to 8.0.0 and regenerate documentation +* Replace `array(1:nexp)` placeholder allocations with `numeric(nexp)`/`integer(nexp)` in multivariable MR functions +* Replace `1:n` with `seq_len(n)` in loop indices across `singlesnp`, `leaveoneout`, `rucker`, `mr_mode`, and `multivariable_mr` for empty-vector safety +* Pre-generate jackknife resample indices in `mr_rucker_jackknife_internal` and switch to `lapply`, matching the bootstrap function pattern +* Vectorise `get_population_allele_frequency` by inlining the quadratic-root logic from `contingency()` and picking the valid root with vectorised constraint checks +* Skip `mr_wald_ratio` inside `mr()` when more than one SNP is provided and other methods are also requested, suppressing the multi-SNP warning for default method runs while preserving the warning when `mr_wald_ratio` is requested on its own + # TwoSampleMR v0.7.4 (Release date 2026-04-02)