Tutorial for CTB ( Samii, Wang and Zhou, 2025 )

Tutorial

In this tutorial, we demonstrate how to use the CTTB package in R for implementing the method of covariate-tightened trimming bounds proposed by Samii, Wang, and Zhou (2025). This method enables empirical researchers to construct sharp bounds for the local average treatment effect (LATE) on a sub-population known as the always-responders in experiments or quasi-experiments, when faced with missing outcome or sample selection driven by potentially unobservable factors.

Problems with endogenous sample selection

Consider an experiment with \(N\) units. For each unit \(i\), we denote the outcome as \(Y_i\), the binary treatment status as \(D_i\), the binary response indicator as \(S_i\), and \(P\) pre-treatment covariates as \(\mathbf{X}_i = (X_{1i}, X_{2i}, \dots, X_{Pi})\). The outcome \(Y_i\) is observed only when \(S_i = 1\). The variable \(S_i\) reflects whether the unit opts to participate in the experiment or reports their outcome, and its value may be influenced by treatment status. Under the potential outcome framework, we have \[ Y_i = \begin{cases} Y_i(1), D_i = 1 \\ Y_i(0), D_i = 0, \end{cases} \text{ and } S_i = \begin{cases} S_i(1), D_i = 1 \\ S_i(0), D_i = 0. \end{cases} \] For simplicity, we assume that the treatment is randomly assigned with a positive and known probability, so that \(D_i\) is exogenous: \[ \begin{aligned} & (Y_i(1), Y_i(0), S_i(1), S_i(0)) \perp D_i, \\ & \varepsilon < P(D_i = 1) < 1 - \varepsilon, \text{ with } \varepsilon > 0. \end{aligned} \] Note that we can estimate the average outcome in either the treatment group or the control group only by conditioning on the event \(S_i = 1\). Since \(S_i\) is not randomly assigned, the difference-in-means estimator may no longer be unbiased. As depicted in the Directed Acyclic Graph (DAG) below, there may exist an unobservable variable \(U_i\) influencing both \(S_i\) and \(Y_i\). In this case, the difference in the average outcome across the treatment and control groups captures the influence of both \(U_i\) and \(D_i\), and thus does not estimate the average treatment effect accurately. Since \(S_i\) is an endogenous variable in the relationship between \(Y_i\) and \((D_i, S_i)\), we refer to this issue as “endogenous sample selection.”

This issue is common in social science and can also be interpreted through the lenses of post-treatment bias (Montgomery, Nyhan, and Torres 2018) or “phantom counterfactual” (Slough 2022). Conventionally, to mitigate the bias arising from conditioning on \(S_i\), researchers often turn to sample selection models (Heckman 1979) or various imputation methods (Honaker, King, and Blackwell 2011; Li et al. 2013; Blackwell, Honaker, and King 2017; Liu 2021). However, these methods generally assume that researchers have access to all variables influencing the selection process, a condition that is often unrealistic and contradicts the core rationale of conducting an experiment.

Introduction of the method

An alternative approach, which does not rely on such comprehensive knowledge, was proposed by Lee (2009). This method involves bounding the average treatment effect for a specific subgroup known as the always-responders—units whose response indicator remains at \(1\) regardless of whether they are under treatment or control conditions. This quantity is formally defined as \[ \tau(1, 1) = E[\tau_i | S_i(0) = S_i(1) = 1]. \] It is sometimes referred to as the “intensive margin effect” in economics literature (Staub 2014; Slough 2022). From a policy standpoint, this particular subgroup represents units for whom the intervention poses minimal concerns about compelling them into an activity they may find undesirable.

