Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 26 additions & 27 deletions src/parallels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,33 +85,33 @@ assume a parallel trends assumption holds over all the relevant time periods.
abstract type TrendParallel{C,S} <: AbstractParallel{C,S} end

"""
NeverTreatedParallel{C,S,T<:Tuple} <: TrendParallel{C,S}
NeverTreatedParallel{C,S} <: TrendParallel{C,S}

Assume a parallel trends assumption holds between any group
that received the treatment during the sample periods
and a group that did not receive any treatment in any sample period.
See also [`nevertreated`](@ref).

# Fields
- `e::T`: group indices for units that did not receive any treatment.
- `e::Vector{Int}`: group indices for units that did not receive any treatment.
- `c::C`: an instance of [`ParallelCondition`](@ref).
- `s::S`: an instance of [`ParallelStrength`](@ref).
"""
struct NeverTreatedParallel{C,S,T<:Tuple} <: TrendParallel{C,S}
e::T
struct NeverTreatedParallel{C,S} <: TrendParallel{C,S}
e::Vector{Int}
c::C
s::S
function NeverTreatedParallel(e, c::ParallelCondition, s::ParallelStrength)
e = (unique!(sort!([e...]))...,)
e = unique!(sort!([e...]))
isempty(e) && error("field `e` cannot be empty")
return new{typeof(c),typeof(s),typeof(e)}(e, c, s)
return new{typeof(c),typeof(s)}(e, c, s)
end
end

istreated(pr::NeverTreatedParallel, x) = !(x in pr.e)

show(io::IO, pr::NeverTreatedParallel) =
print(IOContext(io, :compact=>true), "NeverTreated{", pr.c, ",", pr.s, "}", pr.e)
print(IOContext(io, :compact=>true), "NeverTreated{", pr.c, ",", pr.s, "}(", pr.e, ")")

function show(io::IO, ::MIME"text/plain", pr::NeverTreatedParallel)
println(io, pr.s, " trends with any never-treated group:")
Expand All @@ -133,14 +133,14 @@ a wrapper method of `nevertreated` calls this method.
```jldoctest; setup = :(using DiffinDiffsBase)
julia> nevertreated(-1)
Parallel trends with any never-treated group:
Never-treated groups: (-1,)
Never-treated groups: [-1]

julia> typeof(nevertreated(-1))
NeverTreatedParallel{Unconditional,Exact,Tuple{Int64}}

julia> nevertreated([-1, 0])
Parallel trends with any never-treated group:
Never-treated groups: (-1, 0)
Never-treated groups: [-1, 0]

julia> nevertreated([-1, 0]) == nevertreated(-1:0) == nevertreated(Set([-1, 0]))
true
Expand All @@ -159,16 +159,16 @@ A wrapper method of `nevertreated` for working with `@formula`.
@unpack nevertreated

"""
NotYetTreatedParallel{C,S,T1<:Tuple,T2<:Tuple} <: TrendParallel{C,S}
NotYetTreatedParallel{C,S} <: TrendParallel{C,S}

Assume a parallel trends assumption holds between any group
that received the treatment relatively early
and any group that received the treatment relatively late (or never receved).
See also [`notyettreated`](@ref).

# Fields
- `e::T1`: group indices for units that received the treatment relatively late.
- `ecut::T2`: user-specified period(s) when units in a group in `e` started to receive treatment.
- `e::Vector{Int}`: group indices for units that received the treatment relatively late.
- `ecut::Vector{Int}`: user-specified period(s) when units in a group in `e` started to receive treatment.
- `c::C`: an instance of [`ParallelCondition`](@ref).
- `s::S`: an instance of [`ParallelStrength`](@ref).

Expand All @@ -177,25 +177,24 @@ See also [`notyettreated`](@ref).
- never-treated groups are included and use indices with smaller values;
- the sample has a rotating panel structure with periods overlapping with some others.
"""
struct NotYetTreatedParallel{C,S,T1<:Tuple,T2<:Tuple} <: TrendParallel{C,S}
e::T1
ecut::T2
struct NotYetTreatedParallel{C,S} <: TrendParallel{C,S}
e::Vector{Int}
ecut::Vector{Int}
c::C
s::S
function NotYetTreatedParallel(e, ecut, c::ParallelCondition, s::ParallelStrength)
e = (unique!(sort!([e...]))...,)
e = unique!(sort!([e...]))
isempty(e) && error("field `e` cannot be empty")
ecut = (unique!(sort!([ecut...]))...,)
ecut = unique!(sort!([ecut...]))
isempty(ecut) && error("field `ecut` cannot be empty")
return new{typeof(c),typeof(s),typeof(e),typeof(ecut)}(e, ecut, c, s)
return new{typeof(c),typeof(s)}(e, ecut, c, s)
end
end

istreated(pr::NotYetTreatedParallel, x) = !(x in pr.e)

function show(io::IO, pr::NotYetTreatedParallel)
print(IOContext(io, :compact=>true), "NotYetTreated{", pr.c, ",", pr.s, "}", pr.e)
end
show(io::IO, pr::NotYetTreatedParallel) =
print(IOContext(io, :compact=>true), "NotYetTreated{", pr.c, ",", pr.s, "}(", pr.e, ")")

function show(io::IO, ::MIME"text/plain", pr::NotYetTreatedParallel)
println(io, pr.s, " trends with any not-yet-treated group:")
Expand All @@ -219,21 +218,21 @@ a wrapper method of `notyettreated` calls this method.
```jldoctest; setup = :(using DiffinDiffsBase)
julia> notyettreated(5)
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (5,)
Treated since: (5,)
Not-yet-treated groups: [5]
Treated since: [5]

