---
title: "Appendix 1: Using cobalt with Other Preprocessing Packages"
author: "Noah Greifer"
date: "`r Sys.Date()`"
output: 
    html_vignette:
        df_print: kable
        toc: true

vignette: >
  %\VignetteIndexEntry{"Appendix 1: Using cobalt with Other Preprocessing Packages"}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r, include = FALSE}
knitr::opts_chunk$set(message = FALSE)
library("cobalt")
```

This is an appendix to the main vignette, "Covariate Balance Tables and Plots: A Guide to the cobalt Package". It contains descriptions and demonstrations of several utility functions in `cobalt` and the use of `bal.tab()` with `twang`, `Matching`, `optmatch`, `CBPS`, and `ebal`. Note that `MatchIt` can perform most of the functions that `Matching` and `optmatch` can, and `WeightIt` completely subsumes and expands the capabilities of `twang`, `CBPS`, and `ebal`. Because `cobalt` has been optimized to work with `MatchIt` and `WeightIt`, it is recommended to use those package to simplify preprocessing and balance assessment, but we recognize users may prefer to use the packages described in this vignette.

## Utilities

In addition to its main balance assessment functions, `cobalt` contains several utility functions. These are meant to reduce the typing and programming burden that often accompany the use of R with a diverse set of packages. 

### `f.build()`

`f.build()` is a small tool that can be helpful in quickly specifying formula inputs to functions. An example is provided below:

```{r}
data("lalonde", package = "cobalt")
covs <- subset(lalonde, select = -c(treat, re78))
f.build("treat", covs)
```

The function creates a `formula` object from two inputs: the first argument is the quoted name of the variable to be the left hand side (response) variable in the formula; the second argument is a vector of right hand side (predictor) variable names or a data frame, the variable names of which are to be the predictor variables. The utility of `f.build()` is that the user does not have to manually type out the name of every covariate when entering a formula into a function. It can be used simply in place of a formula, as in the following examples, which make use of the objects defined above:

```{r, eval = FALSE}
# Generating propensity scores using logistic regression
p.score <- glm(f.build("treat", covs), data = lalonde, family = "binomial")$fitted.values

# Using matchit() from the MatchIt package
library("MatchIt")
m.out <- matchit(f.build("treat", covs), data = lalonde, method = "nearest")
```

`f.build()` can also be used in the `Matching`, `optmatch`, `ebalance`, and formula interfaces in `bal.tab()`.

*Note*: in an earlier version of `cobalt`, the first argument of `f.build()` was unquoted; this has been changed to reap the benefits of standard evaluation, including the ability to loop through response variables.

### `splitfactor()` and `unsplitfactor()`

Some functions (outside of `cobalt`) are not friendly to factor or character variables, and require numeric variables to operate correctly. For example, some regression-style functions, such as `ebalance()` in `ebal`, can only take in non-singular numeric matrices. Other functions will process factor variables, but will return output in terms of dummy coded version of the factors. For example, `lm()` will create dummy variables out of a factor and drop the reference category to create regression coefficients.

To prepare data sets for use in functions that do not allow factors or to mimic the output of functions that split factor variables, users can use `splitfactor()`, which takes in a data set and the names of variables to split, and outputs a new data set with newly created dummy variables. Below is an example splitting the `race` variable in the Lalonde data set into dummies, eliminating the reference category (`"black"`):

```{r}
head(lalonde)
lalonde.split <- splitfactor(lalonde, "race")
head(lalonde.split)
```

It is possible to undo the action of `splitfactor()` with `unsplitfactor()`, which takes in a data set with dummy variables formed from `splitfactor()` or otherwise and recreates the original factor variable. If the reference category was dropped, its value needs to be supplied.

```{r}
lalonde.unsplit <- unsplitfactor(lalonde.split, "race", 
                                 dropped.level = "black")
