In this tutorial, we showcase all of PyFixest formulas syntax; including syntax for fitting models with fixed effects, interactions, and multiple-estimation operators.
Setup
import numpy as npimport pyfixest as pfdata = pf.get_data()data.head()
Y
Y2
X1
X2
f1
f2
f3
group_id
Z1
Z2
weights
0
NaN
2.357103
0.0
0.457858
15.0
0.0
7.0
9.0
-0.330607
1.054826
0.661478
1
-1.458643
5.163147
NaN
-4.998406
6.0
21.0
4.0
8.0
NaN
-4.113690
0.772732
2
0.169132
0.751140
2.0
1.558480
NaN
1.0
7.0
16.0
1.207778
0.465282
0.990929
3
3.319513
-2.656368
1.0
1.560402
1.0
10.0
11.0
3.0
2.869997
0.467570
0.021123
4
0.134420
-1.866416
2.0
-3.472232
19.0
20.0
6.0
14.0
0.835819
-3.115669
0.790815
PyFixest specifies different regression models by Wilkinson Formulas via the formulaic package. Wilkinson formulas should be familiar to you if you have used R’s lm() or statsmodels formula API. Many additional ideas implemented in PyFixest have been developed in the fixest package (most notably multiple estimation syntax, the i-operator, sample splitting). By default, all formula options presented here are supported by all models available via the pf.feols(), pf.feglm(), and pf.fepois() APIs.
Basic Syntax
In the simplest case, we regress covariates X1 and X2 on Y.
All transformations that are supported via formulaic are also supported via PyFixest. To name just a few important ones, you can create categorical variables via the C() operator:
fit2 = pf.feols("Y ~ X1 + X2 + C(f1)", data=data)
You can interact variables via the * and : operators:
Last, PyFixest provides syntactic sugar to fit multiple estimations in one go. This not only economizes on lines-of-code, but also allows for performance optimizations via caching. When several models are fit on the same sample and share the same fixed-effect structure, PyFixest can reuse already-demeaned columns across models. For the LSMR demeaner backend, it can also reuse the fixed-effect preconditioner across models with the same structure. If you fit many regression models on a fixed set of fixed effects and many overlapping covariates or dependent variables, and performance is poor, we highly recommend you to try out multiple estimations.
For multiple estimations, we provide 5 custom operators: sw, csw, sw0, csw0 and mvsw. In addition, it is possible to specify multiple dependent variables.
Multiple dependent variables
Multiple depvars are expanded to multiple estimations: "Y1 + Y2 ~ X1" behaves like "sw(Y1, Y2) ~ X1".
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
mvsw(): multiverse stepwise
y ~ mvsw(x1, x2, x3) expands to all non-empty combinations plus the zero step: y ~ 1, y ~ x1, y ~ x2, y ~ x3, y ~ x1 + x2, y ~ x1 + x3, y ~ x2 + x3, y ~ x1 + x2 + x3.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Combining operators
Multiple estimation operators can be combined. For example, y ~ csw(x1, x2) + sw(z1, z2) expands to y ~ x1 + z1, y ~ x1 + z2, y ~ x1 + x2 + z1, y ~ x1 + x2 + z2.
Significance levels: * p < 0.05, ** p < 0.01, *** p < 0.001. Format of coefficient cell: Coefficient (Std. Error)
Multiple Estimation, caching and performance
The speedup from multiple-estimation syntax is most visible when models have the same fixed effects and overlapping covariates. Below, we therefore benchmark
y ~ csw(x1, x2, ..., x16) | indiv_id + firm_id + year
to demonstrate the speed gains possible with multiple estimation and caching.
In the specified multiple estimation formula, we fit sixteen models via onecsw() call. Those sixteen models fit can be sped up by caching already demeaned variables. The idea is very simple - after the first fit, y and x1 are already demeaned, so we do not have to do it again in fit 2. After fit 2, x2 is demeaned, so we do not need to do it again in fit 3, and so on.
In contrast, if you fit the sixteen models one by one, you have to demean y and x1 in all 16 specifications and set up the preconditioner for every fit.
How much faster could the caching of already-demeaned variables potentially make the estimation?
For simplicity, let’s assume that the demeaning step takes 100% of the runtime of a call to pf.feols(), and that the runtime of demeaning scales perfectly linearly in the number of columns that are demeaned.
In the 16 separate regression fits, in total we would have to demean y and x1 16 times, x2 15 times, …, and x16 once. So in total, we would have to demean 16 + 16 + 15 + 14 + ... + 1 = 152 columns across the sixteen separate fits. In contrast, in the multiple-estimation path, we only have to demean each of the 17 unique columns (y, x1, …, x16) once. If demeaning were 100% of the algorithm runtime and scaled perfectly linearly in the number of columns, the upper bound on the speedup from caching already-demeaned variables would be 152 / 17 = 8.9x.
In practice, the realized speedup is lower because each model still pays for formula and model-matrix construction, sample handling, fitting the OLS model, inference, and post-processing.
Note that in addition to caching already-demeaned variables, all model fits can also share preconditioners. As building a preconditioner can be costly (in particular the “additive” preconditioner), this can lead to large speedups when fitting many models on the same sample with the same fixed effects.
To demonstrate realistic speed gains, we compare the multiple-estimation call specified above with fitting the same sixteen formulas one by one using the fixest benchmark DGPs described in When Are Fixed Effects Estimations Difficult?.
Absolute timings depend on your machine, but the direction is the important part: multiple estimation avoids recomputing the same within-transform work for every formula.
Regressions on Multiple Samples
Via the split and fsplit argument, you can easily separate identical models on different samples.
split estimates separate models by subgroup.
fsplit does the same but also keeps the full-sample fit.