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

Commit 3dde6e7

Browse files
committed
Add EstVcov
1 parent 5105123 commit 3dde6e7

File tree

5 files changed

+138
-5
lines changed

5 files changed

+138
-5
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
1818
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
1919
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
2020
TypedTables = "9d95f2ec-7b3d-5a63-8d20-e2491e220bb9"
21+
Vcov = "ec2bfdc2-55df-4fc9-b9ae-4958c2cf2486"
2122

2223
[compat]
2324
Combinatorics = "1"
@@ -32,6 +33,7 @@ StatsFuns = "0.9"
3233
StatsModels = "0.6"
3334
Tables = "1"
3435
TypedTables = "1.2"
36+
Vcov = "0.4"
3537
julia = "1.3"
3638

3739
[extras]

src/InteractionWeightedDIDs.jl

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,27 @@ module InteractionWeightedDIDs
33
using Base: Callable
44
using Combinatorics: combinations
55
using Dictionaries: Dictionary
6-
using FixedEffectModels: Vcov
76
using FixedEffectModels: FixedEffectTerm, Combination,
87
fe, has_fe, parse_fixedeffect, omitsintercept, hasintercept,
9-
basecol, isnested, tss, Fstat
8+
basecol, isnested, tss, Fstat, nunique
109
using FixedEffects
11-
using LinearAlgebra: Symmetric, cholesky!
10+
using LinearAlgebra: Factorization, Symmetric, cholesky!
1211
using Printf
1312
using Reexport
1413
using SplitApplyCombine: group, groupfind, groupreduce
15-
using StatsBase: AbstractWeights, UnitWeights, NoQuote
14+
using StatsBase: AbstractWeights, UnitWeights, NoQuote, vcov
1615
using StatsModels: termvars, FullRank
1716
using Tables: columntable, getcolumn, rowtable
1817
using TypedTables: Table
18+
using Vcov
1919
@reexport using DiffinDiffsBase
2020

2121
import Base: show
2222
import DiffinDiffsBase: required, default, transformed, combinedargs, _getsubcolumns
2323

24+
# Handle naming conflicts
25+
const getvcov = vcov
26+
2427
export Vcov,
2528
fe
2629

@@ -30,6 +33,7 @@ export CheckVcov,
3033
MakeYXCols,
3134
MakeTreatCols,
3235
SolveLeastSquares,
36+
EstVcov,
3337

3438
RegressionBasedDID,
3539
Reg

src/procedures.jl

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,3 +309,49 @@ may be shared across multiple specifications.
309309
const SolveLeastSquares = StatsStep{:SolveLeastSquares, typeof(solveleastsquares)}
310310

311311
required(::SolveLeastSquares) = (:tr, :yterm, :xterms, :yxcols, :treatcols)
312+
313+
function _vcov(data, esample::BitArray,
314+
vcov::Union{Vcov.SimpleCovariance,Vcov.RobustCovariance}, fes::Vector{FixedEffect})
315+
dof_absorb = 0
316+
for fe in fes
317+
dof_absorb += nunique(fe)
318+
end
319+
return vcov, dof_absorb
320+
end
321+
322+
function _vcov(data, esample::BitArray, vcov::Vcov.ClusterCovariance,
323+
fes::Vector{FixedEffect})
324+
cludata = _getsubcolumns(data, vcov.clusters, esample)
325+
concrete_vcov = Vcov.materialize(cludata, vcov)
326+
dof_absorb = 0
327+
for fe in fes
328+
any(c->isnested(fe, c.refs), concrete_vcov.clusters) && (dof_absorb += 1)
329+
end
330+
return concrete_vcov, dof_absorb
331+
end
332+
333+
"""
334+
estvcov(args...)
335+
336+
Estimate variance-covariance matrix.
337+
See also [`EstVcov`](@ref).
338+
"""
339+
function estvcov(data, esample::BitArray, vcov::Vcov.CovarianceEstimator,
340+
X::Matrix, crossx::Factorization, residuals::Vector, fes::Vector{FixedEffect})
341+
concrete_vcov, dof_absorb = _vcov(data, esample, vcov, fes)
342+
dof_residuals = max(1, sum(esample) - size(X,2) - dof_absorb)
343+
vcov_data = Vcov.VcovData(X, crossx, residuals, dof_residuals)
344+
vcov_mat = getvcov(vcov_data, concrete_vcov)
345+
return (vcov_mat=vcov_mat, dof_residuals=dof_residuals), true
346+
end
347+
348+
"""
349+
EstVcov <: StatsStep
350+
351+
Call [`InteractionWeightedDIDs.estvcov`](@ref) to estimate variance-covariance matrix.
352+
The returned object named `vcov_mat` and `dof_residuals`
353+
may be shared across multiple specifications.
354+
"""
355+
const EstVcov = StatsStep{:EstVcov, typeof(estvcov)}
356+
357+
required(::EstVcov) = (:data, :esample, :vcov, :X, :crossx, :residuals, :fes)

