Panel data — fixed effects

linearmodels.PanelOLS (entity, time, or two-way fixed effects), RandomEffects, BetweenOLS, AbsorbingLS, and FamaMacBeth are each handled by a dedicated adapter. The session API is unchanged.

import numpy as np
import pandas as pd
from linearmodels.panel import PanelOLS

from pymargins import Margins

rng = np.random.default_rng(13)
n_entities, n_periods = 200, 6
entity = np.repeat(np.arange(n_entities), n_periods)
period = np.tile(np.arange(n_periods), n_entities)
df = pd.DataFrame({
    "entity": entity, "period": period,
    "x": rng.normal(0, 1, n_entities * n_periods),
    "alpha_i": np.repeat(rng.normal(0, 1, n_entities), n_periods),
}).set_index(["entity", "period"])
df["y"] = 1.0 + 0.6 * df["x"] + df["alpha_i"] + rng.normal(0, 0.5, len(df))

fit = PanelOLS(df["y"], df[["x"]], entity_effects=True).fit(
    cov_type="clustered", cluster_entity=True
)

AME of x with cluster-robust SEs

The fit’s clustered covariance is consumed automatically — the session does not need a separate vcov= argument when the model already carries one.

m = Margins.linear_scale(fit, at="overall")
print(m.dydx("x").summary())
======================================================
          Margins Result (delta, level=0.95)          
======================================================
   estimate  std err        z  P>|z|  [95% Conf. Int.]
------------------------------------------------------
x    0.6004   0.0157  38.2756  0.000    0.5696, 0.6311
======================================================

n = 1200
κ: 0.000
Delta-vs-sim disagreement: 0.244%

Predicted y at representative x

Entity and time fixed effects are absorbed during estimation; they do not appear as explicit regressors in the margin calculations. Predictions are therefore evaluated at the population-averaged profile (or the representative profile you specify with at=).

print(m.predict(atexog={"x": [-1.0, 0.0, 1.0]}).summary())
=============================================================
           Margins Result (simulation, level=0.95)           
=============================================================
        estimate  std err  statistic  P>|z|  [95% Conf. Int.]
-------------------------------------------------------------
x=-1.0   -0.5996   0.0155    -0.5996  0.000  -0.6304, -0.5691
x=0.0     0.0000   0.0000     0.0000  1.000    0.0000, 0.0000
x=1.0     0.5996   0.0155     0.5996  0.000    0.5691, 0.6304
=============================================================

n = 1200
WARNING — Fallback triggered: kappa=inf>threshold=0.3
κ: max=inf
/home/hunter/Workspace/pymargins/pymargins/margins/_session.py:704: UserWarning: Delta-method curvature κ=inf exceeds threshold (0.3); falling back to simulation.
  result_data = run_inference(

Plot: predicted outcome over representative x

import matplotlib.pyplot as plt

x_vals = np.linspace(-2, 2, 50)
res = m.predict(atexog={"x": x_vals})
df_plot = res.to_frame()

fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(df_plot["x"], df_plot["estimate"], color="navy")
ax.fill_between(
    df_plot["x"], df_plot["ci_lower"], df_plot["ci_upper"],
    alpha=0.2, color="navy"
)
ax.set(xlabel="x", ylabel="Predicted y")
[Text(0.5, 0, 'x'), Text(0, 0.5, 'Predicted y')]
../_images/4f1a3e641be6e4c71512fc5b3f0e54d4df240d1d4722997811e4c927318bb432.png

See Cluster and block bootstrap for cluster-resampling bootstrap in panels where the analytic clustered SE is suspect.