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

Commit 8c572ae

Browse files
authored
Add UnspecifiedParallel (#31)
1 parent 52f9b16 commit 8c572ae

File tree

5 files changed

+103
-14
lines changed

5 files changed

+103
-14
lines changed

src/DiffinDiffsBase.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ export cb,
7070
exact,
7171
Approximate,
7272
AbstractParallel,
73+
UnspecifiedParallel,
74+
unspecifiedpr,
7375
TrendParallel,
76+
TrendOrUnspecifiedPR,
7477
NeverTreatedParallel,
7578
nevertreated,
7679
NotYetTreatedParallel,

src/parallels.jl

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ show(io::IO, ::Unconditional) =
2222
"""
2323
unconditional()
2424
25-
Alias for [`Unconditional()`](@ref).
25+
Alias of [`Unconditional()`](@ref).
2626
"""
2727
unconditional() = Unconditional()
2828

@@ -56,7 +56,7 @@ show(io::IO, ::Exact) =
5656
"""
5757
exact()
5858
59-
Alias for [`Exact()`](@ref).
59+
Alias of [`Exact()`](@ref).
6060
"""
6161
exact() = Exact()
6262

@@ -76,6 +76,49 @@ abstract type AbstractParallel{C<:ParallelCondition, S<:ParallelStrength} end
7676

7777
@fieldequal AbstractParallel
7878

79+
"""
80+
UnspecifiedParallel{C,S} <: AbstractParallel{C,S}
81+
82+
A parallel trends assumption (PTA) without explicitly specified
83+
relations across treatment groups.
84+
See also [`unspecifiedpr`](@ref).
85+
86+
With this parallel type,
87+
operations for complying with a PTA are suppressed.
88+
This is useful, for example,
89+
when the user-provided regressors and sample restrictions
90+
need to be accepted without any PTA-specific alteration.
91+
92+
# Fields
93+
- `c::C`: an instance of [`ParallelCondition`](@ref).
94+
- `s::S`: an instance of [`ParallelStrength`](@ref).
95+
"""
96+
struct UnspecifiedParallel{C,S} <: AbstractParallel{C,S}
97+
c::C
98+
s::S
99+
function UnspecifiedParallel(c::ParallelCondition=Unconditional(),
100+
s::ParallelStrength=Exact())
101+
return new{typeof(c),typeof(s)}(c, s)
102+
end
103+
end
104+
105+
"""
106+
unspecifiedpr(c::ParallelCondition=Unconditional(), s::ParallelStrength=Exact())
107+
108+
Construct an [`UnspecifiedParallel`](@ref) with fields set by the arguments.
109+
This is an alias of the inner constructor of [`UnspecifiedParallel`](@ref).
110+
"""
111+
unspecifiedpr(c::ParallelCondition=Unconditional(), s::ParallelStrength=Exact()) =
112+
UnspecifiedParallel(c, s)
113+
114+
show(io::IO, pr::UnspecifiedParallel) =
115+
print(IOContext(io, :compact=>true), "Unspecified{", pr.c, ",", pr.s, "}")
116+
117+
function show(io::IO, ::MIME"text/plain", pr::UnspecifiedParallel)
118+
print(io, pr.s, " among unspecified treatment groups")
119+
pr.c isa Unconditional || print(io, ":\n ", pr.c)
120+
end
121+
79122
"""
80123
TrendParallel{C,S} <: AbstractParallel{C,S}
81124
@@ -84,6 +127,13 @@ assume a parallel trends assumption holds over all the relevant time periods.
84127
"""
85128
abstract type TrendParallel{C,S} <: AbstractParallel{C,S} end
86129

130+
"""
131+
TrendOrUnspecifiedPR{C,S}
132+
133+
Union type of [`TrendParallel{C,S}`](@ref) and [`UnspecifiedParallel{C,S}`](@ref).
134+
"""
135+
const TrendOrUnspecifiedPR{C,S} = Union{TrendParallel{C,S}, UnspecifiedParallel{C,S}}
136+
87137
"""
88138
istreated(pr::TrendParallel, x)
89139
@@ -306,5 +356,6 @@ termvars(s::ParallelStrength) =
306356
termvars(::Exact) = Symbol[]
307357
termvars(pr::AbstractParallel) =
308358
error("StatsModels.termvars is not defined for $(typeof(pr))")
359+
termvars(pr::UnspecifiedParallel) = union(termvars(pr.c), termvars(pr.s))
309360
termvars(pr::NeverTreatedParallel) = union(termvars(pr.c), termvars(pr.s))
310361
termvars(pr::NotYetTreatedParallel) = union(termvars(pr.c), termvars(pr.s))

src/procedures.jl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ function _checkscales(col1::AbstractArray, col2::AbstractArray, treatvars::Vecto
8080
end
8181
end
8282

83-
function checktreatvars(::DynamicTreatment{SharpDesign}, pr::TrendParallel{Unconditional},
84-
treatvars::Vector{Symbol}, data)
83+
function checktreatvars(::DynamicTreatment{SharpDesign},
84+
pr::TrendOrUnspecifiedPR{Unconditional}, treatvars::Vector{Symbol}, data)
8585
# treatvars should be cohort and time variables
8686
col1 = getcolumn(data, treatvars[1])
8787
col2 = getcolumn(data, treatvars[2])
@@ -91,7 +91,9 @@ function checktreatvars(::DynamicTreatment{SharpDesign}, pr::TrendParallel{Uncon
9191
"nonmissing elements from columns $(treatvars[1]) and $(treatvars[2]) have different types $T1 and $T2"))
9292
T1 <: ValidTimeType ||
9393
throw(ArgumentError("column $(treatvars[1]) has unaccepted element type $(T1)"))
94-
eltype(pr.e) == T1 || throw(ArgumentError("element type $(eltype(pr.e)) of control cohorts from $pr does not match element type $T1 from data; expect $T1"))
94+
if pr isa TrendParallel
95+
eltype(pr.e) == T1 || throw(ArgumentError("element type $(eltype(pr.e)) of control cohorts from $pr does not match element type $T1 from data; expect $T1"))
96+
end
9597
if T1 <: RotatingTimeValue
9698
col1 isa RotatingTimeArray && col2 isa RotatingTimeArray ||
9799
throw(ArgumentError("columns $(treatvars[1]) and $(treatvars[2]) must be RotatingTimeArrays; see settime"))
@@ -147,6 +149,9 @@ function overlap!(esample::BitVector, tr_rows::BitVector, aux::BitVector, tr::Dy
147149
tr_rows .&= esample
148150
end
149151

152+
overlap!(esample::BitVector, tr_rows::BitVector, aux::BitVector, tr::DynamicTreatment,
153+
pr::UnspecifiedParallel{Unconditional}, treatname::Symbol, data) = nothing
154+
150155
"""
151156
checkvars!(args...)
152157
@@ -171,8 +176,10 @@ function checkvars!(data, tr::AbstractTreatment, pr::AbstractParallel,
171176
end
172177
# Values of treatintterms from untreated units are ignored
173178
tr_rows = copy(esample)
174-
istreated!(view(aux, esample), pr, view(getcolumn(data, treatname), esample))
175-
tr_rows[esample] .&= view(aux, esample)
179+
if !(pr isa UnspecifiedParallel)
180+
istreated!(view(aux, esample), pr, view(getcolumn(data, treatname), esample))
181+
tr_rows[esample] .&= view(aux, esample)
182+
end
176183
treatintvars = termvars(treatintterms)
177184
for v in treatintvars
178185
col = getcolumn(data, v)

test/parallels.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,17 @@
1313
end
1414
end
1515

16+
@testset "unspecifiedpr" begin
17+
us = UnspecifiedParallel()
18+
@test unspecifiedpr() === us
19+
@test unspecifiedpr(Unconditional()) === us
20+
@test unspecifiedpr(Unconditional(), Exact()) === us
21+
22+
@test sprint(show, us) == "Unspecified{U,P}"
23+
@test sprint(show, MIME("text/plain"), us) ==
24+
"Parallel among unspecified treatment groups"
25+
end
26+
1627
@testset "nevertreated" begin
1728
nt0 = NeverTreatedParallel([0], Unconditional(), Exact())
1829
nt0y = NeverTreatedParallel(Date(0), Unconditional(), Exact())
@@ -223,6 +234,7 @@ end
223234
@testset "termvars" begin
224235
@test termvars(Unconditional()) == Symbol[]
225236
@test termvars(Exact()) == Symbol[]
237+
@test termvars(unspecifiedpr()) == Symbol[]
226238
@test termvars(nevertreated(-1)) == Symbol[]
227239
@test termvars(notyettreated(5)) == Symbol[]
228240

test/procedures.jl

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,13 @@ end
6262
@testset "checkvars!" begin
6363
hrs = exampledata("hrs")
6464
N = size(hrs,1)
65-
nt = (data=hrs, tr=dynamic(:wave, -1), pr=nevertreated(11), yterm=term(:oop_spend),
65+
us = unspecifiedpr()
66+
nt = (data=hrs, tr=dynamic(:wave, -1), pr=us, yterm=term(:oop_spend),
6667
treatname=:wave_hosp, esample=trues(N), aux=BitVector(undef, N),
6768
treatintterms=TermSet(), xterms=TermSet())
69+
@test checkvars!(nt...) == (esample=trues(N), tr_rows=trues(N))
70+
71+
nt = merge(nt, (pr=nevertreated(11),))
6872
@test checkvars!(nt...) == (esample=trues(N), tr_rows=hrs.wave_hosp.!=11)
6973

7074
nt = merge(nt, (pr=notyettreated(11),))
@@ -76,19 +80,31 @@ end
7680
(esample=.!(hrs.wave_hosp.∈(10,)).& .!(hrs.wave.∈((10,11),)),
7781
tr_rows=(.!(hrs.wave_hosp.∈((10,11),)).& .!(hrs.wave.∈((10,11),))))
7882

83+
nt = merge(nt, (pr=us, treatintterms=TermSet(term(:male)),
84+
xterms=TermSet(term(:white)), esample=trues(N)))
85+
@test checkvars!(nt...) == (esample=trues(N), tr_rows=trues(N))
86+
7987
nt = merge(nt, (pr=nevertreated(11), treatintterms=TermSet(term(:male)),
8088
xterms=TermSet(term(:white)), esample=trues(N)))
8189
@test checkvars!(nt...) == (esample=trues(N), tr_rows=hrs.wave_hosp.!=11)
8290

8391
df = DataFrame(hrs)
8492
allowmissing!(df, :male)
85-
df.male .= ifelse.(df.wave_hosp.==11, missing, df.male)
86-
nt = merge(nt, (data=df,))
93+
df.male .= ifelse.(hrs.wave_hosp.==11, missing, df.male)
94+
95+
nt = merge(nt, (data=df, pr=us, esample=trues(N)))
96+
@test checkvars!(nt...) == (esample=hrs.wave_hosp.!=11, tr_rows=hrs.wave_hosp.!=11)
97+
df.male .= ifelse.(hrs.wave_hosp.==10, missing, hrs.male)
98+
nt = merge(nt, (esample=trues(N),))
99+
@test checkvars!(nt...) == (esample=hrs.wave_hosp.!=10, tr_rows=hrs.wave_hosp.!=10)
100+
101+
df.male .= ifelse.(hrs.wave_hosp.==11, missing, hrs.male)
102+
nt = merge(nt, (pr=nevertreated(11), esample=trues(N)))
87103
@test checkvars!(nt...) == (esample=trues(N), tr_rows=hrs.wave_hosp.!=11)
88-
df.male .= ifelse.(df.wave_hosp.==10, missing, df.male)
89-
@test checkvars!(nt...) == (esample=df.wave_hosp.!=10, tr_rows=hrs.wave_hosp.∈((8,9),))
90-
df.white = ifelse.(df.wave_hosp.==9, missing, df.white.+0.5)
91-
ret = (esample=df.wave_hosp.∈((8,11),), tr_rows=hrs.wave_hosp.==8)
104+
df.male .= ifelse.(hrs.wave_hosp.==10, missing, df.male)
105+
@test checkvars!(nt...) == (esample=hrs.wave_hosp.!=10, tr_rows=hrs.wave_hosp.∈((8,9),))
106+
df.white = ifelse.(hrs.wave_hosp.==9, missing, df.white.+0.5)
107+
ret = (esample=hrs.wave_hosp.∈((8,11),), tr_rows=hrs.wave_hosp.==8)
92108
@test checkvars!(nt...) == ret
93109

94110
df.wave_hosp = Date.(df.wave_hosp)

0 commit comments

Comments
 (0)