Music exposure reduces anxiety- and depression-like behavior in rodents: a systematic review and multilevel meta-analysis
  • OSF
  • Paper
  1. Publication bias
  • Online supplementary
  • Effect size calculation
  • Overall effects
  • Uni-moderators
  • Publication bias
  • Leave-one-out
  • Code for figures
  • Alluvial plot

Table of contents

  • Visual inspection: funnel plots
  • Egger’s Regression (Testing Small-Study Effects)
  • Decline Effect (Time-Lag Bias)

Publication bias

First, we reload the data and run a simple intercept-only model (ma_all) to establish the overall mean effect size, which acts as the centerline for our funnel plots.

pacman::p_load(
               DT,
               dtplyr,
               here, 
               knitr,
               tidyverse,
               patchwork,
               metafor,
               orchaRd, emmeans,scales
               )
db <- readr::read_csv(here("..","data","db_effect_sizes.csv")) %>%
mutate(across( c(C_n, Ex_n, C_mean, Ex_mean, C_SE, Ex_SE, C_SD, Ex_SD, lnRR, lnRR_var), as.numeric))%>%
  mutate(across(where(is.character), as.factor))



VCV <- metafor::vcalc(
  vi = lnRR_var,
  cluster = Cohort_ID, # The clustering variable (Study_ID + Ex_ID)
  obs = ES_ID,         # The unique observation/effect size ID
  rho = 0.5,           # Assumed correlation between outcomes from the same cohort
  data = db 
)

ma_all <- rma.mv(yi = lnRR,
                  V = VCV, 
                  random = list(~1 | Study_ID,
                                ~1 | ES_ID,
                                ~1 | Strain),
                  test = "t",
                  method = "REML", 
                  sparse = TRUE,
                  data = db)

summary(ma_all)

Multivariate Meta-Analysis Model (k = 298; method: REML)

   logLik   Deviance        AIC        BIC       AICc   
-242.8851   485.7702   493.7702   508.5452   493.9072   

Variance Components:

            estim    sqrt  nlvls  fixed    factor 
sigma^2.1  0.0494  0.2223     20     no  Study_ID 
sigma^2.2  0.2302  0.4798    298     no     ES_ID 
sigma^2.3  0.0000  0.0001      6     no    Strain 

Test for Heterogeneity:
Q(df = 297) = 5799.5331, p-val < .0001

Model Results:

estimate      se     tval   df    pval    ci.lb    ci.ub     
 -0.2120  0.0662  -3.2046  297  0.0015  -0.3422  -0.0818  ** 

---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Visual inspection: funnel plots

pdf(file = "funnel_plot.pdf", width = 8, height = 6)


funnel_plot <- funnel(ma_all, 
       yaxis = "seinv",
       las = 1,
       digits = c(2, 1),
       xlab = "Standarised residuals (lnRR)",
       ylab = "Precision (inverse of SE)", 
       pch = 21,
       bg = alpha("black", 0.2),   
       col = "black",              
       cex = 1.2,
       ylim = c(0.01, 40),
       xlim = c(-6, 10)
)


dev.off()
png 
  2 

Egger’s Regression (Testing Small-Study Effects)

We tested for small-study effects using an Egger-type meta-regression adapted for lnRR. Because lnRR and its sampling variance are mathematically coupled, regressions on the standard error can be biased. Following recommendations for multilevel meta-analysis, we used an N-based precision proxy and fitted it as a moderator in the same multilevel model.

# 1. Calculate the effective sample size term (sqrt_inv_e_n)
# Formula: sqrt(1/Ex_n + 1/C_n) approximates the standard error based on N
dt_egger_full <- db %>%
  mutate(sqrt_inv_e_n = sqrt((1 / Ex_n) + (1 / C_n)))

# 2. Run the Meta-Regression
# If 'sqrt_inv_e_n' is significant (p < 0.05), we have evidence of small-study effects.
egger_model_full <- rma.mv(yi = lnRR,
                           V = VCV, 
                           mods = ~ 1 + sqrt_inv_e_n, # The 'Egger' predictor
                           random = list(~1 | Study_ID, ~1|Strain, ~1 | ES_ID), 
                           test = "t",       
                           method = "REML",
                           data = dt_egger_full)

summary(egger_model_full)

Multivariate Meta-Analysis Model (k = 298; method: REML)

   logLik   Deviance        AIC        BIC       AICc   
-241.6861   483.3723   493.3723   511.8241   493.5792   

Variance Components:

            estim    sqrt  nlvls  fixed    factor 
