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

Commit 1fdce87

Browse files
committed
Improve pretty-printing of results and fix other issues
1 parent 2939aaa commit 1fdce87

File tree

6 files changed

+97
-58
lines changed

6 files changed

+97
-58
lines changed

Project.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@ authors = ["Junyuan Chen <norman.chen.git@gmail.com>"]
44
version = "0.1.0"
55

66
[deps]
7-
Combinatorics = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
87
Dictionaries = "85a47980-9c8c-11e8-2b9f-f7ca1fa99fb4"
98
DiffinDiffsBase = "7fc23ce2-6e83-4e3c-822f-a79085ccb3e8"
109
FixedEffectModels = "9d5cd8c9-2029-5cab-9928-427838db53e3"
1110
FixedEffects = "c8885935-8500-56a7-9867-7708b20db0eb"
1211
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
13-
Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1412
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1513
SplitApplyCombine = "03a91e81-4c3e-53e1-a0a4-9c0c8f19dd66"
1614
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
@@ -21,7 +19,6 @@ TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"
2119
Vcov = "ec2bfdc2-55df-4fc9-b9ae-4958c2cf2486"
2220

2321
[compat]
24-
Combinatorics = "1"
2522
Dictionaries = "0.3"
2623
DiffinDiffsBase = "0.2"
2724
FixedEffectModels = "1.4"

src/InteractionWeightedDIDs.jl

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
module InteractionWeightedDIDs
22

33
using Base: Callable
4-
using Combinatorics: combinations
54
using Dictionaries: Dictionary
65
using FixedEffectModels: FixedEffectTerm, Combination,
7-
fe, has_fe, parse_fixedeffect, basecol, isnested, nunique
6+
fe, parse_fixedeffect, basecol, isnested, nunique
87
using FixedEffects
98
using LinearAlgebra: Factorization, Symmetric, cholesky!
10-
using Printf
119
using Reexport
1210
using SplitApplyCombine: group, mapview
13-
using StatsBase: AbstractWeights, CovarianceEstimator, UnitWeights, NoQuote, vcov
11+
using StatsBase: AbstractWeights, CovarianceEstimator, UnitWeights, PValue, TestStat, NoQuote
1412
using StatsFuns: fdistccdf
1513
using StatsModels: coefnames
1614
using Tables: getcolumn, columnnames
@@ -43,6 +41,5 @@ export CheckVcov,
4341
include("utils.jl")
4442
include("procedures.jl")
4543
include("did.jl")
46-
include("lsweights.jl")
4744

4845
end

src/did.jl

Lines changed: 50 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -66,67 +66,74 @@ function result(::Type{Reg}, @nospecialize(nt::NamedTuple))
6666
return merge(nt, (result=didresult,))
6767
end
6868

69+
"""
70+
has_fe(r::RegressionBasedDIDResult)
71+
72+
Test whether any fixed effect is involved in regression.
73+
"""
6974
has_fe(r::RegressionBasedDIDResult) = !isempty(r.fenames)
7075

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
76+
_top_info(r::RegressionBasedDIDResult) = (
77+
"Number of obs" => nobs(r),
78+
"Degrees of freedom" => nobs(r) - dof_residual(r),
79+
"F-statistic" => TestStat(r.F),
80+
"p-value" => PValue(r.p)
81+
)
8482

8583
_nunique(t::Table, s::Symbol) = length(unique(getproperty(t, s)))
8684

87-
_excluded_rel_str(tr::DynamicTreatment) =
88-
isempty(tr.exc) ? "None" : join(string.(tr.exc), " ")
85+
_excluded_rel_str(tr::DynamicTreatment) =
86+
isempty(tr.exc) ? "none" : join(string.(tr.exc), " ")
87+
88+
_treat_info(r::RegressionBasedDIDResult{true}, tr::DynamicTreatment) = (
89+
"Number of cohorts" => _nunique(r.treatinds, r.treatname),
90+
"Interactions within cohorts" => length(columnnames(r.treatinds)) - 2,
91+
"Relative time periods" => _nunique(r.treatinds, :rel),
92+
"Excluded periods" => NoQuote(_excluded_rel_str(tr))
93+
)
8994

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+
_treat_info(r::RegressionBasedDIDResult{false}, tr::DynamicTreatment) = (
96+
"Relative time periods" => _nunique(r.treatinds, :rel),
97+
"Excluded periods" => NoQuote(_excluded_rel_str(tr))
98+
)
9599