head(lalonde.unsplit)
```

Notice the original data set and the unsplit data set look identical.

### `get.w()`

`get.w()` allows users to extract weights from the output of a call to a preprocessing function in one of the supported packages. Because each package stores weights in different ways, it can be helpful to have a single function that applies equally to all outputs. `twang` has a function called `get.weights()` that performs the same functions with slightly finer control for the output of a call to `ps()`. See the section "Comparing balancing methods" in the main vignette for examples of the use of `get.w()`.

##`bal.tab()`

The next sections describe the use of `bal.tab()` with packages other than those described in the main vignette. Even if you are using `bal.tab()` with one of these packages, it may be useful to read the main vignette to understand `bal.tab()`'s main options, which are not detailed here.

### Using `bal.tab()` with `twang`

Generalized boosted modeling (GBM), as implemented in `twang`, can be an effective way to generate propensity scores and weights for use in propensity score weighting. `bal.tab()` functions similarly to the functions `bal.table()` and `summary()` when used with GBM in `twang`. Below is a simple example of its use:
```{r, include=FALSE}
if (!requireNamespace("twang")) knitr::opts_chunk$set(eval = FALSE)
```

```{r, warning = FALSE}
library("twang")
data("lalonde", package = "cobalt") ##If not yet loaded
covs0 <- subset(lalonde, select = -c(treat, re78, nodegree, married))

ps.out <- ps(f.build("treat", covs0), data = lalonde, 
             stop.method = c("es.mean", "es.max"), 
             estimand = "ATT", n.trees = 1000, verbose = FALSE)
bal.tab(ps.out, stop.method = "es.mean")
```

The output looks a bit different from `twang`'s `bal.table()` output. First is the original call to `ps()`. Next is the balance table containing mean differences for the covariates included in the input to `ps()`. Last is a table displaying sample size information, similar to what would be generated using `twang`'s `summary()` function. The "effective" sample size is displayed when weighting is used; it is calculated as is done in `twang`. See the `twang` documentation, `?bal.tab`, or "Details on Calculations" in the main vignette for details on this calculation.

When using `bal.tab()` with `twang`, the user must specify the `ps` object, the output of a call to `ps()`, as the first argument. The second argument, `stop.method`, is the name of the stop method(s) for which balance is to be assessed, since a `ps` object may contain more than one if so specified. `bal.tab()` can  display the balance for more than one stop method at a time by specifying a vector of stop method names. If this argument is left empty or if the argument to `stop.method` does not correspond to any of the stop methods in the `ps` object, `bal.tab()` will default to displaying balance for all stop methods available. Abbreviations are allowed for the stop method, which is not case sensitive.

The other arguments to `bal.tab()` when using it with `twang` have the same form and function as those given when using it without a conditioning package, except for `s.d.denom`. If the estimand of the stop method used is the ATT, `s.d.denom` will default to `"treated"` if not specified, and if the estimand is the ATE, `s.d.denom` will default to `"pooled"`, mimicking the behavior of `twang`. The user can specify their own argument to `s.d.denom`, but using the defaults is advised.

If sampling weights are used in the call to `ps()`, they will be automatically incorporated into the `bal.tab()` calculations for both the adjusted and unadjusted samples, just as `twang` does.

`mnps` objects resulting from fitting models in `twang` with multinomial treatments are also compatible with `cobalt`. See the section "Using `cobalt` with multinomial treatments" in the main vignette. `iptw` objects resulting from fitting models in `twang` with longitduinal treatments are also compatible with `cobalt`. See the Appendix 3 vignette.
```{r, include=FALSE, eval=TRUE}
#knitr::opts_chunk$set(eval = TRUE)
```
### Using `bal.tab()` with `Matching`

The `Matching` package is used for propensity score matching, and was also the first package to implement genetic matching. `MatchIt` calls `Matching` to use genetic matching and can accomplish many of the matching methods `Matching` can, but `Matching` is still a widely used package with its own strengths. `bal.tab()` functions similarly to `Matching`'s `MatchBalance()` command, which yields a thorough presentation of balance, and makes `Matching` the only package of those integrated with `cobalt` to display variance ratios by default. Below is a simple example of the use of `bal.tab()` with `Matching`:
```{r, include=FALSE}
#if (!requireNamespace("Matching")) knitr::opts_chunk$set(eval = FALSE)
```

```{r}
library("Matching")
data("lalonde", package = "cobalt") #If not yet loaded
covs0 <- subset(lalonde, select = -c(treat, re78, nodegree, married))

