# Covariate Balance Tables and Plots: A Guide to the cobalt Package

#### 2018-03-12

This is an introductory guide for the use of cobalt in most common scenarios. Two appendices are available for its use with more complicated data scenarios.1

# Introduction

Preprocessing data through matching, weighting, or subclassification can be an effective way to reduce model dependence and improve efficiency when estimating the causal effect of a treatment (Ho, Imai, King, & Stuart, 2007). Propensity scores and other related methods (e.g., coarsened exact matching, Mahalanobis distance matching, genetic matching) have become popular in the social and health sciences as tools for this purpose. Two excellent introductions to propensity scores and other preprocessing methods are Stuart (2010) and Austin (2011), which describe them simply and clearly and point to other sources of knowledge. The logic and theory behind preprocessing will not be discussed here, and reader’s knowledge of the causal assumption of strong ignorability is assumed.

Several packages in R exist to perform preprocessing and causal effect estimation, and some were reviewed by Keller & Tipton (2016). Of primary note are MatchIt (Ho, Imai, King, & Stuart, 2011), twang (Ridgeway, McCaffrey, Morral, Burgette, & Griffin, 2016), Matching (Sekhon, 2011), optmatch (Hansen & Klopfer, 2006), CBPS (Fong, Ratkovic, Hazlett, Yang, & Imai, 2016), ebal (Hainmueller, 2014) and WeightIt (Greifer, 2017); these together provide a near complete set of preprocessing tools in R to date.

The following are the basic steps in performing a causal analysis using data preprocessing (Stuart, 2010):

1. Decide on covariates for which balance must be achieved
2. Estimate the distance measure (e.g., propensity score)
3. Condition on the distance measure (e.g., using matching, weighting, or subclassification)
4. Assess balance on the covariates of interest; if poor, repeat steps 2-4
5. Estimate the treatment effect in the conditioned sample

Steps 2, 3, and 4 are accomplished by all of the packages mentioned above. However, Step 4, assessing balance, is often overlooked in propensity score applications, with researchers failing to report the degree of covariate balance achieved by their conditioning (Thoemmes & Kim, 2011). Achieving balance is the very purpose of preprocessing because covariate balance is what justifies ignorability on the observed covariates, allowing for the potential for a valid causal inference after effect estimation (Ho et al., 2007).

In addition to simply achieving balance, researchers must also report balance to convince readers that their analysis was performed adequately and that their causal conclusions are valid (Thoemmes & Kim, 2011). Covariate balance is typically assessed and reported by using statistical measures, including standardized mean differences, variance ratios, and t-test or Kolmogorov-Smirnov-test p-values. Balance can be reported in an article by means of balance tables or plots displaying the balance measures before and after conditioning. If a defensible measure of balance is used and presented, readers are empowered to judge for themselves whether the causal claim made is valid or not based on the methods used and covariates chosen.

cobalt is meant to supplement or replace the balance assessment tools in the above packages and allow researchers to assess and report balance on covariates simply, clearly, and flexibly before and after conditioning. It integrates seamlessly with the above packages so that users can employ both the conditioning package of their choice and cobalt in conjunction to assess and report balance. It is important to note that cobalt does not replace the highly sophisticated conditioning tools of these packages, as it does no conditioning or estimation of its own.

The rest of this guide explains how to use cobalt with the above packages and others, as well as the choices instituted by the functions and customizable by the user.

### Citing cobalt

When using cobalt, please cite your use of it along with the conditioning package used. The full APA reference for cobalt is the following:

Greifer, N. (2018). cobalt: Covariate Balance Tables and Plots. R package version 3.2.0.

For example, if you use Matching for propensity score estimation and matching and cobalt for balance assessment and/or reporting, a possible citation might go as follows:

Matching was performed using the Matching package (Sekhon, 2011), and covariate balance was assessed using cobalt (Greifer, 2018), both in R (R Core Team, 2017).

# Why cobalt?

If most of the major conditioning packages contain functions to assess balance, why use cobalt at all? cobalt arose out of several desiderata when using these packages: to have standardized measures that were consistent across all conditioning packages, to allow for flexibility in the calculation and display of balance measures, and to incorporate recent methodological recommendations in the assessment of balance. However, some users of these packages may be completely satisfied with their capabilities and comfortable with their output; for them, cobalt still has value in its unique plotting capabilities that make use of ggplot2 in R.

The following are some reasons why cobalt may be attractive to users of MatchIt, twang, Matching, optmatch, CBPS, ebal, and WeightIt:

### Visual clarity

cobalt presents one table in its balance output, and it contains all the information required to assess balance. twang and CBPS present two tables, MatchIt presents three tables, and Matching presents as many tables as there are covariates. Although each of these tables contains valuable information, the bal.tab() function in cobalt allows for a quick and easy search for the information desired, which is often a single column containing a balance statistic (such as the standardized mean difference) for the adjusted sample.

### Useful summaries

Although a thorough balance assessment requires examining the balance of each covariate individually, cobalt’s bal.tab() function can also produce quick balance summaries that can aid in model selection when there are many covariates or higher order terms to examine. These summaries include the proportion of covariates that have met a user-specified threshold for balance and the covariate with the highest degree of imbalance, two values that have been shown to be effective in diagnosing imbalance and potential bias (Stuart, Lee, & Leacy, 2013).

### One tool to rule them all

Because there is no a priori way to know which conditioning method will work best for a given sample, users should try several methods, and these methods are spread across various packages; for example, full matching is available only in MatchIt and optmatch, generalized boosted modeling only in twang, covariate balancing propensity score weighting only in CBPS, genetic matching only in MatchIt and Matching, and entropy balancing only in ebal2. If a user wants to compare these methods on their ability to generate balance in the sample, they cannot do so on the same metrics and with the same output. Each package computes balance statistics differently (if at all), and the relevant balance measures are in different places in each package. By using cobalt to assess balance across packages, users can be sure they are using a single, equivalent balance metric across methods, and the relevant balance statistics will be in the same place and computed the same way regardless of the conditioning package used.

### Flexibility

cobalt gives users choice in which statistics are presented and how they are calculated, but intelligently uses defaults that are in line with the goals of unified balance assessment and with the data available. Rather than displaying all values calculated, bal.tab() only displays what the user wants; at a bare minimum, the standardized mean difference for each covariate is displayed, which is traditionally considered sufficient for model selection and justification in preprocessing analysis for binary treatments. Even if the user doesn’t want other values displayed, they are all still calculated, and thus available for use in programming (though this can be disabled for increased speed).

### Pretty plots

The main conditioning packages produce plots that can be useful in assessing balance, summarizing balance, and understanding the intricacies of the conditioning method for which simple text would be insufficient. Many of these plots are unique to each package, and cobalt has not attempted to replace or replicate them. For other plots, though, cobalt uses ggplot2 to present clean, clear, customizable, and high-quality displays for balance assessment and presentation. The two included plotting functions are bal.plot(), which generates plots of the distributions of covariates and treatment levels so that more complete distributional balance can be assessed beyond numerical summaries, and love.plot(), which generates a plot summarizing covariate balance before and after conditioning, popularized by Dr. Thomas E. Love. Because these plots use ggplot2 as their base, users familiar with ggplot2 can customize various elements of the plots for use in publications or presentations.

### Unique features

There are unique features in cobalt that do not exist in any other package. These include the handling of clustered and grouped data and the handling of data generated with multiple imputation. These more advanced uses of cobalt are described in detail in the accompanying Appendix 2. In addition, cobalt includes tools for handling data sets with continuous and multinomial treatments.

# How To Use cobalt

There are three main functions for use in cobalt: bal.tab(), bal.plot(), and love.plot(). There are also several utility functions which can be used to ease the use of cobalt and other packages. The next sections describe how to use each, complete with example code and output. To start, install and load cobalt with the following code:

## install.packages("cobalt")
library("cobalt")

###Utilities

In addition to its main functions, cobalt contains several utility functions, which include f.build(), splitfactor() and unsplitfactor(), and get.w(). These are meant to reduce the typing and programming burden that often accompany the use of R with a diverse set of packages. To simplify this vignette, decriptions of these functions are in Appendix 1. To understand the code in this vignette, you should be aware of f.build(), which creates a formula from its inputs, and get.w() which extracts weights from its input.

## bal.tab()

bal.tab() is the primary function of cobalt. It produces balance tables for the objects given as inputs. The balance tables can be customized with a variety of inputs, which affect both calculation and presentation of values. It performs similar functions to summary() in MatchIt; bal.table(), summary(), and dx.wts() in twang; MatchBalance() and summary() in Matching; and balance() in CBPS. It can be seen as a replacement or a supplement to these functions.

For more help using bal.tab(), see ?bal.tab in R, which contains information on how certain values are calculated and links to the help files for the bal.tab() methods that integrate with the above packages.

For simplicity, the description of the use of bal.tab() will be most complete in its use without any other package. The demonstration will display bal.tab()’s many options, several of which differ based on with which package, if any, bal.tab() is used. The other demonstrations will be minimal, highlighting how to use bal.tab() effectively with MatchIt and WeightIt, but not detailing all its possible options with these packages, to avoid redundancy. The use of bal.tab() with other packages is described in the accompanying Appendix 1, “Using cobalt with Other Preprocessing Packages”.

### Using bal.tab() on its own

bal.tab() can take in any data set and set of weights, subclasses, or matching strata and evaluate balance on them. This can be useful if propensity score weights, subclasses, or matching strata were generated outside of the supported packages, if balance assessment is desired prior to adjustment, or if package output is adjusted in such a way as to make it unusable with one of bal.tab()’s other methods (e.g., if cases were manually removed or weights manually changed). In twang, the function dx.wts() performs a similar action by allowing for the balance assessment of groups weighted not using GBM, but it does not provide support for matching strata (e.g., those generated from optmatch) or subclasses. Below is an example of the use of bal.tab() with ATT weights generating using logistic regression for a weighting-by-the-odds analysis:

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

