<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Abhimanyu Sunil</title>
    <description>The latest articles on DEV Community by Abhimanyu Sunil (@abhimanyu_sunil_ecb3a0dca).</description>
    <link>https://dev.to/abhimanyu_sunil_ecb3a0dca</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4199569%2Fd05d202c-563d-4a9d-98f7-2b7e6f551fc8.jpg</url>
      <title>DEV Community: Abhimanyu Sunil</title>
      <link>https://dev.to/abhimanyu_sunil_ecb3a0dca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/abhimanyu_sunil_ecb3a0dca"/>
    <language>en</language>
    <item>
      <title>Fixed vs random effects in Python, and what a Hausman test result actually tells you</title>
      <dc:creator>Abhimanyu Sunil</dc:creator>
      <pubDate>Thu, 22 Oct 2026 04:53:55 +0000</pubDate>
      <link>https://dev.to/abhimanyu_sunil_ecb3a0dca/fixed-vs-random-effects-in-python-and-what-a-hausman-test-result-actually-tells-you-2343</link>
      <guid>https://dev.to/abhimanyu_sunil_ecb3a0dca/fixed-vs-random-effects-in-python-and-what-a-hausman-test-result-actually-tells-you-2343</guid>
      <description>&lt;p&gt;&lt;a href="https://dev.tourl"&gt;&lt;/a&gt;Most panel data tutorials stop at "run the Hausman test, and if p &amp;lt; 0.05 use fixed effects." That rule is fine as a starting point, but it hides a few things that matter once you look at real output. So here is a small worked example you can run yourself, with the numbers I got.&lt;/p&gt;

&lt;p&gt;The setup&lt;/p&gt;

&lt;p&gt;I used the wage_panel dataset that ships with linearmodels: 545 individuals observed over 8 years, with log wages as the outcome. The regressors are experience squared, marital status and union membership.&lt;/p&gt;

&lt;p&gt;bash&lt;br&gt;
pip install linearmodels&lt;br&gt;
python&lt;br&gt;
import numpy as np&lt;br&gt;
from scipy import stats&lt;br&gt;
from linearmodels.panel import PanelOLS, RandomEffects&lt;br&gt;
from linearmodels.datasets import wage_panel&lt;/p&gt;

&lt;p&gt;df = wage_panel.load().set_index(["nr", "year"])&lt;br&gt;
y = df["lwage"]&lt;br&gt;
X = df[["expersq", "married", "union"]]&lt;/p&gt;

&lt;h1&gt;
  
  
  the RE model needs an intercept, the FE (within) model does not
&lt;/h1&gt;

&lt;p&gt;X_re = X.copy()&lt;br&gt;
X_re.insert(0, "const", 1.0)&lt;/p&gt;

&lt;p&gt;fe = PanelOLS(y, X, entity_effects=True).fit()&lt;br&gt;
re = RandomEffects(y, X_re).fit()&lt;br&gt;
The two sets of estimates&lt;br&gt;
variable    fixed effects   random effects&lt;br&gt;
expersq 0.0037  0.0032&lt;br&gt;
married 0.1073  0.1316&lt;br&gt;
union   0.0828  0.1035&lt;/p&gt;

&lt;p&gt;The random effects numbers for married and union are noticeably larger. That gap is what the Hausman test formalises. If the unobserved person-level effects (ability, motivation, whatever drives wages and also correlates with marrying or joining a union) are correlated with the regressors, the RE estimator picks that correlation up as if it were an effect of the regressor, and the coefficients drift upward.&lt;/p&gt;

&lt;p&gt;Computing the Hausman statistic by hand&lt;/p&gt;

&lt;p&gt;linearmodels doesn't ship a one-line Hausman test, but the statistic is short:&lt;/p&gt;

&lt;p&gt;python&lt;br&gt;
cols = fe.params.index&lt;br&gt;
b = fe.params - re.params[cols]&lt;br&gt;
V = fe.cov - re.cov.loc[cols, cols]&lt;/p&gt;

&lt;p&gt;stat = float(b @ np.linalg.inv(V) @ b)&lt;br&gt;
p = 1 - stats.chi2.cdf(stat, len(cols))&lt;br&gt;
print(f"Hausman chi2 = {stat:.3f}, df = {len(cols)}, p = {p:.4f}")&lt;/p&gt;

&lt;p&gt;Output on this data:&lt;/p&gt;

&lt;p&gt;Hausman chi2 = 112.118, df = 3, p = 0.0000&lt;/p&gt;

&lt;p&gt;A clear rejection. The RE assumption (effects uncorrelated with the regressors) doesn't hold here, so the fixed effects estimates are the ones to report.&lt;/p&gt;

&lt;p&gt;Three things the textbook rule skips&lt;/p&gt;

&lt;p&gt;A rejection is evidence against RE, not a proof that FE is right. FE only removes confounding that is constant over the sample window. Anything that changes over time and correlates with both wages and marriage is still in your error term.&lt;/p&gt;

&lt;p&gt;You pay for FE with the time-invariant variables. The within estimator can't estimate the effect of anything that doesn't change for a person (education at entry, say, or a fixed demographic). I left those out above for exactly that reason. If they are the point of your study, FE alone won't answer your question, and a Mundlak or correlated random effects specification is worth a look.&lt;/p&gt;

&lt;p&gt;The classic Hausman test assumes homoskedastic errors. With clustered standard errors, which is the usual choice in applied work, the difference of covariance matrices isn't the right object anymore. A regression-based version (the Mundlak approach: add the group means of each regressor to the RE model and test them jointly) works with robust errors.&lt;/p&gt;

&lt;p&gt;If you'd rather not write code&lt;/p&gt;

&lt;p&gt;For a quick check, or to hand something to a colleague who doesn't use Python, there is a &lt;a href="https://freestatisticaltools.com/panel-regression-fixed-random-effects/" rel="noopener noreferrer"&gt;panel data regression online tool&lt;/a&gt; that fits fixed and random effects models in the browser. Full disclosure: I'm connected to that site. For anything you plan to publish, I'd still keep the script above so the analysis is reproducible.&lt;/p&gt;

&lt;p&gt;Curious whether people go Mundlak, or just default to FE and move on.RE model and test them jointly) works with robust errors.&lt;/p&gt;

&lt;p&gt;If you'd rather not write code&lt;/p&gt;

&lt;p&gt;For a quick check, or to hand something to a colleague who doesn't use Python, there is a panel data regression online tool that fits fixed and random effects models in the browser. Full disclosure: I'm connected to that site. For anything you plan to publish, I'd still keep the script above so the analysis is reproducible.&lt;/p&gt;

&lt;p&gt;What do you use when the Hausman test and clustered errors disagree? Curious whether people go Mundlak, or just default to FE and&lt;/p&gt;

</description>
      <category>python</category>
      <category>statistics</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