Lee (2009) shows that we can bound \(\tau(1, 1)\) under a much weaker assumption known as monotonic selection: \[ S_i(1) \geq S_i(0) \text{ for any unit } i, \] which means that the treatment encourages all units to participate in the experiment and report the outcome. Obviously, we can also assume the opposition direction is true. This assumption is implied by all sample selection models but does not require us to know the actual selection process. When it is satisfied, Lee (2009) proves that we can construct sharp bounds for \(\tau(1, 1)\) as follows: \[ \begin{aligned} & \tau^L_{TB}(1, 1) \leq \tau(1, 1) \leq \tau^U_{TB}(1, 1), \text{ with} \\ & \tau^L_{TB}(1, 1) = E[Y_{i} | D_i = 1, S_i = 1, Y_i \leq y_{q}] - E[Y_{i} | D_i = 0, S_i = 1], \text{ and} \\ & \tau^U_{TB}(1, 1) = E[Y_{i} | D_i = 1, S_i = 1, Y_i \geq y_{1-q}] - E[Y_{i} | D_i = 0, S_i = 1]. \end{aligned} \] Here \(y_{q}\) is the \(q\)th-quantile of the observed outcome’s conditional distribution in the treatment group. It satisfies the condition \(\int_{-\infty}^{y_{q}} dF_{Y|D=1, S=1}(y) = q\), with \(F_{Y|D=1, S=1}(\cdot)\) being the distribution function for observed treatment group outcomes. The quantile \(y_{1-q}\) is similarly defined. This approach is known in the literature as trimming bounds (TB). Theoretical justifications for trimming bounds can be found in Section 3 of Samii, Wang, and Zhou (2025).

One limitation of the basic trimming bounds is that they can be quite wide in practice and may provide little useful information about the LATE for the always-responders. Samii, Wang, and Zhou (2025) extend this method by combining it with a machine learning algorithm, generalized random forests or grf (Athey, Tibshirani, and Wager 2019), to incorporate information from a potentially high-dimensional set of covariates. Using machine learning, researchers can estimate parameters required by the trimming bounds, such as \(q\), \(y_{q}\), and \(y_{1-q}\), for any covariate profile and derive conditional trimming bounds. Then, by integrating these conditional bounds over the covariates distribution of the always-responders, one obtains the “covariate-tightened trimming bounds” (CTTB), which are typically much narrower than the basic trimming bounds. Moreover, the direction of monotonic selection is allowed to vary across covariate profiles. This relaxed assumption—termed conditionally monotonic selection in Semenova (2020)—is more realistic in many applications.

The algorithm implemented in the CTTB package uses both Neyman orthogonalization and cross-fitting, following the recommendations of Chernozhukov et al. (2018), such that the estimator does not suffer from any efficiency loss and is equipped with honest confidence intervals. The package also offers the option to relax the assumption of monotonic selection for the basic trimming bounds. Additionally, users can compute confidence intervals for the treatment effect itself, rather than for the bounds, using the method proposed by Imbens and Manski (2004).

Simulated data

We now show how to use the main functions in the package with a simulated experiment (dat_full). The dataset includes \(1,000\) units. The treatment is randomly assigned with a probability of \(0.5\) for all units. For each unit, we have \(10\) covariates, \(X1\) to \(X10\). All the covariates are uniformly distributed on \([0, 1]\). Among them, only one (\(X1\)) affects both the outcome and the response. In addition, there exists a variable (\(U \sim unif[-2, 2]\)) that is unobservable to researchers but also affects both the outcome and the response. We describe the DGP with more details in the paper. The assumption of monotonic selection is satisfied in the simulated data.

rm(list=ls())

seed <- 2023
set.seed(seed)
library(grf)
library(ggplot2)
library(CTTB)
data(simData)

N <- nrow(dat_full)

yrange <- range(c(dat_full$Y0, dat_full$Y1))
xrange <- range(dat_full$X2)

par(mfrow = c(1, 1))
plot(Y1 ~ X2, dat_full,
     pch=19,
     col="black",
     ylim=yrange, xlim=xrange,
     main="Potential outcomes",
     xlab=expression('X'[1]), ylab="Y", 
     cex.lab=1, cex.axis=1, cex = .4)
points(Y0 ~ X2, dat_full,
       pch=21,
       col="black",
       bg="white", 
       cex = .4)


plot(subset(dat_full, D==1&S==1)$X2,
     subset(dat_full, D==1&S==1)$Y,
     ylim=yrange, xlim=xrange,
     pch=19,
     col="black",
     main="Experimental outcomes \n (red means attrited)",
     xlab=expression('X'[1]), ylab="Y", 
     cex.lab=1, cex.axis=1, cex = .4)
