Design-based estimation

The estimator layer delegates regression parsing and fitting to PyFixest and Formulaic. This supplies fixest-style fixed effects, instrumental variables, categorical expansions, interactions, stepwise model operators, and mature covariance estimators. declaredesign only adds a thin Polars-input adapter and returns the native PyFixest model, preserving its complete post-estimation API.

import numpy as np
import polars as pl
import declaredesign as dd

rng = np.random.default_rng(37)
n = 600
x = rng.normal(size=n)
z = dd.complete_ra(n, m=n // 2, rng=rng)
y = 1 + 1.5 * z + 0.8 * x + rng.normal(size=n)
data = pl.DataFrame({"Y": y, "Z": z, "X": x})

fit = dd.lm_robust("Y ~ Z + X", data, se_type="HC2")
fit.tidy()
Estimate Std. Error t value Pr(>|t|) 2.5% 97.5%
Coefficient
Intercept 0.936765 0.060521 15.478443 0.0 0.817906 1.055624
Z 1.467508 0.085443 17.175276 0.0 1.299703 1.635314
X 0.743967 0.044191 16.835382 0.0 0.657179 0.830755

Direct wrappers feols(), fepois(), feglm(), and quantreg() also accept Polars frames and return native PyFixest models.

fixed_effect_data = data.with_columns(
    pl.Series("group", np.repeat(np.arange(30), 20))
)
fixed_effect_fit = dd.feols(
    "Y ~ Z + X | group",
    fixed_effect_data,
    vcov="HC1",
)
fixed_effect_fit.tidy()
Estimate Std. Error t value Pr(>|t|) 2.5% 97.5%
Coefficient
Z 1.459250 0.088801 16.432811 0.0 1.284831 1.633668
X 0.734447 0.045965 15.978447 0.0 0.644165 0.824729

Experimental estimators

unadjusted = dd.difference_in_means("Y ~ Z", data)
lin = dd.lm_lin("Y ~ Z", data, covariates=["X"])
pl.concat([
    unadjusted.tidy().with_columns(pl.lit("Difference in means").alias("estimator")),
    lin.tidy().with_columns(pl.lit("Lin adjustment").alias("estimator")),
]).select("estimator", "estimate", "std_error", "conf_low", "conf_high")
shape: (2, 5)
estimator estimate std_error conf_low conf_high
str f64 f64 f64 f64
"Difference in means" 1.413853 0.104229 1.209154 1.618553
"Lin adjustment" 1.467512 0.085514 1.299566 1.635458

difference_in_means() uses the Neyman standard error in an unblocked two-arm experiment and supports block-weighted contrasts. lm_lin() centers baseline covariates and fully interacts them with treatment. horvitz_thompson() accepts known condition probabilities. iv_robust() uses PyFixest’s IV formula and solver. lm_robust() maps the familiar classical and HC0–HC3 names to PyFixest and supports CRV1, CRV3, Newey–West, and Driscoll–Kraay through the same backend. Its HC2 and HC3 settings reproduce estimatr’s small-sample scaling.

PyFixest supplies CRV1 and leave-one-cluster-out CRV3. The prototype still does not relabel either as estimatr’s Bell–McCaffrey CR2 adjustment; CR2 remains a distinct unimplemented covariance estimator.