Skip to content

方差分解

预测误差方差分解(FEVD)。

核心代码

py
    def _fit_fevd(self, decimals: int, title: str) -> None:
        """
        执行 VAR 模型的方差分解(FEVD),对应 Stata 的 irf table fevd 命令。
        基于 Cholesky 分解,计算各变量对预测误差方差的贡献比例。
        输出格式为论文可用的表格:每个响应变量一张表,行为期数,列为各冲击变量的贡献占比。
        """
        from statsmodels.tsa.api import VAR

        target_cols = self.x_vars
        if not target_cols or len(target_cols) < 2:
            raise ValueError(_('方差分解至少需要选择 2 个变量。'))

        valid_cols = [c for c in target_cols if c in self.data.columns]
        if len(valid_cols) < 2:
            raise ValueError(_('有效的数值型变量不足 2 个。'))

        df_clean = self.data[valid_cols].select_dtypes(include=[np.number]).dropna()
        if df_clean.shape[0] < 20:
            raise ValueError(_('有效数据行数不足(至少需要 20 行)。'))

        # 获取参数
        max_lags = int(self.ts_options.get('fevd_max_lags', 8))
        ic = self.ts_options.get('fevd_ic', 'aic')
        trend = self.ts_options.get('fevd_trend', 'c')
        diff_order = int(self.ts_options.get('fevd_diff_order', 0))
        periods = int(self.ts_options.get('fevd_periods', 10))
        # 响应变量:用户指定的单个响应变量,默认为全部
        response_var = self.ts_options.get('fevd_response_var', '')

        # 差分处理
        if diff_order > 0:
            for _step in range(diff_order):
                df_clean = df_clean.diff().dropna()
            if df_clean.shape[0] < 20:
                raise ValueError(_('经过 %(diff_order)s 阶差分后数据行数不足(至少需要 20 行)。') % {'diff_order': diff_order})

        # 构建并拟合 VAR 模型
        model = VAR(df_clean)

        try:
            lag_order_result = model.select_order(maxlags=max_lags)
        except Exception as e:
            raise ValueError(_('滞后阶数选择失败: %(error)s') % {'error': str(e)})

        ic_map = {'aic': 'aic', 'bic': 'bic', 'hqic': 'hqic', 'fpe': 'fpe'}
        selected_ic = ic_map.get(ic, 'aic')
        optimal_lag = getattr(lag_order_result, selected_ic, None)
        if optimal_lag is None or optimal_lag == 0:
            optimal_lag = 1

        try:
            var_result = model.fit(maxlags=optimal_lag, trend=trend)
        except Exception as e:
            raise ValueError(_('VAR 模型拟合失败: %(error)s') % {'error': str(e)})

        # 计算方差分解(多取一期,因为 statsmodels step=0 对应 Stata step=1)
        fevd = var_result.fevd(periods=periods)
        # decomp 维度: (n_vars, periods, n_vars) 即 decomp[response][step][impulse]

        var_names = list(var_result.names)
        n_vars = len(var_names)

        # 差分后的显示名称
        display_names = [f'D{diff_order}.{c}' for c in valid_cols] if diff_order > 0 else valid_cols

        # 确定要输出的响应变量
        if response_var and response_var in var_names:
            # 用户指定了单个响应变量
            response_indices = [var_names.index(response_var)]
        else:
            # 未指定则输出全部
            response_indices = list(range(n_vars))

        # 构建 HTML 输出
        diff_note = _('(%(diff_order)s阶差分)') % {'diff_order': diff_order} if diff_order > 0 else ""
        overall_title = title if title else _('方差分解(FEVD)%(diff_note)s') % {'diff_note': diff_note}
        html = f'<div class="table-editable-container">'
        html += render_table_title_editor(overall_title)

        # 模型信息摘要
        html += '<table class="academic-table" style="margin-bottom: 16px;">'
        html += '<tbody>'
        html += '<tr><td style="text-align:center;font-weight:bold;">' + _('内生变量') + '</td><td style="text-align:center;">' + ", ".join(display_names) + '</td></tr>'
        html += '<tr><td style="text-align:center;font-weight:bold;">' + _('VAR 滞后阶数') + '</td><td style="text-align:center;">' + f'{optimal_lag}{selected_ic.upper()} 准则选择)</td></tr>'
        html += _('<tr><td style="text-align:center;font-weight:bold;">样本量</td><td style="text-align:center;">%(var_result_nobs)s</td></tr>') % {'var_result_nobs': var_result.nobs}
        html += _('<tr><td style="text-align:center;font-weight:bold;">预测期数</td><td style="text-align:center;">%(periods)s</td></tr>') % {'periods': periods}
        html += _('<tr><td style="text-align:center;font-weight:bold;">分解方法</td><td style="text-align:center;">Cholesky 正交分解(变量排序影响结果)</td></tr>')
        html += '</tbody></table>'

        # 为每个响应变量生成一张方差分解表
        for resp_idx in response_indices:
            resp_name = display_names[resp_idx]
            html += self._build_fevd_table(
                fevd.decomp[resp_idx], resp_name, display_names, periods, decimals
            )

        html += '</div>'
        self.custom_html = html

Released under the AGPL-3.0 License.