sigma^2.1  0.0426  0.2064     20     no  Study_ID 
sigma^2.2  0.0000  0.0002      6     no    Strain 
sigma^2.3  0.2322  0.4819    298     no     ES_ID 

Test for Residual Heterogeneity:
QE(df = 296) = 5797.7206, p-val < .0001

Test of Moderators (coefficient 2):
F(df1 = 1, df2 = 296) = 0.6675, p-val = 0.4146

Model Results:

              estimate      se     tval   df    pval    ci.lb   ci.ub    
intrcpt         0.1280  0.4223   0.3031  296  0.7620  -0.7032  0.9592    
sqrt_inv_e_n   -0.7243  0.8866  -0.8170  296  0.4146  -2.4691  1.0204    

---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
egger<-bubble_plot(egger_model_full,
            mod = "sqrt_inv_e_n",
            group = "Study_ID",
            xlab = "Square root of inverse of effective sample size", ylab="Effect size (lnRR)")

egger

Decline Effect (Time-Lag Bias)

Often in science, early studies show large effects (novelty bias), but as time goes on and methods improve (or replication attempts increase), the effect size shrinks. This is called the Decline Effect or Time-Lag Bias.

We test this by adding Publication Year to the model. A significant negative slope indicates the effect is shrinking over time.

# 1. Mean-Center the Year (Interpretation: Intercept = Effect in average year)
db_decline <- dt_egger_full %>%
  mutate(Year_c = Year - mean(Year, na.rm = TRUE))

# 2. Run model: Controlling for Precision (Egger) AND Year simultaneously
year_lag_model <- rma.mv(yi = lnRR,
                         V = VCV, 
                         mods = ~ 1 + Year_c + sqrt_inv_e_n, 
                         random = list(~1 | Study_ID, ~1|Strain,~1 | ES_ID), 
                         test = "t", 
                         method = "REML",
                         sparse = TRUE,
                         data = db_decline)

summary(year_lag_model)

Multivariate Meta-Analysis Model (k = 298; method: REML)

   logLik   Deviance        AIC        BIC       AICc   
-240.5301   481.0602   493.0602   515.1820   493.3518   

Variance Components:

            estim    sqrt  nlvls  fixed    factor 
sigma^2.1  0.0462  0.2150     20     no  Study_ID 
sigma^2.2  0.0011  0.0329      6     no    Strain 
sigma^2.3  0.2322  0.4819    298     no     ES_ID 

Test for Residual Heterogeneity:
QE(df = 295) = 5787.4371, p-val < .0001

Test of Moderators (coefficients 2:3):
F(df1 = 2, df2 = 295) = 0.3582, p-val = 0.6992

Model Results:

              estimate      se     tval   df    pval    ci.lb   ci.ub    
intrcpt         0.1139  0.4306   0.2646  295  0.7915  -0.7334  0.9613    
Year_c         -0.0051  0.0123  -0.4161  295  0.6777  -0.0294  0.0191    
sqrt_inv_e_n   -0.6709  0.9023  -0.7435  295  0.4578  -2.4467  1.1049    

---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
time_lag<-bubble_plot(year_lag_model,
            mod = "Year_c",
            group = "Study_ID",
            xlab = "Year of publication",ylab="Effect size (lnRR)")



mean_year <- mean(dt_egger_full$Year, na.rm = TRUE)

# Define the years you want to see on the axis
target_years <- seq(2007, 2025, by = 3)

lag2<-time_lag + 
  scale_x_continuous(
    name = "Year of publication",
    breaks = target_years - mean_year, 
    labels = target_years 
  )

lag2

final_dashboard <- egger / lag2 +
  plot_annotation(tag_levels = "A")  & 
  theme(
    plot.tag = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10, face = "bold")
  )

final_dashboard

ggsave(filename = here("..","Plots", "bias_lnRR.pdf"),
       plot = final_dashboard,
       dpi = 300, device = cairo_pdf,width = 10,  
       height = 7)

ggsave(filename = here("..","Plots", "bias_lnRR.jpg"),
       plot = final_dashboard,
       width = 10,  
       height = 7,  
       dpi = 300)

We treat all publication-bias diagnostics as exploratory because heterogeneity is high and effect sizes are dependent. We interpret agreement across the funnel plot, Egger-type regression, and the year moderator as stronger evidence than any single test.

