Skip to content

Commit f6ec906

Browse files
update (#34)
1 parent b4c068d commit f6ec906

File tree

6 files changed

+153
-37
lines changed

6 files changed

+153
-37
lines changed

Project.toml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
name = "InteractiveFixedEffectModels"
22
uuid = "80307280-efb2-5c5d-af8b-a9c15821677b"
3-
version = "1.0.1"
3+
version = "1.1.0"
44

55
[deps]
66
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
7-
Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f"
87
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
98
FixedEffectModels = "9d5cd8c9-2029-5cab-9928-427838db53e3"
109
FixedEffects = "c8885935-8500-56a7-9867-7708b20db0eb"
@@ -14,22 +13,23 @@ Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7"
1413
Reexport = "189a3867-3050-52da-a836-e630ba90ab69"
1514
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
1615
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
16+
StatsFuns = "4c63d2b9-4356-54db-8cca-17b64c39e42c"
1717
StatsModels = "3eaba693-59b7-5ba5-a881-562e759f1c8d"
1818
Tables = "bd369af6-aec1-5ad0-b16a-f7cc5008161c"
1919
Vcov = "ec2bfdc2-55df-4fc9-b9ae-4958c2cf2486"
2020

2121
[compat]
2222
DataFrames = "0.21"
23-
Distributions = "0.23"
2423
FillArrays = "0.7, 0.8, 0.9"
25-
FixedEffectModels = "1.2"
26-
FixedEffects = "1.1"
24+
FixedEffectModels = "1.3"
25+
FixedEffects = "2"
2726
LeastSquaresOptim = "0.7"
2827
Reexport = "0.2"
2928
StatsBase = "0.33"
3029
StatsModels = "0.6"
30+
StatsFuns = "0.9"
3131
Tables = "1"
32-
Vcov = "0.3"
32+
Vcov = "0.4"
3333
julia = "1.3"
3434

3535
[extras]

src/InteractiveFixedEffectModels.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ module InteractiveFixedEffectModels
55
##
66
##############################################################################
77
using DataFrames
8-
using Distributions
98
using FillArrays
109
using FixedEffects
1110
using LeastSquaresOptim
1211
using LinearAlgebra
1312
using Printf
1413
using Statistics
1514
using StatsBase
15+
using StatsFuns
1616
using StatsModels
1717
using Tables
1818
using Vcov
@@ -36,6 +36,9 @@ regife
3636
##
3737
##############################################################################
3838
include("types.jl")
39+
include("utils/tss.jl")
40+
include("utils/formula.jl")
41+
3942
include("methods/gauss_seidel.jl")
4043
include("methods/ls.jl")
4144
include("fit.jl")

src/fit.jl

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@ function regife(
1919
##
2020
##############################################################################
2121
df = DataFrame(df; copycols = false)
22-
if (ConstantTerm(0) FixedEffectModels.eachterm(f.rhs)) & (ConstantTerm(1) FixedEffectModels.eachterm(f.rhs))
23-
formula = FormulaTerm(f.lhs, tuple(ConstantTerm(1), FixedEffectModels.eachterm(f.rhs)...))
22+
if (ConstantTerm(0) eachterm(f.rhs)) & (ConstantTerm(1) eachterm(f.rhs))
23+
formula = FormulaTerm(f.lhs, tuple(ConstantTerm(1), eachterm(f.rhs)...))
2424
end
2525

26-
formula, formula_endo, formula_iv = FixedEffectModels.parse_iv(f)
2726

28-
m, formula = parse_interactivefixedeffect(df, formula)
27+
m, formula = parse_interactivefixedeffect(df, f)
2928
has_weights = (weights != nothing)
3029

3130

@@ -60,10 +59,10 @@ function regife(
6059
weights = uweights(sum(esample))
6160
sqrtw = ones(length(weights))
6261
end
63-
for a in FixedEffectModels.eachterm(formula.rhs)
62+
for a in eachterm(formula.rhs)
6463
if has_fe(a)
6564
isa(a, InteractionTerm) && error("Fixed effects cannot be interacted")
66-
Symbol(FixedEffectModels.fesymbol(a)) factor_vars && error("FixedEffect should correspond to id or time dimension of the factor model")
65+
Symbol(fesymbol(a)) factor_vars && error("FixedEffect should correspond to id or time dimension of the factor model")
6766
end
6867
end
6968
fes, ids, formula = FixedEffectModels.parse_fixedeffect(df, formula)
@@ -72,15 +71,15 @@ function regife(
7271
## Compute factors, an array of AbtractFixedEffects
7372
if has_fes
7473
if any([isa(fe.interaction, Ones) for fe in fes])
75-
formula = FormulaTerm(formula.lhs, tuple(ConstantTerm(0), (t for t in FixedEffectModels.eachterm(formula.rhs) if t!= ConstantTerm(1))...))
74+
formula = FormulaTerm(formula.lhs, tuple(ConstantTerm(0), (t for t in eachterm(formula.rhs) if t!= ConstantTerm(1))...))
7675
has_fes_intercept = true
7776
end
78-
fes = FixedEffect[FixedEffectModels._subset(fe, esample) for fe in fes]
79-
feM = FixedEffectModels.AbstractFixedEffectSolver{Float64}(fes, weights, Val{:cpu})
77+
fes = FixedEffect[fe[esample] for fe in fes]
78+
feM = AbstractFixedEffectSolver{Float64}(fes, weights, Val{:cpu})
8079
end
8180

8281

83-
has_intercept = ConstantTerm(1) FixedEffectModels.eachterm(formula.rhs)
82+
has_intercept = ConstantTerm(1) eachterm(formula.rhs)
8483

8584

8685
iterations = 0
@@ -100,7 +99,7 @@ function regife(
10099
formula_schema = apply_schema(formula, schema(formula, subdf, contrasts), StatisticalModel)
101100

102101
y = convert(Vector{Float64}, response(formula_schema, subdf))
103-
tss_total = FixedEffectModels.tss(y, has_intercept || has_fes_intercept, weights)
102+
tss_total = tss(y, has_intercept || has_fes_intercept, weights)
104103

105104
X = convert(Matrix{Float64}, modelmatrix(formula_schema, subdf))
106105

@@ -123,8 +122,8 @@ function regife(
123122

124123
# demean variables
125124
if has_fes
126-
FixedEffectModels.solve_residuals!(y, feM)
127-
FixedEffectModels.solve_residuals!(X, feM, progress_bar = false)
125+
solve_residuals!(y, feM)
126+
solve_residuals!(X, feM, progress_bar = false)
128127
end
129128

130129

@@ -136,8 +135,8 @@ function regife(
136135
##############################################################################
137136

138137
# initialize factor models at 0.1
139-
idpool = fill(0.1, length(id.pool), m.rank)
140-
timepool = fill(0.1, length(time.pool), m.rank)
138+
idpool = fill(0.1, id.n, m.rank)
139+
timepool = fill(0.1, time.n, m.rank)
141140

142141
y .= y .* sqrtw
143142
X .= X .* sqrtw
@@ -169,12 +168,12 @@ function regife(
169168
# y ~ x + γ1 x factors + γ2 x loadings
170169
# if not, this means fit! ended up on a a local minimum.
171170
# restart with randomized coefficients, factors, loadings
172-
newfeM = FixedEffectModels.AbstractFixedEffectSolver{Float64}(getfactors(fp, fs), weights, Val{:cpu})
171+
newfeM = AbstractFixedEffectSolver{Float64}(getfactors(fp, fs), weights, Val{:cpu})
173172
ym .= ym ./ sqrtw
174-
FixedEffectModels.solve_residuals!(ym, newfeM, tol = tol, maxiter = maxiter)
173+
solve_residuals!(ym, newfeM, tol = tol, maxiter = maxiter)
175174
ym .= ym .* sqrtw
176175
Xm .= Xm ./ sqrtw
177-
FixedEffectModels.solve_residuals!(Xm, newfeM, tol = tol, maxiter = maxiter)
176+
solve_residuals!(Xm, newfeM, tol = tol, maxiter = maxiter)
178177
Xm .= Xm .* sqrtw
179178
ydiff = Xm * (fs.b - Xm \ ym)
180179
if iterations >= maxiter || norm(ydiff) <= 0.01 * norm(y)
@@ -226,7 +225,7 @@ function regife(
226225
# compute various r2
227226
nobs = sum(esample)
228227
rss = sum(abs2, residualsm)
229-
_tss = FixedEffectModels.tss(ym ./ sqrtw, has_intercept || has_fes_intercept, weights)
228+
_tss = tss(ym ./ sqrtw, has_intercept || has_fes_intercept, weights)
230229
r2_within = 1 - rss / _tss
231230

232231
rss = sum(abs2, residuals)
@@ -266,7 +265,7 @@ function regife(
266265
subtract_factor!(fp, fs)
267266
axpy!(-1.0, residuals, oldresiduals)
268267
# get fixed effect
269-
newfes, b, c = FixedEffectModels.solve_coefficients!(oldresiduals, feM; tol = tol, maxiter = maxiter)
268+
newfes, b, c = solve_coefficients!(oldresiduals, feM; tol = tol, maxiter = maxiter)
270269
for j in 1:length(fes)
271270
augmentdf[!, ids[j]] = Vector{Union{Float64, Missing}}(missing, length(esample))
272271
augmentdf[esample, ids[j]] = newfes[j]

src/types.jl

Lines changed: 99 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,36 @@ StatsBase.islinear(x::InteractiveFixedEffectModel) = false
281281
StatsBase.rss(x::InteractiveFixedEffectModel) = x.rss
282282
StatsBase.predict(::InteractiveFixedEffectModel, ::AbstractDataFrame) = error("predict is not defined for linear factor models. Use the option save = true")
283283
StatsBase.residuals(::InteractiveFixedEffectModel, ::AbstractDataFrame) = error("residuals is not defined for linear factor models. Use the option save = true")
284-
function StatsBase.confint(x::InteractiveFixedEffectModel)
285-
scale = quantile(TDist(dof_residual(x)), 1 - (1-0.95)/2)
284+
function StatsBase.confint(x::InteractiveFixedEffectModel; level::Real = 0.95)
285+
scale = tdistinvcdf(dof_residual(x), 1 - (1 - level) / 2)
286286
se = stderror(x)
287287
hcat(x.coef - scale * se, x.coef + scale * se)
288288
end
289+
function StatsBase.coeftable(x::InteractiveFixedEffectModel; level = 0.95)
290+
cc = coef(x)
291+
se = stderror(x)
292+
coefnms = coefnames(x)
293+
conf_int = confint(x; level = level)
294+
# put (intercept) last
295+
if !isempty(coefnms) && ((coefnms[1] == Symbol("(Intercept)")) || (coefnms[1] == "(Intercept)"))
296+
newindex = vcat(2:length(cc), 1)
297+
cc = cc[newindex]
298+
se = se[newindex]
299+
conf_int = conf_int[newindex, :]
300+
coefnms = coefnms[newindex]
301+
end
302+
tt = cc ./ se
303+
FixedEffectModels.CoefTable(
304+
hcat(cc, se, tt, fdistccdf.(Ref(1), Ref(dof_residual(x)), abs2.(tt)), conf_int[:, 1:2]),
305+
["Estimate","Std.Error","t value", "Pr(>|t|)", "Lower 95%", "Upper 95%" ],
306+
["$(coefnms[i])" for i = 1:length(cc)], 4)
307+
end
308+
309+
##############################################################################
310+
##
311+
## Display Result
312+
##
313+
##############################################################################
289314

290315
title(::InteractiveFixedEffectModel) = "Interactive Fixed Effect Model"
291316
top(x::InteractiveFixedEffectModel) = [
@@ -297,10 +322,9 @@ top(x::InteractiveFixedEffectModel) = [
297322
"Converged" sprint(show, x.converged; context=:compact => true)
298323
]
299324

325+
format_scientific(x) = @sprintf("%.3f", x)
326+
300327
function Base.show(io::IO, x::InteractiveFixedEffectModel)
301-
show(io, coeftable(x))
302-
end
303-
function StatsBase.coeftable(x::InteractiveFixedEffectModel)
304328
ctitle = title(x)
305329
ctop = top(x)
306330
cc = coef(x)
@@ -316,8 +340,73 @@ function StatsBase.coeftable(x::InteractiveFixedEffectModel)
316340
coefnms = coefnms[newindex]
317341
end
318342
tt = cc ./ se
319-
FixedEffectModels.CoefTable2(
320-
hcat(cc, se, tt, ccdf.(Ref(FDist(1, dof_residual(x))), abs2.(tt)), conf_int[:, 1:2]),
321-
["Estimate","Std.Error","t value", "Pr(>|t|)", "Lower 95%", "Upper 95%" ],
322-
["$(coefnms[i])" for i = 1:length(cc)], 4, ctitle, ctop)
323-
end
343+
mat = hcat(cc, se, tt, fdistccdf.(Ref(1), Ref(dof_residual(x)), abs2.(tt)), conf_int[:, 1:2])
344+
nr, nc = size(mat)
345+
colnms = ["Estimate","Std.Error","t value", "Pr(>|t|)", "Lower 95%", "Upper 95%"]
346+
rownms = ["$(coefnms[i])" for i = 1:length(cc)]
347+
pvc = 4
348+
349+
350+
# print
351+
if length(rownms) == 0
352+
rownms = AbstractString[lpad("[$i]",floor(Integer, log10(nr))+3) for i in 1:nr]
353+
end
354+
if length(rownms) > 0
355+
rnwidth = max(4,maximum([length(nm) for nm in rownms]) + 1)
356+
else
357+
# if only intercept, rownms is empty collection, so previous would return error
358+
rnwidth = 4
359+
end
360+
rownms = [rpad(nm,rnwidth) for nm in rownms]
361+
widths = [length(cn)::Int for cn in colnms]
362+
str = [sprint(show, mat[i,j]; context=:compact => true) for i in 1:nr, j in 1:nc]
363+
if pvc != 0 # format the p-values column
364+
for i in 1:nr
365+
str[i, pvc] = format_scientific(mat[i, pvc])
366+
end
367+
end
368+
for j in 1:nc
369+
for i in 1:nr
370+
lij = length(str[i, j])
371+
if lij > widths[j]
372+
widths[j] = lij
373+
end
374+
end
375+
end
376+
widths .+= 1
377+
totalwidth = sum(widths) + rnwidth
378+
if length(ctitle) > 0
379+
halfwidth = div(totalwidth - length(ctitle), 2)
380+
println(io, " " ^ halfwidth * string(ctitle) * " " ^ halfwidth)
381+
end
382+
if length(ctop) > 0
383+
for i in 1:size(ctop, 1)
384+
ctop[i, 1] = ctop[i, 1] * ":"
385+
end
386+
println(io, "=" ^totalwidth)
387+
halfwidth = div(totalwidth, 2) - 1
388+
interwidth = 2 + mod(totalwidth, 2)
389+
for i in 1:(div(size(ctop, 1) - 1, 2)+1)
390+
print(io, ctop[2*i-1, 1])
391+
print(io, lpad(ctop[2*i-1, 2], halfwidth - length(ctop[2*i-1, 1])))
392+
print(io, " " ^interwidth)
393+
if size(ctop, 1) >= 2*i
394+
print(io, ctop[2*i, 1])
395+
print(io, lpad(ctop[2*i, 2], halfwidth - length(ctop[2*i, 1])))
396+
end
397+
println(io)
398+
end
399+
end
400+
println(io,"=" ^totalwidth)
401+
println(io," " ^ rnwidth *
402+
join([lpad(string(colnms[i]), widths[i]) for i = 1:nc], ""))
403+
println(io,"-" ^totalwidth)
404+
for i in 1:nr
405+
print(io, rownms[i])
406+
for j in 1:nc
407+
print(io, lpad(str[i,j],widths[j]))
408+
end
409+
println(io)
410+
end
411+
println(io,"=" ^totalwidth)
412+
end

src/utils/formula.jl

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
##############################################################################
2+
##
3+
## Iterate on terms
4+
##
5+
##############################################################################
6+
7+
eachterm(@nospecialize(x::AbstractTerm)) = (x,)
8+
eachterm(@nospecialize(x::NTuple{N, AbstractTerm})) where {N} = x
9+
TermOrTerms = Union{AbstractTerm, NTuple{N, AbstractTerm} where N}
10+
11+
##############################################################################
12+
##
13+
## Parse FixedEffect
14+
##
15+
##############################################################################
16+
fesymbol(t::FixedEffectModels.FixedEffectTerm) = t.x
17+
fesymbol(t::FunctionTerm{typeof(fe)}) = Symbol(t.args_parsed[1])

src/utils/tss.jl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
function tss(y::AbstractVector, hasintercept::Bool, weights::AbstractWeights)
2+
if hasintercept
3+
m = mean(y, weights)
4+
sum(@inbounds (y[i] - m)^2 * weights[i] for i in eachindex(y))
5+
else
6+
sum(@inbounds y[i]^2 * weights[i] for i in eachindex(y))
7+
end
8+
end

0 commit comments

Comments
 (0)