fit <- glm(f.build("treat", covs0), data = lalonde, family = "binomial")
p.score <- fit$fitted.values
match.out <- Match(Tr = lalonde$treat, X = p.score, estimand = "ATT")

bal.tab(match.out, formula = f.build("treat", covs0), data = lalonde)
```

The output looks quite different from `Matching`'s `MatchBalance()` output. Rather than being stacked vertically, balance statistics are arranged horizontally in a table format, allowing for quick balance checking. Below the balance table is a summary of the sample size before and after matching, similar to what `Matching`'s `summary()` command would display. The sample size can include a "weighted" and "unweighted" count; the "weighted" count is the sum of the matching weights, while the  "unweighted" is the count of units with nonzero matching weights. 

The input to `bal.tab()` is similar to that given to `MatchBalance()`: the `Match` object resulting from the call to `Match()`, a formula relating treatment to the covariates for which balance is to be assessed, and the original data set. This is not the only way to call `bal.tab()`: instead of a formula and a data set, one can also input a data frame of covariates and a vector of treatment status indicators, just as when using `bal.tab()` wihout a conditioning package. For example, the code below will yield the same results as the call to `bal.tab()` above:

```{r, eval = FALSE}
bal.tab(match.out, treat = lalonde$treat, covs = covs0)
```

The other arguments to `bal.tab()` when using it with `Matching` have the same form and function as those given when using it without a conditioning package, except for `s.d.denom`. If the estimand of the original call to `Match()` is the ATT, `s.d.denom` will default to `"treated"` if not specified; if the estimand is the ATE, `s.d.denom` will default to `"pooled"`; if the estimand is the ATC, `s.d.denom` will default to `"control"`. The user can specify their own argument to `s.d.denom`, but using the defaults is advisable. In addition, the use of the `addl` argument is unnecessary because the covariates are entered manually as arguments, so all covariates for which balance is to be assessed can be entered through the `formula` or `covs` argument. If the covariates are stored in two separate data frames, it may be useful to include one in `formula` or `covs` and the other in `addl`.
```{r, include=FALSE, eval=TRUE}
#knitr::opts_chunk$set(eval = TRUE)
```
### Using `bal.tab()` with `optmatch`

The `optmatch` package is useful for performing optimal pairwise or full matching. Most functions in `optmatch` are subsumed in `MatchIt`, but `optmatch` sees use from those who want finer control of the matching process than `MatchIt` allows. The output of calls to functions in `optmatch` is an `optmatch` object, which contains matching stratum membership for each unit in the given data set. Units that are matched with each other are assigned the same matching stratum. The user guide for `optmatch` recommends using the `RItools` package for balance assessment, but below is an example of how to use `bal.tab()` for the same purpose. Note that some results will differ between `cobalt` and `RItools` because of differences in how balance is calculated in each.
```{r, include=FALSE}
#if (!requireNamespace("optmatch")) knitr::opts_chunk$set(eval = FALSE)
```
```{r}
#Optimal full matching on the propensity score
library("optmatch")
data("lalonde", package = "cobalt") #If not yet loaded
covs0 <- subset(lalonde, select = -c(treat, re78, nodegree, married))

fit <- glm(f.build("treat", covs0), data = lalonde, family = "binomial")
lalonde$p.score <- fit$fitted.values #get the propensity score
fm <- fullmatch(treat ~ p.score, data = lalonde)

bal.tab(fm, formula = f.build("treat", covs0), data = lalonde)
```
```{r, include=FALSE, eval=TRUE}
#knitr::opts_chunk$set(eval = TRUE)
```
Most details for the use of `bal.tab()` with `optmatch` are similar to those when using `bal.tab()` with `Matching`. Users can enter either a formula and a data set or a vector of treatment status and a set of covariates. 

### Using `bal.tab()` with `CBPS`

The `CBPS` (Covariate Balancing Propensity Score) package is a great tool for generating covariate balancing propensity scores, a class of propensity scores that are quite effective at balancing covariates among groups. `CBPS` includes functions for estimating propensity scores for binary, multinomial, and continuous treatments. `bal.tab()` functions similarly to `CBPS`'s `balance()` command. Below is a simple example of its use with a binary treatment:
```{r, include=FALSE}
#if (!requireNamespace("CBPS")) knitr::opts_chunk$set(eval = FALSE)
```
```{r}
library("CBPS")
data("lalonde", package = "cobalt") #If not yet loaded
covs0 <- subset(lalonde, select = -c(treat, re78, nodegree, married))

