Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit 5fd9d16

Browse files
committed
Add a type RegressionBasedDIDResult
1 parent bd17334 commit 5fd9d16

File tree

5 files changed

+154
-12
lines changed

5 files changed

+154
-12
lines changed

src/InteractionWeightedDIDs.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,16 @@ using SplitApplyCombine: group, mapview
1313
using StatsBase: AbstractWeights, CovarianceEstimator, UnitWeights, NoQuote, vcov
1414
using StatsFuns: fdistccdf
1515
using StatsModels: coefnames
16-
using Tables: getcolumn
16+
using Tables: getcolumn, columnnames
1717
using TypedTables: Table
1818
using Vcov
1919
@reexport using DiffinDiffsBase
20-
using DiffinDiffsBase: termvars, hasintercept, omitsintercept, isintercept, parse_intercept
20+
using DiffinDiffsBase: termvars, hasintercept, omitsintercept, isintercept, parse_intercept,
21+
_getsubcolumns, _treatnames
2122

2223
import Base: show
23-
import DiffinDiffsBase: required, default, transformed, combinedargs, _getsubcolumns,
24-
valid_didargs, result
24+
import DiffinDiffsBase: required, default, transformed, combinedargs,
25+
_get_default, valid_didargs, result
2526
import FixedEffectModels: has_fe
2627

2728
export Vcov,

src/did.jl

Lines changed: 123 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,129 @@
44
Estimation procedure for regression-based difference-in-differences.
55
"""
66
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}}
99

1010
const Reg = RegressionBasedDID
1111

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

src/procedures.jl

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,10 @@ Construct `FixedEffects.AbstractFixedEffectSolver`.
6767
See also [`MakeFESolver`](@ref).
6868
"""
6969
function makefesolver(fenames::Vector{Symbol}, weights::AbstractWeights, esample::BitVector,
70-
fes::Vector{FixedEffect})
70+
nfethreads::Int, fes::Vector{FixedEffect})
7171
if !isempty(fes)
7272
fes = FixedEffect[fe[esample] for fe in fes]
73-
feM = AbstractFixedEffectSolver{Float64}(fes, weights, Val{:cpu}, Threads.nthreads())
73+
feM = AbstractFixedEffectSolver{Float64}(fes, weights, Val{:cpu}, nfethreads)
7474
return (feM=feM,), true
7575
else
7676
return (feM=nothing,), true
@@ -86,6 +86,7 @@ The returned object named `feM` may be shared across multiple specifications.
8686
const MakeFESolver = StatsStep{:MakeFESolver, typeof(makefesolver)}
8787

8888
required(::MakeFESolver) = (:fenames, :weights, :esample)
89+
default(::MakeFESolver) = (nfethreads=Threads.nthreads(),)
8990
# Determine equality of fes by fenames
9091
combinedargs(::MakeFESolver, allntargs) = (allntargs[1].fes,)
9192

@@ -214,7 +215,7 @@ function maketreatcols(data, treatname::Symbol, treatintterms::Terms,
214215
weightname::Union{Symbol, Nothing}, weights::AbstractWeights,
215216
esample::BitVector, tr_rows::BitVector,
216217
cohortinteracted::Bool, fetol::Real, femaxiter::Int,
217-
::Type{<:DynamicTreatment{SharpDesign}}, time::Symbol, exc::Set{<:Integer})
218+
::Type{DynamicTreatment{SharpDesign}}, time::Symbol, exc::Set{<:Integer})
218219

219220
nobs = sum(esample)
220221
tnames = (time, treatname, termvars(treatintterms)...)
@@ -279,7 +280,7 @@ transformed(::MakeTreatCols, @nospecialize(nt::NamedTuple)) =
279280
combinedargs(step::MakeTreatCols, allntargs) =
280281
combinedargs(step, allntargs, typeof(allntargs[1].tr))
281282

282-
combinedargs(::MakeTreatCols, allntargs, ::Type{<:DynamicTreatment{SharpDesign}}) =
283+
combinedargs(::MakeTreatCols, allntargs, ::Type{DynamicTreatment{SharpDesign}}) =
283284
(Set(intersect((nt.tr.exc for nt in allntargs)...)),)
284285

285286
"""
@@ -351,7 +352,8 @@ function _vce(data, esample::BitVector, vce::Vcov.ClusterCovariance,
351352
concrete_vce = Vcov.materialize(cludata, vce)
352353
dof_absorb = 0
353354
for fe in fes
354-
any(c->isnested(fe, c.refs), concrete_vce.clusters) && (dof_absorb += 1)
355+
# ! To be fixed
356+
dof_absorb += any(c->isnested(fe, c.refs), concrete_vce.clusters) ? 1 : nunique(fe)
355357
end
356358
return concrete_vce, dof_absorb
357359
end

test/did.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@testset "RegressionBasedDID" begin
2+
hrs = exampledata("hrs")
3+
r = @did(Reg, data=hrs, dynamic(:wave, -1), notyettreated([11]), vce=Vcov.cluster(:hhidpn),
4+
yterm=term(:oop_spend), treatname=:wave_hosp, treatintterms=(),
5+
xterms=(fe(:wave)+fe(:hhidpn)))
6+
@test coef(r, "rel: -3 & wave_hosp: 10") 591.04639 atol=1e-5
7+
@test coef(r, "rel: -2 & wave_hosp: 9") 298.97735 atol=1e-5
8+
@test coef(r, "rel: -2 & wave_hosp: 10") 410.58102 atol=1e-5
9+
@test coef(r, "rel: 0 & wave_hosp: 8") 2825.5659 atol=1e-4
10+
@test coef(r, "rel: 0 & wave_hosp: 9") 3030.8408 atol=1e-4
11+
@test coef(r, "rel: 0 & wave_hosp: 10") 3091.5084 atol=1e-4
12+
@test coef(r, "rel: 1 & wave_hosp: 8") 825.14585 atol=1e-5
13+
@test coef(r, "rel: 1 & wave_hosp: 9") 106.83785 atol=1e-5
14+
@test coef(r, "rel: 2 & wave_hosp: 8") 800.10647 atol=1e-5
15+
@test nobs(r) == 2624
16+
17+
end

test/procedures.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ end
4343
nobs = size(hrs, 1)
4444
fes = FixedEffect[FixedEffect(hrs.hhidpn)]
4545
fenames = [:fe_hhidpn]
46-
nt = (fenames=fenames, weights=uweights(nobs), esample=trues(nobs), fes=fes)
46+
nt = (fenames=fenames, weights=uweights(nobs), esample=trues(nobs),
47+
default(MakeFESolver())..., fes=fes)
4748
ret, share = makefesolver(nt...)
4849
@test ret.feM isa FixedEffects.FixedEffectSolverCPU{Float64}
4950
nt = merge(nt, (fenames=Symbol[], fes=FixedEffect[]))

0 commit comments

Comments
 (0)