# Generating ATT weights as specified in Austin (2011)
lalonde$p.score <- glm(f.build("treat", covs0), data = lalonde, family = "binomial")$fitted.values
lalonde$att.weights <- with(lalonde, treat + (1-treat)*p.score/(1-p.score)) bal.tab(covs0, treat = lalonde$treat, weights = lalonde$att.weights, method = "weighting") ## Balance Measures: ## Type Diff.Adj ## age Contin. 0.1112 ## educ Contin. -0.0641 ## race_black Binary -0.0044 ## race_hispan Binary 0.0016 ## race_white Binary 0.0028 ## re74 Contin. -0.0039 ## re75 Contin. -0.0428 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 Displayed first is the balance table, and last is a summary of sample size information. Because weighting was specified as the method used, effective sample sizes are given, as is done in twang. See the twang documentation, ?bal.tab, or “Details on Calculations” below for details on this calculation. There are several ways to specify input to bal.tab() when using data outside a conditioning package. The first, as shown above, is to use a data frame of covariates and vectors for treatment status and weights or subclasses. The user can additionally specify a vector of distance measures (e.g., propensity scores) if balance is to be assessed on those as well. If weights is left empty, balance on the unadjusted sample will be reported. The user can also optionally specify a data set to the data argument; this makes it so that the arguments to treat, weights, distance, subclass, and others can be specified either with a vector or with the name of a variable in the argument to data that contains the respective values. Another way to specify input to bal.tab() is to use the formula interface. Below is an example of its use: bal.tab(f.build("treat", covs0), data = lalonde, weights = "att.weights", distance = "p.score", method = "weighting") ## Balance Measures: ## Type Diff.Adj ## p.score Distance -0.0397 ## age Contin. 0.1112 ## educ Contin. -0.0641 ## race_black Binary -0.0044 ## race_hispan Binary 0.0016 ## race_white Binary 0.0028 ## re74 Contin. -0.0039 ## re75 Contin. -0.0428 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 To use the formula interface, the user must specify a formula relating treatment to the covariates for which balance is to be assessed and the data set where the elements of the formula can be found. Here, f.build() was used for simplicity, but the traditional formula input of treat ~ v1 + v2 + v3 + ... is also acceptible. As above, the arguments to weights, distance, subclass, and others can be specified either as vectors or data frames containing the values or as names of the variables in the argument to data containing the values. In the above example, an argument to distance was specified, and balance measures for the propensity score now appear in the balance table. An argument to method must be specified for bal.tab() to process the data correctly. If left empty, weighting will be assumed, but a warning will appear. "matching" and "subclassification" are also allowed, and can be abbreviated. If no weights or subclasses are provided (i.e., to examine balance without preprocessing), no argument to method is required. By default, bal.tab() outputs standardized mean differences for continuous variables and raw differences in proportion for binary variables. For more details on how these values are computed and determined, see ?bal.tab or “Details on Calculations” below. To see raw or standardized mean differences for binary or continuous variables, you can manually set binary and/or continuous to "raw" or "std". bal.tab(f.build("treat", covs0), data = lalonde, weights = "att.weights", method = "weighting", binary = "std", continuous = "std") ## Balance Measures: ## Type Diff.Adj ## age Contin. 0.1112 ## educ Contin. -0.0641 ## race_black Binary -0.0120 ## race_hispan Binary 0.0068 ## race_white Binary 0.0093 ## re74 Contin. -0.0039 ## re75 Contin. -0.0428 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 Users can specify additional variables for which to display balance using the argument to addl. Users can also add all squared terms and two-way interactions between covariates, including those in addl, by specifying int = TRUE. Interactions will not be computed for the distance measure (i.e., the propensity score), and squared terms will not be computed for binary variables. For more details on interactions, see “Details on Calculations”, below. # Balance on all covariates in data set, including interactions and squares bal.tab(f.build("treat", covs0), data = lalonde, weights = "att.weights", method = "weighting", addl = c("nodegree", "married"), int = TRUE) ## Balance Measures: ## Type Diff.Adj ## age Contin. 0.1112 ## educ Contin. -0.0641 ## race_black Binary -0.0044 ## race_hispan Binary 0.0016 ## race_white Binary 0.0028 ## re74 Contin. -0.0039 ## re75 Contin. -0.0428 ## nodegree Binary 0.1151 ## married Binary -0.0938 ## age_2 Contin. -0.0194 ## educ_2 Contin. -0.1353 ## re74_2 Contin. 0.0675 ## re75_2 Contin. 0.0196 ## age_educ Contin. 0.0950 ## age_race_black Contin. 0.0741 ## age_race_hispan Contin. -0.0096 ## age_race_white Contin. -0.0013 ## age_re74 Contin. -0.0498 ## age_re75 Contin. -0.0144 ## age_nodegree Contin. 0.2507 ## age_married Contin. -0.2843 ## educ_race_black Contin. -0.0441 ## educ_race_hispan Contin. 0.0122 ## educ_race_white Contin. 0.0085 ## educ_re74 Contin. -0.0149 ## educ_re75 Contin. -0.0834 ## educ_nodegree Contin. 0.3051 ## educ_married Contin. -0.2453 ## race_black_re74 Contin. 0.0477 ## race_black_re75 Contin. -0.0297 ## race_black_nodegree Binary 0.1067 ## race_black_married Binary -0.0579 ## race_hispan_re74 Contin. -0.0129 ## race_hispan_re75 Contin. 0.0258 ## race_hispan_nodegree Binary 0.0084 ## race_hispan_married Binary -0.0060 ## race_white_re74 Contin. -0.2488 ## race_white_re75 Contin. -0.0926 ## race_white_nodegree Binary 0.0001 ## race_white_married Binary -0.0300 ## re74_re75 Contin. 0.0636 ## re74_nodegree Contin. 0.0467 ## re74_married Contin. -0.1122 ## re75_nodegree Contin. 0.1487 ## re75_married Contin. -0.1177 ## nodegree_married Binary 0.0079 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 Standardized mean differences can be computed several ways, and the user can decide how bal.tab() does so using the argument to s.d.denom, which controls whether the measure of spread in the denominator is the standard deviation of the treated group ("treated"), most apprpriate when computing the ATT; the standard deviation of the control group ("control"), most appropriate when computing the ATC; or the pooled standard deviation ("pooled"), computed as in Austin (2009, p. 3087), most appropriate when computing the ATE. If using weights for weighted analysis, bal.tab() can determine if the ATT or ATC are being estimated and supply s.d.denom accordingly. Otherwise, the default is "pooled", and when matching or subclassification are used, the default is "treated". See Austin (2011) for an explanation on how to calculate weights for estimation of the ATT, ATE, and ATC. The next options only affect display, not the calculation of any statistics. First is disp.means, which controls whether the group means for each covariate are displayed in addition to the mean differences. This can be useful in reporting balance for publication, but in balance assessment mean differences are typically more useful. Next is disp.v.ratio, which controls whether variance ratios are displayed, in addition to mean differences. Variance ratios are another important tool for assessing balance beyond mean differences because they pertain to the shape of the covariate distributions beyond their centers. Variance ratios close to 1 (i.e., equal variances in both groups) are indicative of group balance (Austin, 2009). Next is disp.ks, which controls whether Kolmogorov-Smirnov (KS) statistics are displayed. KS statistics measure the greatest distance between the emiprical cumulative dsitribution functions (ECDFs) for each variable between two groups. The statistic is bounded at 0 and 1, with 0 indicting perfectly identical distributions and 1 indicating perfect seperation between the distributions (i.e., no overlap at all); values close to 0 are thus indicative of balance. The use of the KS statistic to formally assess balance is debated. Intutively, it makes sense to use it to diagnose balance because the interpretation and computation of the statistics are directly related to balance. Austin & Stuart (2015) recommend its use, and it or a variant appears as a default balance statistic in MatchIt, twang, and Matching. On the other hand, Belitser et al. (2011), Stuart et al. (2013), and Ali et al. (2014) all found that global balance assessments using the KS statistic performed uniformly worse than standardized mean differences, esepcially at sample sizes less than 1000 in their simulations. Next is un, which controls whether the statistics to be displayed should be displayed for the unadjusted group as well. This can be useful the first time balance is assessed to see the initial group imbalance. Setting un = FALSE, which is the default, can declutter the output to maintain the spotlight on the group balance after adjustment. # Balance tables with variance ratios and statistics for the unadjusted sample bal.tab(f.build("treat", covs0), data = lalonde, weights = "att.weights", method = "weighting", disp.v.ratio = TRUE, un = TRUE) ## Balance Measures: ## Type Diff.Un V.Ratio.Un Diff.Adj V.Ratio.Adj ## age Contin. -0.3094 2.2728 0.1112 1.9663 ## educ Contin. 0.0550 2.0166 -0.0641 1.6616 ## race_black Binary 0.6404 -0.0044 ## race_hispan Binary -0.0827 0.0016 ## race_white Binary -0.5577 0.0028 ## re74 Contin. -0.7211 1.9300 -0.0039 1.4821 ## re75 Contin. -0.2903 1.0457 -0.0428 1.1712 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 Finally, the user can specify a threshold for balance for mean differences, variance ratios, and KS statistics through the arguments to m.threshold, v.threshold, and ks.threshold. Thresholds can be useful in determining whether satisfactory balance has been achieved. For standardized mean differences, thresholds of .1 and .25 have been proposed, but Stuart et al. (2013) found that a threshold of .1 was more effective at assessing imbalance that would lead to biased effect estimation. In general, standardized mean differences should be as close to 0 as possible, but a conservative upper limit such as .1 can be a valuable heuristic in selecting models and defending the conditioning choice. When m.threshold is specified, a few components are added to the balance output: an extra column in the balance table stating whether each covariate is or is not balanced according to the threshold; an extra table below the balance table with a tally of how many covariates are or are not balanced according to the threshold; and a notice of which covariate has the greatest imbalance after conditioning and whether it exceeded the threshold. Using a threshold for the mean difference implies that the differences are standardized (at least for continuous variables), because the threshold is meant to be scale invariant, which standardized mean differences are. The argument to v.threshold determines the threshold for variance ratios that are acceptable for balance. Variance ratios are calculated in cobalt such that the greater variance is in the numerator, so they will always be greater than or equal to 1. Variance ratios close to 1 are indicative of good balance, but ratios as large as 2 may be acceptable (Rubin, 2001). When v.threshold is specified, the same additional output is created as above, except in evaluation of variance ratios. Because variance ratios are not displayed in the balance table by default, specifying v.threshold will also display variance ratios in the table. When ks.threshold is specified, the same additional output is created as above, except in evaluation of KS statistics. Because KS statistics are not displayed in the balance table by default, specifying ks.threshold will also display KS statistics in the table. The value provided to ks.threshold should be between 0 and 1. Though no research has been done on acceptible thresholds for the KS statistics, we recommend seeking values less than 0.05 for continuous variables, which roughly corresponds to the KS statistic computed for two normally distributed samples wih a standardized mean difference of 0.1. For binary variables, KS statistics are not produced because they are equal to the raw difference in proportion. # Balance tables with thresholds for mean differences and variance ratios bal.tab(f.build("treat", covs0), data = lalonde, weights = "att.weights", method = "weighting", m.threshold = .1, v.threshold = 2) ## Balance Measures: ## Type Diff.Adj M.Threshold V.Ratio.Adj V.Threshold ## age Contin. 0.1112 Not Balanced, >0.1 1.9663 Balanced, <2 ## educ Contin. -0.0641 Balanced, <0.1 1.6616 Balanced, <2 ## race_black Binary -0.0044 Balanced, <0.1 ## race_hispan Binary 0.0016 Balanced, <0.1 ## race_white Binary 0.0028 Balanced, <0.1 ## re74 Contin. -0.0039 Balanced, <0.1 1.4821 Balanced, <2 ## re75 Contin. -0.0428 Balanced, <0.1 1.1712 Balanced, <2 ## ## Balance tally for mean differences: ## count ## Balanced, <0.1 6 ## Not Balanced, >0.1 1 ## ## Variable with the greatest mean difference: ## Variable Diff.Adj M.Threshold ## age 0.1112 Not Balanced, >0.1 ## ## Balance tally for variance ratios: ## count ## Balanced, <2 4 ## Not Balanced, >2 0 ## ## Variable with the greatest variance ratio: ## Variable V.Ratio.Adj V.Threshold ## age 1.9663 Balanced, <2 ## ## Effective sample sizes: ## Control Treated ## Unadjusted 429.0 185 ## Adjusted 108.2 185 To simplify output when many covariates are included or when int = TRUE is specified, imbalanced.only can be set to TRUE, which will only reveal imbalanced covariates in the output. These rae covariates that have failed to meet any of the balance thresholds set. In addition, disp.bal.tab can be set TRUE, which will hide the balance table (revealing only the balance summaries accompanying the threshold). If sampling weights are used and are to be applied to both the adjusted and unadjusted groups, they can be specified with an argument to s.weights, which can be specified either by providing a vector of sampling weights for each unit or by providing the name of a variable in data containing the sampling weights. The adjusted and unadjusted samples will each be weighted by the sampling weights by multiplying the adjustment weights (if any) by the sampling weights. It is possible to view balance for more than one set of weights at a time. The input to weights should be either the names of variables in data containing the desired weights or a named data frame containing each set of weights. The arguments to method and s.d.denom or estimand must have the same length as the number of sets of weights, or else be of length 1, applying the sole input to all sets of weights. Below is an example comparing the weights estimated above to a new set of weights. Another example can be found in the section “Comparing Balancing Methods”. # Generating ATT weights with different covariates lalonde$p.score2 <- glm(treat ~ age + I(age^2) + race + educ + re74,
data = lalonde, family = "binomial")$fitted.values lalonde$att.weights2 <- with(lalonde, treat + (1-treat)*p.score2/(1-p.score2))

