# Introduction to metaLong

## Overview

`metaLong` provides a coherent workflow for synthesising evidence from studies that report outcomes at multiple follow-up time points. The package covers:

- Pooling — `ml_meta()` fits a random-effects model at each time point using robust variance estimation (RVE) with Tipton small-sample corrections.
- Sensitivity — `ml_sens()` computes the time-varying Impact Threshold for a Confounding Variable (ITCV).
- Benchmark calibration — `ml_benchmark()` compares observed covariate partial correlations against the ITCV threshold.
- Nonlinear trends — `ml_spline()` fits a natural cubic spline over time.
- Fragility — `ml_fragility()` identifies how many study removals flip significance.

## 1. Simulating data

    library(metaLong)

    dat <- sim_longitudinal_meta(
      k    = 10,
      times = c(0, 6, 12, 24),
      mu   = c("0" = 0.30, "6" = 0.50, "12" = 0.42, "24" = 0.20),
      tau  = 0.20,
      seed = 42
    )

    head(dat, 6)
    #>   study time         vi        yi pub_year quality   n
    #> 1   s01    0 0.11040314 0.7255240     2003    0.58 372
    #> 2   s01    6 0.09375956 0.8372688     2003    0.58 372
    #> 3   s01   12 0.05795592 0.7716921     2003    0.58 372
    #> 4   s01   24 0.05334272 0.3893750     2003    0.58 372
    #> 5   s02    0 0.03387102 0.3167792     2001    0.77 223
    #> 6   s02    6 0.10110551 0.2722546     2001    0.77 223

The data are in long format: one row per study x time combination.

## 2. Longitudinal pooling: `ml_meta()`

`ml_meta()` fits an intercept-only random-effects model at each time point with CR2 sandwich variance and Satterthwaite degrees of freedom.

    meta <- ml_meta(dat, yi = "yi", vi = "vi", study = "study", time = "time")
    #> Registered S3 method overwritten by 'clubSandwich':
    #>   method    from
    #>   bread.mlm sandwich
    print(meta)
    #>
    #> === metaLong: Longitudinal Pooled Effects ===
    #> Engine: rma.uni
    #> Time points: 4
    #> Small-sample correction: TRUE
    #>
    #>  time  k theta    se    df p_val ci_lb ci_ub  tau2
    #>     0 10 0.315 0.077 6.565 0.005 0.131 0.498 0.000
    #>     6 10 0.602 0.073 5.638 0.000 0.419 0.784 0.001
    #>    12 10 0.534 0.097 7.705 0.001 0.308 0.761 0.023
    #>    24 10 0.417 0.081 7.769 0.001 0.229 0.606 0.010

    plot(meta, main = "Pooled Effects Across Follow-Up")

## 3. Sensitivity analysis: `ml_sens()`

`ml_sens()` computes ITCV_alpha(t) — the minimum partial correlation an omitted confounder must have with both treatment and outcome to render the result non-significant.

    sens <- ml_sens(dat, meta, yi = "yi", vi = "vi",
                    study = "study", time = "time")
    print(sens)
    #>
    #> === metaLong: Longitudinal Sensitivity (ITCV) ===
    #> delta (fragility benchmark): 0.15
    #> ITCV_alpha range: [ 0.691 , 0.961 ]
    #> Fragile proportion: 0
    #>
    #>  time theta    sy  itcv itcv_alpha fragile
    #>     0 0.315 0.242 0.890      0.691   FALSE
    #>     6 0.602 0.174 0.980      0.961   FALSE
    #>    12 0.534 0.298 0.935      0.848   FALSE
    #>    24 0.417 0.220 0.941      0.849   FALSE

    plot(sens)

Key trajectory summaries:

    cat("Minimum ITCV_alpha:", round(attr(sens, "itcv_min"),  3), "\n")
    #> Minimum ITCV_alpha: 0.691
    cat("Mean ITCV_alpha:   ", round(attr(sens, "itcv_mean"), 3), "\n")
    #> Mean ITCV_alpha:    0.837
    cat("Fragile proportion:", round(attr(sens, "fragile_prop"), 3), "\n")
    #> Fragile proportion: 0