Note
sessionInfo()
R version 4.5.2 (2025-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 11 x64 (build 26200)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=English_Guernsey.utf8  LC_CTYPE=English_Guernsey.utf8   
[3] LC_MONETARY=English_Guernsey.utf8 LC_NUMERIC=C                     
[5] LC_TIME=English_Guernsey.utf8    

time zone: America/Edmonton
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] scales_1.4.0        emmeans_2.0.1       orchaRd_2.1.3      
 [4] metafor_4.8-0       numDeriv_2016.8-1.1 metadat_1.4-0      
 [7] Matrix_1.7-4        patchwork_1.3.2     lubridate_1.9.4    
[10] forcats_1.0.1       stringr_1.6.0       dplyr_1.1.4        
[13] purrr_1.2.1         readr_2.1.6         tidyr_1.3.2        
[16] tibble_3.3.1        ggplot2_4.0.1       tidyverse_2.0.0    
[19] knitr_1.51          here_1.0.2          dtplyr_1.3.2       
[22] DT_0.34.0          

loaded via a namespace (and not attached):
 [1] gtable_0.3.6        xfun_0.56           htmlwidgets_1.6.4  
 [4] lattice_0.22-7      mathjaxr_2.0-0      tzdb_0.5.0         
 [7] vctrs_0.7.1         tools_4.5.2         generics_0.1.4     
[10] parallel_4.5.2      sandwich_3.1-1      pacman_0.5.1       
[13] pkgconfig_2.0.3     data.table_1.18.2.1 RColorBrewer_1.1-3 
[16] S7_0.2.1            lifecycle_1.0.5     compiler_4.5.2     
[19] farver_2.1.2        codetools_0.2-20    htmltools_0.5.9    
[22] yaml_2.3.12         crayon_1.5.3        pillar_1.11.1      
[25] MASS_7.3-65         multcomp_1.4-29     nlme_3.1-168       
[28] tidyselect_1.2.1    digest_0.6.39       mvtnorm_1.3-3      
[31] stringi_1.8.7       labeling_0.4.3      splines_4.5.2      
[34] latex2exp_0.9.8     rprojroot_2.1.1     fastmap_1.2.0      
[37] grid_4.5.2          cli_3.6.5           magrittr_2.0.4     
[40] survival_3.8-6      TH.data_1.1-5       withr_3.0.2        
[43] bit64_4.6.0-1       timechange_0.3.0    estimability_1.5.1 
[46] rmarkdown_2.30      bit_4.6.0           otel_0.2.0         
[49] zoo_1.8-15          hms_1.1.4           coda_0.19-4.1      
[52] evaluate_1.0.5      mgcv_1.9-4          rlang_1.1.7        
[55] xtable_1.8-4        glue_1.8.0          vroom_1.7.0        
[58] rstudioapi_0.18.0   jsonlite_2.0.0      R6_2.6.1           
Uni-moderators
Leave-one-out
Source Code
---
title: "Publication bias"
---

First, we reload the data and run a simple intercept-only model (ma_all) to establish the overall mean effect size, which acts as the centerline for our funnel plots.

```{r}
#| label: setup
#| 
pacman::p_load(
               DT,
               dtplyr,
               here, 
               knitr,
               tidyverse,
               patchwork,
               metafor,
               orchaRd, emmeans,scales
               )
db <- readr::read_csv(here("..","data","db_effect_sizes.csv")) %>%
mutate(across( c(C_n, Ex_n, C_mean, Ex_mean, C_SE, Ex_SE, C_SD, Ex_SD, lnRR, lnRR_var), as.numeric))%>%
  mutate(across(where(is.character), as.factor))



VCV <- metafor::vcalc(
  vi = lnRR_var,
  cluster = Cohort_ID, # The clustering variable (Study_ID + Ex_ID)
  obs = ES_ID,         # The unique observation/effect size ID
  rho = 0.5,           # Assumed correlation between outcomes from the same cohort
  data = db 
)

ma_all <- rma.mv(yi = lnRR,
                  V = VCV, 
                  random = list(~1 | Study_ID,
                                ~1 | ES_ID,
                                ~1 | Strain),
                  test = "t",
                  method = "REML", 
                  sparse = TRUE,
                  data = db)

summary(ma_all)
```

## Visual inspection: funnel plots




```{r}
#| label: funnel_plot

pdf(file = "funnel_plot.pdf", width = 8, height = 6)


funnel_plot <- funnel(ma_all, 
       yaxis = "seinv",
       las = 1,
       digits = c(2, 1),
       xlab = "Standarised residuals (lnRR)",
       ylab = "Precision (inverse of SE)", 
       pch = 21,
       bg = alpha("black", 0.2),   
       col = "black",              
       cex = 1.2,
       ylim = c(0.01, 40),
       xlim = c(-6, 10)
)


dev.off()

```