julia> typeof(notyettreated(5))
NotYetTreatedParallel{Unconditional,Exact,Tuple{Int64},Tuple{Int64}}

julia> notyettreated([-1, 5, 6], 5)
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (-1, 5, 6)
Treated since: (5,)
Not-yet-treated groups: [-1, 5, 6]
Treated since: [5]

julia> notyettreated([4, 5, 6], [4, 5, 6])
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (4, 5, 6)
Treated since: (4, 5, 6)
Not-yet-treated groups: [4, 5, 6]
Treated since: [4, 5, 6]
```
"""
notyettreated(e, ecut, c::ParallelCondition, s::ParallelStrength) =
Expand Down
3 changes: 3 additions & 0 deletions src/terms.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const Terms{N} = NTuple{N, AbstractTerm} where N

# A fix to changes made in StatsModels v0.6.21
termvars(::Tuple{}) = Symbol[]

"""
eachterm(t)

Expand Down
23 changes: 12 additions & 11 deletions src/treatments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,33 +34,34 @@ abstract type AbstractTreatment end
@fieldequal AbstractTreatment

"""
DynamicTreatment{S<:TreatmentSharpness, E<:Tuple} <: AbstractTreatment
DynamicTreatment{S<:TreatmentSharpness} <: AbstractTreatment

Specify an absorbing binary treatment with effects allowed to evolve over time.
See also [`dynamic`](@ref).

# Fields
- `time::Symbol`: column name of data representing calendar time.
- `exc::E`: excluded relative time.
- `exc::Vector{Int}`: excluded relative time.
- `s::S`: an instance of [`TreatmentSharpness`](@ref).
"""
struct DynamicTreatment{S<:TreatmentSharpness, E<:Tuple} <: AbstractTreatment
struct DynamicTreatment{S<:TreatmentSharpness} <: AbstractTreatment
time::Symbol
exc::E
exc::Vector{Int}
s::S
function DynamicTreatment(time::Symbol, exc, s::TreatmentSharpness)
exc = exc !== nothing ? (unique!(sort!([exc...]))...,) : ()
return new{typeof(s),typeof(exc)}(time, exc, s)
exc = exc !== nothing ? unique!(sort!([exc...])) : Int[]
return new{typeof(s)}(time, exc, s)
end
end

show(io::IO, tr::DynamicTreatment) =
print(IOContext(io, :compact=>true), "Dynamic{", tr.s, "}", tr.exc)
print(IOContext(io, :compact=>true), "Dynamic{", tr.s, "}(",
isempty(tr.exc) ? "none" : tr.exc, ")")

function show(io::IO, ::MIME"text/plain", tr::DynamicTreatment)
println(io, tr.s, " dynamic treatment:")
println(io, " column name of time variable: ", tr.time)
print(io, " excluded relative time: ", tr.exc===() ? "none" : tr.exc)
print(io, " excluded relative time: ", isempty(tr.exc) ? "none" : tr.exc)
end

"""
Expand All @@ -76,20 +77,20 @@ a wrapper method of `dynamic` calls this method.
julia> dynamic(:month, -1)
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: (-1,)
excluded relative time: [-1]

julia> typeof(dynamic(:month, -1))
DynamicTreatment{SharpDesign,Tuple{Int64}}

julia> dynamic(:month, -3:-1)
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: (-3, -2, -1)
excluded relative time: [-3, -2, -1]

julia> dynamic(:month, [-2,-1], sharp())
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: (-2, -1)
excluded relative time: [-2, -1]
```
"""
dynamic(time::Symbol, exc, s::TreatmentSharpness=sharp()) =
Expand Down
8 changes: 4 additions & 4 deletions test/did.jl
Original file line number Diff line number Diff line change
Expand Up @@ -106,20 +106,20 @@ end
@test sprint(show, sp) == "unnamed"
@test sprint(show, MIME("text/plain"), sp) == """
unnamed (StatsSpec for TestDID):
Dynamic{S}(-1,)
NeverTreated{U,P}(-1,)"""
Dynamic{S}([-1])
NeverTreated{U,P}([-1])"""

sp = StatsSpec("", TestDID, (tr=dynamic(:time,-1),))
@test sprint(show, sp) == "unnamed"
@test sprint(show, MIME("text/plain"), sp) == """
unnamed (StatsSpec for TestDID):
Dynamic{S}(-1,)"""
Dynamic{S}([-1])"""

sp = StatsSpec("name", TestDID, (pr=nevertreated(-1),))
@test sprint(show, sp) == "name"
@test sprint(show, MIME("text/plain"), sp) == """
name (StatsSpec for TestDID):
NeverTreated{U,P}(-1,)"""
NeverTreated{U,P}([-1])"""
end
end

