Skip to content

正态性检验

基于偏度与峰度检验变量是否服从正态分布。

核心代码

py
    def _fit_normality(self, df: pd.DataFrame, decimals: int, title: str) -> None:
        """
        执行正态性检验,口径与 Stata sktest 保持一致。

        参数:
            df: 原始数据框
            decimals: 小数位数
            title: 表格标题
        """
        valid_cols = [col for col in self.x_vars if col in df.columns]
        if not valid_cols:
            raise ValueError(_('没有找到可用于正态性检验的变量。'))

        result_rows = []
        skipped_notes = []

        for col in valid_cols:
            series = pd.to_numeric(df[col], errors='coerce').dropna()
            sample_size = int(series.shape[0])

            if sample_size == 0:
                skipped_notes.append(_('%(col)s 无有效数值样本') % {'col': col})
                continue

            if sample_size < 8:
                skipped_notes.append(_('%(col)s 有效样本量不足 8 个') % {'col': col})
                continue

            if series.nunique() < 2:
                skipped_notes.append(_('%(col)s 没有足够变异') % {'col': col})
                continue

            with warnings.catch_warnings():
                warnings.simplefilter("ignore", category=RuntimeWarning)
                warnings.simplefilter("ignore", category=UserWarning)
                skew_result = skewtest(series)
                kurt_result = kurtosistest(series)

            if not (
                np.isfinite(skew_result.statistic)
                and np.isfinite(skew_result.pvalue)
                and np.isfinite(kurt_result.statistic)
                and np.isfinite(kurt_result.pvalue)
            ):
                skipped_notes.append(_('%(col)s 无法计算稳定的偏度/峰度统计量') % {'col': col})
                continue

            adj_chi2, prob_chi2 = self._stata_adjusted_normality_chi2(
                skew_result.statistic,
                kurt_result.statistic,
                sample_size,
            )
            if not (np.isfinite(adj_chi2) and np.isfinite(prob_chi2)):
                skipped_notes.append(_('%(col)s 联合检验统计量计算失败') % {'col': col})
                continue

            result_rows.append({
                'Variable': col,
                'N': sample_size,
                'Pr(skewness)': float(skew_result.pvalue),
                'Pr(kurtosis)': float(kurt_result.pvalue),
                'Adj chi2(2)': float(adj_chi2),
                'Prob>chi2': float(prob_chi2),
                '结论': _('拒绝正态分布') if prob_chi2 < 0.05 else _('不能拒绝正态分布'),
            })

        if not result_rows:
            raise ValueError(_('没有足够的数据完成正态性检验。'))

        result_df = pd.DataFrame(result_rows).set_index('Variable')
        title = title if title else _('正态性检验')
        note = (
            _('H0: 数据服从正态分布;采用与 Stata sktest 一致的偏度/峰度联合检验口径;')
            + _('当 Prob>chi2 < 0.05 时拒绝正态性。')
        )
        if skipped_notes:
            note += _(' 未纳入变量:') + ";".join(skipped_notes) + "。"

        self.custom_html = self._generate_html_table(
            result_df,
            title,
            index_name="Variable",
            decimals=decimals,
            bottom_note=note,
        )

Released under the AGPL-3.0 License.