|
4 | 4 | Estimation procedure for regression-based difference-in-differences. |
5 | 5 | """ |
6 | 6 | const RegressionBasedDID = DiffinDiffsEstimator{:RegressionBasedDID, |
7 | | - Tuple{CheckData, CheckVcov, CheckVars, MakeWeights, CheckFEs, MakeFESolver, |
8 | | - MakeYXCols, MakeTreatCols}} |
| 7 | + Tuple{CheckData, CheckVcov, CheckVars, CheckFEs, MakeWeights, MakeFESolver, |
| 8 | + MakeYXCols, MakeTreatCols, SolveLeastSquares, EstVcov}} |
9 | 9 |
|
10 | 10 | const Reg = RegressionBasedDID |
11 | 11 |
|
| 12 | +function _get_default(::Reg, @nospecialize(ntargs::NamedTuple)) |
| 13 | + defaults = (subset=nothing, weightname=nothing, vce=Vcov.RobustCovariance(), |
| 14 | + treatintterms=(), xterms=(), drop_singletons=true, nfethreads=6, |
| 15 | + contrasts=Dict{Symbol,Any}(), fetol=1.0e-8, femaxiter=10000, cohortinteracted=true) |
| 16 | + return merge(defaults, ntargs) |
| 17 | +end |
| 18 | + |
| 19 | +function valid_didargs(d::Type{Reg}, ::DynamicTreatment{SharpDesign}, |
| 20 | + ::TrendParallel{Unconditional, Exact}, @nospecialize(ntargs::NamedTuple)) |
| 21 | + ntargs = _get_default(d(), ntargs) |
| 22 | + name = haskey(ntargs, :name) ? ntargs.name : "" |
| 23 | + return name, d, ntargs |
| 24 | +end |
| 25 | + |
| 26 | +struct RegressionBasedDIDResult{CohortInteracted} <: DIDResult |
| 27 | + coef::Vector{Float64} |
| 28 | + vcov::Matrix{Float64} |
| 29 | + vce::CovarianceEstimator |
| 30 | + tr::AbstractTreatment |
| 31 | + pr::AbstractParallel |
| 32 | + cellweights::Vector{Float64} |
| 33 | + cellcounts::Vector{Int} |
| 34 | + esample::BitVector |
| 35 | + nobs::Int |
| 36 | + dof_residual::Int |
| 37 | + F::Float64 |
| 38 | + p::Float64 |
| 39 | + yname::String |
| 40 | + coefnames::Vector{String} |
| 41 | + coefinds::Dict{String, Int} |
| 42 | + treatinds::Table |
| 43 | + yxterms::Dict{AbstractTerm, AbstractTerm} |
| 44 | + treatname::Symbol |
| 45 | + contrasts::Dict{Symbol, Any} |
| 46 | + weightname::Union{Symbol, Nothing} |
| 47 | + fenames::Vector{Symbol} |
| 48 | + nfeiterations::Union{Int, Nothing} |
| 49 | + feconverged::Union{Bool, Nothing} |
| 50 | + nfesingledropped::Int |
| 51 | +end |
| 52 | + |
| 53 | +function result(::Type{Reg}, @nospecialize(nt::NamedTuple)) |
| 54 | + cellweights = [nt.cellweights[k] for k in nt.treatinds] |
| 55 | + cellcounts = [nt.cellcounts[k] for k in nt.treatinds] |
| 56 | + yname = coefnames(nt.yxterms[nt.yterm]) |
| 57 | + tnames = _treatnames(nt.treatinds) |
| 58 | + cnames = [coefnames(nt.yxterms[x]) for x in nt.xterms if width(nt.yxterms[x])>0] |
| 59 | + cnames = vcat(tnames, cnames)[nt.basecols] |
| 60 | + coefinds = Dict(cnames .=> 1:length(cnames)) |
| 61 | + didresult = RegressionBasedDIDResult{nt.cohortinteracted}( |
| 62 | + nt.coef, nt.vcov_mat, nt.vce, nt.tr, nt.pr, cellweights, cellcounts, |
| 63 | + nt.esample, sum(nt.esample), nt.dof_resid, nt.F, nt.p, |
| 64 | + yname, cnames, coefinds, nt.treatinds, nt.yxterms, nt.treatname, nt.contrasts, |
| 65 | + nt.weightname, nt.fenames, nt.nfeiterations, nt.feconverged, nt.nsingle) |
| 66 | + return merge(nt, (result=didresult,)) |
| 67 | +end |
| 68 | + |
| 69 | +has_fe(r::RegressionBasedDIDResult) = !isempty(r.fenames) |
| 70 | + |
| 71 | +format_scientific(f::Number) = @sprintf("%.3f", f) |
| 72 | + |
| 73 | +function _summary(r::RegressionBasedDIDResult) |
| 74 | + top = ["Number of obs" sprint(show, nobs(r), context=:compact=>true); |
| 75 | + "Degrees of freedom" sprint(show, nobs(r) - dof_residual(r), context=:compact=>true); |
| 76 | + "F-statistic" sprint(show, r.F, context = :compact => true); |
| 77 | + "p-value" format_scientific(r.p)] |
| 78 | + fe_info = has_fe(r) ? |
| 79 | + ["Converged" sprint(show, r.feconverged, context=:compact=>true); |
| 80 | + "Singletons dropped" sprint(show, r.nfesingledropped, context=:compact=>true); |
| 81 | + ] : nothing |
| 82 | + return top, fe_info |
| 83 | +end |
| 84 | + |
| 85 | +_nunique(t::Table, s::Symbol) = length(unique(getproperty(t, s))) |
| 86 | + |
| 87 | +_excluded_rel_str(tr::DynamicTreatment) = |
| 88 | + isempty(tr.exc) ? "None" : join(string.(tr.exc), " ") |
| 89 | + |
| 90 | +_treat_info(tr::DynamicTreatment, trinds::Table, treatname::Symbol) = |
| 91 | + ["Number of cohorts" sprint(show, _nunique(trinds, treatname), context=:compact=>true); |
| 92 | + "Interactions within cohorts" sprint(show, length(columnnames(trinds))-2, context=:compact=>true); |
| 93 | + "Relative time periods" sprint(show, _nunique(trinds, :rel), context=:compact=>true); |
| 94 | + "Excluded periods" sprint(show, NoQuote(_excluded_rel_str(tr)), context=:compact=>true)] |
| 95 | + |
| 96 | +_treat_spec(r::RegressionBasedDIDResult{true}, tr::DynamicTreatment{SharpDesign}) = |
| 97 | + "Cohort-interacted sharp dynamic specification" |
| 98 | + |
| 99 | +_treat_spec(r::RegressionBasedDIDResult{false}, tr::DynamicTreatment{SharpDesign}) = |
| 100 | + "Sharp dynamic specification" |
| 101 | + |
| 102 | +function show(io::IO, r::RegressionBasedDIDResult; |
| 103 | + totalwidth::Int=70, interwidth::Int=4+mod(totalwidth,2)) |
| 104 | + halfwidth = div(totalwidth-interwidth, 2) |
| 105 | + top, fe_info = _summary(r) |
| 106 | + tr_info = _treat_info(r.tr, r.treatinds, r.treatname) |
| 107 | + blocks = [top, tr_info, fe_info] |
| 108 | + fes = has_fe(r) ? join(string.(r.fenames), " ") : "none" |
| 109 | + fetitle = string("Fixed effects: ", fes) |
| 110 | + blocktitles = ["Summary of results", |
| 111 | + _treat_spec(r, r.tr), |
| 112 | + fetitle[1:min(totalwidth,length(fetitle))]] |
| 113 | + for (ib, b) in enumerate(blocks) |
| 114 | + for i in 1:size(b, 1) |
| 115 | + b[i, 1] = b[i, 1] * ":" |
| 116 | + end |
| 117 | + println(io, "─" ^totalwidth) |
| 118 | + println(io, blocktitles[ib]) |
| 119 | + println(io, "─" ^totalwidth) |
| 120 | + for i in 1:(div(size(b, 1) - 1, 2)+1) |
| 121 | + print(io, b[2*i-1, 1]) |
| 122 | + print(io, lpad(b[2*i-1, 2], halfwidth - length(b[2*i-1, 1]))) |
| 123 | + print(io, " " ^interwidth) |
| 124 | + if size(b, 1) >= 2*i |
| 125 | + print(io, b[2*i, 1]) |
| 126 | + print(io, lpad(b[2*i, 2], halfwidth - length(b[2*i, 1]))) |
| 127 | + end |
| 128 | + println(io) |
| 129 | + end |
| 130 | + end |
| 131 | + println(io, "─" ^totalwidth) |
| 132 | +end |
0 commit comments