Skip to content

Commit d9c59d0

Browse files
authored
Merge pull request #609 from remlapmot/mrgrip
TwoSampleMR 0.6.13
2 parents 6199e90 + 3e50571 commit d9c59d0

File tree

13 files changed

+190
-44
lines changed

13 files changed

+190
-44
lines changed

.github/workflows/check-full.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,9 @@ jobs:
3131
- {os: macos-latest, r: 'release'}
3232
- {os: macos-13, r: 'release'}
3333
- {os: windows-latest, r: 'release'}
34-
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
3534
- {os: ubuntu-latest, r: 'release'}
3635
- {os: ubuntu-latest, r: 'oldrel-1'}
36+
- {os: ubuntu-latest, r: 'devel', http-user-agent: 'release'}
3737
- {os: ubuntu-latest, r: '4.3.2'}
3838
- {os: ubuntu-24.04-arm, r: 'release', rspm: 'no' }
3939

@@ -53,6 +53,10 @@ jobs:
5353
http-user-agent: ${{ matrix.config.http-user-agent }}
5454
use-public-rspm: ${{ matrix.config.rspm || 'true' }}
5555

56+
- name: Set pak options
57+
shell: bash
58+
run: echo 'options(pkg.sysreqs_db_update_timeout = as.difftime(59, units = "secs"))' >> ~/.Rprofile
59+
5660
- uses: r-lib/actions/setup-r-dependencies@v2
5761
with:
5862
extra-packages: >