96100
_treat_spec(r::RegressionBasedDIDResult{true}, tr::DynamicTreatment{SharpDesign}) =
97101
"Cohort-interacted sharp dynamic specification"
98102

99103
_treat_spec(r::RegressionBasedDIDResult{false}, tr::DynamicTreatment{SharpDesign}) =
100104
"Sharp dynamic specification"
101-
102-
function show(io::IO, r::RegressionBasedDIDResult;
103-
totalwidth::Int=70, interwidth::Int=4+mod(totalwidth,2))
105+
106+
_fe_info(r::RegressionBasedDIDResult) = (
107+
"Converged" => r.feconverged,
108+
"Singletons dropped" => r.nfesingledropped
109+
)
110+
111+
show(io::IO, ::RegressionBasedDIDResult) = print(io, "Regression-based DID result")
112+
113+
function show(io::IO, ::MIME"text/plain", r::RegressionBasedDIDResult;
114+
totalwidth::Int=70, interwidth::Int=4+mod(totalwidth,2))
104115
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]
116+
top_info = _top_info(r)
117+
fe_info = has_fe(r) ? _fe_info(r) : ()
118+
tr_info = _treat_info(r, r.tr)
119+
blocks = (top_info, tr_info, fe_info)
108120
fes = has_fe(r) ? join(string.(r.fenames), " ") : "none"
109121
fetitle = string("Fixed effects: ", fes)
110-
blocktitles = ["Summary of results",
111-
_treat_spec(r, r.tr),
112-
fetitle[1:min(totalwidth,length(fetitle))]]
122+
blocktitles = ("Summary of results: Regression-based DID",
123+
_treat_spec(r, r.tr), fetitle[1:min(totalwidth,length(fetitle))])
124+
113125
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)
126+
println(io, repeat('', totalwidth))
118127
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])))
128+
if !isempty(b)
129+
# Print line between block title and block content
130+
println(io, repeat('', totalwidth))
131+
for (i, e) in enumerate(b)
132+
print(io, e[1], ':')
133+
print(io, lpad(e[2], halfwidth - length(e[1]) - 1))
134+
print(io, isodd(i) ? repeat(' ', interwidth) : '\n')
127135
end
128-
println(io)
129136
end
130137
end
131-
println(io, "" ^totalwidth)
138+
print(io, repeat('', totalwidth))
132139
end

