Skip to content

Commit 8341322

Browse files
slightly better performance (#257)
* Update fit.jl * Update fit.jl * Update fixedeffects.jl * Update fit.jl * Update fixedeffects.jl * Update fixedeffects.jl * Update fixedeffects.jl * Update fixedeffects.jl * Update fixedeffects.jl * simplify parsing * update * simplify a bit * Update fit.jl * Update fit.jl * Update fit.jl * Update fit.jl * remove dof_ad * Update fit.jl
1 parent cf7c0f7 commit 8341322

File tree

7 files changed

+195
-223
lines changed

7 files changed

+195
-223
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "FixedEffectModels"
22
uuid = "9d5cd8c9-2029-5cab-9928-427838db53e3"
3-
version = "1.10.2"
3+
version = "1.11.0"
44

55
[deps]
66
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"

src/FixedEffectModel.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,14 @@ struct FixedEffectModel <: RegressionModel
3636
# for FE
3737
iterations::Int # Number of iterations
3838
converged::Bool # Has the demeaning algorithm converged?
39-
r2_within::Union{Float64, Nothing} # within r2 (with fixed effect
39+
r2_within::Float64 # within r2 (with fixed effect
4040

4141
# for IV
42-
F_kp::Union{Float64, Nothing} # First Stage F statistics KP
43-
p_kp::Union{Float64, Nothing} # First Stage p value KP
42+
F_kp::Float64 # First Stage F statistics KP
43+
p_kp::Float64 # First Stage p value KP
4444
end
4545

46-
has_iv(m::FixedEffectModel) = m.F_kp !== nothing
46+
has_iv(m::FixedEffectModel) = has_iv(m.formula)
4747
has_fe(m::FixedEffectModel) = has_fe(m.formula)
4848

4949

src/fit.jl

Lines changed: 129 additions & 176 deletions
Large diffs are not rendered by default.

src/partial_out.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ function partial_out(
4646
f = FormulaTerm(f.lhs, tuple(ConstantTerm(1), eachterm(f.rhs)...))
4747
end
4848
formula, formula_endo, formula_iv = parse_iv(f)
49-
has_iv = formula_iv !== nothing
49+
has_iv = formula_iv != FormulaTerm(ConstantTerm(0), ConstantTerm(0))
5050
has_iv && throw("partial_out does not support instrumental variables")
51+
formula, formula_fes = parse_fe(formula)
5152
has_weights = weights !== nothing
5253

5354

@@ -63,7 +64,7 @@ function partial_out(
6364
convergeds = Bool[]
6465

6566
# Build fixedeffects, an array of AbtractFixedEffects
66-
fes, ids, ids_fes, formula = parse_fixedeffect(df, formula)
67+
fes, ids, ids_fes = parse_fixedeffect(df, formula_fes)
6768
has_fes = !isempty(fes)
6869

6970

src/utils/fixedeffects.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,18 @@
44
##
55
##############################################################################
66

7+
function drop_singletons!(esample, fes::Vector{<:FixedEffect})
8+
ns = Int[]
9+
for fe in Iterators.cycle(fes)
10+
# break loop if number of singletons did not change since the last time fe was iterated on
11+
if length(ns) >= length(fes) && sum(view(ns, (length(ns)-length(fes)+1):length(ns))) == ns[end-length(fes)+1]
12+
break
13+
end
14+
push!(ns, drop_singletons!(esample, fe))
15+
end
16+
return sum(ns)
17+
end
18+
719
function drop_singletons!(esample, fe::FixedEffect)
820
n = 0
921
cache = zeros(Int, fe.n)

src/utils/formula.jl

Lines changed: 35 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,24 @@ eachterm(@nospecialize(x::NTuple{N, AbstractTerm})) where {N} = x
1212
## Parse IV
1313
##
1414
##############################################################################
15-
15+
has_iv(@nospecialize(f::FormulaTerm)) = any(x -> x isa FormulaTerm, eachterm(f.rhs))
1616
function parse_iv(@nospecialize(f::FormulaTerm))
17-
for term in eachterm(f.rhs)
18-
if term isa FormulaTerm
19-
both = intersect(eachterm(term.lhs), eachterm(term.rhs))
20-
endos = setdiff(eachterm(term.lhs), both)
21-
exos = setdiff(eachterm(term.rhs), both)
22-
# otherwise empty collection. Later, there will be check
23-
isempty(endos) && throw("There are no endogeneous variables")
24-
length(exos) < length(endos) && throw("Model not identified. There must be at least as many instrumental variables as endogeneneous variables")
25-
formula_endo = FormulaTerm(ConstantTerm(0), tuple(ConstantTerm(0), endos...))
26-
formula_iv = FormulaTerm(ConstantTerm(0), tuple(ConstantTerm(0), exos...))
27-
formula_exo = FormulaTerm(f.lhs, tuple((term for term in eachterm(f.rhs) if !isa(term, FormulaTerm))..., both...))
28-
return formula_exo, formula_endo, formula_iv
29-
end
30-
end
31-
return f, nothing, nothing
17+
if has_iv(f)
18+
i = findfirst(x -> x isa FormulaTerm, eachterm(f.rhs))
19+
term = eachterm(f.rhs)[i]
20+
both = intersect(eachterm(term.lhs), eachterm(term.rhs))
21+
endos = setdiff(eachterm(term.lhs), both)
22+
exos = setdiff(eachterm(term.rhs), both)
23+
# otherwise empty collection. Later, there will be check
24+
isempty(endos) && throw("There are no endogeneous variables")
25+
length(exos) < length(endos) && throw("Model not identified. There must be at least as many instrumental variables as endogeneneous variables")
26+
formula_endo = FormulaTerm(ConstantTerm(0), tuple(ConstantTerm(0), endos...))
27+
formula_iv = FormulaTerm(ConstantTerm(0), tuple(ConstantTerm(0), exos...))
28+
formula_exo = FormulaTerm(f.lhs, tuple((term for term in eachterm(f.rhs) if !isa(term, FormulaTerm))..., both...))
29+
return formula_exo, formula_endo, formula_iv
30+
else
31+
return f, FormulaTerm(ConstantTerm(0), ConstantTerm(0)), FormulaTerm(ConstantTerm(0), ConstantTerm(0))
32+
end
3233
end
3334

3435
##############################################################################
@@ -47,13 +48,25 @@ has_fe(::FixedEffectTerm) = true
4748
has_fe(::FunctionTerm{typeof(fe)}) = true
4849
has_fe(@nospecialize(t::InteractionTerm)) = any(has_fe(x) for x in t.terms)
4950
has_fe(::AbstractTerm) = false
50-
5151
has_fe(@nospecialize(t::FormulaTerm)) = any(has_fe(x) for x in eachterm(t.rhs))
5252

53+
function parse_fe(@nospecialize(f::FormulaTerm))
54+
if has_fe(f)
55+
formula_main = FormulaTerm(f.lhs, Tuple(term for term in eachterm(f.rhs) if !has_fe(term)))
56+
formula_fe = FormulaTerm(ConstantTerm(0), Tuple(term for term in eachterm(f.rhs) if has_fe(term)))
57+
return formula_main, formula_fe
58+
else
59+
return f, FormulaTerm(ConstantTerm(0), ConstantTerm(0))
60+
end
61+
end
62+
5363

5464
fesymbol(t::FixedEffectTerm) = t.x
5565
fesymbol(t::FunctionTerm{typeof(fe)}) = Symbol(t.args[1])
5666

67+
68+
69+
5770
"""
5871
parse_fixedeffect(data, formula::FormulaTerm)
5972
parse_fixedeffect(data, ts::NTuple{N, AbstractTerm})
@@ -68,24 +81,17 @@ Construct any `FixedEffect` specified with a `FixedEffectTerm`.
6881
"""
6982
function parse_fixedeffect(data, @nospecialize(formula::FormulaTerm))
7083
fes = FixedEffect[]
71-
ids = Symbol[]
84+
feids = Symbol[]
7285
fekeys = Symbol[]
73-
for term in eachterm(formula.rhs)
86+
for term in eachterm(formula.rhs)
7487
result = _parse_fixedeffect(data, term)
7588
if result !== nothing
7689
push!(fes, result[1])
77-
push!(ids, result[2])
90+
push!(feids, result[2])
7891
append!(fekeys, result[3])
7992
end
8093
end
81-
if !isempty(fes)
82-
if any(fe.interaction isa UnitWeights for fe in fes)
83-
formula = FormulaTerm(formula.lhs, (InterceptTerm{false}(), (term for term in eachterm(formula.rhs) if !isa(term, Union{ConstantTerm,InterceptTerm}) && !has_fe(term))...))
84-
else
85-
formula = FormulaTerm(formula.lhs, Tuple(term for term in eachterm(formula.rhs) if !has_fe(term)))
86-
end
87-
end
88-
return fes, ids, unique(fekeys), formula
94+
return fes, feids, unique(fekeys)
8995
end
9096

9197
# Method for external packages
@@ -108,7 +114,7 @@ function parse_fixedeffect(data, @nospecialize(ts::NTuple{N, AbstractTerm})) whe
108114
ts = Tuple(term for term in eachterm(ts) if !has_fe(term))
109115
end
110116
end
111-
return fes, ids, unique(fekeys), ts
117+
return fes, ids, unique(fekeys)
112118
end
113119

114120
# Construct FixedEffect from a generic term

test/formula.jl

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ for data in [df, csvfile]
2222
@test _parse_fixedeffect(data, fe(:State)&fe(:Year)) ==
2323
(FixedEffect(data.State, data.Year), Symbol("fe_State&fe_Year"), [:State, :Year])
2424

25-
@test parse_fixedeffect(data, ()) == (FixedEffect[], Symbol[], Symbol[], ())
25+
@test parse_fixedeffect(data, ()) == (FixedEffect[], Symbol[], Symbol[])
2626

2727
f = @formula(y ~ 1 + Price)
2828
ts1 = f.rhs
2929
ts2 = term(1) + term(:Price)
30-
@test parse_fixedeffect(data, f) == (FixedEffect[], Symbol[], Symbol[], f)
31-
@test parse_fixedeffect(data, ts1) == (FixedEffect[], Symbol[], Symbol[], ts1)
30+
@test parse_fixedeffect(data, f) == (FixedEffect[], Symbol[], Symbol[])
31+
@test parse_fixedeffect(data, ts1) == (FixedEffect[], Symbol[], Symbol[])
3232
@test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1)
3333

3434
fparsed = term(:y) ~ InterceptTerm{false}() + term(:Price)
@@ -37,28 +37,28 @@ for data in [df, csvfile]
3737
f = @formula(y ~ 1 + Price + fe(State))
3838
ts1 = f.rhs
3939
ts2 = term(1) + term(:Price) + fe(:State)
40-
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State)], [:fe_State], [:State], fparsed)
41-
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State)], [:fe_State], [:State], tsparsed)
40+
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State)], [:fe_State], [:State])
41+
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State)], [:fe_State], [:State])
4242
@test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1)
4343