points(subset(dat_full, D==0&S==0)$X2,
       subset(dat_full, D==0&S==0)$Y,
       ylim=yrange, xlim=xrange,
       pch=19,
       col="red", 
       cex = .4)
points(subset(dat_full, D==1&S==0)$X2,
       subset(dat_full, D==1&S==0)$Y,
       ylim=yrange, xlim=xrange,
       pch=19,
       col="red", 
       cex = .4)
points(subset(dat_full, D==0&S==1)$X2,
       subset(dat_full, D==0&S==1)$Y,
       pch=21,
       col="black",
       bg="white", 
       cex = .4)


plot(subset(dat_full, D==1&S==1)$X2,
     subset(dat_full, D==1&S==1)$Y,
     ylim=yrange, xlim=xrange,
     pch=19,
     col="black",
     main="Observed outcomes",
     xlab=expression('X'[1]), ylab="Y", 
     cex.lab=1, cex.axis=1, cex = .4)
points(subset(dat_full, D==0)$X2,
       subset(dat_full, D==0)$Y,
       pch=21,
       col="black",
       bg="white", 
       cex = .4)

The first two plots above display the distributions of the potential outcomes without and with sample attrition, respectively, while the last plot shows the distribution of observed outcomes in the data. Due to endogenous sample selection, the difference-in-means estimator is biased for either the ATE or the LATE on the always-responders.

# generate the data with missing outcome values
dat <- dat_full
dat[dat$S == 0, "Y"] <- NA
ate <- mean(dat_full$Y1 - dat_full$Y0)
late_ar <- mean(true_parameters$Ete_ar)
cat("The ATE equals ", ate, "\n")
#> The ATE equals  1.907555
cat("The LATE for always-responders equals ", late_ar, "\n")
#> The LATE for always-responders equals  1.714583
cat("The difference-in-means estimator generates an estimate of ", mean(dat$Y[dat$D == 1 & dat$S == 1]) - mean(dat$Y[dat$D == 0 & dat$S == 1]), "\n")
#> The difference-in-means estimator generates an estimate of  1.966238

Aggregated trimming bounds

We first use the CTTB() function to estimate the aggregated bounds, including the basic trimming bounds (LeeBounds = TRUE) and the covariate-tightened trimming bounds, for the LATE on the always-responders in the data. The function requires users to specify the dataset (\(data\)), the outcome variable (\(Y\)), the treatment (\(D\)), the response indicator (\(S\)), the covariates (\(X\)), and the propensity score (\(Ps\)). In observational studies, propensity score does not need to be specified and will be estimated from data (see the application below). We allow for the possibility of conditionally monotonic selection (cond_mono = TRUE) when estimating the basic trimming bounds and construct confidence intervals for both the effect estimate and the bounds (IM_cv = TRUE).

By applying the summary() function to the output of CTTB(), researchers can view the estimated lower and upper bounds using either TB or CTTB, along with their standard error estimates and 95% confidence intervals for both the bounds and the treatment effect—all presented in data frame format. Relevant information can then be easily extracted for further analysis. The results suggest that bounds estimated by either CTTB or TB cover the LATE for the always-responders, consistent with the theoretical conclusion. In addition, the identified set estimated by CTTB—the range between the lower and the upper bound—are indeed narrower than those obtained using TB, and they indicate that the LATE for the always-responders is significantly greater than zero in the simulated data.

result <- CTTB(data = dat, Y = "Y", D = "D", S = "S", X = c(names(dat)[c(3:12)]), Pscore = "Ps", LeeBounds = TRUE, IM_cv = TRUE, cond_mono = TRUE)
#> The average missing rate is  0.249

summary(result)
#> Estimates from trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1    0.141 0.195     -0.240      0.523        -0.179         0.461     TB Lower
#> 2    3.791 0.198      3.403      4.180         3.465         4.118     TB Upper
#> Estimates from covariate-tightened trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1    1.128 0.139      0.854      1.401         0.898         1.357   CTTB Lower
#> 2    2.380 0.140      2.105      2.655         2.150         2.611   CTTB Upper
p <- plot(result) 
p

