Overview
This tutorial covers the fundamentals of using leanfe for fixed effects regression.
leanfe is fast because it combines two optimizations:
YOCO Compression : For IID/HC1 SEs, rows with identical (regressors, FEs) are grouped together. With binary treatment × 500 firms × 10 years, 100K rows compress to ~10K unique combinations.
Sparse Matrices : FE dummies are 99%+ zeros. Sparse storage skips zeros entirely, making matrix operations 10-20x faster.
Result : 7-25x speedup with identical coefficients and standard errors.
Example
import numpy as np
import polars as pl
from leanfe import leanfe
np.random.seed(42 )
n_obs = 100_000
n_firms = 500
n_years = 10
# True coefficients: treatment=2.5, x1=1.5, x2=0.8
firm_fe = np.random.normal(0 , 1 , n_firms + 1 )
year_fe = np.random.normal(0 , 0.5 , n_years + 1 )
firm_id = np.random.randint(1 , n_firms + 1 , n_obs)
year = np.random.randint(1 , n_years + 1 , n_obs)
df = pl.DataFrame({
"firm_id" : firm_id,
"year" : year,
"treatment" : np.random.binomial(1 , 0.3 , n_obs).astype(float ),
"x1" : np.random.normal(0 , 1 , n_obs),
"x2" : np.random.normal(0 , 1 , n_obs),
})
df = df.with_columns(
y = (pl.lit(2.5 ) * pl.col("treatment" )
+ pl.lit(1.5 ) * pl.col("x1" )
+ pl.lit(0.8 ) * pl.col("x2" )
+ pl.lit(firm_fe[firm_id])
+ pl.lit(year_fe[year])
+ pl.lit(np.random.normal(0 , 1 , n_obs)))
)
print (f"Data: { df. shape[0 ]:,} observations" )
Data: 100,000 observations
result = leanfe(
formula= "y ~ treatment + x1 + x2 | firm_id + year" ,
data= df,
vcov= "iid"
)
print ("Coefficients (true: treatment=2.5, x1=1.5, x2=0.8):" )
for var, coef in result['coefficients' ].items():
print (f" { var} : { coef:.4f} (SE: { result['std_errors' ][var]:.4f} )" )
Coefficients (true: treatment=2.5, x1=1.5, x2=0.8):
treatment: 2.4928 (SE: 0.0069)
x1: 1.4968 (SE: 0.0032)
x2: 0.7953 (SE: 0.0032)
Standard Errors
# IID
result_iid = leanfe(data= df, formula= "y ~ treatment + x1 + x2 | firm_id + year" , vcov= "iid" )
# HC1 (robust)
result_hc1 = leanfe(data= df, formula= "y ~ treatment + x1 + x2 | firm_id + year" , vcov= "HC1" )
# Clustered
result_cluster = leanfe(
data= df, formula= "y ~ treatment + x1 + x2 | firm_id + year" ,
vcov= "cluster" , cluster_cols= ["firm_id" ]
)
print ("SE for treatment:" )
print (f" IID: { result_iid['std_errors' ]['treatment' ]:.4f} " )
print (f" HC1: { result_hc1['std_errors' ]['treatment' ]:.4f} " )
print (f" Clustered: { result_cluster['std_errors' ]['treatment' ]:.4f} " )
SE for treatment:
IID: 0.0069
HC1: 0.0069
Clustered: 0.0074
Backends
Polars (default)
Fastest
Higher
Data fits in RAM
DuckDB
Slower
Minimal
Data larger than RAM
import time
start = time.time()
result_polars = leanfe(data= df, formula= "y ~ treatment | firm_id + year" , backend= "polars" )
polars_time = time.time() - start
start = time.time()
result_duckdb = leanfe(data= df, formula= "y ~ treatment | firm_id + year" , backend= "duckdb" )
duckdb_time = time.time() - start
print (f"Polars: { polars_time:.2f} s" )
print (f"DuckDB: { duckdb_time:.2f} s" )
Polars: 0.06s
DuckDB: 0.16s