```{r}
#| label: funnel
#| echo: false

funnel_plot <- funnel(ma_all, 
       yaxis = "seinv",
       las = 1,
       digits = c(2, 1),
       xlab = "Standarised residuals (lnRR)",
       ylab = "Precision (inverse of SE)", 
       pch = 21,
       bg = alpha("black", 0.2),   
       col = "black",              
       cex = 1.2,
       ylim = c(0.01, 40),
       xlim = c(-6, 10)
)

```


## Egger's Regression (Testing Small-Study Effects)


We tested for small-study effects using an Egger-type meta-regression adapted for lnRR. Because lnRR and its sampling variance are mathematically coupled, regressions on the standard error can be biased. Following recommendations for multilevel meta-analysis, we used an N-based precision proxy and fitted it as a moderator in the same multilevel model.


```{r}
#| label: eggers_test

# 1. Calculate the effective sample size term (sqrt_inv_e_n)
# Formula: sqrt(1/Ex_n + 1/C_n) approximates the standard error based on N
dt_egger_full <- db %>%
  mutate(sqrt_inv_e_n = sqrt((1 / Ex_n) + (1 / C_n)))

# 2. Run the Meta-Regression
# If 'sqrt_inv_e_n' is significant (p < 0.05), we have evidence of small-study effects.
egger_model_full <- rma.mv(yi = lnRR,
                           V = VCV, 
                           mods = ~ 1 + sqrt_inv_e_n, # The 'Egger' predictor
                           random = list(~1 | Study_ID, ~1|Strain, ~1 | ES_ID), 
                           test = "t",       
                           method = "REML",
                           data = dt_egger_full)

summary(egger_model_full)
```

```{r}
#| label: egger_bubble_plot
egger<-bubble_plot(egger_model_full,
            mod = "sqrt_inv_e_n",
            group = "Study_ID",
            xlab = "Square root of inverse of effective sample size", ylab="Effect size (lnRR)")

egger
```

## Decline Effect (Time-Lag Bias)

Often in science, early studies show large effects (novelty bias), but as time goes on and methods improve (or replication attempts increase), the effect size shrinks. This is called the Decline Effect or Time-Lag Bias.

We test this by adding Publication Year to the model. A significant negative slope indicates the effect is shrinking over time.

```{r}
#| label: time_lag_analysis

# 1. Mean-Center the Year (Interpretation: Intercept = Effect in average year)
db_decline <- dt_egger_full %>%
  mutate(Year_c = Year - mean(Year, na.rm = TRUE))

# 2. Run model: Controlling for Precision (Egger) AND Year simultaneously
year_lag_model <- rma.mv(yi = lnRR,
                         V = VCV, 
                         mods = ~ 1 + Year_c + sqrt_inv_e_n, 
                         random = list(~1 | Study_ID, ~1|Strain,~1 | ES_ID), 
                         test = "t", 
                         method = "REML",
                         sparse = TRUE,
                         data = db_decline)

summary(year_lag_model)


```

```{r}
#| label: time_lag_bubble_plot
time_lag<-bubble_plot(year_lag_model,
            mod = "Year_c",
            group = "Study_ID",
            xlab = "Year of publication",ylab="Effect size (lnRR)")



mean_year <- mean(dt_egger_full$Year, na.rm = TRUE)

# Define the years you want to see on the axis
target_years <- seq(2007, 2025, by = 3)

lag2<-time_lag + 
  scale_x_continuous(
    name = "Year of publication",
    breaks = target_years - mean_year, 
    labels = target_years 
  )

lag2
```






```{r}
#| label: final_dashboard
final_dashboard <- egger / lag2 +
  plot_annotation(tag_levels = "A")  & 
  theme(
    plot.tag = element_text(size = 12, face = "bold"),
    plot.subtitle = element_text(size = 10, face = "bold")
  )

final_dashboard

```

```{r}
#| label: save-figure_behaviour
#| eval: false

ggsave(filename = here("..","Plots", "bias_lnRR.pdf"),
       plot = final_dashboard,
       dpi = 300, device = cairo_pdf,width = 10,  
       height = 7)

ggsave(filename = here("..","Plots", "bias_lnRR.jpg"),
       plot = final_dashboard,
       width = 10,  
       height = 7,  
       dpi = 300)

```

We treat all publication-bias diagnostics as exploratory because heterogeneity is high and effect sizes are dependent. We interpret agreement across the funnel plot, Egger-type regression, and the year moderator as stronger evidence than any single test.


::: callout-note
```{r}
sessionInfo()
```
:::