QTE in RDD

Introduction

The QTE.RD package provides tools to test, estimate, and conduct uniform inference on quantile treatment effects (QTEs) in sharp regression discontinuity (RD) designs. These methods are particularly useful for detecting and analyzing heterogeneous treatment effects.

Instead of focusing solely on average effects, users can assess a policy’s impact across the entire distribution of an outcome variable. When significant heterogeneity is identified by examining the distributional effects for the full sample, the package can help pinpoint the source of the heterogeneity by allowing for covariate-specific effects in the RD design.

The statistical theory underlying these methods was developed by Qu and Yoon (2019) and Qu, Yoon, and Perron (2024), who introduced uniform inference procedures and robust bias correction for QTE in RD designs. QTE.RD implements these methods in a user-friendly interface.


Scope of this Vignette

This vignette serves as a practical guide for users with some familiarity with quantile regression and RD designs. For those seeking a more detailed introduction to the methodology and its applications, we refer to Qu and Yoon (2025), which discusses both the theoretical foundations and the practical use of QTE.RD in detail.


Getting Started

QTE.RD can be installed from CRAN via

install.packages("QTE.RD")

The package depends on quantreg for quantile regression. If it is not already installed, install it with: install.packages("quantreg").


Overview of the package

After installing the QTE.RD package, it can be loaded by

library(QTE.RD)

The package provides four main functions:

  1. rd.qte() - Estimates QTEs over a range of quantiles using local-linear regressions and constructs uniform confidence bands. Users can include covariates and apply either no bias correction or robust bias correction (Qu, Yoon, and Perron 2024).

  2. rdq.test() - Tests three hypotheses on treatment effects: Treatment Significance, Homogeneity, and Unambiguity. It supports covariates and robust bias correction, with critical values obtained via simulation.

  3. rdq.bandwidth() - Selects bandwidths using either cross-validation or MSE-optimal rules. Both can be applied to check robustness.

  4. plot.qte() - Produces figures of QTE estimates and uniform confidence bands to visualize estimation and testing results.


Illustration by Example

For illustration, we apply these functions to study the effect of tracking (assigning students into separate classes based on prior achievement) on student performance. We use a subset of the dataset from Duflo, Dupas, and Kremer (2011), which is included in the package and can be loaded with:

data("ddk_2011")

Data and Background

The data ddk_2011 comes from an experiment in 121 Kenyan primary schools that received funds in 2005 to hire an extra teacher and split their single first-grade class into two sections. Schools were randomly assigned to treatment group, 61 tracking schools, or control group, 60 non-tracking schools. In tracking schools, students were assigned to upper or lower section based on baseline test scores. In non-tracking schools, students were randomly assigned.

The experimental design has rich random variations, featuring elements of both randomized controlled trials (RCT) and RD. By comparing tracking and non-tracking schools, that is, by exploiting the RCT structure, one can study the effect of tracking on all students. Additionally, by analyzing median students within tracking schools, that is, by exploiting the RD structure, one can study the effect of tracking on marginal students who barely made or missed the opportunity of being assigned to a high ability section.

This vignette will focus on the RD-based evidence, which provides insight into how tracking affects students at the margin. Readers interested in the RCT-based evidence can find a more detailed analysis in Qu and Yoon (2025).

The experiment lasted 18 months. The outcome is the sum of math and language scores on endline tests, and the running variable for RD is student’s percentile rank from the baseline test. To explore heterogeneity in treatment effects, we include baseline test scores, gender, age at the endline test, and teacher status (civil servant vs. contract) as covariates.


Variables

Define some key variables:

trk <- ddk_2011$tracking
con <- ddk_2011$etpteacher
hgh <- ddk_2011$highstream
yy  <- ddk_2011$ts_std
xx  <- ddk_2011$percentile

There are three indicator variables; trk takes 1 for tracking schools (and 0 for non-tracking schools), con takes 1 for students assigned to a contract teacher, hgh takes 1 for students assigned to high-achieving sections (if in tracking schools). The variable yy is the endline test scores normalized by the mean and standard deviation of non-tracking schools and xx is student’s percentile rank from the baseline test. Because the outcome variable is normalized, the unit of the effect is a standard deviation of the endline test score (of non-tracking schools).