bal.tab(f.build("treat", covs0), data = lalonde, weights = c("att.weights", "att.weights2"),
method = "weighting", estimand = "ATT")
## Balance Measures:
##                Type Diff.att.weights Diff.att.weights2
## age         Contin.           0.1112           -0.0400
## educ        Contin.          -0.0641           -0.0227
## race_black   Binary          -0.0044           -0.0005
## race_hispan  Binary           0.0016           -0.0010
## race_white   Binary           0.0028            0.0015
## re74        Contin.          -0.0039            0.0037
## re75        Contin.          -0.0428           -0.0083
##
## Effective sample sizes:
##              Control Treated
## All           429.00     185
## att.weights   108.20     185
## att.weights2   72.04     185

When subclassification is used in conditioning, an argument to subclass must be specified; this can be a vector of subclass membership or the name of a variable in data containing subclass membership. bal.tab() produces a different type of output from when matching or weighting are used, though it has all of the same features. The default output is a balance table displaying balance aggregated across subclasses. Each cell contains the average statistic across the subclasses, but does not contain information on variance ratios. Using the arguments discussed above (except disp.v.ratio and v.threshold) will change the output as it does when only matching or weighting is used.

To examine balance within each subclass, the user can specify disp.subclass = TRUE, which will produce output for the subclasses in aggregate as well as for each subclass. Within subclasses, all the information above, including variance ratios, will be presented, except for statistics for the unadjusted groups (since the adjustment occurs by creating the subclasses), as specified by the user. See ?bal.tab.subclass for more details.

# Subclassification for ATT with 6 subclasses
lalonde$p.score <- glm(f.build("treat", covs0), data = lalonde, family = "binomial")$fitted.values
nsub <- 5 #number of subclasses
lalonde$subclass <- findInterval(lalonde$p.score,
quantile(lalonde$p.score[lalonde$treat == 1],
seq(0, 1, length.out = nsub + 1)),
all.inside = T)

bal.tab(f.build("treat", covs0), data = lalonde, subclass = "subclass",
method = "subclassification", disp.subclass = TRUE)
## Balance by subclass:
##  - - - Subclass 1 - - -
## age         Contin.  -0.4040
## educ        Contin.   0.1074
## race_black   Binary   0.1934
## race_hispan  Binary   0.1230
## race_white   Binary  -0.3164
## re74        Contin.  -0.1466
## re75        Contin.  -0.0414
##
##  - - - Subclass 2 - - -
## age         Contin.  -0.2868
## educ        Contin.   0.1922
## race_black   Binary   0.0000
## race_hispan  Binary   0.0000
## race_white   Binary   0.0000
## re74        Contin.   0.0598
## re75        Contin.   0.2594
##
##  - - - Subclass 3 - - -
## age         Contin.   0.6204
## educ        Contin.  -0.1802
## race_black   Binary   0.0000
## race_hispan  Binary   0.0000
## race_white   Binary   0.0000
## re74        Contin.  -0.4049
## re75        Contin.  -0.2909
##
##  - - - Subclass 4 - - -
## age         Contin.   0.2034
## educ        Contin.  -0.4610
## race_black   Binary   0.0000
## race_hispan  Binary   0.0000
## race_white   Binary   0.0000
## re74        Contin.  -0.7676
## re75        Contin.  -0.0716
##
##  - - - Subclass 5 - - -
## age         Contin.   0.2697
## educ        Contin.  -0.1214
## race_black   Binary   0.0000
## race_hispan  Binary   0.0000
## race_white   Binary   0.0000
## re74        Contin.  -0.4968
## re75        Contin.  -0.2319
##
## Balance measures across subclasses:
## age         Contin.  -0.1855
## educ        Contin.   0.0194
## race_black   Binary   0.1219
## race_hispan  Binary   0.0775
## race_white   Binary  -0.1994
## re74        Contin.  -0.2354
## re75        Contin.  -0.0534
##
## Sample sizes:
##         Subclass 1 Subclass 2 Subclass 3 Subclass 4 Subclass 5
## Control        350         25         21         14         19
## Treated         37         37         34         40         37
## Total          387         62         55         54         56

When using bal.tab() with continuous treatments, the balance statistic presented is the (weighted) Pearson correlation between each covariate and treatment. Because of this, no parameters related to the computation of mean differences or variance ratios need to be specified. An additional parameter, r.threshold, functions like m.threshold and v.threshold and specifies the threshold for the absolute correlation between each covariate and treatment. Presently, there are no specific guidelines for the value r.threshold should take, but correlations close to 0 indicate (linear) independnece between each covariate and treatment, the same way mean differences close to 0 and variance ratios close to 1 indicate covariate-treatment independence. See the section “Using cobalt with continuous treatments” for more details.

The next two sections describe the use of bal.tab() with the MatchIt and WeightIt. As stated above, the arguments controlling calculations and display are largely the same across inputs types, so they will not be described again except when their use differs from that described in the present section.

### Using bal.tab() with MatchIt

When using bal.tab() with MatchIt, fewer arguments need to be specified because information is stored in the matchit object, the output of a call to matchit(). bal.tab() is used very similarly to summary() in MatchIt: it takes in a matchit object as its input, and prints a balance table with the requested information. Below is a simple example of its use:

data("lalonde", package = "cobalt")
covs0 <- subset(lalonde, select = -c(treat, re78, nodegree, married))

# Nearest neighbor 2:1 matching with replacement
m.out <- matchit(f.build("treat", covs0), data = lalonde, method = "nearest",
ratio = 1,  replace = TRUE)

bal.tab(m.out)
##
## Call:
##   matchit(formula = f.build("treat", covs0), data = lalonde, method = "nearest",
##       ratio = 1, replace = TRUE)
##
## Balance Measures:
## distance    Distance  -0.0010
## age          Contin.   0.1964
## educ         Contin.  -0.0726
## race_black    Binary   0.0108
## race_hispan   Binary  -0.0054
## race_white    Binary  -0.0054
## re74         Contin.   0.0667
## re75         Contin.   0.0936
##
## Sample sizes:
##           Control Treated
## All           429     185
## Matched        83     185
## Unmatched     346       0

The output looks very similar to MatchIt’s summary() function: at the top is the original matchit() call; next is the balance table, and finally is a summary of the sample size before and after adjustment.

Setting binary = "std" in bal.tab() will produce identical calculations to those in MatchIt’s summary(m.out, standardize = TRUE), which produces standardized differences for binary variables as well as continuous variables. The other arguments to bal.tab() when using it with MatchIt have the same form and function as those given when using it without a conditioning package. The output when using MatchIt for subclassification is the same as that displayed previously.

### Using bal.tab() with WeightIt