src/procedures.jl

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,14 +188,16 @@ end
188188
function _genindicator(idx::Vector{Int}, esample::BitVector, n::Int)
189189
v = zeros(n)
190190
iv, r = 1, 1
191+
nr = length(idx)
191192
@inbounds for i in eachindex(esample)
192193
if esample[i]
193-
if i == idx[r]
194+
if idx[r] == i
194195
v[iv] = 1.0
195196
r += 1
196197
end
197198
iv += 1
198199
end
200+
r > nr && break
199201
end
200202
return v
201203
end
@@ -378,16 +380,15 @@ function estvcov(data, esample::BitVector, vce::CovarianceEstimator, coef::Vecto
378380
has_intercept = has_intercept || has_fe_intercept
379381
df_F = max(1, Vcov.df_FStat(vce_data, concrete_vce, has_intercept))
380382
p = fdistccdf(max(length(coef) - has_intercept, 1), df_F, F)
381-
382-
return (vcov_mat=vcov_mat, dof_resid=dof_resid, F=F, p=p), true
383+
return (vcov_mat=vcov_mat, vce=concrete_vce, dof_resid=dof_resid, F=F, p=p), true
383384
end
384385

385386
"""
386387
EstVcov <: StatsStep
387388
388389
Call [`InteractionWeightedDIDs.estvcov`](@ref) to
389390
estimate variance-covariance matrix and F-statistic.
390-
The returned objects named `vcov_mat`, `dof_resid`, `F` and `p`
391+
The returned objects named `vcov_mat`, `vce`, `dof_resid`, `F` and `p`
391392
may be shared across multiple specifications.
392393
"""
393394
const EstVcov = StatsStep{:EstVcov, typeof(estvcov)}

test/did.jl

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
@testset "RegressionBasedDID" begin
22
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)))
3+
r = @did(Reg, data=hrs, dynamic(:wave, -1), notyettreated([11]),
4+
vce=Vcov.cluster(:hhidpn), yterm=term(:oop_spend), treatname=:wave_hosp,
5+
treatintterms=(), xterms=(fe(:wave)+fe(:hhidpn)))
66
@test coef(r, "rel: -3 & wave_hosp: 10") 591.04639 atol=1e-5
77
@test coef(r, "rel: -2 & wave_hosp: 9") 298.97735 atol=1e-5
88
@test coef(r, "rel: -2 & wave_hosp: 10") 410.58102 atol=1e-5
@@ -14,4 +14,38 @@
1414
@test coef(r, "rel: 2 & wave_hosp: 8") 800.10647 atol=1e-5
1515
@test nobs(r) == 2624
1616

17+
@test sprint(show, r) == "Regression-based DID result"
18+
@test sprint(show, MIME("text/plain"), r) == """
19+
──────────────────────────────────────────────────────────────────────
20+
Summary of results: Regression-based DID
21+
──────────────────────────────────────────────────────────────────────
22+
Number of obs: 2624 Degrees of freedom: 670
23+
F-statistic: 4.81 p-value: <1e-5
24+
──────────────────────────────────────────────────────────────────────
25+
Cohort-interacted sharp dynamic specification
26+
──────────────────────────────────────────────────────────────────────
27+
Number of cohorts: 3 Interactions within cohorts: 0
28+
Relative time periods: 5 Excluded periods: -1
29+
──────────────────────────────────────────────────────────────────────
30+
Fixed effects: fe_wave fe_hhidpn
31+
──────────────────────────────────────────────────────────────────────
32+
Converged: true Singletons dropped: 0
33+
──────────────────────────────────────────────────────────────────────"""
34+
35+
r = @did(Reg, data=hrs, dynamic(:wave, -1), notyettreated([11]),
36+
vce=Vcov.cluster(:hhidpn), yterm=term(:oop_spend), treatname=:wave_hosp,
37+
treatintterms=(), cohortinteracted=false)
38+
@test sprint(show, MIME("text/plain"), r) == """
39+
──────────────────────────────────────────────────────────────────────
40+
Summary of results: Regression-based DID
41+
──────────────────────────────────────────────────────────────────────
42+
Number of obs: 2624 Degrees of freedom: 6
43+
F-statistic: 12.50 p-value: <1e-10
44+
──────────────────────────────────────────────────────────────────────
45+
Sharp dynamic specification
46+
──────────────────────────────────────────────────────────────────────
47+
Relative time periods: 5 Excluded periods: -1
48+
──────────────────────────────────────────────────────────────────────
49+
Fixed effects: none
50+
──────────────────────────────────────────────────────────────────────"""
1751
end

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import Base: ==
1717
==(x::FixedEffect{R,I}, y::FixedEffect{R,I}) where {R,I} =
1818
x.refs == y.refs && x.interaction == y.interaction && x.n == y.n
1919

20+
# A workaround to be replaced
21+
==(x::Vcov.ClusterCovariance, y::Vcov.ClusterCovariance) = true
22+
2023
const tests = [
2124
"procedures",
2225
"did"

0 commit comments

Comments
 (0)