Skip to content

豪斯曼检验

固定效应与随机效应的模型设定检验:H=(β^FEβ^RE)[V^FEV^RE]1(β^FEβ^RE)χ2(k),原假设为"个体效应与解释变量不相关(随机效应合适)"。协方差矩阵差非正定时可启用 sigmamore 选项(对齐 Stata hausman, sigmamore)。

核心代码

py
    @staticmethod
    def hausman_test(
        df: pd.DataFrame,
        y_var: str,
        x_vars: List[str],
        entity_col: str,
        time_col: str,
        decimals: int = 4,
        sigmamore: bool = False
    ) -> Dict[str, Any]:
        """
        Hausman检验:固定效应模型 vs 随机效应模型

        参数:
            df: 数据框
            y_var: 因变量名
            x_vars: 自变量列表
            entity_col: 个体标识列
            time_col: 时间标识列
            decimals: 小数位数
            sigmamore: 是否使用sigmamore选项(基于随机效应统一方差估计)

        返回:
            包含检验结果的字典
        """
        # 准备数据
        cols = [y_var] + x_vars + [entity_col, time_col]
        df_clean = df[cols].dropna()
        df_clean, entity_alias, time_alias = ModelTests._prepare_panel_test_df(
            df_clean,
            entity_col,
            time_col
        )
        df_clean = drop_singletons_func(df_clean, entity_alias)

        df_panel = df_clean.set_index([entity_alias, time_alias])
        y = df_panel[y_var]
        x = df_panel[x_vars]

        # 1. 估计固定效应模型(添加常数项)
        # 注意:Stata的hausman检验使用constant选项时会包含常数项
        x_with_const_fe = add_constant(x)
        fe_model = PanelOLS(y, x_with_const_fe, entity_effects=True, drop_absorbed=True)
        fe_result = fe_model.fit(cov_type='unadjusted')

        # 2. 估计随机效应模型(添加常数项)
        x_with_const = add_constant(x)
        re_model = RandomEffects(y, x_with_const)
        re_result = re_model.fit(cov_type='unadjusted')

        # 提取系数
        beta_fe = fe_result.params
        beta_re = re_result.params

        # 确保两个模型的变量顺序一致
        common_vars = [v for v in beta_fe.index if v in beta_re.index]
        beta_fe = beta_fe[common_vars]
        beta_re = beta_re[common_vars]

        # 3. 应用sigmamore选项(如果启用)
        if sigmamore:
            # 根据Stata官方hausman.ado源代码(第157-169行):
            # if "`sigmamore'" != "" {
            #     matrix `V1' = ((`s2_2'/`s2_1')^2) * `V1'
            # }
            # 其中:
            # - V1 是固定效应模型的协方差矩阵
            # - s2_1 是FE模型的sigma_e
            # - s2_2 是RE模型的rmse

            # 计算FE模型的sigma_e
            s2_fe = np.sqrt(fe_result.resid_ss / fe_result.df_resid)

            # 计算RE模型的rmse(使用Pooled OLS,因为sigma_u可能为0)
            pooled_model = PooledOLS(y, x_with_const)
            pooled_result = pooled_model.fit(cov_type='unadjusted')
            s2_re = np.sqrt(pooled_result.resid_ss / pooled_result.df_resid)

            # 计算缩放因子
            scaling_factor = (s2_re / s2_fe) ** 2

            # 应用缩放到FE的协方差矩阵
            cov_fe = scaling_factor * fe_result.cov.loc[common_vars, common_vars]

            # RE使用pooled结果的协方差矩阵
            cov_re = pooled_result.cov.loc[common_vars, common_vars]
        else:
            # 不使用sigmamore,直接使用原始协方差矩阵
            cov_fe = fe_result.cov.loc[common_vars, common_vars]
            cov_re = re_result.cov.loc[common_vars, common_vars]

        # 3. 计算Hausman统计量
        # H = (beta_fe - beta_re)' * [Var(beta_fe) - Var(beta_re)]^(-1) * (beta_fe - beta_re)
        beta_diff = beta_fe - beta_re
        cov_diff = cov_fe - cov_re

        try:
            # 计算协方差矩阵差的逆(使用Moore-Penrose伪逆处理奇异矩阵)
            cov_diff_inv = np.linalg.pinv(cov_diff)

            # 计算Hausman统计量
            h_stat = float(beta_diff.T @ cov_diff_inv @ beta_diff)

            # 如果统计量为负数,说明协方差矩阵差不是正定的
            if h_stat < 0:
                # 计算标准误差异的标准误(用于显示)
                std_err_diff = {}
                for var in beta_diff.index:
                    var_diff = cov_diff.loc[var, var]
                    if var_diff > 0:
                        std_err_diff[var] = round(np.sqrt(var_diff), decimals)
                    else:
                        std_err_diff[var] = 'negative'

                return {
                    'test_name': 'Hausman Test (Fixed Effects vs Random Effects)',
                    'warning': _('协方差矩阵差不是正定的(V_b-V_B is not positive definite)'),
                    'note': _('Stata也会显示此警告。这通常表明固定效应和随机效应模型的估计非常接近。'),
                    'chi2_statistic': round(abs(h_stat), decimals),
                    'df': len(beta_diff),
                    'p_value': 'N/A',
                    'significance': '',
                    'conclusion': _('由于协方差矩阵差不是正定的,无法进行标准的Hausman检验。建议:1) 两种模型估计结果非常接近,可能都适用;2) 检查数据质量和模型设定;3) 考虑使用其他模型选择方法。'),
                    'fe_coeffs': {k: round(v, decimals) for k, v in beta_fe.items()},
                    're_coeffs': {k: round(v, decimals) for k, v in beta_re.items()},
                    'coeff_diff': {k: round(beta_diff[k], decimals) for k in beta_diff.index},
                    'fe_std_err': {k: round(np.sqrt(cov_fe.loc[k, k]), decimals) for k in beta_fe.index},
                    're_std_err': {k: round(np.sqrt(cov_re.loc[k, k]), decimals) for k in beta_re.index},
                    'std_err_diff': std_err_diff
                }

            # 自由度等于参数个数
            df = len(beta_fe)

            # 计算p值(卡方分布)
            p_value = 1 - chi2.cdf(h_stat, df)

            # 判断结果
            if p_value < 0.01:
                conclusion = _('强烈拒绝原假设,应使用固定效应模型')
                stars = "***"
            elif p_value < 0.05:
                conclusion = _('拒绝原假设,应使用固定效应模型')
                stars = "**"
            elif p_value < 0.1:
                conclusion = _('弱拒绝原假设,倾向使用固定效应模型')
                stars = "*"
            else:
                conclusion = _('不能拒绝原假设,可使用随机效应模型')
                stars = ""

            # 计算标准误差异的标准误(用于显示)
            std_err_diff = {}
            for var in beta_diff.index:
                var_diff = cov_diff.loc[var, var]
                if var_diff > 0:
                    std_err_diff[var] = round(np.sqrt(var_diff), decimals)
                else:
                    std_err_diff[var] = 'negative'

            return {
                'test_name': 'Hausman Test (Fixed Effects vs Random Effects)',
                'null_hypothesis': _('随机效应模型合适(个体效应与解释变量不相关)'),
                'alternative_hypothesis': _('固定效应模型合适(个体效应与解释变量相关)'),
                'chi2_statistic': round(h_stat, decimals),
                'df': df,
                'p_value': round(p_value, decimals),
                'significance': stars,
                'conclusion': conclusion,
                'fe_coeffs': {k: round(v, decimals) for k, v in beta_fe.items()},
                're_coeffs': {k: round(v, decimals) for k, v in beta_re.items()},
                'coeff_diff': {k: round(beta_diff[k], decimals) for k in beta_diff.index},
                'fe_std_err': {k: round(np.sqrt(cov_fe.loc[k, k]), decimals) for k in beta_fe.index},
                're_std_err': {k: round(np.sqrt(cov_re.loc[k, k]), decimals) for k in beta_re.index},
                'std_err_diff': std_err_diff
            }

        except Exception as e:
            # 其他错误
            return {
                'test_name': 'Hausman Test (Fixed Effects vs Random Effects)',
                'error': _('计算Hausman统计量时出错: %(error)s') % {'error': str(e)},
                'suggestion': _('可能是因为模型设定问题或数据共线性,建议检查数据质量'),
                'fe_coeffs': {k: round(v, decimals) for k, v in beta_fe.items()},
                're_coeffs': {k: round(v, decimals) for k, v in beta_re.items()}
            }

极小样本复现

与固定效应模型同一份样本 fe_panel.csv(个体效应与 x1 相关,预期拒绝原假设)。

bash
python examples/run_hausman.py

期望输出:

============================================================
豪斯曼检验(固定效应 vs 随机效应)
============================================================
卡方统计量 chi2(3) = 104.2409
p 值 = 0.0***
结论: 强烈拒绝原假设,应使用固定效应模型
------------------------------------------------------------
变量              FE 系数        RE 系数       系数差
const         -0.6587      -1.0145       0.3557
x1             0.6309       0.8773      -0.2464
x2             -0.401      -0.3057      -0.0953

Released under the AGPL-3.0 License.