The WeightIt package is a convenient wrapper for the weighting functions of several packages, including twang, CBPS, ebal, sbw, and ATE, and allows for the estimation weights for binary, multinomial, and categorical treatments and both point and longitudinal treatments. It was designed to work seamlessly with cobalt, so using it with cobalt is very straightforward. Below is a simple example of using bal.tab() with WeightIt:

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

#Generating propensity score weights for the ATT
W.out <- weightit(f.build("treat", covs0), data = lalonde,
method = "ps", estimand = "ATT")

bal.tab(W.out)
##
## Call:
##   weightit(formula = f.build("treat", covs0), data = lalonde, method = "ps",
##       estimand = "ATT")
##
## Balance Measures:
## prop.score  Distance  -0.0397
## age          Contin.   0.1112
## educ         Contin.  -0.0641
## race_black    Binary  -0.0044
## race_hispan   Binary   0.0016
## race_white    Binary   0.0028
## re74         Contin.  -0.0039
## re75         Contin.  -0.0428
##
## Effective sample sizes:
##            Control Treated
## Adjusted     108.2     185

## bal.plot()

The gold standard for covariate balance is multidimensional independence between treatment and covariates. Because this is hard to visualize and assess with the large numbers of covariates typical of causal effect analysis, univariate balance is typically assessed as a proxy. Most conditioning packages, as well as cobalt, will provide numerical summaries of balance, typically by comparing moments between the treated and control groups. But even univariate balance is more complicated than simple numerical summaries can address; examining distributional balance is a more thorough method to assess balance between groups. Although there are statistics such as the Kolmogorov-Smirnov distance that attempt to summarize distributional balance beyond the first few moments (Ali et al., 2016; Austin & Stuart, 2015), complimenting statistics with a visual examination of the distributional densities can be an effective way of assessing distributional similarity between the groups (Ho et al., 2007).

bal.plot() allows users to do so by displaying density plots, bar graphs, and scatterplots so that users can visually assess independence between treatment and covariates before and after conditioning. Below is an example of the use of bal.plot() after using propensity score weighting for the ATT using the output from WeightIt generated above.:

bal.plot(W.out, var.name = "age")
bal.plot(W.out, var.name = "race")

The first argument (or set of arguments) is the sufficient set of arguments for a simple call to bal.tab(), defining the data object (e.g., the output of a conditioning function), the treatment indicators, and the weights or subclasses. See above for examples. The next argument is the name of the covariate for which distributional balance is to be assessed. If subclassification is used (i.e., if subclasses are present in the input data object or arguments), an additional argument which.sub can be specified, with a number corresponding to the subclass number for which balance is to be assessed on the specified covariate; if it is not specified, plots for all subclasses will be displayed.

The user can also specify whether distributional balance is to be shown before or after adjusting or both by using the argument to which. If which = "unadjusted", balance will be displayed for the unadjusted sample only. If which = "both", balance will be displayed for both the unadjusted sample and the adjusted sample. The default is to display balance for the adjusted sample.

The output of bal.plot() is a density plot for the two groups on the given covariate. For categorical or binary variables, a bar graph is displayed instead. The x-axis is the value of the variable, and the y-axis is the proportion density or proportion of the variable in the given group. When multi-category categorical variables are given, bars will be created for each level, unlike in bal.tab(), which splits the variable into several binary variables. The degree to which the density plots for the two groups overlap is a good measure of group balance on the given covariate; significant differences in shape can be indicative of poor balance, even when the mean differences and variance ratios are well within thresholds. Strong distributional similarity is especially important for variables strongly related to the outcome of interest.

Distributional balance can also be assessed on the distance measure, and this can form an alternative to other common support checks, like MatchIt’s plot(..., type = "hist") or twang’s plot(..., plots = "boxplot"). To examine the distributions of the distance measure, the input to var.name must be the name of the distance variable. If the data input object doesn’t already contain the distance measure (e.g., if not using one of the conditioning packages), the distance measure must be manually added as an input to bal.plot() through distance, in addition to being called through var.name. Below is an example using the formula input to bal.plot() to display the distributions of propensity scores before and after weighting adjustment:

#Before and after weighting; which = "both"
bal.plot(W.out, var.name = "prop.score", which = "both")

It is generally not a useful assessment of balance to examine the overlap of the distance measure distributions after adjustment, as most conditioning methods will yield good distributional overlap on the distance measure whether or not balance is achieved on the covariates (Stuart et al., 2013). However, it may be useful to see the new range of the distance measure if calipers or common support pruning are used.

The output plot is made using ggplot2, which means that users familiar with ggplot2 can adjust the plot with ggplot2 commands.

When the treatment variable is continuous, users can use bal.plot() to examine and assess dependence between the covariate and treatment. The arguments given to bal.plot() are the same as in the binary treatment case, but the resulting plots are different. If the covariate is continuous, a scatterplot between the covariate and the treatment variable will be displayed, along with a linear fit line, a Loess curve, and a reference line indicating linear independence. Used together, these lines can help diagnose departurses from independence beyond the simple correlation coefficient. Proximity of the fit lines to the reference line is suggestive of independence between the covariate and treatment variable. If the covariate is categorical (including binary), density plots of the treatment variable for each category will be displayed. Densities that overlap completely are indicative of independence between the covariate and treatment. See the section “Using cobalt with continuous treatments” for more details and an example.

## love.plot()

The Love plot is a summary plot of covariate balance before and after conditioning popularized by Dr. Thomas E. Love. In a visually appealing and clear way, balance can be presented to demonstrate to readers that balance has been met within a threshold, and that balance has improved after conditioning (which is not always the case; cf. King & Nielsen, 2016). love.plot() does just this, providing the user with several options to customize their plot for presentation. Below is an example of its use:

data("lalonde", package = "cobalt")
covs <- subset(lalonde, select = -c(treat, re78))

# Nearest neighbor 1:1 matching with replacement
m.out <- matchit(f.build("treat", covs), data = lalonde, method = "nearest", replace = TRUE)

love.plot(bal.tab(m.out), threshold = .1)

love.plot() takes as its first argument the output of a call to bal.tab(); this can be accomplished simply by inserting the call into the first argument or by saving the result of a call to bal.tab() to an object and inserting the object as the argument. There are several other arguments, all of which control display, and are described below.

The output is a plot with the balance statistic on the X-axis and the covariates output in bal.tab() on the Y-axis. Each point represents the balance statistic for that covariate, colored based on whether it is calculated before or after adjustment. The dotted lines represent the threshold set in the threshold argument; if most or all of the points after adjustment are within the threshold, that is good evidence that balance has been achieved.

The default is to present mean differences as they are calculated in the call to bal.tab(); by specifying stat = "variance.ratios" or stat = "ks.statistics" (abbreviations allowed), the user can request variance ratios or KS statistics instead. Because binary variables don’t have variance ratios calculated, there may be empty rows if variance ratios are requested, but these rows can be deleted by setting no.missing = TRUE.

The threshold argument works similarly to m.threshold, v.threshold, and ks.threshold in bal.tab(); specifying it is optional, but doing so will provide an additional point of reference on which to evaluate the displayed balance measures. If these thresholds are specified in bal.tab(), love.plot() will use them in the resultant plot with the corresponding balance statistic. The threshold argument to love.plot() takes precedence.

If mean difference are requested, love.plot() will use the mean differences as they are calculated by bal.tab() and presented in the mean differences columns of the balance table. See the section on using bal.tab() to see what the default calculations are for these values. If abs = TRUE in love.plot(), the plot will display absolute mean differences, which can aid in display clarity since the magnitude is generally the more important aspect of the statistic.

The order of the covariates displayed can be adjusted using the argument to var.order. If left empty or NULL, the covariates will be listed in alphabetical order. If "adjusted", covariates will be ordered by the requested balance statistic of the adjusted sample. If "unadjusted", covariates will be ordered by the requested balance statistic of the unadjusted sample, which tends to be more visually appealing. Abbreviations are allowed. The distance variable(s), if any, will always be displayed at the top. They can be omitted by setting drop.distance = TRUE.

The plot uses the original variable names as they are given in the data set, which may not be the names desired for display in publication. By using the argument to var.names, users can specify their own variable names to be used instead. To specify new variable names with var.names, the user must enter an object containing the new variable names and, optionally, the old variable names to replace. For options of how to do so, see the help file for love.plot() with ?love.plot. Below is an example, creating a publication-ready plot:

v <- data.frame(old = c("age", "educ", "race_black", "race_hispan",
"race_white", "married", "nodegree", "re74", "re75", "distance"),
new = c("Age", "Years of Education", "Black",
"Hispanic", "White", "Married", "No Degree Earned",
"Earnings 1974", "Earnings 1975", "Propensity Score"))

love.plot(bal.tab(m.out), stat = "mean.diffs", threshold = .1,
var.order = "unadjusted", var.names = v, abs = TRUE,
line = TRUE, limits = c(0, 1))
## Warning: Some points will be removed from the plot by the limits.

This plot shows that balance was improved on almost all variables after adjustment, bringing all but one below the threshold of .1 for absolute mean differences.

When the treatment variable is continuous, love.plot() will display Pearson correlations between each covariate and treatment. The same arguments apply except that stat is ignored and threshold corresponds to r.threshold, the threshold for correlations.

Like the output of bal.plot(), the output of love.plot() is a ggplot2 object, which means ggplot2 users can modify the plot to some extent for presentation or publication. Several aspects of the appearance of the plot can be customized using the love.plot() syntax, including the size, shape, and color of the points, the title and subtitle of the plot, and whether to display lines connecting the points. See ?love.plot for details. It may be challenging to make adjustments to these aspects using ggplot2 syntax, so these arguments allow for some simple adjustments.

#Additional Features ## Using cobalt with continuous treatments Although the most common use of propensity scores is in the context of binary treatments, it is also possible to use propensity scores with continuous treatment to estimate dose-response functions while controlling for background variables (Hirano & Imbens, 2004). As in the binary case, the goal of propensity score adjustment in the continuous case is to arrive at a scenario in which, conditional on the propensity score, treatment is independent of background covariates. When this is true (and there are no unmeasured confounders), treatment is also indepnendent of potential outcomes, thereby meeting the strong ignorability requirememnt for causal inference.