RDD

We focus on tracking schools to evaluate the effects of tracking using the RD design. Specifically, we examine students near the median of the baseline test score, comparing those who just qualified for the upper section to those who narrowly missed it. The outcome and running variables (yc and xc below) include only students from tracking schools. The cutoff is the median of the baseline percentile (x0 = 50), and the treatment indicator (dc below) equals 1 for students placed in the high-achieving section.

yc <- yy[trk==1]
xc <- xx[trk==1]
dc <- hgh[trk==1]
x0 <- 50
tlevel <- 1:9/10
hh <- 20

The last two lines set the values of two parameters; tlevel defines the range of quantile index to be [0.1,0.9] and hh is the bandwidth at the median of the outcome distribution.


QTE from RDD without covariates

In rd.qte(), when x includes the running variable only and z0 is unspecified, one can estimate quantile effects at x0 without covariate.

A <- rd.qte(y=yc,x=xc,d=dc,x0,z0=NULL,tau=tlevel,bdw=hh,bias=1)
A2 <- summary(A,alpha=0.1)
A2
#> 
#> 
#>                                  QTE                                   
#> ---------------------------------------------------------------------- 
#>              Bias cor.    Pointwise         Uniform      
#>     Tau         Est.     Robust S.E.    90% Conf. Band  
#>      0.1      -0.104       0.140      -0.436       0.227
#>      0.2      -0.001       0.145      -0.343       0.341
#>      0.3      -0.068       0.153      -0.430       0.294
#>      0.4      -0.074       0.161      -0.455       0.307
#>      0.5      -0.157       0.186      -0.596       0.283
#>      0.6      -0.069       0.225      -0.603       0.464
#>      0.7      -0.020       0.276      -0.673       0.634
#>      0.8      -0.023       0.318      -0.776       0.729
#>      0.9      -0.003       0.268      -0.636       0.631

The outcome table shows some essential elements of the analysis including point estimates of QTE, standard errors, and uniform confidence bands. Because the bias option is activated, bias=1, the table reports the bias corrected point estimate and the robust standard error and robust uniform band. If bias=0, one would obtain QTE estimates and uniform bands without the bias correction. In the second line, alpha=0.1, so a 90% uniform confidence band is reported. If alpha=0.05, one would get a 95% uniform confidence band.

The estimated quantile effects are small in magnitude (the maximum effect is -0.157 standard deviation when \(\tau=0.5\)) and the uniform confidence band includes zero throughout the quantile range. This confirms a finding in Duflo, Dupas, and Kremer (2011) who concluded that ``the median student in tracking schools scores similarly whether assigned to the upper or lower section.’’ The QTE estimate provides even stronger evidence that not only on average but also on the entire endline score distribution, students near the median of the initial test scores fare similarly regardless of whether they were assigned to the upper or lower ability section.

To examine the shape of the effect graphically, plot.qte() function can be used to produce QTE plots along with uniform confidence bands.

y.text <- "test scores"
m.text <- "Effects of assignment to lower vs. upper sections"
plot(A2,ytext=y.text,mtext=m.text)


It is of interest to examine the conditional quantile functions from two sides of the cutoff. The function plot.qte() can make such plots with the option ptype=2. The inputs for conditional quantile plots include estimates for two conditional quantile functions, qp and qm, and their uniform bands, bandp and bandm. These outputs are produced by summary.qte() and already saved in A2, as the next example shows.

y.text <- "test scores"
m.text <- "Conditional quantile functions"
sub.text <- c("Upper section","Lower section")
plot(A2,ptype=2,ytext=y.text,mtext=m.text,subtext=sub.text)


To test the (lack of) effect, one can use the rdq.test() function. When alpha=c(0.1,0.05), it provides critical values at the 10% and 5% levels. The type option determines the type of tests to be conducted. To test the treatment significance, set type=1 and to test the treatment homogeneity hypothesis, change it to type=2. For the unambiguity hypothesis with the effects unambiguously positive (or negative) under the null hypothesis, set type=3 (or type=4). These options can be combined: the lines below set type=c(1,2,3,4), leading to tests for all four hypotheses.