## 4. Nonlinear time trend: `ml_spline()`

    spl <- ml_spline(meta, df = 2)
    print(spl)
    #>
    #> === metaLong: Spline Time Trend ===
    #> Spline df: 2
    #> Weighted R-squared: 0.807
    #> Nonlinearity test p-value: 0.291
    #>
    #> Prediction range: time in [ 0 , 24 ]

    plot(spl, main = "Spline Fit: Nonlinear Trajectory")

## 5. Combined figure: `ml_plot()`

    ml_plot(meta, sens_obj = sens, spline_obj = spl,
            main = "Longitudinal Meta-Analysis Profile")

## 6. Benchmark calibration: `ml_benchmark()`

`ml_benchmark()` regresses each observed study-level covariate and flags those whose partial correlation exceeds the ITCV_alpha threshold.

    bench <- ml_benchmark(
      dat, meta, sens,
      yi         = "yi", vi = "vi", study = "study", time = "time",
      covariates = c("pub_year", "quality")
    )
    print(bench)
    #>
    #> === metaLong: Benchmark Calibration ===
    #>
    #> Fragility summary by time:
    #>  time itcv_alpha n_covariates n_beats prop_beats any_beats
    #>     0      0.691            2       0          0     FALSE
    #>     6      0.961            2       0          0     FALSE
    #>    12      0.848            2       0          0     FALSE
    #>    24      0.849            2       0          0     FALSE
    #>
    #>  time covariate  k r_partial itcv_alpha beats_threshold p_val
    #>     0  pub_year 10    -0.331      0.691           FALSE 0.477
    #>     0   quality 10    -0.495      0.691           FALSE 0.380
    #>     6  pub_year 10    -0.279      0.961           FALSE 0.577
    #>     6   quality 10     0.598      0.961           FALSE 0.242
    #>    12  pub_year 10     0.167      0.848           FALSE 0.702
    #>    12   quality 10     0.068      0.848           FALSE 0.900
    #>    24  pub_year 10    -0.119      0.849           FALSE 0.800
    #>    24   quality 10     0.236      0.849           FALSE 0.664

## 7. Fragility analysis: `ml_fragility()`

The fragility index is the minimum number of study removals that flip significance at a given time point.

    frag <- ml_fragility(dat, meta,
                         yi = "yi", vi = "vi", study = "study", time = "time",
                         max_k = 1L, seed = 1)
    print(frag)
    #>
    #> === metaLong: Fragility Analysis ===
    #>
    #>  time k_studies p_original sig_original fragility_index fragility_quotient
    #>     0        10      0.005         TRUE              NA                 NA
    #>     6        10      0.000         TRUE              NA                 NA
    #>    12        10      0.001         TRUE              NA                 NA
    #>    24        10      0.001         TRUE              NA                 NA
    #>  study_removed
    #>           <NA>
    #>           <NA>
    #>           <NA>
    #>           <NA>

## 8. Accessing stored fits

    f <- fits(meta)
    cat("Stored model objects:", sum(!sapply(f, is.null)), "/", length(f), "\n")
    #> Stored model objects: 4 / 4

## References

Frank, K. A. (2000). Impact of a confounding variable on a regression coefficient. Sociological Methods & Research, 29(2), 147–194. doi:10.1177/0049124100029002003

Hedges, L. V., Tipton, E., & Johnson, M. C. (2010). Robust variance estimation in meta-regression with dependent effect size estimates. Research Synthesis Methods, 1(1), 39–65. doi:10.1002/jrsm.5

Tipton, E. (2015). Small sample adjustments for robust variance estimation with meta-regression. Psychological Methods, 20(3), 375–393. doi:10.1037/met0000011