Bia & Mattei (2008) describe the use of the gpscore function in Stata, which appears to be highly effective for estimating and assessing dose-response functions for continuous treatments. In R, there are not many ways to estimate and condition on the propensity score in these contexts. It is possible, using the formulas described by Hirano & Imbens (2004), to generate the propensity scores manually and perform weighting, subclassification, or covariate adjustment on them. In addition, the CBPS package is able to generate balancing weights for continuous treatments, using the method described by Fong & Imai (2014). The balance() function in the CBPS package assesses balance after propensity score weighting using the covariate balancing propensity score.

In cobalt, users can assess and present balance for continuous treatments using bal.tab(), bal.plot(), and love.plot(), just as with binary treatments. The syntax is almost identical in both cases regardless of the type of treatment variable considered, but there are a few differences and specifics worth noting. The approach cobalt takes to assessing balance is to display correlations between each covariate and the treatment variable, which is the approach used in CBPS, but not that described in Hirano & Imbens (2004) or implemented in gpscore (Bia & Mattei, 2008), which involves stratifying on both the treatment variable and the propensity score and calculating mean differences.

Below is an example of the workflow for using propensity scores for continuous treatments in the WeightIt package. To demonstrate, we use the lalonde package included in cobalt, using an arbitrary continuous variable as the treatment, though substantively this analysis makes little sense.

data("lalonde", package = "cobalt")
library("WeightIt")
cov.c <- subset(lalonde, select = -c(treat, re78, re75))

#Generating weights with re75 as the continuous treatment
W.out.c <- weightit(re75 ~ age + educ + race + married + nodegree +
re74 + I(re74^2),
data = lalonde, method = "ps")

First, we can assess balance numerically using bal.tab(). The balance statistic used is the Pearson correlation between each covariate and the treatment variable. A threshold for balance on correlations can be specified using r.threshold; though there are no specific guidelines for this value, we choose .1 as indicating balance. Because the goal is complete independence between treatment and covariates, not simply the absence of a linear correlation between treatment and covariates, including interactions and squared terms through the use of int = TRUE is recommended. Here we use imbalanced.only = TRUE, which is available with all uses of bal.tab(), to restrict output to just those variables that have failed to meet the set balance threshold.

#Assessing balance numerically
bal.tab(W.out.c, un = TRUE, r.threshold = .1, int = TRUE,
imbalanced.only = TRUE)
##
## Call:
##   weightit(formula = re75 ~ age + educ + race + married + nodegree +
##       re74 + I(re74^2), data = lalonde, method = "ps")
##
## Balance Measures:
## I(re74^2)             Contin.  0.4607  -0.1187 Not Balanced, >0.1
## I(re74^2)_2           Contin.  0.3142  -0.1278 Not Balanced, >0.1
## age_I(re74^2)         Contin.  0.3908  -0.1231 Not Balanced, >0.1
## educ_I(re74^2)        Contin.  0.4356  -0.1064 Not Balanced, >0.1
## race_black_married     Binary  0.1470   0.1336 Not Balanced, >0.1
## race_black_re74       Contin.  0.3217   0.1134 Not Balanced, >0.1
## race_hispan_I(re74^2) Contin.  0.1203  -0.1139 Not Balanced, >0.1
## married_I(re74^2)     Contin.  0.4464  -0.1312 Not Balanced, >0.1
## nodegree_I(re74^2)    Contin.  0.2992  -0.1038 Not Balanced, >0.1
## re74_I(re74^2)        Contin.  0.3790  -0.1288 Not Balanced, >0.1
##
## Balance tally for correlations:
##                    count
## Balanced, <0.1        35
## Not Balanced, >0.1    10
##
## Variable with the greatest treatment correlation:
##  race_black_married   0.1336 Not Balanced, >0.1
##
## Effective sample sizes:
##            Total
## Adjusted   189.4

We can also visually assess balance using bal.plot(). For continuous covariates, bal.plot() dispalys a scatterplot of treatment against the covariate, and includes a linear fit line (blue, dashed), a Loess curve (blue, solid), and a reference line (black, solid) at the (weighted) mean of the treatment variable. These three lines can be used to diagnose dependence. If either fit line is not close to flat (i.e., lying on top of the reference line), there may be some remaining dependence between treatment and the covariate. For categorical covariates, including binary, bal.plot() displays a density plot of the treatment variable in each category. If treatment and the covariate are independent, the densities for each category should overlap with each other. A distinct lack of overlap is indicative of remaining dependence between treatment and the covariate.

#Assessing balance graphically
bal.plot(W.out.c, "re74", which = "both")

bal.plot(W.out.c, "married", which = "both")

When balance has been achieved to a satisfactory level, users can present balance improvements in a Love plot using the love.plot() command, just as with binary treatments.

#Summarizing balance in a Love plot
love.plot(bal.tab(W.out.c), threshold = .1, abs = TRUE,
var.order = "unadjusted", line = TRUE)

## Using cobalt with multinomial treatments

When multiple categorical treatment are to be compared with each other, it is possible to create balance across the treatment groups using preprocessing methods. Lopez & Gutman (2017) compare methods used to create balance with multinomial treatments and briefly describe balance assessment for these scenarios. An important note is the choice of estimand to be examined. The ATE represents the causal effect of moving from one tretament group to another for all units in the population; the ATT represents the causal effect of moving from one treatment group to another “focal” treatment group for just the units that wuld have been in the focal treatment group. The way balance is assessed in these scnaerios differs. For the ATE, all possible treatment pairs must be assessed for balance because all possible comparisons are potentially meaningful, but for the ATT, only treatment pairs that include the focal treatment group can be meaningfully compared, so balance needs only to be assessed in these pairs.

In cobalt, users can assess and present balance for multinomial treatments using bal.tab(), bal.plot(), and love.plot(), just as with binary treatments. The output is slightly different, though, and is similar to the output generated when using these functions with clusters. bal.tab() computes balance statistics for all pairwise comparsions between tretament groups and a table containing the worst balance for each covariate across pairwise comparisons. For mean differences, this is described in Lopez & Gutman (2017) as “Max2SB,” or the maximum pairwise standardized bias. In cobalt, this has been extended to variance ratios and KS statistics as well. If the worst imbalance is not too great, then imbalance for all pairwsie comparisons will not be too great either. When the ATT is desired, a focal group must be specified (unless done so automatically for some methods), and only the treatment group comparisons that involve that focal group will be computed and displayed.

love.plot() allows for the display of each pairwise treatment or the range of balance across treatment pairs for each covariate. bal.plot() displays distributional balance for the requetsed covariate across all treatment groups. Currently, multinomial treatments are not supported with clustered or multiply imputed data.

Below is an example of using cobalt with multinomial treatments. For this example, race will be the “treatment”; this type of analysis is not meant to be causal, but rather represents a method to examine disparities among groups accounting for covariates that might otherwise explain differences among groups. We will use WeightIt to generate balanced groups by estimating propensity score weights with multinomial logistic regression.

data("lalonde", package = "cobalt")
library("WeightIt")
cov.mn <- subset(lalonde, select = -c(treat, re78, race))

#Using WeightIt to generate weights with multinomial
#logistic regression
W.out.mn <- weightit(f.build("race", cov.mn), data = lalonde,
method = "ps")

First, we can examine balance numerically using bal.tab(). There are three possible pairwise comparisons, all of which can be requested with which.treat = NULL. The summary across all pairwise comparisons is displayed as well, but it can be suppressed with multi.summary = FALSE. See ?bal.tab.multi for more details.

#Assessing balance numerically
bal.tab(W.out.mn, un = TRUE, which.treat = NULL)
##
## Call:
##   weightit(formula = f.build("race", cov.mn), data = lalonde, method = "ps")
##
## Balance by treatment pair:
##
##  - - - black (0) vs. hispan (1) - - -
## Balance Measures:
## age      Contin. -0.0102   0.0410
## educ     Contin. -0.4542  -0.1147
## married   Binary  0.2222  -0.0269
## nodegree  Binary  0.0684   0.0546
## re74     Contin.  0.3211  -0.1240
## re75     Contin.  0.3442  -0.1333
##
## Effective sample sizes:
##            black hispan
##
##  - - - black (0) vs. white (1) - - -
## Balance Measures:
## age      Contin.  0.2981   0.0560
## educ     Contin.  0.1355  -0.0681
## married   Binary  0.3430   0.0028
## nodegree  Binary -0.1503   0.0240
## re74     Contin.  0.6249  -0.1540
## re75     Contin.  0.2752  -0.1491
##
## Effective sample sizes:
##            black white
##
##  - - - hispan (0) vs. white (1) - - -
## Balance Measures:
## age      Contin.  0.3083   0.0150
## educ     Contin.  0.5897   0.0467
## married   Binary  0.1208   0.0297
## nodegree  Binary -0.2187  -0.0306
## re74     Contin.  0.3039  -0.0300
## re75     Contin. -0.0691  -0.0158
##
## Effective sample sizes:
##            hispan white
##  - - - - - - - - - - - - - - - - - - - -
##
## Balance summary across all treatment pairs:
## age      Contin.      0.3083       0.0560
## educ     Contin.      0.5897       0.1147
## married   Binary      0.3430       0.0297
## nodegree  Binary      0.2187       0.0546
## re74     Contin.      0.6249       0.1540
## re75     Contin.      0.3442       0.1491
##
## Effective sample sizes:
##            black hispan white
## Adjusted   138.4  54.99 259.6
#Just black vs. white
bal.tab(W.out.mn, un = TRUE, disp.means = TRUE,
which.treat = c("black", "white"),
multi.summary = FALSE)
##
## Call:
##   weightit(formula = f.build("race", cov.mn), data = lalonde, method = "ps")
##
## Balance by treatment pair:
##
##  - - - black (0) vs. white (1) - - -
## Balance Measures:
## age      Contin.   26.0123   28.8094  0.2981   26.6155   27.1412   0.0560
## educ     Contin.   10.2346   10.5987  0.1355   10.5033   10.3204  -0.0681
## married   Binary    0.2222    0.5652  0.3430    0.4109    0.4137   0.0028
## nodegree  Binary    0.6955    0.5452 -0.1503    0.6005    0.6246   0.0240
## re74     Contin. 2499.4492 6260.5029  0.6249 5486.7976 4560.0207  -0.1540
## re75     Contin. 1613.7752 2515.1320  0.2752 2665.9702 2177.6625  -0.1491
##
## Effective sample sizes:
##            black white
##  - - - - - - - - - - - - - - - - - - - -

