Skip to content

statspai.fixest

fixest

High-dimensional fixed effects estimation via pyfixest.

This module provides thin wrappers around pyfixest's estimation functions, converting results into StatsPAI's EconometricResults for seamless integration with outreg2 and the rest of the ecosystem.

Requires: pip install pyfixest

Examples:

>>> from statspai.fixest import feols, fepois
>>>
>>> # Two-way fixed effects with clustered SEs
>>> result = feols("wage ~ experience | firm + year",
...               data=df, vcov={"CRV1": "firm"})
>>> print(result.summary())
>>>
>>> # Poisson regression
>>> result = fepois("patents ~ rd_spending | firm", data=df)
>>>
>>> # Works with outreg2
>>> from statspai import outreg2
>>> outreg2(result, filename="table.xlsx")

feols

feols(fml: str, data: DataFrame, vcov: Optional[Union[str, Dict[str, str]]] = None, *, weights: Optional[str] = None, ssc: Optional[Any] = None, fixef_rm: str = 'none', collin_tol: float = 1e-06, lean: bool = False, cluster: Optional[str] = None, wild_reps: int = 999, wild_weight_type: str = 'rademacher', seed: Optional[int] = None, conley_lat: Optional[str] = None, conley_lon: Optional[str] = None, conley_cutoff: Optional[float] = None, **kwargs: Any) -> Union[EconometricResults, List[EconometricResults]]

Estimate OLS / IV with high-dimensional fixed effects via pyfixest.

Uses the Frisch-Waugh-Lovell theorem for fast absorption of high-dimensional fixed effects.

Parameters:

Name Type Description Default
fml str

A pyfixest-style formula. Examples:

  • "Y ~ X1 + X2" — plain OLS
  • "Y ~ X1 | firm + year" — two-way fixed effects
  • "Y ~ 1 | firm | X1 ~ Z1" — IV with fixed effects
  • "Y ~ X1 | csw0(firm, year)" — multiple estimations
required
data DataFrame

Input dataset.

required
vcov str or dict

Variance-covariance estimator (vce= is the canonical alias).

  • "iid" — classical
  • "HC1", "HC2", "HC3" — heteroskedasticity-robust
  • {"CRV1": "firm"} — cluster-robust
  • {"CRV1": "firm + year"} — two-way clustering
  • vce="CR2" / "CR3" / "jackknife" (with cluster=) — Pustejovsky-Tipton bias-reduced cluster-robust on the FE-absorbed within design; matches R clubSandwich::vcovCR(plm).
  • vce="wild" (with cluster=) — WCR wild cluster bootstrap (Cameron-Gelbach-Miller 2008); validated against Stata boottest.
  • vce="conley" (with conley_lat=/conley_lon=/conley_cutoff=) — Conley spatial HAC (Stata acreg planar-distance convention).
None
weights str

Column name for regression weights.

None
ssc optional

Small-sample correction via pyfixest.ssc().

None
fixef_rm str

How to handle singleton fixed effects: "none" (keep) or "singleton" (drop).

"none"
collin_tol float

Collinearity tolerance.

1e-6
lean bool

If True, drop large intermediate arrays to save memory.

False
cluster str

Cluster id column for the extended vce= menu; also a shorthand for one-way {"CRV1": cluster}.

None
wild_reps int

Bootstrap replications for vce="wild".

999
wild_weight_type str

Wild weight distribution ("rademacher", "webb", "mammen").

"rademacher"
seed int

RNG seed for vce="wild".

None
conley_lat str

Coordinate columns (decimal degrees) for vce="conley".

None
conley_lon str

Coordinate columns (decimal degrees) for vce="conley".

None
conley_cutoff float

Conley distance cutoff in km for vce="conley".

None
**kwargs Any

Additional arguments passed to pyfixest.feols().

{}

Returns:

Type Description
EconometricResults or list of EconometricResults

Single result for simple formulas, list for multiple estimations (e.g. csw0/sw/sw0 syntax).

Examples:

Two-way fixed effects with clustered SEs:

>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 400
>>> firm = rng.integers(0, 8, n)
>>> year = rng.integers(0, 5, n)
>>> x1 = rng.normal(size=n)
>>> y = 1.0 + 0.5 * x1 + 0.1 * firm + 0.2 * year + rng.normal(0, 0.5, n)
>>> df = pd.DataFrame({"y": y, "x1": x1, "firm": firm, "year": year})
>>> res = sp.feols("y ~ x1 | firm + year", data=df, vcov={"CRV1": "firm"})
>>> "x1" in res.params.index
True

Multiple estimation (csw0 returns a list, one fit per FE set):

>>> results = sp.feols("y ~ x1 | csw0(firm, year)", data=df)
>>> summaries = [r.summary() for r in results]

Use with outreg2 to build a regression table:

>>> r1 = sp.feols("y ~ x1", data=df)
>>> r2 = sp.feols("y ~ x1 | firm", data=df)
>>> sp.outreg2(r1, r2, filename="table.xlsx")

fepois

fepois(fml: str, data: DataFrame, vcov: Optional[Union[str, Dict[str, str]]] = None, *, weights: Optional[str] = None, ssc: Optional[Any] = None, fixef_rm: str = 'none', collin_tol: float = 1e-06, iwls_tol: float = 1e-08, iwls_maxiter: int = 25, cluster: Optional[str] = None, wild_reps: int = 9999, wild_weight_type: str = 'rademacher', seed: Optional[int] = None, conley_lat: Optional[str] = None, conley_lon: Optional[str] = None, conley_cutoff: Optional[float] = None, **kwargs: Any) -> Union[EconometricResults, List[EconometricResults]]

Estimate Poisson regression with high-dimensional fixed effects via pyfixest.

Parameters:

Name Type Description Default
fml str

pyfixest formula. E.g. "Y ~ X1 | firm".

required
data DataFrame

Input dataset.

required
vcov str or dict

Variance-covariance estimator (vce= is the canonical alias). Besides the pyfixest values ("iid", "HC1", {"CRV1": "firm"}, ...), accepts the extended menu:

  • vce="CR2" / "CR3" / "jackknife" (with cluster=) — clubSandwich glm bias-reduced cluster-robust SEs on the FE-as-dummies design; matches R clubSandwich::vcovCR(glm).
  • vce="wild" (with cluster=) — restricted score wild cluster bootstrap (Kline-Santos 2012) with Stata boottest's exact studentization; bit-exact vs boottest in the enumerated regime.
None
weights str

Column name for regression weights (not supported with the extended vce= menu).

None
ssc optional

Small-sample correction.

None
fixef_rm str

Singleton fixed effect handling.

"none"
collin_tol float

Collinearity tolerance.

1e-6
iwls_tol float

IWLS convergence tolerance.

1e-8
iwls_maxiter int

Max IWLS iterations.

25
cluster str

Cluster id column for the extended vce= menu; also a shorthand for one-way {"CRV1": cluster}.

None
wild_reps int

Replications for vce="wild". When 2**G <= wild_reps the full Rademacher grid is enumerated (deterministic).

9999
wild_weight_type str

Wild weight distribution ("rademacher" or "webb").

"rademacher"
seed int

RNG seed for sampled (non-enumerated) vce="wild" draws.

None
**kwargs Any

Additional arguments passed to pyfixest.fepois().

{}

Returns:

Type Description
EconometricResults or list of EconometricResults

Examples:

Poisson regression (PPML-style) with firm fixed effects:

>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 400
>>> firm = rng.integers(0, 8, n)
>>> x1 = rng.normal(size=n)
>>> mu = np.exp(0.3 + 0.5 * x1 + 0.1 * firm)
>>> df = pd.DataFrame({"y": rng.poisson(mu), "x1": x1, "firm": firm})
>>> res = sp.fepois("y ~ x1 | firm", data=df)
>>> "x1" in res.params.index
True

Bias-reduced cluster-robust SEs (matches R clubSandwich::vcovCR):

