library(knitr) knitr::opts_chunk$set( collapse = FALSE, comment = "#>", fig.path = "man/figures/README-", out.width = "100%" )
The diftrans
package applies the novel methods described in Daljord, Pouliot, Hu, and Xiao (2021) to compute the transport costs between two univariate distributions and the differences-in-transports estimator.
Appealing to optimal transport theory, the diftrans
package builds off of the
transport
package
to compute the change between the distributions of a univariate variable of interest.
Extending this application, the diftrans
package allows users to compute the before-and-after estimator (Daljord et al., 2021), which controls for sampling uncertainty by trivializing small changes in the distributions.
The diftrans
package also computes the differences-in-transports estimator, which controls for unobservable reasons that the distributions may change by comparing the transport costs to those from another source of measurement.
The only function in the diftrans
package is diftrans
, which is explained below by way of a toy example.
The documentation file for diftrans::diftrans
can be found by running the following in an R console:
?diftrans::diftrans
Install the diftrans
from GitHub with:
# install.packages("devtools") devtools::install_github("omkarakatta/diftrans")
To install the package with the vignette installed, set the build_vignettes
argument to TRUE
.
Note that the installation will take more time this way.
# install.packages("devtools") devtools::install_github("omkarakatta/diftrans", build_vignettes = TRUE)
To cite the diftrans
package, use the BibTeX entry provided by:
citation("diftrans")
The workhorse function in the diftrans
package is also called diftrans
, which serves two purposes:
library(dplyr) library(magrittr) devtools::load_all() mykable <- function(dt){ kable(dt, align = 'c') }
library(dplyr) library(magrittr) library(diftrans)
We begin by computing the transport cost between two distributions of some
variable x
. These distributions should be represented as tibbles with two columns:
Suppose that the shift in the distribution is due to some treatment.
We refer to the first and second distributions of some random variable x
as the
pre-distribution and post-distribution for the treated group, respectively.
Below are the tibbles for the pre- and post-distributions as well as the
corresponding plots.
Both tibbles contain the full support of our variable of interest, i.e., their
support
column is the same.
Pre-Distribution for Treated Group
set.seed(1) support_min <- 1 support_max <- 10 support <- seq(support_min, support_max, 1) pre_treated <- data.frame(x = sample(support, 100, replace = T)) %>% group_by(x) %>% count() %>% ungroup() %>% rename(count = n) shift <- 5 for (i in 1:shift){ pre_treated[pre_treated$x == support_max-i+1, "count"] <- 0 } mykable(pre_treated)
Post-Distribution for Treated Group
post_treated <- data.frame(x = support, count = lag(pre_treated$count, shift)) %>% tidyr::replace_na(list(count = 0)) mykable(post_treated)
Observe that all the mass from the pre-distribution was shifted by r shift
units
of the support to form the post-distribution. That is, the treatment resulted in all
the mass to change. For instance, the mass that was given
given to r pre_treated[1, "x"]
in the pre-distribution is now given
to r pre_treated[1+shift, "x"]
.
In this toy example, we should expect that the transport cost is 100% because all the mass was transported due to the treatment.
We can compute the transport cost as follows:
tc <- diftrans(pre_main = pre_treated, post_main = post_treated, estimator = "tc", var = x, bandwidth = 0) tc
The transport cost is r scales::percent(tc$main)
as expected.
The estimator
argument is specified to be "tc"
, which stands for transport cost.
(Since we only have two distributions, estimator will take on the value of "tc"
by default.)
Since the post-distribution is a r shift
-unit shift in the pre-distribution, any bandwidth
at least r shift
must result in a transport cost of 0. We verify this by computing
the transport cost for a sequence of bandwidths from 0 to 10:
tc <- diftrans(pre_main = pre_treated, post_main = post_treated, estimator = "tc", var = x, bandwidth = seq(0, 10)) tc
Above, we assumed that the treatment is sole reason why any shifts between the pre- and post-distributions of x
might occur.
Now suppose there are unobserved trends in our variable that might also explain this shift.
To elicit how much of this shift is due to the treatment
and how much of this shift is due to unobserved trends, we can compare our distributions
above with distributions of the same variable measured in some control group that
did not receive the treatment but was also subjected to the same unobserved trends.
The novel differences-in-transports estimator described in Daljord et al. (2021) quantifies the change in distribution due to some treatment relative to the change in distribution of some control group.
control_shift <- 2 pre_control <- data.frame(x = support, count = pre_treated$count) post_control <- data.frame(x = support, count = lag(pre_treated$count, control_shift)) %>% tidyr::replace_na(list(count = 0))
First, we need to set up the distributions of our variable for our control group,
both before and after the treatment was administered to the treatment group.
For our purposes, we let the pre-distribution of our control group be the same
as that of our treated group. Then, let our post-distribution for our control group
be a r control_shift
-unit shift of our pre-distribution. We therefore have the
following tibbles that represent our control group distributions:
Pre-Distribution for Control Group
mykable(pre_control)
Post-Distribution for Control Group
mykable(post_control)
Above, we used estimator = "tc"
and specified our pre- and post-distributions
for our treated group.
Now, we use estimator = "dit"
(which stands for differences-in-treatments estimator)
while also specifying our pre- and post-distributions for our control group.
dit <- diftrans(pre_main = pre_treated, post_main = post_treated, pre_control = pre_control, post_control = post_control, estimator = "dit", var = x, bandwidth_seq = seq(0, 10, 1), save_dit = TRUE) dit$out dit$dit dit$optimal_bandwidth
Another difference between computing the transport cost and the differences-in-differences
estimator is that the differences-in-transports estimator is printed as a message.
To save this result in dit
, we use save_dit = TRUE
.
Thus, the differences-in-transports estimator is r scales::percent(dit$dit)
at an optimal bandwidth of r dit$optimal_bandwidth
.
Often times, the data will be a sample from a population, and sample variability is enough to cause differences between distributions. To account for this discrepancy, we can increase the bandwidth to ignore transfers of mass between nearby values of the support.
d_a <- 1
While Daljord et al. (2021) offer a disciplined way to determine what the appropriate
bandwidths are, for simplicity, we will suppose that there will not be any sampling
variation with a bandwidth greater than r d_a
.
d_a <- 1
dit <- diftrans(pre_main = pre_treated, post_main = post_treated, pre_control = pre_control, post_control = post_control, estimator = "dit", var = x, bandwidth_seq = seq(d_a, 10, 1), # smallest bandwidth will be d_a save_dit = TRUE) dit$out dit$dit dit$optimal_bandwidth
Note that this restriction is not binding, so our result is unaffected.
Accounting for sampling variability, our differences-in-transports estimate is still r scales::percent(dit$dit)
.
If we want to be more conservative, we can use
conservative = TRUE
, which essentially uses twice the bandwidth for computing the
treated group's transport costs relative to the bandwidth for computing the control
group's transport costs.
dit <- diftrans(pre_main = pre_treated, post_main = post_treated, pre_control = pre_control, post_control = post_control, estimator = "dit", var = x, bandwidth_seq = seq(d_a, 10, 1), save_dit = TRUE, conservative = TRUE) dit$out dit$dit dit$optimal_bandwidth
Finally, we have that the conservative differences-in-transports estimator is r scales::percent(dit$dit)
at an optimal bandwidth of r dit$optimal_bandwidth
.
Note that estimator = "dit"
is not necessary. Since we have two sets of pre- and
post-distributions, diftrans
is smart enough to return the differences-in-transports
estimator by default.
1 There is more nuance to computing the differences-in-transports estimator than is presented in this expository document. See Daljord et al. (2021) for a more complete treatment on how to compute the differences-in-transports estimator. ↩
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.