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

Commit 5af7e5b

Browse files
authored
Allow selecting cells and coefficients for contrast (#13)
1 parent f215fcb commit 5af7e5b

File tree

5 files changed

+64
-31
lines changed

5 files changed

+64
-31
lines changed

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ Vcov = "ec2bfdc2-55df-4fc9-b9ae-4958c2cf2486"
2020
DataAPI = "1.5"
2121
DataFrames = "1"
2222
DiffinDiffsBase = "0.3.3"
23-
FixedEffectModels = "1.5"
23+
FixedEffectModels = "1.6"
2424
FixedEffects = "2"
2525
Reexport = "0.2, 1"
2626
StatsBase = "0.33"
2727
StatsFuns = "0.9"
2828
StatsModels = "0.6"
2929
Tables = "1"
30-
Vcov = "0.4.1"
30+
Vcov = "0.5"
3131
julia = "1.3"
3232

3333
[extras]

src/InteractionWeightedDIDs.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ using DiffinDiffsBase: ValidTimeType, termvars, isintercept, parse_intercept!,
2020
import Base: show
2121
import DiffinDiffsBase: required, default, transformed, combinedargs, copyargs,
2222
valid_didargs, result, vce, treatment, nobs, outcomename, weights, treatnames,
23-
dof_residual, agg, post!, _parse_subset
23+
dof_residual, agg, post!
2424
import FixedEffectModels: has_fe
2525

2626
export Vcov,

src/did.jl

Lines changed: 33 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,10 @@ with weights proportional to `treatweights` within each relative time.
278278
function agg(r::RegressionBasedDIDResult{<:DynamicTreatment}, names=nothing;
279279
bys=nothing, subset=nothing)
280280
inds = subset === nothing ? Colon() : _parse_subset(r, subset, false)
281-
ptcells = treatcells(r)
282-
bycells = view(ptcells, inds)
283-
_parse_bycells!(getfield(bycells, :columns), ptcells, bys)
281+
bycells = view(treatcells(r), inds)
282+
isempty(bycells) && throw(ArgumentError(
283+
"No coefficient left for aggregation after taking the subset"))
284+
_parse_bycells!(getfield(bycells, :columns), bycells, bys)
284285
names === nothing || (bycells = subcolumns(bycells, names, nomissing=false))
285286

286287
tcells, rows = cellrows(bycells, findcell(bycells))
@@ -386,28 +387,43 @@ Base.getproperty(cr::ContrastResult, n::Symbol) = getproperty(_getmat(cr), n)
386387
Base.parent(cr::ContrastResult) = getfield(cr, :rs)
387388

388389
"""
389-
contrast(r1::RegDIDResultOrAgg, rs::RegDIDResultOrAgg...)
390+
contrast(r1::RegDIDResultOrAgg, rs::RegDIDResultOrAgg...; kwargs)
390391
391392
Construct a [`ContrastResult`](@ref) by collecting the computed least-square weights
392393
from each of the [`RegDIDResultOrAgg`](@ref).
394+
395+
# Keywords
396+
- `subset=nothing`: indices for cells to be included (rows in output).
397+
- `coefs=nothing`: indices for coefficients from each result to be included (columns in output).
393398
"""
394-
function contrast(r1::RegDIDResultOrAgg, rs::RegDIDResultOrAgg...)
399+
function contrast(r1::RegDIDResultOrAgg, rs::RegDIDResultOrAgg...;
400+
subset=nothing, coefs=nothing)
395401
has_lsweights(r1) && all(r->has_lsweights(r), rs) || throw(ArgumentError(
396402
"Results must contain computed least-sqaure weights"))
397403
ri = r1.lsweights.r
398-
ncoef = ntreatcoef(r1)
399-
m = r1.lsweights.m
400-
for r in rs
401-
r.lsweights.r == ri || throw(ArgumentError(
404+
rinds = subset === nothing ? Colon() : _parse_subset(ri, subset)
405+
# Make a copy to avoid accidentally overwriting cells for DIDResult
406+
ri = deepcopy(view(ri, rinds))
407+
dcoefs = coefs === nothing ? NamedTuple() : IdDict{Int,Any}(coefs)
408+
rs = RegDIDResultOrAgg[r1, rs...]
409+
m = Vector{Array}(undef, length(rs)+1)
410+
m[1] = view(r1.cellymeans, rinds)
411+
iresult = [0]
412+
icoef = [0]
413+
names = ["cellymeans"]
414+
for (i, r) in enumerate(rs)
415+
view(r.lsweights.r, rinds) == ri || throw(ArgumentError(
402416
"Cells for least-square weights comparisons must be identical across the inputs"))
403-
ncoef += ntreatcoef(r)
417+
cinds = get(dcoefs, i, nothing)
418+
cinds = cinds === nothing ? (1:ntreatcoef(r)) : _parse_subset(treatcells(r), cinds)
419+
m[i+1] = view(r.lsweights.m, rinds, cinds)
420+
ic = view(1:ntreatcoef(r), cinds)
421+
push!(iresult, (i for k in 1:length(ic))...)
422+
append!(icoef, ic)
423+
append!(names, view(treatnames(r), cinds))
404424
end
405-
rs = RegDIDResultOrAgg[r1, rs...]
406-
m = hcat(r1.cellymeans, (r.lsweights.m for r in rs)...)
407-
rinds = vcat(0, (fill(i+1, ntreatcoef(r)) for (i, r) in enumerate(rs))...)
408-
cinds = vcat(0, (1:ntreatcoef(r) for r in rs)...)
409-
names = vcat("cellymeans", (treatnames(r) for r in rs)...)
410-
ci = VecColumnTable((iresult=rinds, icoef=cinds, name=names))
425+
m = hcat(m...)
426+
ci = VecColumnTable((iresult=iresult, icoef=icoef, name=names))
411427
return ContrastResult(rs, TableIndexedMatrix(m, ri, ci))
412428
end
413429

@@ -428,18 +444,8 @@ function Base.sort!(cr::ContrastResult; @nospecialize(kwargs...))
428444
return cr
429445
end
430446

431-
_parse_subset(cr::ContrastResult, by::Pair) = (inds = apply(cr.r, by); return inds)
432-
433-
function _parse_subset(cr::ContrastResult, inds)
434-
eltype(inds) <: Pair || return inds
435-
inds = apply_and(cr.r, inds...)
436-
return inds
437-
end
438-
439-
_parse_subset(::ContrastResult, ::Colon) = Colon()
440-
441447
function Base.view(cr::ContrastResult, subset)
442-
inds = _parse_subset(cr, subset)
448+
inds = _parse_subset(cr.r, subset)
443449
r = view(cr.r, inds)
444450
m = view(cr.m, inds, :)
445451
return ContrastResult(parent(cr), TableIndexedMatrix(m, r, cr.c))

src/procedures.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,7 +508,7 @@ function _vce(data, esample::BitVector, vce::Vcov.ClusterCovariance,
508508
concrete_vce = Vcov.materialize(cludata, vce)
509509
dof_absorb = 0
510510
for fe in fes
511-
dof_absorb += any(c->isnested(fe, c.refs), concrete_vce.clusters) ? 1 : nunique(fe)
511+
dof_absorb += any(c->isnested(fe, c.groups), concrete_vce.clusters) ? 1 : nunique(fe)
512512
end
513513
return concrete_vce, dof_absorb
514514
end

test/did.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,11 @@ end
212212
@test length(coef(a1)) == 2
213213
@test coef(a1, "rel: false") sum(coef(a, "rel: $r") for r in ["-2", "0", "2"])
214214
@test coef(a1, "rel: true") sum(coef(a, "rel: $r") for r in ["-3", "1"])
215+
216+
a2 = agg(r, :rel, bys=:rel=>isodd, subset=:rel=>isodd)
217+
@test coef(a2) == coef(a1, 2:2)
218+
219+
@test_throws ArgumentError agg(r, subset=:rel=>x->x>10)
215220
end
216221

217222
@testset "@specset" begin
@@ -267,9 +272,12 @@ end
267272
@test c2.r == r1.lsweights.r == r2.lsweights.r
268273
@test c2.c.name[1] == "cellymeans"
269274
@test c2.c.name[2] == r1.coefnames[c2.c.icoef[2]]
275+
@test c2.c.iresult == [0, (1 for i in 1:9)..., (2 for i in 1:5)...]
276+
@test c2.c.icoef == [0, 1:9..., 1:5...]
270277
@test parent(c2)[1] === r1
271278
@test parent(c2)[2] === r2
272279

280+
@test c1.r !== r1.lsweights.r
273281
sort!(c1, rev=true)
274282
@test c1.r == sort(r1.lsweights.r, rev=true)
275283

@@ -282,6 +290,25 @@ end
282290
v = view(c1, [:wave=>x->x==10, :wave_hosp=>x->x==10])
283291
@test v.m == c1.m[(c1.r.wave.==10).&(c1.r.wave_hosp.==10),:]
284292
@test v.r == view(c1.r, (c1.r.wave.==10).&(c1.r.wave_hosp.==10))
293+
294+
c3 = contrast(r1, r2, subset=:wave=>7, coefs=1=>(:wave_hosp,2)=>(x,y)->x+y==8)
295+
@test size(c3) == (4, 8)
296+
@test parent(c3)[1] === r1
297+
@test c3.c.name[2] == "wave_hosp: 8 & rel: 0"
298+
@test c3.c.name[3] == "wave_hosp: 10 & rel: -2"
299+
@test c3.c.iresult == [0, (1 for i in 1:2)..., (2 for i in 1:5)...]
300+
@test c3.c.icoef == [0, 1, 8, 1:5...]
301+
@test all(c3.r.wave.==7)
302+
ri = r1.lsweights.r.wave.==7
303+
@test c3[:,1] == r1.cellymeans[ri]
304+
@test c3[:,2:end] == c2[ri,[2,9,11:15...]]
305+
306+
c4 = contrast(r1, r2, subset=1:3, coefs=(1=>1:2, 2=>:))
307+
@test size(c4) == (3, 8)
308+
@test c4.c.iresult == [0, (1 for i in 1:2)..., (2 for i in 1:5)...]
309+
@test c4.c.icoef == [0:2..., 1:5...]
310+
@test c4.r == view(r1.lsweights.r, 1:3)
311+
@test c4[:,2:end] == c2[1:3,[2,3,11:15...]]
285312
end
286313

287314
@testset "post!" begin

0 commit comments

Comments
 (0)