Skip to content

Commit dae81ea

Browse files
Predict with only fixed effects (#269)
* Refactor prediction logic for fixed effects model Allow only fixed effects in predict * Enhance predict.jl with fixed effects tests Add tests for fixed effects prediction in predict.jl * Fix condition check for formula schema in FixedEffectModel * Improve save argument handling for fixed effects Add error handling for save keyword argument when no fixed effects are present. * Refactor fixed effects saving condition Remove error throw for missing fixed effects when saving. * Fix variable name for DataFrame in test case * Update type check for interaction terms in formula * Add nonmissings initialization for FixedEffectModel * Fix error message in StatsAPI.predict function Corrected error message formatting in predict function. * Change test condition to use 'all' for comparison
1 parent cfe091c commit dae81ea

3 files changed

Lines changed: 25 additions & 9 deletions

File tree

src/FixedEffectModel.jl

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ end
122122

123123
# Does the formula have InteractionTerms?
124124
function has_cont_fe_interaction(x::FormulaTerm)
125-
if x.rhs isa Term # only one term
125+
if x.rhs isa AbstractTerm # only one term
126126
is_cont_fe_int(x)
127127
elseif hasfield(typeof(x.rhs), :lhs) # Is an IV term
128128
false # Is this correct?
@@ -133,19 +133,27 @@ end
133133

134134
function StatsAPI.predict(m::FixedEffectModel, data)
135135
Tables.istable(data) ||
136-
throw(ArgumentError("expected second argument to be a Table, got $(typeof(data))"))
136+
throw(ArgumentError("Expected second argument to be a Table, got $(typeof(data))"))
137137

138138
has_cont_fe_interaction(m.formula) &&
139139
throw(ArgumentError("Interaction of fixed effect and continuous variable detected in formula; this is currently not supported in `predict`"))
140140

141+
# only fixed effects
141142
cdata = StatsModels.columntable(data)
142-
cols, nonmissings = StatsModels.missing_omit(cdata, m.formula_schema.rhs)
143-
Xnew = modelmatrix(m.formula_schema, cols)
144-
if all(nonmissings)
145-
out = Xnew * m.coef
143+
nrows = length(Tables.rows(cdata))
144+
if m.formula_schema.rhs == MatrixTerm((InterceptTerm{false}(),))
145+
has_fe(m) || throw(ArgumentError("To be used with predict, a model requires regressors or fixed effects"))
146+
out = zeros(Float64, nrows)
147+
nonmissings = trues(nrows)
146148
else
147-
out = Vector{Union{Float64, Missing}}(missing, length(Tables.rows(cdata)))
148-
out[nonmissings] = Xnew * m.coef
149+
cols, nonmissings = StatsModels.missing_omit(cdata, m.formula_schema.rhs)
150+
Xnew = modelmatrix(m.formula_schema, cols)
151+
if all(nonmissings)
152+
out = Xnew * m.coef
153+
else
154+
out = Vector{Union{Float64, Missing}}(missing, nrows)
155+
out[nonmissings] = Xnew * m.coef
156+
end
149157
end
150158

151159
# Join FE estimates onto data and sum row-wise

src/fit.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,8 @@ function StatsAPI.fit(::Type{FixedEffectModel},
124124
has_iv = formula_iv != FormulaTerm(ConstantTerm(0), ConstantTerm(0))
125125
formula, formula_fes = parse_fe(formula)
126126
has_fes = formula_fes != FormulaTerm(ConstantTerm(0), ConstantTerm(0))
127-
save_fes = (save == :fe) | ((save == :all) & has_fes)
127+
# when save = :fe but there are no fixed effects in the formula, don't save fixed effects
128+
save_fes = save (:fe, :all) && has_fes
128129
has_weights = weights !== nothing
129130

130131
# Compute feM, an AbstractFixedEffectSolver

test/predict.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,13 @@ end
162162
4.0 .* (df.g4 .== "h") .* df.z
163163
m = reg(df, @formula(y ~ x + fe(g1) + fe(g2)&fe(g3) + fe(g4)&z))
164164
@test_throws ArgumentError pred = predict(m, df)
165+
166+
167+
# only fixed effects
168+
df = DataFrame(y=rand(10), id = rand(1:2, 10), t = rand(1:2, 10))
169+
out1 = predict(reg(df, @formula(y ~ fe(id) + fe(t)), save = :fe), df)
170+
out2 = predict(reg(df, @formula(y ~ 1 + fe(id) + fe(t)), save = :fe), df)
171+
@test all(out1 .≈ out2)
165172
end
166173

167174
@testset "Continuous/FE detection" begin

0 commit comments

Comments
 (0)