Residual balancing is a method of constructing weights for marginal structural models, which can be used to estimate marginal effects of time-varying treatments and controlled direct/mediator effects in causal mediation analysis. Compared with inverse probability-of-treatment weights (IPW), residual balancing weights tend to be more robust and more efficient, and are easier to use with continuous exposures. This package provides two main functions, `rbwPanel()`

and `rbwMed()`

, that produce residual balancing weights for analyzing time-varying treatments and causal mediation, respectively.

**Reference**

- Zhou, Xiang and Geoffrey T Wodtke. 2020. “Residual Balancing: A Method of Constructing Weights for Marginal Structural Models” Political Analysis.

You can install the released version of rbw from CRAN with:

And the development version from GitHub with:

The `rbwPanel()`

function constructs residual balancing weights for estimating marginal effects of time-varying treatments. The following example illustrates its use by estimating the effect of negative campaign advertising (`d.gone.neg`

) on election outcomes (`demprcnt`

) for 113 Democratic candidates in US Senate and Gubernatorial elections.

```
library(rbw)
# install.packages("survey")
library(survey)
#> Warning: package 'survey' was built under R version 4.0.5
# models for time-varying confounders
m1 <- lm(dem.polls ~ (d.gone.neg.l1 + dem.polls.l1 + undother.l1) * factor(week), data = campaign_long)
m2 <- lm(undother ~ (d.gone.neg.l1 + dem.polls.l1 + undother.l1) * factor(week), data = campaign_long)
xmodels <- list(m1, m2)
# residual balancing weights
rbwPanel_fit <- rbwPanel(treatment = d.gone.neg, xmodels = xmodels, id = id, time = week, data = campaign_long)
#> Entropy minimization converged within tolerance level
# merge weights into wide-format data
campaign_wide2 <- merge(campaign_wide, rbwPanel_fit$weights, by = "id")
# fit a marginal structural model (adjusting for baseline confounders)
rbw_design <- svydesign(ids = ~ 1, weights = ~ rbw, data = campaign_wide2)
msm_rbw <- svyglm(demprcnt ~ cum_neg * deminc + camp.length + factor(year) + office, design = rbw_design)
summary(msm_rbw)
#>
#> Call:
#> svyglm(formula = demprcnt ~ cum_neg * deminc + camp.length +
#> factor(year) + office, design = rbw_design)
#>
#> Survey design:
#> svydesign(ids = ~1, weights = ~rbw, data = campaign_wide2)
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 50.39769 2.52269 19.978 < 2e-16 ***
#> cum_neg 0.96579 0.45496 2.123 0.036143 *
#> deminc 17.04229 2.66426 6.397 4.62e-09 ***
#> camp.length -0.09085 0.06175 -1.471 0.144222
#> factor(year)2002 -5.57359 1.53081 -3.641 0.000425 ***
#> factor(year)2004 -6.22630 1.67340 -3.721 0.000322 ***
#> factor(year)2006 -1.51220 1.93697 -0.781 0.436751
#> office 0.02811 1.11988 0.025 0.980026
#> cum_neg:deminc -2.99678 0.65932 -4.545 1.49e-05 ***
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for gaussian family taken to be 25.21453)
#>
#> Number of Fisher Scoring iterations: 2
```

In causal mediation analysis, the `rbwMed()`

function can be used to construct residual balancing weights for estimating the controlled direct effect or the controlled mediator effect with a marginal structural model. The following example illustrates its use by estimating the controlled direct effect of shared democracy (`democ`

) on public support for war (`strike`

) at different levels of perceived morality of war (`immoral`

) for a sample of respondents in a survey experiment.

```
# models for post-treatment confounders
m1 <- lm(threatc ~ ally + trade + h1 + i1 + p1 + e1 + r1 +
male + white + age + ed4 + democ, data = peace)
m2 <- lm(cost ~ ally + trade + h1 + i1 + p1 + e1 + r1 +
male + white + age + ed4 + democ, data = peace)
m3 <- lm(successc ~ ally + trade + h1 + i1 + p1 + e1 + r1 +
male + white + age + ed4 + democ, data = peace)
# residual balancing weights
rbwMed_fit <- rbwMed(treatment = democ, mediator = immoral,
zmodels = list(m1, m2, m3), interact = TRUE,
baseline_x = c(ally, trade, h1, i1, p1, e1, r1, male, white, age, ed4),
data = peace)
#> Entropy minimization converged within tolerance level
# attach residual balancing weights to data
peace$rbw_cde <- rbwMed_fit$weights
# fit marginal structural model
rbw_design <- svydesign(ids = ~ 1, weights = ~ rbw_cde, data = peace)
msm_rbwMed <- svyglm(strike ~ democ * immoral, design = rbw_design)
summary(msm_rbwMed)
#>
#> Call:
#> svyglm(formula = strike ~ democ * immoral, design = rbw_design)
#>
#> Survey design:
#> svydesign(ids = ~1, weights = ~rbw_cde, data = peace)
#>
#> Coefficients:
#> Estimate Std. Error t value Pr(>|t|)
#> (Intercept) 2.74428 0.06255 43.875 < 2e-16 ***
#> democ -0.37399 0.09893 -3.780 0.000164 ***
#> immoral -1.36569 0.15082 -9.055 < 2e-16 ***
#> democ:immoral 0.09091 0.19782 0.460 0.645899
#> ---
#> Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
#>
#> (Dispersion parameter for gaussian family taken to be 1.384994)
#>
#> Number of Fisher Scoring iterations: 2
```