B <- rdq.test(y=yc,x=xc,d=dc,x0,z0=NULL,tau=tlevel,bdw=hh,bias=1,
              alpha=c(0.1,0.05),type=c(1,2,3,4),std.opt=1)
B
#> 
#> 
#>                      Testing hypotheses on quantile process                      
#> -------------------------------------------------------------------------------- 
#> NULL Hypthoesis                            test stat.  critical value   p value    
#>                                                         10%       5%        
#> ================================================================================ 
#> Significance: QTE(tau|x,z)=0 for all taus      0.87     2.42     2.70     0.93 
#> Homogeneity: QTE(tau|x,z) is constant          0.52     1.86     2.09     0.98 
#> Dominance: QTE(tau|x,z)>=0 for all taus        0.87     2.08     2.42     0.58 
#> Dominance: QTE(tau|x,z)<=0 for all taus        0.00     2.10     2.42     1.00

When std.opt=1, the test statistic is standardized by the pointwise standard deviations of the limiting process. As a result, the quantiles that are estimated imprecisely receive less weight in the construction. When std.opt=0, the tests are not standardized. The default is std.opt=1.

The outcome table displays the null hypotheses to be tested, test statistics, critical values, and p-values. All four tests indicate that QTEs are likely to be zero over the entire quantile range.

The empirical evidence from the RDD indicates that there is no difference in endline achievement between marginal students regardless of whether they were assigned to the upper or lower section. Because students in the upper section had much higher achieving peers, this implies that there may be a factor that offsets the positive peer effect. One possibility is that tracking may allow teachers to adjust their instruction to students’ needs. Duflo, Dupas, and Kremer (2011) explored this potential channel and documented evidence that teachers had incentives to focus on the students at the top of the distribution.

The bandwidth (at the median) can be estimated as follows.

C <- rdq.bandwidth(y=yc,x=xc,d=dc,x0,z0=NULL,cv=1,val=(5:20),pm.each=0)
C
#> 
#> 
#>                     Selected Bandwidths                      
#> ------------------------------------------------------------ 
#> Method                               Values        
#> ============================================================ 
#> Cross Validation                        20 
#> MSE Optimal                           16.3         16.0

The cross-validation option is enabled by cv=1, so the table reports both CV and MSE optimal bandwidths. The candidate bandwidth values for cross-validation are set to \(\{5,6,\ldots,20\}\) by val=(5:20). In this example, bandwidth 20 (at the median) was selected by the cross-validation method and used accordingly.

For very large samples, computing the CV bandwidth may take a long time. In such a case, set cv=0 and use the MSE optimal bandwidth at least for the initial stage of data exploration. The function rdq.bandwidth() offers flexibility by allowing users to adjust several option arguments; see Qu and Yoon (2025) for more details.


QTE from RDD with covariates

To see heterogeneity in the effect of tracking, one can include additional covariates. This section compares effects of tracking for boys and girls. The covariate zc is a female dummy and the evaluation point \(z_0\) is set by z.eval = c(0,1). The order of display in the outcome table is the same as the order of the group in \(z_0\).

zc <- ddk_2011$girl[trk==1]
z.eval <- c(0,1)
A <- rd.qte(y=yc,x=cbind(xc,zc),d=dc,x0,z0=z.eval,tau=tlevel,bdw=hh,
            bias=1)