We can also assess balance graphically. The same guidelines apply for multinomial treatments as do for binary treatments. Ideally, covariate distributions will look similar across all treatment groups. If only a subset of treatment groups are to be compared, these can be specified with an argument to which.treat.

#Assessing balance graphically
bal.plot(W.out.mn, "age", which = "both")

bal.plot(W.out.mn, "married", which = "both",
which.treat = c("black", "white"))

Finally, we can use love.plot() to display balance across treatments. By default, love.plot() displays the values in the summary across pairwsie comparisons. To request individual treatment comparisons, use which.treat = NULL in either bal.tab() or love.plot().

#Summarizing balance in a Love plot
love.plot(bal.tab(W.out.mn), threshold = .1,
which.treat = NULL)

## Comparing balancing methods

It is possible to display balance for multiple balancing methods at the same time in bal.tab(), bal.plot(), and love.plot(). Do do so, weights generated from each balancing mehtod need to be supplid together in each call. For example, we can compare matching and inverse probability weighting for the ATT using the following code and the output generated above.

bal.tab(f.build("treat", covs0), data = lalonde,
weights = data.frame(Matched = get.w(m.out),
IPW = get.w(W.out)),
method = c("matching", "weighting"),
disp.v.ratio = TRUE)
## Balance Measures:
##                Type Diff.Matched V.Ratio.Matched Diff.IPW V.Ratio.IPW
## age         Contin.       0.2395          1.7965   0.1112      1.9663
## educ        Contin.      -0.0161          1.7317  -0.0641      1.6616
## race_black   Binary       0.0054                  -0.0044
## race_hispan  Binary      -0.0054                   0.0016
## race_white   Binary       0.0000                   0.0028
## re74        Contin.      -0.0493          1.0366  -0.0039      1.4821
## re75        Contin.       0.0087          2.1299  -0.0428      1.1712
##
## Sample sizes:
##         Control Treated
## All       429.0     185
## Matched    83.0     185
## IPW       108.2     185 *
## * indicates effective sample size

To use bal.plot(), the same syntax can be used:

bal.plot(f.build("treat", covs0), data = lalonde,
weights = data.frame(Matched = get.w(m.out),
IPW = get.w(W.out)),
method = c("matching", "weighting"),
var.name = "age", which = "both")

With love.plot(), var.order can be "unadjusted", "alphabetical", or one of the names of the weights to order the variables. Also, colors and shapes should have the same length as the number of weights or have length 1.

love.plot(bal.tab(f.build("treat", covs0), data = lalonde,
weights = data.frame(Matched = get.w(m.out),
IPW = get.w(W.out)),
method = c("matching", "weighting")), var.order = "unadjusted",
abs = TRUE, colors = c("red", "blue", "darkgreen"),
shapes = c(21, 22, 23))

## Using the prognostic score for balance assessment

The prognostic score is the model-predicted outcome for an individual, excluding the treatment variable in the model (Hansen, 2008). Stuart et al. (2013) found that prognostic scores can be an extremely effective tool for assessing balance, greatly outperforming mean differences on covariates and significance tests. This is true even if the prognostic score model is slightly misspecified. Although the use of prognostic scores appears to violate the spirit of preprocessing in that users observe the outcome variable prior to treatment effect estimation, typically the prognostic score model is estimated in just the control group, so that the outcome of the treated group (which may contain treatment effect information) is excluded from analysis.

Assessing balance on the prognostic score is simple in cobalt, and highly recommended when available. The steps are:

1. Estimate the outcome model in the control group
2. Generate model-predicted outcome values for both the treated and control groups
3. Assess balance on prognostic scores by comparing standardized mean differences

To use prognostic scores in cobalt, simply add the prognostic score as a variable in the argument to distance. Below is an example of how to do so after a call to matchit():

ctrl.data <- lalonde[lalonde$treat == 0,] ctrl.fit <- glm(re78 ~ age + educ + race + married + nodegree + re74 + re75, data = ctrl.data) lalonde$prog.score <- predict(ctrl.fit, lalonde)

bal.tab(m.out, distance = lalonde["prog.score"])
##
## Call:
##   matchit(formula = f.build("treat", covs), data = lalonde, method = "nearest",
##       replace = TRUE)
##
## Balance Measures:
## prog.score  Distance  -0.0485
## distance    Distance   0.0044
## age          Contin.   0.2395
## educ         Contin.  -0.0161
## race_black    Binary   0.0054
## race_hispan   Binary  -0.0054
## race_white    Binary   0.0000
## married       Binary   0.0595
## nodegree      Binary   0.0054
## re74         Contin.  -0.0493
## re75         Contin.   0.0087
##
## Sample sizes:
##           Control Treated
## All           429     185
## Matched        83     185
## Unmatched     346       0

Although the prognostic score is sensitive to the outcome estimation model used, a defensible prognostic score model can yield valid prognostic scores, which can then be used in balance assessment. In the above example, balance on the estimated prognostic score was good, so we can have some confidence that the effect estimate will be relatively unbiased, even though the “age” variable remains imbalanced. The logic is that that age is not a highly prognostic variable, which could be demonstrated by examining the standardized regression output of prognostic score model, so even though imbalance remains, such imbalance is unlikely to affect the effect estimate. The variables “re74” and “re75”, though, which are highly prognostic of the outcome, are quite balanced, thereby supporting an unbiased treatment effect estimate.

# Details on Calculations

There are calculations in cobalt that may be opaque to users; this section explains them.

## Variance in Standardized Mean Differences

When computing a standardized mean difference, the raw mean difference is divided by a standard deviation, yielding a d-type effect size statistic. In bal.tab(), the user can control whether the standard deviation is that of the treated group or control group or a pooled estimate, calculated as the square root of the average of the group variances. In most applications, the standard deviation corresponding to the default for the method is the most appropriate.

A key detail is that the standard deviation, no matter how it is calculated, is always calculated using the unadjusted sample3. This is line with how MatchIt computes standardized mean differences, and is recommended by Stuart (2008; 2010). One reason to favor the use of the standard deviation of the unadjusted sample is that it prevents the paradoxical situation that occurs when adjustment decreases both the mean difference and the spread of the sample, yielding a larger standardized mean difference than that prior to adjustment, even though the adjusted groups are now more similar. By using the same standard deviation before and after adjusting, the change in balance is isolated to the change in mean difference, rather than being conflated with an accompanying change in spread. It is important to note that both twang and Matching calculate standardized mean differences using the standard deviation of the sample in question, not the unadjusted sample, though CBPS uses the standard deviation of the unadjusted sample.

## Weighted Variance

When using weighting or matching, summary values after adjustment are calculated by using weights generated from the matching or weighting process. For example, group means are computed using the standard formula for a weighted mean, incorporating the weighting or matching weights into the calculation. To estimate a weighted sample variance for use in variance ratios4, there are two formulas that have been proposed:

1. $$\frac{\sum_{i=1}^{n} w_{i}(x_{i} - \bar{x}_{w})^2}{(\sum_{i=1}^{n} w_{i}) - 1}$$

2. $$\frac{\sum_{i=1}^{n} w_{i}}{(\sum_{i=1}^{n} w_{i})^2 - \sum_{i=1}^{n} w^2_{i}} \sum_{i=1}^{n} w_{i}(x_{i} - \bar{x}_{w})^2$$

The weights used in the first formula are often called “frequency weights”, while the weights in the second formula are often called normalized or “reliability weights”. MatchIt, twang, and Matching all use the first formula when calculating any weighted variance (CBPS does not compute a weighted variance). However, Austin (2008) and Austin & Stuart (2015) recommend the second formula when considering matching weights for k:1 matching or weights for propensity score weighting. In cobalt, as of version 2.0.0, the second formula is used to remain in line with recommended practice. For some applications (e.g., when all weights are either 0 or 1, as in 1:1 matching), the two formulas yield the same variance estimate. In other cases, the estimates are nearly the same.

## Effective Sample Size for Weighting

Knowledge of the sample size after adjustment is important not just for outcome analysis but also for assessing the adequacy of a conditioning specification. For example, pruning many units through common support cutoffs, caliper matching, or matching with replacement can yield very small sample sizes that hurt both the precision of the outcome estimate and the external validity of the conclusion. In matching, the sample size is fairly straightforward; it is simply the number of units remaining after matching5. In weighting, the sample size is not so straightforward because the purpose of weighting is to down- and up-weight observations to create two similar samples. The “effective sample size” (ESS) is a measure of the sample size a non-weighted sample would have to have to achieve the same level of precision as the weighted sample (Ridgeway et al., 2016). This measure is implemented in twang using the following formula: $ESS = \frac{(\sum_{i=1}^{n} w_{i})^2}{\sum_{i=1}^{n} w_{i}^2}$ The authors claim that the ESS as calculated above is accurate when the estimand is the ATT, but will underestimate the true effective sample size when the estimate is the ATE.

# What’s Missing in cobalt

Compared to the other packages, cobalt may appear quite sparse. A fair amount is missing in cobalt that is present in other packages. Though there is value in many of the aspects that cobalt lacks, many were purposefully excluded based on methodological recommendations that conflict with the current use of some other packages. Below are aspects that are intentionally missing from cobalt that users may be used to from other packages. Their reasons for exclusion are included, with the hope that users of cobalt will be satisfied with what is available and be confident they are using the most methodologically sound tools for balance assessment.

## Test Statistics and P-values

Some of the early literature on propensity score matching included measures for balance assessment that relied on hypothesis tests for the independence of treatment assignment and covariates after adjustment (e.g., Rosenbaum & Rubin, 1983; Hansen, 2004). In a review of propensity score applications in the social sciences, Thoemmes & Kim (2011) found that over 66% of studies used significance tests to assess balance. Likewise, Austin (2008) found that over 70% of studies using propensity scores in the medical literature used significance tests to assess balance, a finding replicated by Ali et al. (2015). These hypothesis tests can come in many forms: t-tests for the difference in means between groups, chi-square tests for the difference in proportion between groups, Kolmogorov-Smirnov tests for the difference in cumulative density between groups, or F-tests for the difference in means between groups across subclasses.

