library(metapower)
I recently put up another RPubs post (https://rpubs.com/JLLJ/P2020H2) and someone asked me how I derived a few numbers. This function is how.
This function wraps the function subgroup_power() from the metapower package. The estimand for subgroup_power() is the amount of power to detect a subgroup difference in a fixed or random effects meta-analysis. The estimand for this wrapper is the minimum detectable group difference in terms of Pearson’s r or equivalent. The function works by iteratively assessing power at different distances from the original effect size. The outputs are the first detectable effects at a certain level of power and p value threshold for fixed and random effects, in the positive and negative directions from the initial r. The reason both directions are not equivalent is that the r used for meta-analyses is atanh(r). The default iteration distance is 0.01 and the defaults for power and alpha are typical: b = 0.80 and p = 0.05.
To use this function, input an effect size for the first group, which I recommend be the best-estimated group; that is E1. Next, put in the degree to which effect sizes should differ at each step. By default this is 0.01, but greater precision might be useful for individuals with larger meta-analyses. For reasons of computational time, it should be remembered that every additional zero is an order of magnitude. Next, specify the average or median study size in the meta-analysis, SS, followed by the number of studies in the meta-analysis, K, and the heterogeneity, I2, to be expected in a random effects meta-analysis with all the same characteristics. Finally, pick a level of power and a p value you wish to find the minimum detectable difference for.
This function can be expanded for additional groups and more granularity in the future.
MinimumMetaCorrelation <- function(E1 = 0.5, step = 0.01, SS, K, I2, power = 0.8, p = 0.05){
library(pacman); p_load(metapower, tidyverse)
DEF = seq(-1, 1, step)
result_list = lapply(DEF, function(x) {
tryCatch(
expr = {
res = subgroup_power(
n_groups = 2,
effect_sizes = c(E1, E1 + x),
study_size = SS,
k = K,
i2 = I2,
es_type = "r",
p = p)
res_ = data.frame(
DEF = x,
FP = res$subgroup_power$fixed_power_b,
RP = res$subgroup_power$random_power_b)
return(res_)},
error = function(cond) {
return(data.frame(
DEF = x,
FP = NA,
RP = NA))})})
FPP <- result_list %>%
bind_rows() %>%
filter(FP > power) %>%
arrange((DEF < 0)) %>%
head(n = 1); FPP #Fixed Power Positive
FPN <- result_list %>%
bind_rows() %>%
filter(FP > power) %>%
arrange((DEF < 0)) %>%
tail(n = 1); FPN #Fixed Power Negative
RPP <- result_list %>%
bind_rows() %>%
filter(RP > power) %>%
arrange((DEF < 0)) %>%
head(n = 1); RPP #Random Power Positive
RPN <- result_list %>%
bind_rows() %>%
filter(RP > power) %>%
arrange((DEF < 0)) %>%
tail(n = 1); RPN #Random Power Negative
PR <- data.frame("FPP" = FPP[1:2], "FPN" = FPN[1:2], "RPP" = RPP[c(1,3)], "RPN" = RPN[c(1,3)])
return(PR)}
FPP.DEF is the fixed meta-analysis detectable positive effect for a specific level of power and p value, FPN.DEF is the same but in the negative direction, RPP.DEF is for random effects, and RPN is as well.
If we have an effect of r = 0.5, median sample size of 100, ten studies per group, and an \(I^2\) of 50% and want to know the minimum detectable effect with 80% power at p = 0.05, we would do
MinimumMetaCorrelation(E1 = 0.5, SS = 100, K = 10, I2 = 0.50)
In such a meta-analysis, we could detect a group difference (fixed/random) of +0.13/0.17 or -0.15/0.22. The power at those levels is sometimes quite off from exactly 0.80, so we can take smaller steps and gain greater accuracy.
MinimumMetaCorrelation(E1 = 0.5, step = 0.0001, SS = 100, K = 10, I2 = 0.50)
These are much closer to exactly 80% power! As a final demonstration, here are the same values for the post that necessitated this one, using the values for the latter method described there.
MinimumMetaCorrelation(E1 = tanh(0.5389), step = 0.0001, SS = 464, #Median used
K = 15, #There were 16 for the White group, but in the interest of underestimation of power being better than overestimation, 15 is used, since that was the number for the Black group
I2 = 0.777)
With the Zr values, the detectable effects are 0.0496/-0.053 in a fixed effects meta-analysis and 0.1007 and -0.1159 in a random effects meta-analysis.
And now to the other one, which uses \(\sqrt{h^2}\), or just h. Note that the \(I^2\) for this value was 0, but I am using the upper-bound of 0.40 to understate power here.
MinimumMetaCorrelation(E1 = 0.7183, step = 0.0001, SS = 464, K = 15, I2 = 0.4)
With the \(\sqrt{h^2}\) method, the detectable effects are 0.0312 and -0.0344 in a fixed effects meta-analysis and 0.0397 and -0.0450 in a random effects meta-analysis.
There needs to be considerable expansion of the subgroup_power() function and this one. But, nevertheless, you may use this function if you are interested in determining the minimum detectable effect for a subgroup difference in correlations in a meta-analysis in R.
The remaining discussion concerned an empirical example inspiring the creation of this function. It is irrelevant to its use otherwise and will be featured in a forthcoming paper.
COR and ZCOR and backtransf stuff that has meager effects.
sessionInfo()
## R version 4.1.2 (2021-11-01)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 10 x64 (build 19042)
##
## Matrix products: default
##
## locale:
## [1] LC_COLLATE=English_United States.1252
## [2] LC_CTYPE=English_United States.1252
## [3] LC_MONETARY=English_United States.1252
## [4] LC_NUMERIC=C
## [5] LC_TIME=English_United States.1252
##
## attached base packages:
## [1] stats graphics grDevices utils datasets methods base
##
## other attached packages:
## [1] forcats_0.5.1 stringr_1.4.0 dplyr_1.0.7 purrr_0.3.4
## [5] readr_2.1.1 tidyr_1.1.4 tibble_3.1.6 ggplot2_3.3.5
## [9] tidyverse_1.3.1 pacman_0.5.1 metapower_0.2.2
##
## loaded via a namespace (and not attached):
## [1] tidyselect_1.1.1 xfun_0.29 haven_2.4.3 colorspace_2.0-2
## [5] vctrs_0.3.8 generics_0.1.1 testthat_3.1.1 htmltools_0.5.2
## [9] yaml_2.2.1 utf8_1.2.2 rlang_0.4.12 jquerylib_0.1.4
## [13] pillar_1.6.4 withr_2.4.3 glue_1.6.0 DBI_1.1.2
## [17] dbplyr_2.1.1 modelr_0.1.8 readxl_1.3.1 lifecycle_1.0.1
## [21] cellranger_1.1.0 munsell_0.5.0 gtable_0.3.0 rvest_1.0.2
## [25] evaluate_0.14 knitr_1.37 tzdb_0.2.0 fastmap_1.1.0
## [29] fansi_0.5.0 Rcpp_1.0.7 broom_0.7.10 scales_1.1.1
## [33] backports_1.4.1 jsonlite_1.7.2 fs_1.5.2 hms_1.1.1
## [37] digest_0.6.29 stringi_1.7.6 grid_4.1.2 cli_3.1.0
## [41] tools_4.1.2 magrittr_2.0.1 crayon_1.4.2 pkgconfig_2.0.3
## [45] ellipsis_0.3.2 xml2_1.3.3 reprex_2.0.1 lubridate_1.8.0
## [49] rstudioapi_0.13 assertthat_0.2.1 rmarkdown_2.11 httr_1.4.2
## [53] R6_2.5.1 compiler_4.1.2