Parked on feature/fixest-fixed-effects while the architecture settles. Reinstate with git revert 96fed9e.
Why it was parked
A bar in the formula meant two different things:
getRHSNames() read y ~ a + b | c as an {lme4} random slope and rewrote it as (b | c).
parse_qap_formula() read a bar without parentheses as a {fixest} fixed effect.
Because the front end always adds the parentheses, fixed effects through the formula were unreachable, and {fixest} was only ever entered through the fixest_se_cluster control. The path itself worked and was tested; the interface around it did not.
With {fixest} parked, a bar means an {lme4} random-effect term and nothing else, and parse_qap_formula() drops from three branches to one.
What it needs before coming back
The grammar question
Three conventions exist, and they conflict:
| Convention |
Used by |
Fixed effects |
Random effects |
| Bar means random |
lme4, glmmTMB, brms |
not supported |
y ~ x + (1 | g) |
| Bar means fixed |
fixest, felm (lfe), plm |
y ~ x | g |
not supported |
| Both, by position |
fixest with {lme4}-style terms |
after the first bar |
in parentheses |
{fixest} itself takes y ~ x | fe1 + fe2 | iv — bars separate parts of the formula, and the parts are positional. {lme4} takes bars only inside parentheses. The two can coexist without ambiguity, since a bar inside parentheses is always random and a bare bar is always a part separator, which is roughly what the engine already assumed. What broke it was the front end normalising every bar into parentheses before the parser saw it.
Options:
- Follow
{lme4} only, and put fixed effects in a control. y ~ x + (1 | sv) for random, fixed = "sv" for fixed. Unambiguous, verbose, and the two kinds of effect stop looking alike.
- Follow
{fixest}'s positional parts. y ~ x | sv for fixed, y ~ x + (1 | sv) for random. Matches the two most likely reference packages, and needs getRHSNames() to stop rewriting bare bars.
- Neither: name the effect.
y ~ x + fixed(sv) and y ~ x + random(sv), alongside the existing ego(), alter(), same() terms. Fits the house convention that a formula term is a function call naming what it constructs, and reads without knowing either package's habits.
Option 3 is the one that fits this package's own grammar, but it is the least familiar. Worth deciding before the branch comes back.
Parked on
feature/fixest-fixed-effectswhile the architecture settles. Reinstate withgit revert 96fed9e.Why it was parked
A bar in the formula meant two different things:
getRHSNames()ready ~ a + b | cas an{lme4}random slope and rewrote it as(b | c).parse_qap_formula()read a bar without parentheses as a{fixest}fixed effect.Because the front end always adds the parentheses, fixed effects through the formula were unreachable, and
{fixest}was only ever entered through thefixest_se_clustercontrol. The path itself worked and was tested; the interface around it did not.With
{fixest}parked, a bar means an{lme4}random-effect term and nothing else, andparse_qap_formula()drops from three branches to one.What it needs before coming back
fixest::feglm()branch against whatever grammar is chosen.feglm()reports an intercept where no fixed effect is absorbed, so the placeholder must only be added when one is.The grammar question
Three conventions exist, and they conflict:
lme4,glmmTMB,brmsy ~ x + (1 | g)fixest,felm(lfe),plmy ~ x | gfixestwith{lme4}-style terms{fixest}itself takesy ~ x | fe1 + fe2 | iv— bars separate parts of the formula, and the parts are positional.{lme4}takes bars only inside parentheses. The two can coexist without ambiguity, since a bar inside parentheses is always random and a bare bar is always a part separator, which is roughly what the engine already assumed. What broke it was the front end normalising every bar into parentheses before the parser saw it.Options:
{lme4}only, and put fixed effects in a control.y ~ x + (1 | sv)for random,fixed = "sv"for fixed. Unambiguous, verbose, and the two kinds of effect stop looking alike.{fixest}'s positional parts.y ~ x | svfor fixed,y ~ x + (1 | sv)for random. Matches the two most likely reference packages, and needsgetRHSNames()to stop rewriting bare bars.y ~ x + fixed(sv)andy ~ x + random(sv), alongside the existingego(),alter(),same()terms. Fits the house convention that a formula term is a function call naming what it constructs, and reads without knowing either package's habits.Option 3 is the one that fits this package's own grammar, but it is the least familiar. Worth deciding before the branch comes back.