A2 <- summary(A,alpha=0.1)
A2
#> 
#> 
#>                                  QTE                                   
#> ---------------------------------------------------------------------- 
#>              Bias cor.    Pointwise         Uniform      
#>     Tau         Est.     Robust S.E.    90% Conf. Band  
#> ---------------------------------------------------------------------- 
#>  Group-1  
#>      0.1       0.295       0.186      -0.155       0.745
#>      0.2       0.090       0.221      -0.444       0.624
#>      0.3       0.063       0.213      -0.449       0.576
#>      0.4      -0.026       0.237      -0.599       0.547
#>      0.5       0.031       0.284      -0.655       0.717
#>      0.6       0.353       0.335      -0.454       1.161
#>      0.7       0.597       0.371      -0.298       1.492
#>      0.8       0.160       0.469      -0.972       1.292
#>      0.9       0.159       0.399      -0.805       1.123
#> ---------------------------------------------------------------------- 
#>  Group-2  
#>      0.1      -0.406       0.145      -0.752      -0.059
#>      0.2      -0.161       0.201      -0.641       0.318
#>      0.3      -0.100       0.220      -0.625       0.425
#>      0.4      -0.233       0.245      -0.818       0.352
#>      0.5      -0.475       0.268      -1.115       0.165
#>      0.6      -0.291       0.287      -0.977       0.395
#>      0.7      -0.158       0.348      -0.989       0.674
#>      0.8      -0.236       0.420      -1.239       0.767
#>      0.9       0.000       0.317      -0.756       0.756

Because z.eval <- c(0,1) and \(z_0 = 0\) means boys, the outcome table shows results for boys first (shown as Group-1) and girls later (Group-2). For boys, the quantile effects of being in the upper ability section is positive but insignificant. For girls, the effects are mostly negative and insignificant. But at the bottom of the outcome distribution, when \(\tau = 0.1\), the negative effect turns to be significant.

To see the group-wise difference graphically, one can draw QTE plots as follows.

y.text <- "test scores"
m.text <- c("Boys","Girls")
plot(A2,ytext=y.text,mtext=m.text)

The plot clearly shows that tracking has positive but insignificant effect for marginal male students, but the effect is negative for marginal female students and significantly so at the left tail. To explore further, it will be useful to draw plots for the conditional quantile functions (by setting ptype=2) separately for each group.

y.text <- "test scores"
m.text <- c("Boys","Girls")
sub.text <- c("Upper section","Lower section")
plot(A2,ptype=2,ytext=y.text,mtext=m.text,subtext=sub.text)

The plot shows that for girls, the conditional quantile function of endline test scores for the upper section is consistently below that of the lower section, and the difference is largest at the left tail.

Tests for hypotheses for each group can be done as well.

B <- rdq.test(y=yc,x=cbind(xc,zc),d=dc,x0,z0=z.eval,tau=tlevel,bdw=hh,
              bias=1,alpha=c(0.1,0.05),type=c(1,2,3,4))
B
#> 
#> 
#>                      Testing hypotheses on quantile process                      
#> -------------------------------------------------------------------------------- 
#> NULL Hypthoesis                            test stat.  critical value   p value    
#>                                                         10%       5%        
#> ================================================================================ 
#>  Group-1  
#> Significance: QTE(tau|x,z)=0 for all taus      1.62     2.35     2.60     0.44 
#> Homogeneity: QTE(tau|x,z) is constant          1.15     1.85     2.19     0.52 
#> Dominance: QTE(tau|x,z)>=0 for all taus        0.11     2.01     2.24     0.88 
#> Dominance: QTE(tau|x,z)<=0 for all taus        1.62     2.10     2.39     0.24 
#> -------------------------------------------------------------------------------- 
#>  Group-2  
#> Significance: QTE(tau|x,z)=0 for all taus      2.88     2.37     2.63    0.022 
#> Homogeneity: QTE(tau|x,z) is constant          1.15     1.98     2.27     0.59 
#> Dominance: QTE(tau|x,z)>=0 for all taus        2.88     2.07     2.39    0.013 
#> Dominance: QTE(tau|x,z)<=0 for all taus     0.00014     2.09     2.35     0.89

The results indicate that it is not possible to reject hypotheses that the QTE is consistently zero for boys, but there is evidence that the effects can be negative for girls. For female students the null hypothesis of no effect (significance) and positive uniform effect (dominance) are rejected at the 5% confidence level.

The bandwidth can be selected as well.

C <- rdq.bandwidth(y=yc,x=cbind(xc,zc),d=dc,x0,z0=z.eval,cv=1,
                   val=(5:20),pm.each=0)