Researchers can also visualize results from CTTB() using the plot() function, which produces a coefficient plot with confidence intervals based on ggplot2. Additional elements can be added to the generated plot to support further analysis. For example, one can compare the estimated bounds against the ATE and the LATE for the always-responders, as illustrated in the plot below.

p + geom_hline(aes(yintercept = ate), colour = "red", lty=2) +
  geom_hline(aes(yintercept = late_ar), colour = "red", lty=4)

There are several additional options that researchers can control. seed is the random seed used by functions in the grf package. cv_fold determines the number of folds used in cross-fitting (default is 5). splitting option specifies whether regression splitting is enabled when estimating conditional quantiles (default is FALSE). trim_l and trim_u allow researchers to truncate propensity score or conditional trimming probability estimates (default values are 0 and 1): values below trim_l (or above trim_u) are replaced by trim_l (or trim_u). alpha is the significance level for confidence intervals (default is 0.05). In the example below, we set cv_fold = 10, splitting = TRUE, trim_l = 0.1, and trim_u = 0.9. The results remain largely unchanged.

result <- CTTB(data = dat, seed = NULL, Y = "Y", D = "D", S = "S", X = c(names(dat)[c(3:12)]), Pscore = "Ps", cv_fold = 10, splitting = TRUE, trim_l = 0.1, trim_u = 0.9,  LeeBounds = TRUE, cond_mono = FALSE)
#> The average missing rate is  0.249 
#> S(1) > S(0)

plot(result)

Conditional trimming bounds

With CTTB(), researchers can also examine how the trimming bounds vary across covariate profiles, which is referred to as conditional trimming bounds by Samii, Wang, and Zhou (2025). When cBounds = TRUE, the function estimates these bounds across covariate profiles, where the moderator specified by the user (\(M\)) varies in 5-percentile increments and all other covariates are fixed at their sample means (or modes for dichotomous variables).

result <- CTTB(data = dat, Y = "Y", D = "D", S = "S", X = c(names(dat)[c(3:12)]), Pscore = "Ps", aggBounds = FALSE, cBounds = TRUE, M = "X2")
#> The average missing rate is  0.249