DESCRIPTION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
Package: TwoSampleMR
22
Title: Two Sample MR Functions and Interface to MRC Integrative
33
Epidemiology Unit OpenGWAS Database
4-
Version: 0.6.12
4+
Version: 0.6.13
55
Authors@R: c(
66
person("Gibran", "Hemani", , "[email protected]", role = c("aut", "cre"),
77
comment = c(ORCID = "0000-0003-0920-1055")),

NAMESPACE

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ export(mr_egger_regression)
5151
export(mr_egger_regression_bootstrap)
5252
export(mr_forest_plot)
5353
export(mr_funnel_plot)
54+
export(mr_grip)
5455
export(mr_heterogeneity)
5556
export(mr_ivw)
5657
export(mr_ivw_fe)

NEWS.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
1+
# TwoSampleMR v0.6.13
2+
3+
(Release date 2025-03-26)
4+
5+
* Added `mr_grip()` function which implements the MR-GRIP (modified MR-Egger with the Genotype Recoding Invariance Property) method of Dudbridge and Bowden et al. (2025).
6+
The new method can be accessed by `mr(dat, method_list = "mr_grip")` or it can be added to the default list of methods with `mr(dat, method_list = c(subset(mr_method_list(), use_by_default)$obj, "mr_grip"))`.
7+
* Added Pub Med IDs for more of the methods.
8+
* The `format_data()` function no longer causes a stack overflow when its `dat` argument is not a variable (thanks to @DarwinAwardWinner)
9+
110
# TwoSampleMR v0.6.12
211

312
(Release date 2025-03-18)

R/mr-grip.R

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#' MR-GRIP: a modified MR-Egger model with the Genotype Recoding Invariant Property
2+
#'
3+
#' This implements the modified MR-Egger model with the Genotype Recoding Invariant Property (MR-GRIP) due to Dudbridge and Bowden et al. (2025).
4+
#' It is well known that the results of MR-Egger are sensitive to which alleles are designated as the effect alleles.
5+
#' A pragmatic convention is to orient all SNPs to have positive effects on the exposure, which has some advantages in interpretation but also brings some philosophical limitations.
6+
#' The MR-GRIP model is a modification to the MR-Egger model in which each term is multiplied by the genotype-phenotype associations.
7+
#' This makes each term in the model invariant to allele coding.
8+
#'
9+
#' @param b_exp Vector of genetic effects on exposure.
10+
#' @param b_out Vector of genetic effects on outcome.
11+
#' @param se_exp Standard errors of genetic effects on exposure.
12+
#' @param se_out Standard errors of genetic effects on outcome.
13+
#' @param parameters List of parameters.
14+
#'
15+
#' @export
16+
#' @return List of with the following elements:
17+
#' \describe{
18+
#' \item{b}{MR estimate}
19+
#' \item{se}{Standard error of MR estimate}
20+
#' \item{pval}{p-value of MR estimate}
21+
#' \item{Q, Q_df, Q_pval}{Heterogeneity stats}
22+
#' \item{b.wi}{MR estimate adjusting for weak instruments}
23+
#' \item{se.wi}{Standard error adjusting for weak instruments}
24+
#' \item{pval.wi}{p-value adjusting for weak instruments}
25+
#' \item{mod}{Summary of regression}
26+
#' \item{dat}{Original data used for MR-GRIP}
27+
#' }
28+
mr_grip <- function(b_exp, b_out, se_exp, se_out, parameters) {
29+
if (length(b_exp) != length(b_out)) stop("The lengths of b_exp and b_out are not equal.")
30+
if (length(se_exp) != length(se_out)) stop("The lengths of se_exp and se_out are not equal.")
31+
if (length(b_exp) != length(se_out)) stop("The lengths of b_exp and se_out are not equal.")
32+
33+
nulllist <- list(
34+
b = NA,
35+
se = NA,
36+
pval = NA,
37+
nsnp = NA,
38+
Q = NA,
39+
Q_df = NA,
40+
Q_pval = NA,
41+
mod = NA,
42+
smod = NA,
43+
dat = NA
44+
)
45+
if (
46+
sum(!is.na(b_exp) & !is.na(b_out) & !is.na(se_exp) & !is.na(se_out)) < 3
47+
) {
48+
return(nulllist)
49+
}
50+
51+
dat <- data.frame(
52+
b_out = b_out,
53+
b_exp = b_exp,
54+
se_exp = se_exp,
55+
se_out = se_out
56+
)
57+
grip_out <- b_out * b_exp
58+
grip_exp <- b_exp^2
59+
# GRIP regression. Includes intercept. Weights designed to replicate IVW under no intercept.
60+
mod <- stats::lm(grip_out ~ grip_exp, weights = 1 / (grip_exp * se_out^2))
61+
smod <- summary(mod)
62+
b <- stats::coefficients(smod)[2, 1]
63+
se <- stats::coefficients(smod)[2, 2]
64+
b.adj <- NA
65+
se.adj <- NA
66+
pval.adj <- NA
67+
pval <- 2 * stats::pt(abs(b / se), length(b_exp) - 2L, lower.tail = FALSE)
68+
return(list(
69+
b = b,
70+
se = se,
71+
pval = pval,
72+
b.adj = b.adj,
73+
se.adj = se.adj,
74+
pval.adj = pval.adj,
75+
nsnp = length(b_exp),
76+
mod = smod,
77+
dat = dat
78+
))
79+
}

R/mr.R

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -118,39 +118,39 @@ mr_method_list <- function()
118118
list(
119119
obj="mr_simple_median",
120120
name="Simple median",
121-
PubmedID="",
121+
PubmedID="27061298",
122122
Description="",
123123
use_by_default=FALSE,
124124
heterogeneity_test=FALSE
125125
),
126126
list(
127127
obj="mr_weighted_median",
128128
name="Weighted median",
129-
PubmedID="",
129+
PubmedID="27061298",
130130
Description="",
131131
use_by_default=TRUE,
132132
heterogeneity_test=FALSE
133133
),
134134
list(
135135
obj="mr_penalised_weighted_median",
136136
name="Penalised weighted median",
137-
PubmedID="",
137+
PubmedID="27061298",
138138
Description="",
139139
use_by_default=FALSE,
140140
heterogeneity_test=FALSE
141141
),
142142
list(
143143
obj="mr_ivw",
144144
name="Inverse variance weighted",
145-
PubmedID="",
145+
PubmedID="24114802",
146146
Description="",
147147
use_by_default=TRUE,
148148
heterogeneity_test=TRUE
149149
),
150150
list(
151151
obj = "mr_ivw_radial",
152152
name = "IVW radial",
153-
PubmedID = "",
153+
PubmedID = "29961852",
154154
Description = "",
155155
use_by_default = FALSE,
156156
heterogeneity_test = TRUE
@@ -174,31 +174,31 @@ mr_method_list <- function()
174174
list(
175175
obj="mr_simple_mode",
176176
name="Simple mode",
177-
PubmedID="",
177+
PubmedID="29040600",
178178
Description="",
179179
use_by_default=TRUE,
180180
heterogeneity_test=FALSE
181181
),
182182
list(
183183
obj="mr_weighted_mode",
184184
name="Weighted mode",
185-
PubmedID="",
185+
PubmedID="29040600",
186186
Description="",
187187
use_by_default=TRUE,
188188
heterogeneity_test=FALSE
189189
),
190190
list(
191191
obj="mr_weighted_mode_nome",
192192
name="Weighted mode (NOME)",
193-
PubmedID="",
193+
PubmedID="29040600",
194194
Description="",
195195
use_by_default=FALSE,
196196
heterogeneity_test=FALSE
197197
),
198198
list(
199199
obj="mr_simple_mode_nome",
200200
name="Simple mode (NOME)",
201-
PubmedID="",
201+
PubmedID="29040600",
202202
Description="",
203203
use_by_default=FALSE,
204204
heterogeneity_test=FALSE
@@ -226,6 +226,14 @@ mr_method_list <- function()
226226
Description="Doesn't use any weights",
227227
use_by_default=FALSE,
228228
heterogeneity_test=TRUE
229+
),
230+
list(
231+
obj = "mr_grip",
232+
name = "MR GRIP",
233+
PubmedID = "",
234+
Description = "Allele coding invariant regression",
235+
use_by_default = FALSE,
236+
heterogeneity_test = FALSE
229237
)
230238
)
231239
a <- lapply(a, as.data.frame)

R/query.R

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ format_d <- function(d)
279279
d$mr_keep.outcome <- apply(d[, mrcols], 1, function(x) !any(is.na(x)))
280280
if(any(!d$mr_keep.outcome))
281281
{
282-
warning("The following SNP(s) are missing required information for the MR tests and will be excluded\n", paste(subset(d, !mr_keep.outcome)$SNP, collapse="\n"))
282+
missinginfosnps <- paste(subset(d, !mr_keep.outcome)$SNP, collapse = " ")
283+
warning("The following SNP(s) are missing required information for the MR tests and will be excluded: ", missinginfosnps)
283284
}
284285
if(all(!d$mr_keep.outcome))
285286
{

R/read_data.R

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,12 +165,11 @@ format_data <- function(dat, type="exposure", snps=NULL, header=TRUE,
165165
{
166166

167167
if (inherits(dat, "data.table")) {
168-
datname <- deparse(substitute(dat))
169-
stop(paste0(
170-
"Your ", datname, " data.frame is also of class 'data.table', ",
171-
"please reformat as simply a data.frame with ", datname, " <- data.frame(",
172-
datname, ") and then rerun your format_data() call."
173-
))
168+
stop(
169+
"Your data.frame is also of class 'data.table' ",
170+
"please reformat as simply a data.frame with data.frame() or as.data.frame() ",
171+
"and then rerun format_data()."
172+
)
174173
}
175174

176175
all_cols <- c(phenotype_col, snp_col, beta_col, se_col, eaf_col, effect_allele_col, other_allele_col, pval_col, units_col, ncase_col, ncontrol_col, samplesize_col, gene_col, id_col, z_col, info_col, chr_col, pos_col)
@@ -482,7 +481,8 @@ if (inherits(dat, "data.table")) {
482481
dat$mr_keep.outcome <- dat$mr_keep.outcome & apply(dat[, mrcols_present], 1, function(x) !any(is.na(x)))
483482
if(any(!dat$mr_keep.outcome))
484483
{
485-
warning("The following SNP(s) are missing required information for the MR tests and will be excluded\n", paste(subset(dat, !mr_keep.outcome)$SNP, collapse="\n"))
484+
missinginfosnps <- paste(subset(dat, !mr_keep.outcome)$SNP, collapse = " ")
485+
warning("The following SNP(s) are missing required information for the MR tests and will be excluded: ", missinginfosnps)
486486
}
487487
}
488488
if(all(!dat$mr_keep.outcome))

man/mr_grip.Rd

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/test_format_data.R

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,8 @@ test_that("format_data() should not error after having its data.table class remo
4545
samplesize_col = "n"
4646
))
4747
})
48+
49+
test_that("format_data() should not cause a stack overflow", {
50+
a <- data.table::data.table(x = sample(1:1e6))
51+
expect_error(do.call(format_data, list(a)))
52+
})

0 commit comments

Comments
 (0)