>>> res = sp.fepois("y ~ x1 | firm", data=df, vce="CR2",
...                 cluster="firm")

feglm

feglm(fml: str, data: DataFrame, family: str = 'gaussian', vcov: Optional[Union[str, Dict[str, str]]] = None, *, cluster: Optional[str] = None, wild_reps: int = 9999, wild_weight_type: str = 'rademacher', seed: Optional[int] = None, conley_lat: Optional[str] = None, conley_lon: Optional[str] = None, conley_cutoff: Optional[float] = None, **kwargs: Any) -> Union[EconometricResults, List[EconometricResults]]

Estimate GLM (logit, probit, Gaussian) with high-dimensional fixed effects.

Parameters:

Name Type Description Default
fml str

pyfixest formula.

required
data DataFrame

Input dataset.

required
family str

GLM family: "gaussian", "logit", "probit".

"gaussian"
vcov str or dict

Variance-covariance estimator (vce= is the canonical alias). Also accepts vce="CR2"/"CR3"/"jackknife" (with cluster=) for the clubSandwich bias-reduced cluster-robust SEs, and vce="wild" (with cluster=) for the restricted score wild cluster bootstrap (Kline-Santos 2012; bit-exact vs Stata boottest in the enumerated regime).

None
cluster str

Cluster id column for the extended vce= menu (also a shorthand for one-way {"CRV1": cluster}).

None
wild_reps int

Replications for vce="wild". When 2**G <= wild_reps the full Rademacher grid is enumerated (deterministic).

9999
wild_weight_type str

Wild weight distribution ("rademacher" or "webb").

"rademacher"
seed int

RNG seed for sampled (non-enumerated) vce="wild" draws.

None
**kwargs Any

Additional arguments passed to pyfixest.feglm().

{}

Returns:

Type Description
EconometricResults or list of EconometricResults

Examples:

Logit GLM with firm fixed effects:

>>> import statspai as sp
>>> import numpy as np
>>> import pandas as pd
>>> rng = np.random.default_rng(0)
>>> n = 500
>>> firm = rng.integers(0, 8, n)
>>> x1 = rng.normal(size=n)
>>> p = 1.0 / (1.0 + np.exp(-(0.2 + 0.8 * x1)))
>>> y = (rng.random(n) < p).astype(int)
>>> df = pd.DataFrame({"y": y, "x1": x1, "firm": firm})
>>> res = sp.feglm("y ~ x1 | firm", data=df, family="logit")
>>> "x1" in res.params.index
True

etable

etable(*results: EconometricResults, **kwargs: Any) -> Any

Display a pyfixest-style regression table for StatsPAI results.

If the results carry a _pyfixest_fit reference, uses pyfixest's native etable. Otherwise falls back to a simple pandas summary.

Parameters:

Name Type Description Default
*results EconometricResults

One or more fitted results.

()
**kwargs Any

Passed to pyfixest.etable().

{}

Returns:

Type Description
str or DataFrame

Examples:

Side-by-side comparison of nested specifications:

>>> import numpy as np
>>> import pandas as pd
>>> import statspai as sp
>>> rng = np.random.default_rng(1)
>>> n = 200
>>> df = pd.DataFrame({"x1": rng.normal(size=n), "x2": rng.normal(size=n),
...                    "firm": rng.integers(0, 10, n)})
>>> df["wage"] = 1.0 + 0.5 * df["x1"] - 0.3 * df["x2"] + rng.normal(0, 0.5, n)
>>> r1 = sp.regress("wage ~ x1", data=df)
>>> r2 = sp.regress("wage ~ x1 + x2", data=df)
>>> tab = sp.etable(r1, r2)  # DataFrame: one coefficient column per model
>>> print(tab.round(2))

With sp.feols fits (requires the fixest extra), pyfixest's native styled table is returned instead:

>>> f1 = sp.feols("wage ~ x1 | firm", data=df)
>>> f2 = sp.feols("wage ~ x1 + x2 | firm", data=df)
>>> tab = sp.etable(f1, f2)