Expand Down
34 changes: 18 additions & 16 deletions test/parallels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ end
@testset "inner constructor" begin
@test NeverTreatedParallel([1,1,0], Unconditional(), Exact()) == nt1
@test_throws ErrorException NeverTreatedParallel([], Unconditional(), Exact())
@test_throws InexactError NeverTreatedParallel([-0.5], Unconditional(), Exact())
end

@testset "without @formula" begin
Expand Down Expand Up @@ -71,15 +72,15 @@ end
end

@testset "show" begin
@test sprint(show, nt0) == "NeverTreated{U,P}(0,)"
@test sprint(show, nt0) == "NeverTreated{U,P}([0])"
@test sprint(show, MIME("text/plain"), nt0) == """
Parallel trends with any never-treated group:
Never-treated groups: (0,)"""
Never-treated groups: [0]"""

@test sprint(show, nt1) == "NeverTreated{U,P}(0, 1)"
@test sprint(show, nt1) == "NeverTreated{U,P}([0, 1])"
@test sprint(show, MIME("text/plain"), nt1) == """
Parallel trends with any never-treated group:
Never-treated groups: (0, 1)"""
Never-treated groups: [0, 1]"""
end
end

Expand All @@ -93,6 +94,7 @@ end
@test NotYetTreatedParallel([1,1,0], [1,0,0], Unconditional(), Exact()) == ny3
@test_throws ErrorException NotYetTreatedParallel([0], [], Unconditional(), Exact())
@test_throws ErrorException NotYetTreatedParallel([], [0], Unconditional(), Exact())
@test_throws InexactError NotYetTreatedParallel([-0.5], [-1], Unconditional(), Exact())
end

@testset "without @formula" begin
Expand Down Expand Up @@ -156,29 +158,29 @@ end
end

@testset "show" begin
@test sprint(show, ny0) == "NotYetTreated{U,P}(0,)"
@test sprint(show, ny0) == "NotYetTreated{U,P}([0])"
@test sprint(show, MIME("text/plain"), ny0) == """
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (0,)
Treated since: (0,)"""
Not-yet-treated groups: [0]
Treated since: [0]"""

@test sprint(show, ny1) == "NotYetTreated{U,P}(0, 1)"
@test sprint(show, ny1) == "NotYetTreated{U,P}([0, 1])"
@test sprint(show, MIME("text/plain"), ny1) == """
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (0, 1)
Treated since: (0,)"""
Not-yet-treated groups: [0, 1]
Treated since: [0]"""

@test sprint(show, ny2) == "NotYetTreated{U,P}(0, 1)"
@test sprint(show, ny2) == "NotYetTreated{U,P}([0, 1])"
@test sprint(show, MIME("text/plain"), ny2) == """
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (0, 1)
Treated since: (0,)"""
Not-yet-treated groups: [0, 1]
Treated since: [0]"""

@test sprint(show, ny3) == "NotYetTreated{U,P}(0, 1)"
@test sprint(show, ny3) == "NotYetTreated{U,P}([0, 1])"
@test sprint(show, MIME("text/plain"), ny3) == """
Parallel trends with any not-yet-treated group:
Not-yet-treated groups: (0, 1)
Treated since: (0, 1)"""
Not-yet-treated groups: [0, 1]
Treated since: [0, 1]"""
end
end

Expand Down
12 changes: 7 additions & 5 deletions test/treatments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ end
@test DynamicTreatment(:month, [], SharpDesign()) == dt0
@test DynamicTreatment(:month, [-1], SharpDesign()) == dt1
@test DynamicTreatment(:month, [-1,-1,-2], SharpDesign()) == dt2
@test DynamicTreatment(:month, [-1.0], SharpDesign()) == dt1
@test_throws InexactError DynamicTreatment(:month, [-1.5], SharpDesign())
end

@testset "without @formula" begin
Expand Down Expand Up @@ -55,23 +57,23 @@ end
end

@testset "show" begin
@test sprint(show, dt0) == "Dynamic{S}"
@test sprint(show, dt0) == "Dynamic{S}(none)"
@test sprint(show, MIME("text/plain"), dt0) == """
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: none"""

@test sprint(show, dt1) == "Dynamic{S}(-1,)"
@test sprint(show, dt1) == "Dynamic{S}([-1])"
@test sprint(show, MIME("text/plain"), dt1) == """
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: (-1,)"""
excluded relative time: [-1]"""

@test sprint(show, dt2) == "Dynamic{S}(-2, -1)"
@test sprint(show, dt2) == "Dynamic{S}([-2, -1])"
@test sprint(show, MIME("text/plain"), dt2) == """
Sharp dynamic treatment:
column name of time variable: month
excluded relative time: (-2, -1)"""
excluded relative time: [-2, -1]"""
end
end

Expand Down