summary(result)
#> Estimates of conditional bounds: 
#>    Estimate    SE CI95_lower CI95_upper             Method  Type    M1    M2
#> 1    -1.404 0.173     -1.743     -1.065 Conditional Bounds Lower 0.059 0.482
#> 2    -1.121 0.169     -1.453     -0.789 Conditional Bounds Lower 0.101 0.482
#> 3    -0.835 0.174     -1.176     -0.493 Conditional Bounds Lower 0.149 0.482
#> 4    -0.442 0.174     -0.784     -0.100 Conditional Bounds Lower 0.197 0.482
#> 5    -0.121 0.174     -0.462      0.220 Conditional Bounds Lower 0.238 0.482
#> 6     0.044 0.165     -0.280      0.368 Conditional Bounds Lower 0.287 0.482
#> 7     0.321 0.188     -0.047      0.688 Conditional Bounds Lower 0.333 0.482
#> 8     0.679 0.223      0.242      1.116 Conditional Bounds Lower 0.383 0.482
#> 9     0.900 0.232      0.445      1.356 Conditional Bounds Lower 0.434 0.482
#> 10    1.249 0.177      0.902      1.596 Conditional Bounds Lower 0.476 0.482
#> 11    1.159 0.200      0.766      1.551 Conditional Bounds Lower 0.532 0.482
#> 12    1.527 0.188      1.159      1.895 Conditional Bounds Lower 0.586 0.482
#> 13    2.108 0.200      1.716      2.500 Conditional Bounds Lower 0.636 0.482
#> 14    2.607 0.189      2.236      2.978 Conditional Bounds Lower 0.679 0.482
#> 15    2.736 0.166      2.411      3.061 Conditional Bounds Lower 0.741 0.482
#> 16    2.861 0.146      2.575      3.147 Conditional Bounds Lower 0.778 0.482
#> 17    2.915 0.156      2.610      3.220 Conditional Bounds Lower 0.835 0.482
#> 18    2.902 0.178      2.554      3.250 Conditional Bounds Lower 0.886 0.482
#> 19    2.889 0.180      2.537      3.241 Conditional Bounds Lower 0.933 0.482
#> 20    0.708 0.173      0.369      1.047 Conditional Bounds Upper 0.059 0.482
#> 21    0.732 0.169      0.400      1.064 Conditional Bounds Upper 0.101 0.482
#> 22    0.847 0.174      0.506      1.188 Conditional Bounds Upper 0.149 0.482
#> 23    0.925 0.174      0.583      1.267 Conditional Bounds Upper 0.197 0.482
#> 24    1.004 0.174      0.663      1.346 Conditional Bounds Upper 0.238 0.482
#> 25    1.121 0.165      0.797      1.445 Conditional Bounds Upper 0.287 0.482
#> 26    1.430 0.188      1.063      1.798 Conditional Bounds Upper 0.333 0.482
#> 27    1.906 0.223      1.469      2.343 Conditional Bounds Upper 0.383 0.482
#> 28    2.158 0.232      1.703      2.613 Conditional Bounds Upper 0.434 0.482
#> 29    2.432 0.177      2.085      2.779 Conditional Bounds Upper 0.476 0.482
#> 30    2.391 0.200      1.998      2.784 Conditional Bounds Upper 0.532 0.482
#> 31    2.746 0.188      2.378      3.114 Conditional Bounds Upper 0.586 0.482
#> 32    3.677 0.200      3.285      4.069 Conditional Bounds Upper 0.636 0.482
#> 33    4.081 0.189      3.710      4.452 Conditional Bounds Upper 0.679 0.482
#> 34    4.316 0.166      3.991      4.641 Conditional Bounds Upper 0.741 0.482
#> 35    4.513 0.146      4.227      4.799 Conditional Bounds Upper 0.778 0.482
#> 36    4.701 0.156      4.396      5.006 Conditional Bounds Upper 0.835 0.482
#> 37    4.842 0.178      4.494      5.190 Conditional Bounds Upper 0.886 0.482
#> 38    4.929 0.180      4.576      5.281 Conditional Bounds Upper 0.933 0.482
#>       M3    M4    M5    M6    M7    M8  M9   M10
#> 1  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 2  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 3  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 4  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 5  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 6  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 7  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 8  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 9  0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 10 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 11 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 12 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 13 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 14 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 15 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 16 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 17 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 18 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 19 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 20 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 21 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 22 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 23 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 24 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 25 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 26 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 27 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 28 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 29 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 30 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 31 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 32 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 33 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 34 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 35 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 36 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 37 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498
#> 38 0.503 0.494 0.494 0.481 0.499 0.512 0.5 0.498

In the simulated data, we estimate the conditional trimming bounds with \(X_1\) being the moderator. Researchers can use the summary() function to inspect the estimates or the plot() function to visualize them by setting type = “conditional”. The plot below illustrates how the estimated bounds and their pointwise 95% confidence intervals vary with \(X_1\). Notably, the lower bound estimates become significantly greater than zero when \(X_1\) exceeds 0.38.

plot(result, type = "conditional")

Application

We now demonstrate the method’s performance using empirical studies analyzed in Samii, Wang, and Zhou (2025).

Santoro and Broockman (2022)

Santoro and Broockman (2022) conduct an online experiment to examine the effect of talking with an outpartisan—someone from a different political party—on attitudes toward the opposing parties. Subjects were randomly assigned to either the treatment group (\(D_i = 1\)), in which they were informed that their partner would be an outpartisan, or the control group (\(D_i = 0\)), in which no such information was provided. \(S_i \in {0, 1}\) indicates whether a subject initiated the online conversation and completed the questionnaire. Always-responders in this study are those who would engage in the conversation regardless of their partner’s partisanship.

We focus on the outcome of “warmth toward outpartisan voters” measured by a feeling thermometer and the following covariates: age, gender, race, education level, party identification, and the outcome’s pre-treatment level. We first estimate the aggregated bounds, using both TB and CTTB.

rm(list=ls())

seed <- 2023
set.seed(seed)
library(CTTB)
library(grf)
library(ggplot2)

dat <- get(load("santoro_replic.RData"))

Y <- "post_therm_voter_outparty_rescaled"
S <- "began_convo"
D <- "condition"
X <- names(dat)[c(4, 10, 19:62)]