4444
f = @formula(y ~ Price + fe(State) + fe(Year))
4545
ts1 = f.rhs
4646
ts2 = term(:Price) + fe(:State) + fe(:Year)
47-
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], fparsed)
48-
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year], tsparsed)
47+
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year])
48+
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year)], [:fe_State, :fe_Year], [:State, :Year])
4949
@test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1)
5050

5151
f = @formula(y ~ Price + fe(State)&Year)
5252
ts1 = f.rhs
5353
ts2 = term(:Price) + fe(:State)&term(:Year)
54-
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], term(:y) ~ (term(:Price),))
55-
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State], (term(:Price),))
54+
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State])
55+
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State, interaction=_multiply(data, [:Year]))], [Symbol("fe_State&Year")], [:State])
5656
@test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1)
5757

5858
f = @formula(y ~ Price + fe(State)*fe(Year))
5959
ts1 = f.rhs
6060
ts2 = term(:Price) + fe(:State) + fe(:Year) + fe(:State)&fe(:Year)
61-
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], fparsed)
62-
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year], tsparsed)
61+
@test parse_fixedeffect(data, f) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year])
62+
@test parse_fixedeffect(data, ts1) == ([FixedEffect(data.State), FixedEffect(data.Year), FixedEffect(data.State, data.Year)], [:fe_State, :fe_Year, Symbol("fe_State&fe_Year")], [:State, :Year])
6363
@test parse_fixedeffect(data, ts2) == parse_fixedeffect(data, ts1)
6464
end

0 commit comments

Comments
 (0)