The use of hypothesis tests appears natural here: if balance is achieved, we would not expect extreme values for the test statistics, and we can quantify the probability of observing imbalance as extreme as the one observed if no imbalance is present as a way to assess whether there is balance, as we can do with standard hypothesis testing. But this view is not shared by the methodological community: virtually all contemporary propensity score methodologists recommend against using hypothesis tests for balance assessment (e.g., Austin, 2009; 2011; Ho et al., 2007; Imai et al., 2008; Stuart, 2010; Thoemmes & Kim, 2011; Ali et al., 2015). There are logical reasons for this preference against hypothesis tests, noted clearly in Ali et al. (2015) and Linden (2015): they are influenced by sample size, which fluctuates during adjustment, and the theory behind them is inappropriate because balance is a quality solely of the sample in question, not in relation to a population. The relevant information in a hypothesis test for group differences is the standardized magnitude of the group difference, and so such a measure is preferred.

Because hypothesis tests can be misleading and their use is discouraged by leading methodologists, they have been completely excluded in cobalt in favor of summary statistics. This stands in contrast to twang, Matching, and RItools, all of which report hypothesis test p-values in their balance output.

## Q-Q Plots and Summaries

Q-Q plots have been recommended as tools to assess distributional balance of covariates between groups (Ho et al., 2007), and are implemented in MatchIt and Matching (twang implements them but for a different purpose). Statistics summarizing the degree of imbalance in Q-Q plots are also reported in both MatchIt and Matching. MatchIt’s summary() command by default reports “eQQ Med”, “eQQ Mean”, and “eQQ Max” for each covariate before and after adjustment. These are the median, mean, and maximum distance between the treated and control group empirical Q-Q plots on the scale of the covariate. When standardize = TRUE, the values reported are “eCDF Med”, “eCDF Mean”, and “eCDF Max”, which are the same values as above but computed using the empircal cumulative densities. The Matching package also provides all of these values. Values close to 0 indicate good balance between groups for the given covariate.

A weakness of empirical Q-Q plots is that they don’t reveal much about the differences in the shapes of the distributions between the groups, which is a key aspect of distributional balance. A density plot essentially contains the same information, but is clearer and more intuitive. Although the assessment of balance using an empirical Q-Q plot is straightforward (i.e., deviations from the 45-degree line indicate imbalances), density plots present the information in a way more in line with the actual goals of conditioning, in particular, that the distributions of treated and control units are similar (Ho et al., 2007). Empirical Q-Q plot summary statistics may be useful in quantifying imbalance, but currently there are no recommendations for their use, and no precedents in the applied literature either.

# What’s Added in cobalt

There are several features in cobalt that are present in few, if any, balance assessment tools in the major packages. These come from methodological recommendations and requests by members of the methodological community.

## Density Plots

As mentioned above, cobalt displays density plots and bar charts rather than empirical Q-Q plots for the assessment of distributional similarity. These charts are not standard in any of the conditioning packages, but can be an intuitive and helpful tool for deciding whether adjustment has yielded similar distributions between the groups for given covariates. Though there are no obvious heuristics for deciding how much dissimilarity is too much dissimilarity, density plots do avoid the sometimes confusing logic of empirical Q-Q plots in favor of simplicity and interpretability. Austin (2009) and Linden (2015) consider density plots as a compliment to empirical Q-Q plots to more finely examine balance after adjusting.

## Variance Ratios

Although mean differences (including t-tests and chi-square tests) are the most reported balance statistic (Thoemmes & Kim, 2011), variance ratios have been recommended in the literature as a means to further examine balance between groups (Austin, 2009, Imai et al., 2008; Ho et al., 2007). When group variances are similar, the variance ratio will be close to 1. Common thresholds for the variance ratio for balanced groups are 1/2 and 2 (Rubin, 2001; Stuart, 2010), though ratios closer to 1 are preferred. The Matching package is the only one of the conditioning packages that presents variance ratios for covariates. Although bal.tab() in cobalt does not display variance ratios by default, they can be easily requested and have thresholds set.

## Distinguishing Continuous and Binary Covariates

Continuous and binary covariates are treated differently by default in bal.tab() and bal.plot(). For continuous covariates, the standard manipulations apply: standardized mean differences, variance ratios, and density plots. For binary covariates, raw differences in proportion and bar charts are preferable, and variance ratios are useless.

The value of standardized mean differences for continuous variables is that they are on the same scale so that they can be compared across variables, and they allow for a simple interpretation even when the details of the variable’s original scale are unclear to the analyst. None of these advantages are passed to binary variables because binary variables are already on the same scale (i.e., a proportion), and the scale is easily interpretable. In addition, the details of standardizing the proportion difference of a binary variable involve dividing the proportion difference by a variance, but the variance of a binary variable is a function of its proportion (Austin, 2009). Standardizing the proportion difference of a binary variable can yield the following counterintuitive result: if $$X_{T} = .2$$ and $$X_{C} = .3$$, the standardized difference in proportion would be different from that if $$X_{T} = .5$$ and $$X_{C} = .6$$, even though the expectation is that the balance statistic should be the same for both scenarios because both would yield the same degree of bias in the effect estimate. In addition, Ali et al. (2014) found that the raw difference in proportion was a better predictor of bias than the standardized mean difference for binary variables6.

MatchIt allows users to view either standardized mean differences for all covariates or raw differences for all covariates, and twang and Matching display standardized differences for all variables but calculate test statistics depending on whether the covariate is continuous or binary (CBPS does not calculate mean differences, but presents both standardized and unstandardized means for all covariates). cobalt allows the user to select how the differences are to be calculated separately for continuous and binary variables, and uses the intuitive default that mean differences for continuous variables should be standardized while proportion differences for binary variables should not be.

Because the variance of a binary variable is a function only of its proportion, the variance ratio of a binary variable in two groups is a function only of their proportions, thereby containing no more information than the simple difference in proportion. Therefore, for binary variables, cobalt does not compute variance ratios, as they can be misleading. Matching, the only package that computes variance ratios for balance assessment, does so both for continuous and binary variables.

## Interactions

Because the goal of a balancing procedure is to achieve independence between treatment and the joint distribution of covariates, evaluating univariate distributional similarity may not be sufficient for assessing balance completely. Some writers have recommended the evaluation of distributional similiarity of interaction terms to account for this. Rather than requiring the user to create interaction variables by hand, bal.tab() can produce balance statistics for interactions by specifying int = TRUE, which also produces polynomial terms, similar to MatchIt’s summary().

When including categorical variables in balance assessment, bal.tab() makes a few adjustments under the hood that deserve explanation. First, if a variable is binary and is entered as a factor variable, balance statistics will only be displayed for one level of the variable (since the other is redundant), but balance on interaction terms will be dislayed for all values of the variables. This distinguishes bal.tab() from MatchIt’s summary(), which always excludes one level of a factor variable when presented in both main effects and interactions. This might be desirable (especially when there are only two levels) in order to declutter output and reduce redunancy, but valuable balance information is made inaccessible.

For example, consider a binary variable “Sex” with values “Male” and “Female”. Many functions, including bal.tab(), lm(), and matchit(), will split this variable into two numeric dummy variables, “Male” and “Female”, each of which take on the values 0 and 1. One of these new variables is completley redundant: all of the relevant informaton is stored in just “Female”, so “Male” can be eliminated. Consider now a variable “Age”: what is desired in balance assessment is the interaction between Age and Sex; distributional similarity on the interaction between the treated and untreated groups is evidence of multivariate balance. Computing the interaction between “Female” and “Age” yields a variable that is “Age” when the unit is female and 0 otherwise. The average value of this variable is the average age of females in the sample, weighted by the proportion of females. If the two treatment groups have similar average values of this variable, this is taken as evidence for balance on the interaction between sex and age, though it is entirely possible that the average age of men differs greatly between the two groups. Thus, an additional variable computed as the product of “Male” and “Age” would be necessary to fully assess balance on the interaction between sex and age. bal.tab() produces this interaction term, which would otherwise be unobserved by the analyst7. The interactions among levels of a single factor, which always be equal to 0, are exlcuded in bal.tab(), but not in MatchIt’s summary().

Interactions between the distance measure and other variables have been excluded in bal.tab(), noting that balance on the distance measure is neither necessary nor sufficient for covariate balance.

Because the number of computations increases both with sample size and number of variables, computing interactions can be slow. In addition to taking the product of varables two at a time, bal.tab() checks variables to ensure no variables were created that contain only a single value (e.g., interactions between mutually exclusive covariates) or are redundant with respect to other variables (e.g., manually created interactions or manually split factors). This results in cleaner, more useful output, but also requires more computing time. It is advisable to store the results of a call to bal.tab() to a variable to be accessed later rather than to call bal.tab() several times when using it with interactions.

## Love Plots

Love plots and other similar plots that graphically display covariate balance before and after matching appear with some frequency in the applied and methodological literature (e.g., Ahmed et al., 2007; Austin, 2009; Austin & Stuart, 2015). Several of the conditioning packages implement a similar plot: MatchIt’s plot(summary(..., standardize = TRUE)) and twang’s plot(..., plots = "es") present line plots showing the change in balance for each covariate. Below are examples of twang’s balance plot (above) and cobalt’s Love plot (below).

## Warning in ps(f.build("treat", covs0), data = lalonde, stop.method =
## c("es.mean", : Optimal number of iterations is close to the specified
## n.trees. n.trees is likely set too small and better balance might be
## obtainable by setting n.trees to be larger.

## Warning in ps(f.build("treat", covs0), data = lalonde, stop.method =
## c("es.mean", : Optimal number of iterations is close to the specified
## n.trees. n.trees is likely set too small and better balance might be
## obtainable by setting n.trees to be larger.

A Love plot displays the same information, but allows for the clear display of both the balance statistic value and the variable name, unlike the line plot above which displays only the balance statistic value. The Love plot in cobalt allows for a great deal of flexibility in appearance, and can present variance ratios as well as mean differences.

## Clusters