result <- CTTB(data = dat, seed = seed, Y = Y, D = D, S = S, X = X, 
               W = NULL, Pscore = "Ps", LeeBounds = TRUE)
#> The average missing rate is  0.5756

summary(result)
#> Estimates from trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1   -0.769 0.112     -0.989     -0.549        -0.953        -0.584     TB Lower
#> 2    1.025 0.093      0.843      1.207         0.872         1.178     TB Upper
#> Estimates from covariate-tightened trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1    0.131 0.119     -0.103      0.365        -0.066         0.327   CTTB Lower
#> 2    0.474 0.125      0.229      0.719         0.268         0.679   CTTB Upper
p <- plot(result)
p

The results are shown in the plot above. The identified set from the CTTB method is again narrower than that from the TB method. The lower bound is significantly greater than zero at the 5% level, suggesting that the LATE for the always-responders is positive. In the plot below, we compare the estimated bounds with the OLS estimate. The OLS estimate falls outside the identified set produced by CTTB in this case.

# estimate the ate using observations that select into the sample
library(sandwich)
reg_formula <- as.formula(paste0(Y, "~", D, "+", paste0(X, collapse = "+")))
tau_reg <- lm(reg_formula, dat[dat[, S] == 1, ])
tau_ate <- coef(tau_reg)[2]
se_tau_ate <- sqrt(vcovHC(tau_reg, type = "HC1")[2, 2])

p.data <- data.frame("Estimate" = round(tau_ate, 3), "CI95_upper" = round(tau_ate + 1.96*se_tau_ate, 3), "CI95_lower" = round(tau_ate - 1.96*se_tau_ate, 3), "Method" = "OLS")

p <- p + geom_point(data = p.data, aes(x = Method, y = Estimate), shape = 17, size = 3, color = "grey5") + 
  geom_errorbar(data = p.data, aes(x = Method, ymin = CI95_lower, ymax = CI95_upper), color = "grey5", width = 0.2)
p

Next, we estimate the conditional bounds using age as the moderator. The results suggest that the effect may be larger among younger subjects.

result <- CTTB(data = dat, seed = seed, Y = Y, D = D, S = S, X = X, Pscore = "Ps", aggBounds = FALSE, cBounds = TRUE, M = "age")
#> The average missing rate is  0.5756

p <- plot(result, type = "conditional")
p

Blattman and Annan (2010)

Blattman and Annan (2010) is an observational study examining the long-term effects of being abducted into a rebel organization as a youth (\(D_i\)) in Northern Uganda. The authors surveyed a random sample of households to measure outcomes such as education, psychological distress, and wages among former abductees. Some selected respondents never returned from abduction, while others could not be located by the research team. We focus on psychological distress (\(Y_i\)) as the outcome of interest and use \(S_i\) to indicate whether a respondent was successfully interviewed. Covariates are drawn from the original analysis and include only variables without missing values, such as the respondent’s age and location and the household’s wealth level.

Results from both CTTB and TB are reported below. We apply the original sampling weights (\(W_i\)) and use 10-fold cross-validation. Estimates of the conditional trimming probability are truncated at 0.1 from below and 0.9 from above. The lower bound estimate is significantly greater than zero, suggesting that abduction has a persistent impact on psychological distress.

rm(list=ls())

seed <- 2023
set.seed(seed)
library(CTTB)
library(grf)
library(ggplot2)

dat <- get(load("blattman_replic.RData"))

Y <- "distress"
S <- "found"
D <- "abd"
W <- "i_weight"
AC <- c("C_ach", "C_akw", "C_ata", "C_kma", "C_oro", "C_pad", "C_paj", "A14", "A15", "A16", "A17", "A18", "A19", "A20", "A21", "A22", "A23", "A24", "A25", "A26", "A27", "A28", "A29") # age dummies and race dummies
ACG <- c(AC, "G1_18_21", "G1_22_25", "G2_14_17", "G2_22_25", "G3_14_17", "G3_18_21", "G4_14_17", "G4_18_21", "G4_22_25", 
         "G5_14_17", "G5_18_21", "G5_22_25", "G6_14_17", "G6_18_21", "G6_22_25", "G7_14_17", "G7_18_21", "G7_22_25", "G8_14_17", "G8_18_21", "G8_22_25")