C
#> 
#> 
#>                     Selected Bandwidths                      
#> ------------------------------------------------------------ 
#> Method                               Values        
#> ============================================================ 
#> Cross Validation                        19 
#> MSE Optimal,Group-1                   14.0         14.2 
#> MSE Optimal,Group-2                   17.8         20.0

When users would like to see the effect of the bias correction on point estimates and uniform bands, it will be convenient to use the function rdq.band(). Its options are the same as those in rd.qte(). The difference is that it implements estimation with and without bias correction and present results side by side.

D <- rdq.band(y=yc,x=cbind(xc,zc),d=dc,x0,z0=z.eval,tau=tlevel,
              bdw=hh,alpha=0.1)
D
#> 
#> 
#>                         QTE and Uniform Bands                          
#> ---------------------------------------------------------------------- 
#>                      Bias cor.         90% Uniform Conf. Band         
#>    Tau        Est.      Est.       Non-robust            Robust        
#> ---------------------------------------------------------------------- 
#>  Group-1  
#>      0.1     0.118     0.295    -0.209     0.445    -0.145     0.734
#>      0.2     0.014     0.090    -0.362     0.389    -0.444     0.624
#>      0.3     0.022     0.063    -0.326     0.369    -0.423     0.550
#>      0.4    -0.023    -0.026    -0.424     0.379    -0.576     0.524
#>      0.5     0.044     0.031    -0.427     0.516    -0.627     0.688
#>      0.6     0.093     0.353    -0.460     0.646    -0.417     1.124
#>      0.7     0.194     0.597    -0.426     0.814    -0.258     1.452
#>      0.8     0.096     0.160    -0.679     0.871    -0.910     1.230
#>      0.9     0.267     0.159    -0.416     0.950    -0.799     1.117
#> ---------------------------------------------------------------------- 
#>  Group-2  
#>      0.1    -0.204    -0.406    -0.466     0.057    -0.739    -0.072
#>      0.2    -0.111    -0.161    -0.469     0.247    -0.617     0.294
#>      0.3    -0.128    -0.100    -0.532     0.275    -0.594     0.394
#>      0.4    -0.186    -0.233    -0.642     0.270    -0.797     0.331
#>      0.5    -0.335    -0.475    -0.838     0.168    -1.109     0.159
#>      0.6    -0.136    -0.291    -0.676     0.404    -0.979     0.397
#>      0.7    -0.142    -0.158    -0.797     0.513    -0.989     0.673
#>      0.8    -0.148    -0.236    -0.942     0.646    -1.271     0.799
#>      0.9     0.085     0.000    -0.520     0.689    -0.788     0.788

Without bias correction, the effect for girls at the 10-th percentile is no longer statistically significant, as the estimate is smaller. Otherwise, the conclusion does not change.


Concluding Remarks

In this vignette, we illustrated the QTE.RD package using a binary covariate in the RD setup. The scope of the analysis can be broadened, and the exploration of heterogeneity becomes more interesting when incorporating a continuous covariate or a combination of both discrete and continuous covariates. Further details can be found in Qu and Yoon (2025). Readers interested in RCT-based evidence on tracking may also refer to the more detailed analysis in Qu and Yoon (2025).


References

Duflo, Esther, Pascaline Dupas, and Michael Kremer. 2011. “Peer Effects, Teacher Incentives, and the Impact of Tracking: Evidence from a Randomized Evaluation in Kenya.” American Economic Review 101 (5): 1739–74. https://doi.org/10.1257/aer.101.5.1739.
Qu, Zhongjun, and Jungmo Yoon. 2019. “Uniform Inference on Quantile Effects Under Sharp Regression Discontinuity Designs.” Journal of Business and Economic Statistics 37 (4): 625–47. https://doi.org/10.1080/07350015.2017.1407323.
———. 2025. “QTE.RD: An r Package for Quantile Treatment Effects in Regression-Discontinuity Designs.” https://sites.google.com/site/jungmoyoon2/research.
Qu, Zhongjun, Jungmo Yoon, and Pierre Perron. 2024. Inference on Conditional Quantile Processes in Partially Linear Models with Applications to the Impact of Unemployment Benefits.” The Review of Economics and Statistics, March, 521–41. https://doi.org/10.1162/rest_a_01168.