Comparing Endid and Linear DiD

This example illustrates a location-scale treatment effect, where the policy changes both the mean and dispersion of the outcome.

Setup

using Endid
using DataFrames
using Statistics
using Random
using Distributions
using Plots

Random.seed!(42)

N = 80
T_total = 6
tpost1 = 4

unit = repeat(1:N, inner=T_total)
time = repeat(1:T_total, outer=N)
alpha = repeat(randn(N) .* 0.3, inner=T_total)
group = repeat(vcat(fill(1, Int(N / 2)), fill(0, Int(N / 2))), inner=T_total)
post = [t >= tpost1 ? 1 : 0 for t in time]
D = post .* group

epsilon = randn(N * T_total)
y = alpha .+ 0.5 .* D .+ (0.5 .+ 1.0 .* D) .* epsilon

df = DataFrame(unit = unit, time = time, y = y, post_treat = post, group = group)
first(df, 10)
10×5 DataFrame
Rowunittimeypost_treatgroup
Int64Int64Float64Int64Int64
111-0.36431501
2120.50367501
313-0.13499801
4141.0725311
5151.4588711
616-0.68853911
7210.7913601
8220.54001201
923-0.27117401
1024-0.52682111

Estimation

fit_endid = endid(df, :y, :unit, :time, :post_treat;
                  dvar=:group, num_epochs=10, nboot=1, seed=42)

println(fit_endid)

Training Engression...  20%|█████▋                      |  ETA: 0:01:33
Training Engression... 100%|████████████████████████████| Time: 0:00:23
Running Parallel Bootstrap (n=1)...
Endid Result (common_timing design)
------------------------------
ATT Estimate: -0.068
Std. Error  : NaN
95% CI      : (-0.068, -0.068)

Quantile Treatment Effects (QTE):
9×3 DataFrame
 Row │ quantile  effect      se
     │ Float64   Float64     Float64
─────┼───────────────────────────────
   1 │      0.1  -0.0677664      NaN
   2 │      0.2  -0.0719812      NaN
   3 │      0.3  -0.0700946      NaN
   4 │      0.4  -0.068942       NaN
   5 │      0.5  -0.0724057      NaN
   6 │      0.6  -0.0728971      NaN
   7 │      0.7  -0.0754968      NaN
   8 │      0.8  -0.0692544      NaN
   9 │      0.9  -0.0737824      NaN

Quantile Treatment Effects

p = plot(fit_endid)
savefig(p, "endid_qte.svg")
nothing

Overlay the analytical QTE, 0.5 + Φ^{-1}(τ):

taus = 0.05:0.05:0.95
true_qte = 0.5 .+ quantile.(Normal(0, 1), taus)

p2 = plot(fit_endid)
plot!(p2, taus, true_qte, label="True QTE", linestyle=:dash, color=:red, linewidth=2)
savefig(p2, "endid_qte_true.svg")
nothing