X <- c("hh_fthr_frm", "hh_size96", "hh_land", "hh_cattle", "hh_stock", "hh_plow", "age", ACG)

result <- CTTB(data = dat, seed = seed, Y = Y, D = D, S = S, X = X, W = W, cv_fold = 10, LeeBounds = TRUE, trim_l = 0.05, trim_u = 0.95)
#> Propensity scores are not specified and will be estimated using X. 
#> Confounders are not specified. Propensity scores will be estimated using X.The average missing rate is  0.229

summary(result)
#> Estimates from trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1   -0.945 0.157     -1.254     -0.637        -1.204        -0.686     TB Lower
#> 2    2.077 0.157      1.769      2.384         1.819         2.335     TB Upper
#> Estimates from covariate-tightened trimming bounds: 
#>   Estimate    SE CI95_lower CI95_upper CI95_lower_IM CI95_upper_IM Method  Type
#> 1    0.299 0.259     -0.209      0.807        -0.127         0.726   CTTB Lower
#> 2    0.807 0.237      0.342      1.273         0.417         1.198   CTTB Upper
p <- plot(result)
p


Reference

Athey, Susan, Julie Tibshirani, and Stefan Wager. 2019. “Generalized Random Forests.” The Annals of Statistics 47 (2): 1148–78.
Blackwell, Matthew, James Honaker, and Gary King. 2017. “A Unified Approach to Measurement Error and Missing Data: Overview and Applications.” Sociological Methods & Research 46 (3): 303–41.
Blattman, Christopher, and Jeannie Annan. 2010. “The Consequences of Child Soldiering.” The Review of Economics and Statistics 92 (4): 882–98.
Chernozhukov, Victor, Denis Chetverikov, Mert Demirer, Esther Duflo, Christian Hansen, Whitney Newey, and James Robins. 2018. “Double/Debiased Machine Learning for Treatment and Structural Parameters.” The Econometrics Journal 21 (1): C1–68.
Heckman, James J. 1979. “Sample Selection Bias as a Specification Error.” Econometrica: Journal of the Econometric Society, 153–61.
Honaker, James, Gary King, and Matthew Blackwell. 2011. “Amelia II: A Program for Missing Data.” Journal of Statistical Software 45: 1–47.
Imbens, Guido W, and Charles F Manski. 2004. “Confidence Intervals for Partially Identified Parameters.” Econometrica 72 (6): 1845–57.
Lee, David S. 2009. “Training, Wages, and Sample Selection: Estimating Sharp Bounds on Treatment Effects.” The Review of Economic Studies 76 (3): 1071–1102.
Li, Lingling, Changyu Shen, Xiaochun Li, and James M Robins. 2013. “On Weighting Approaches for Missing Data.” Statistical Methods in Medical Research 22 (1): 14–30.
Liu, Naijia. 2021. “A Latent Factor Approach to Missing Not at Random.”
Montgomery, Jacob M, Brendan Nyhan, and Michelle Torres. 2018. “How Conditioning on Posttreatment Variables Can Ruin Your Experiment and What to Do about It.” American Journal of Political Science 62 (3): 760–75.
Samii, Cyrus, Ye Wang, and Junlong Aaron Zhou. 2025. “Generalizing Trimming Bounds for Endogenously Missing Outcome Data Using Random Forests.” Political Analysis, 1–15.
Santoro, Erik, and David E Broockman. 2022. “The Promise and Pitfalls of Cross-Partisan Conversations for Reducing Affective Polarization: Evidence from Randomized Experiments.” Science Advances 8 (25): eabn5515.
Semenova, Vira. 2020. “Better Lee Bounds.” arXiv Preprint arXiv:2008.12720.
Slough, Tara. 2022. “Phantom Counterfactuals.” American Journal of Political Science Forthcoming. URL: Http://Taraslough. Com/Assets/Pdf/Phantom_counterfactuals. Pdf.
Staub, Kevin E. 2014. “A Causal Interpretation of Extensive and Intensive Margin Effects in Generalized Tobit Models.” Review of Economics and Statistics 96 (2): 371–75.