#Generating covariate balancing propensity score weights for ATT
cbps.out <- CBPS(f.build("treat", covs0), data = lalonde)

bal.tab(cbps.out)
```

First is the original call to `CBPS()`. Next is the balance table containing mean differences for the covariates included in the input to `CBPS()`. Last is a table displaying sample size information. The "effective" sample size is displayed when weighting (rather than matching or subclassification) is used; it is calculated as is done in `twang`. See the `twang` documentation, `?bal.tab`, or "Details on Calculations" in the main vignette for details on this calculation.

The other arguments to `bal.tab()` when using it with `CBPS` have the same form and function as those given when using it without a conditioning package, except for `s.d.denom`. If the estimand of the original call to `CBPS()` is the ATT, `s.d.denom` will default to `"treated"` if not specified, and if the estimand is the ATE, `s.d.denom` will default to `"pooled"`. The user can specify their own argument to `s.d.denom`, but using the defaults is advisable.

When using `CBPS` and `bal.tab()` with continuous treatments, the same guidelines apply as when using `bal.tab()` with continuous treatments without a conditioning package. See the section "Using cobalt with continuous treatments" in the main vignette for more details.

`CBPSContinuous` objects resulting from fitting models in `CBPS` with continuous treatments are also compatible with `cobalt`. See the section "Using `cobalt` with continuous treatments" in the main vignette. `CBPS` objects resulting from fitting models in `CBPS` with multinomial treatments are also compatible with `cobalt`. See the section "Using `cobalt` with multinomial treatments" in the main vignette. `CBMSM` objects resulting from fitting models in `CBPS` with longitudinal treatments are also compatible with `cobalt`. See the Appendix 3 vignette.
```{r, include=FALSE, eval=TRUE}
#knitr::opts_chunk$set(eval = TRUE)
```
### Using `bal.tab()` with `ebal`

The `ebal` package implements entropy balancing, a method of weighting for the ATT that yields perfect balance on all desired moments of the covariate distributions between groups. Rather than estimate a propensity score, entropy balancing generates weights directly that satisfy a user-defined moment condition, specifying which moments are to be balanced. `ebal` does not have its own balance assessment function; thus, `cobalt` is the only way to assess balance without programming, which the `ebal` documentation instructs. Below is a simple example of using `bal.tab()` with `ebal`:
```{r, include=FALSE}
#if (!requireNamespace("ebal")) knitr::opts_chunk$set(eval = FALSE)
```
```{r}
library("ebal")
data("lalonde", package = "cobalt") #If not yet loaded
covs0 <- subset(lalonde, select = -c(treat, re78, race))

#Generating entropy balancing weights
e.out <- ebalance(lalonde$treat, covs0)

bal.tab(e.out, treat = lalonde$treat, covs = covs0)
```

First is the balance table containing mean differences for covariates included in the original call to `ebalance`. In general, these will all be very close to 0. Next is a table displaying effective sample size information. The "effective" sample size is calculated as is done in `twang`. See the `twang` documentation, `?bal.tab`, or "Details on Calculations" in the main vignette for details on this calculation. A common issue when using entropy balancing is small effective sample size, which can yield low precision in effect estimation when using weighted regression, so it is important that users pay attention to this measure. That said, in simulations by Zhao and Percival (2015), entropy balancing reliably had smaller empirical standard errors than did covariate balancing propensity scores.

The input is similar to that for using `bal.tab()` with `Matching` or `optmatch`. In additon to the `ebalance` object, one must specify either both a formula and a data set or both a treatment vector and a data frame of covariates.

```{r, include=FALSE}
#knitr::opts_chunk$set(eval = TRUE)
```