test/procedures.jl

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,9 @@ end
201201
nt = (tr=tr, yterm=term(:oop_spend), xterms=(term(1),), yxcols=yxcols0, treatcols=tcols0)
202202
ret, share = solveleastsquares(nt...)
203203
# Compare estimates with Stata
204+
# gen col0 = wave_hosp==10 & wave==10
205+
# gen col1 = wave_hosp==10 & wave==11
206+
# reg oop_spend col0 col1
204207
@test ret.coef[1] 2862.4141 atol=1e-4
205208
@test ret.coef[2] 490.44869 atol=1e-4
206209
@test ret.coef[3] 3353.6565 atol=1e-4
@@ -224,3 +227,79 @@ end
224227
res = SolveLeastSquares()(nt)
225228
@test res.coef == ret.coef
226229
end
230+
231+
@testset "EstVcov" begin
232+
hrs = exampledata("hrs")
233+
nobs = size(hrs, 1)
234+
col0 = convert(Vector{Float64}, (hrs.wave_hosp.==10).&(hrs.wave.==10))
235+
col1 = convert(Vector{Float64}, (hrs.wave_hosp.==10).&(hrs.wave.==11))
236+
y = convert(Vector{Float64}, hrs.oop_spend)
237+
X = hcat(col0, col1, ones(nobs, 1))
238+
crossx = cholesky!(Symmetric(X'X))
239+
coef = crossx \ (X'y)
240+
residuals = y - X * coef
241+
nt = (data=hrs, esample=trues(nobs), vcov=Vcov.simple(), X=X, crossx=crossx,
242+
residuals=residuals, fes=FixedEffect[])
243+
ret, share = estvcov(nt...)
244+
# Compare estimates with Stata
245+
# reg oop_spend col0 col1
246+
# mat list e(V)
247+
@test ret.vcov_mat[1,1] 388844.2 atol=0.1
248+
@test ret.vcov_mat[2,2] 388844.2 atol=0.1
249+
@test ret.vcov_mat[3,3] 20334.169 atol=1e-3
250+
@test ret.vcov_mat[2,1] 20334.169 atol=1e-3
251+
@test ret.vcov_mat[3,1] -20334.169 atol=1e-3
252+
@test ret.dof_residuals == nobs - 3
253+
254+
nt = merge(nt, (vcov=Vcov.robust(), fes=FixedEffect[]))
255+
ret, share = estvcov(nt...)
256+
# Compare estimates with Stata
257+
# reg oop_spend col0 col1, r
258+
# mat list e(V)
259+
@test ret.vcov_mat[1,1] 815817.44 atol=0.1
260+
@test ret.vcov_mat[2,2] 254993.93 atol=1e-2
261+
@test ret.vcov_mat[3,3] 19436.209 atol=1e-3
262+
@test ret.vcov_mat[2,1] 19436.209 atol=1e-3
263+
@test ret.vcov_mat[3,1] -19436.209 atol=1e-3
264+
@test ret.dof_residuals == nobs - 3
265+
266+
nt = merge(nt, (vcov=Vcov.cluster(:hhidpn),))
267+
ret, share = estvcov(nt...)
268+
# Compare estimates with Stata
269+
# reghdfe oop_spend col0 col1, noa clu(hhidpn)
270+
# mat list e(V)
271+
@test ret.vcov_mat[1,1] 744005.01 atol=0.1
272+
@test ret.vcov_mat[2,2] 242011.45 atol=1e-2
273+
@test ret.vcov_mat[3,3] 28067.783 atol=1e-3
274+
@test ret.vcov_mat[2,1] 94113.386 atol=1e-2
275+
@test ret.vcov_mat[3,1] 12640.559 atol=1e-2
276+
@test ret.dof_residuals == nobs - 3
277+
278+
fes = FixedEffect[FixedEffect(hrs.hhidpn)]
279+
wt = uweights(nobs)
280+
feM = AbstractFixedEffectSolver{Float64}(fes, wt, Val{:cpu}, Threads.nthreads())
281+
X = hcat(col0, col1)
282+
_feresiduals!(Combination(y, X), feM, 1e-8, 10000)
283+
crossx = cholesky!(Symmetric(X'X))
284+
coef = crossx \ (X'y)
285+
residuals = y - X * coef
286+
nt = merge(nt, (X=X, crossx=crossx, residuals=residuals, fes=fes, vcov=Vcov.robust()))
287+
ret, share = estvcov(nt...)
288+
# Compare estimates with Stata
289+
# reghdfe oop_spend col0 col1, a(hhidpn) vce(robust)
290+
# mat list e(V)
291+
@test ret.vcov_mat[1,1] 654959.97 atol=0.1
292+
@test ret.vcov_mat[2,2] 503679.27 atol=0.1
293+
@test ret.vcov_mat[2,1] 192866.2 atol=0.1
294+
@test ret.dof_residuals == nobs - nunique(fes[1]) - 2
295+
296+
nt = merge(nt, (vcov=Vcov.cluster(:hhidpn),))
297+
ret, share = estvcov(nt...)
298+
# Compare estimates with Stata
299+
# reghdfe oop_spend col0 col1, a(hhidpn) clu(hhidpn)
300+
# mat list e(V)
301+
@test ret.vcov_mat[1,1] 606384.66 atol=0.1
302+
@test ret.vcov_mat[2,2] 404399.89 atol=0.1
303+
@test ret.vcov_mat[2,1] 106497.43 atol=0.1
304+
@test ret.dof_residuals == nobs - 3
305+
end

test/runtests.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ using InteractionWeightedDIDs
44
using DataFrames
55
using Dictionaries
66
using DiffinDiffsBase: required, default, transformed, combinedargs
7+
using FixedEffectModels: Combination, nunique
78
using FixedEffects
89
using InteractionWeightedDIDs: checkvcov!, checkfes!, makefesolver,
9-
_feresiduals!, makeyxcols, maketreatcols, solveleastsquares
10+
_feresiduals!, makeyxcols, maketreatcols, solveleastsquares, estvcov
11+
using LinearAlgebra
1012
using StatsBase: Weights, uweights
1113

1214
import Base: ==

0 commit comments

Comments
 (0)