The use of preprocessing techniques in the context of multilevel data (e.g., students within schools, patients within hospitals) has been increasing. Currently no other package allows for any balance assessment with respect to clusters, except by manually examining balance on clusters specified manually. It can be useful to examine balance within each cluster, but, especially if there are many clusters, it may also be useful to examine a summary of balance across clusters. In all of its functions, cobalt provides options for displaying balance on clustered data sets. This can occur either within specified clusters or across all clusters. Details on using cobalt with clustered data can be found in the accompanying Appendix 2.

## Multiply Imputed Data

Missing data is frequent in all research involving human subjects, and especially in the large survey data sets that are often used to answer causal questions in the social sciences. Multiple imputation is a popular solution to this problem, and cobalt has features designed especially for assessing balance on preprocessed data sets that have been multiply imputed to address covariate missingness. Although the guidelines on assessing balance on multiply imputed data sets are thin, it is valuable to assess balance within and across imputed data sets to ensure the preprocessing solution is applicable to all imputaitons. Details on using cobalt with multiply imputed data can be found in the accompanying Appendix 2.

# For Programmers: Integrating cobalt with Your Package

If you are designing a new R package for preprocessing that performs similar functions to MatchIt, twang, Matching, optmatch, CBPS, or ebal, you might consider integrating cobalt into your package to avoid programming your own balance assessment tool. The simplest way to do so is to have the output of your preprocessing function contain all the elements for use with the formula or data frame interfaces for bal.tab() and bal.plot(). You are also welcome to contact me to discuss the addition of a bal.tab() method for the output of your preprocessing function, which might ease the burden on users by allowing for fewer inputs and decisions (as is done with MatchIt’s output objects, for example).

As cobalt is updated to remain in line with methodological recommendations, the balance assessment capabilities for your function’s output will also improve if cobalt is a balance assessment tool for your package. In this way, users of your package can use the most up-to-date balance assessment tools programmed in cobalt without you having to update your package.

If you develop a new balance assessment tool, this may also be able to be integrated into cobalt, especially if it would be applicable to balance assessment generally in matching, weighting, or subclassification. Incorporating a new tool into cobalt may be a good way to broaden its use.

# Acknowledgments

I thank Kirsten Kainz and Elizabeth Stuart for their support and advice on cobalt’s creation. I thank Zachary Fisher and Brian Barkley for their advice on developing an R package.

# References

Ahmed, A., Rich, M.W., Sanders, P.W., Perry, G.J., Bakris, G.L., Zile, M.R., Love, T.E., Aban, I.B. & Shlipak, M.G. (2007) Chronic kidney disease associated mortality in diastolic versus systolic heart failure: a propensity matched study. The American journal of cardiology, 99(3), pp.393-398.

Ali, M. S., Groenwold, R. H. H., Belitser, S. V., Pestman, W. R., Hoes, A. W., Roes, K. C. B., … Klungel, O. H. (2015). Reporting of covariate selection and balance assessment in propensity score analysis is suboptimal: a systematic review. Journal of Clinical Epidemiology, 68(2), 122–131. http://doi.org/10.1016/j.jclinepi.2014.08.011

Ali, M. S., Groenwold, R. H. H., Pestman, W. R., Belitser, S. V., Roes, K. C. B., Hoes, A. W., … Klungel, O. H. (2014). Propensity score balance measures in pharmacoepidemiology: a simulation study. Pharmacoepidemiology and Drug Safety, 23(8), 802–811. https://doi.org/10.1002/pds.3574

Austin, P. C. (2008). A critical appraisal of propensity-score matching in the medical literature between 1996 and 2003. Statistics in Medicine, 27(12), 2037–2049. http://doi.org/10.1002/sim.3150

Austin, P. C. (2009). Balance diagnostics for comparing the distribution of baseline covariates between treatment groups in propensity-score matched samples. Statistics in Medicine, 28(25), 3083–3107. http://doi.org/10.1002/sim.3697

Austin, P. C. (2011). An Introduction to Propensity Score Methods for Reducing the Effects of Confounding in Observational Studies. Multivariate Behavioral Research, 46(3), 399–424. http://doi.org/10.1080/00273171.2011.568786

Austin, P. C., & Stuart, E. A. (2015). Moving towards best practice when using inverse probability of treatment weighting (IPTW) using the propensity score to estimate causal treatment effects in observational studies. Statistics in Medicine, 34(28), 3661–3679. http://doi.org/10.1002/sim.6607

Belitser, S. V., Martens, E. P., Pestman, W. R., Groenwold, R. H. H., de Boer, A., & Klungel, O. H. (2011). Measuring balance and model selection in propensity score methods. Pharmacoepidemiology and Drug Safety, 20(11), 1115–1129. http://doi.org/10.1002/pds.2188

Fong, C., Ratkovic, M., Hazlett, C., Yang, X., & Imai, K. (2016). CBPS: Covariate Balancing Propensity Score. R package version 0.11. https://CRAN.R-project.org/package=CBPS

Greifer, N. (2017). WeightIt: Weighting for Covariate Balance in Observational Studies. R package version 0.1.0.

Hainmueller, J. (2014). ebal: Entropy reweighting to create balanced samples. R package version 0.1-6. https://CRAN.R-project.org/package=ebal

Hansen, B. B. (2004). Full matching in an observational study of coaching for the SAT. Journal of the American Statistical Association, 99(467), 609-618.

Hansen, B. B. (2008). The prognostic analogue of the propensity score. Biometrika, 95(2), 481-488.

Hansen, B.B. and Klopfer, S.O. (2006) Optimal full matching and related designs via network flows. Journal of Computational and Graphical Statistics, 15, 609-627.

Hirano, K., and G. W. Imbens. (2004) The Propensity Score with Continuous Treatments. in Applied Bayesian Modeling and Causal Inference From Incomplete Data Perspectives, edited by A.Gelman and X. L.Meng. England : West Sussex, 2004: 73–84.

Ho, D. E., Imai, K., King, G., & Stuart, E. A. (2007). Matching as Nonparametric Preprocessing for Reducing Model Dependence in Parametric Causal Inference. Political Analysis, 15(3), 199–236. http://doi.org/10.1093/pan/mpl013

Ho, D. E., Imai, K., King, G., & Stuart, E. A. (2011). MatchIt: Nonparametric preprocessing for parametric causal inference. Journal of Statistical Software, 42, 1–28. Retrieved from http://www.jstatsoft.org/v42/i08/

Imai, K., King, G., & Stuart, E. A. (2008). Misunderstandings between experimentalists and observationalists about causal inference. Journal of the royal statistical society: series A (statistics in society), 171(2), 481-502.

Keller, B., & Tipton, E. (2016). Propensity Score Analysis in R: A Software Review. Journal of Educational and Behavioral Statistics, 41(3), 326-348.

King, G., & Nielsen, R. (2016). Why propensity scores should not be used for matching.

Linden, A. (2015), Graphical displays for assessing covariate balance in matching studies. Journal of Evaluation in Clinical Practice, 21: 242–247. http://doi.org/10.1111/jep.12297

Lopez, M. J., & Gutman, R. (2017). Estimation of causal effects with multiple treatments: a review and new ideas. arXiv:1701.05132 [Stat]. Retrieved from http://arxiv.org/abs/1701.05132

Ridgeway, G., McCaffrey, D., Morral, A., Burgette, L., & Griffin, B. A. (2016). Toolkit for Weighting and Analysis of Nonequivalent Groups: A tutorial for the twang package. R Vignette. RAND. Retrieved from https://CRAN.R-project.org/package=twang/vignettes/twang.pdf

Rosenbaum, P. R., & Rubin, D. B. (1983). The central role of the propensity score in observational studies for causal effects. Biometrika, 70(1), 41–55. http://doi.org/10.1093/biomet/70.1.41

R Core Team (2016). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. URL https://www.R-project.org/.

Rubin, D. B. (2001). Using Propensity Scores to Help Design Observational Studies: Application to the Tobacco Litigation. Health Services and Outcomes Research Methodology, 2(3-4), 169–188. http://doi.org/10.1023/A:1020363010465

Sekhon, J. S. (2011). Multivariate and Propensity Score Matching Software with Automated Balance Optimization: The Matching Package for R. Journal of Statistical Software, 42(7), 1-52. http://www.jstatsoft.org/v42/i07/.

Stuart, E. A. (2008). Developing practical recommendations for the use of propensity scores: Discussion of “A critical appraisal of propensity score matching in the medical literature between 1996 and 2003” by Peter Austin, Statistics in Medicine. Statistics in Medicine, 27(12), 2062–2065. http://doi.org/10.1002/sim.3207

Stuart, E. A. (2010). Matching Methods for Causal Inference: A Review and a Look Forward. Statistical Science, 25(1), 1–21. http://doi.org/10.1214/09-STS313

Stuart, E. A., Lee, B. K., & Leacy, F. P. (2013). Prognostic score-based balance measures can be a useful diagnostic for propensity score methods in comparative effectiveness research. Journal of Clinical Epidemiology, 66(8), S84. http://dx.doi.org/10.1016/j.jclinepi.2013.01.013

Thoemmes, F. J., & Kim, E. S. (2011). A Systematic Review of Propensity Score Methods in the Social Sciences. Multivariate Behavioral Research, 46(1), 90–118. http://doi.org/10.1080/00273171.2011.540475

Zhao, Q., & Percival, D. (2015). Primal-dual Covariate Balance and Minimal Double Robustness via Entropy Balancing. arXiv preprint arXiv:1501.03571.

1. If none of the examples are working, this may be because MatchIt, twang, or WeightIt is missing. Such issues should be fixed soon.

2. WeightIt is a wrapper for twang, CBPS, ebal, and other packages for point treatments, so it can perform their functions as well.

3. Unless common support is used in Matching, in which case the standard deviation is computed using the remaining unadjusted sample.

4. Standardized mean difference use only unweighted variance estimates; see section “Variance in Standardized Mean Differences” for details.

5. In full matching, this is not so straightforward because units are weighted; cobalt differs from MatchIt in that it calculates the effective sample size, consistent with weighting, whereas MatchIt calculates the regular sample size.

6. Ali et al. (2014) compared the KS statitic to standardized mean differences, but the KS statistic is equivalent to the raw difference in proportion for binary variables.

7. This value is a linear function of the other observed values, so it would in principle be able to be computed by the analyst, but it seems more valuable to explicitly include this “redundant calculation” for the sake of ease and completeness.