直方图回答“各数值区间里有多少样本”,ECDF 回答“有多少比例的样本小于等于某个值”。检查 AI 训练表格的数值特征时,两者一起看,能区分主体集中、零值堆积和长尾,避免只用均值概括。
本题使用 NumPy 和 Matplotlib 完成可视化,它们不提供模型自动解释能力。图用于数据检查,后续是否清洗、变换或建模仍要结合字段含义。

固定输入、单位和缺失口径
输入为 0、0、1、2、2、5、10、50 和一个 NaN。Feature value 表示同一列的教学数值,实际使用时改成明确的字段名和单位;不能把不同单位的列拼成同一个分布。
这九条数据全部由人为构造,不代表某个真实模型、行业或总体分布,也没有训练或比较模型效果。缺失值单独报告,本题三个比例都使用 8 条非缺失记录作分母;没有把缺失当成零。
示例已在 Windows、Python 3.11.15、NumPy 2.4.6、Matplotlib 3.10.7 运行。Matplotlib 的 ecdf 接口从 3.8 提供,较旧版本出现属性不存在时先核对安装版本。
在空的练习目录里创建独立环境。以下为 Windows PowerShell 命令;macOS/Linux 的激活命令用 source .venv/bin/activate。输出文件只用于本次演示,重复执行会更新这些演示文件。
python -m venv .venv
.\.venv\Scripts\Activate.ps1
python -m pip install numpy==2.4.6 matplotlib==3.10.7
按明确边界画直方图和累计分布
将下面代码保存为 distribution_check.py,运行 python distribution_check.py。它会输出统计结果并生成 distribution_output/distribution.png。
from pathlib import Path
import numpy as np
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
raw = np.array([0, 0, 1, 2, 2, 5, 10, 50, np.nan], dtype=float)
missing_count = int(np.isnan(raw).sum())
values = raw[~np.isnan(raw)]
if not len(values) or not np.isfinite(values).all():
raise ValueError('No finite data or infinity found')
bins = [0, 10, 20, 30, 40, 50]
counts, edges = np.histogram(values, bins=bins)
assert counts.tolist() == [6, 1, 0, 0, 1]
assert int(counts.sum()) == len(values)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
heights, _, _ = axes[0].hist(values, bins=bins, edgecolor='white')
axes[0].set(xlabel='Feature value', ylabel='Samples', title='Explicit bin boundaries')
line = axes[1].ecdf(values)
axes[1].set(xlabel='Feature value', ylabel='Fraction <= value', title='Empirical CDF')
assert np.array_equal(heights, counts)
x, y = line.get_data()
assert np.isclose(y[np.where(x == 2)[0][-1]], 5 / 8)
out = Path('distribution_output')
out.mkdir(exist_ok=True)
fig.tight_layout()
fig.savefig(out / 'distribution.png', dpi=150)
plt.close(fig)
print('all_rows:', len(raw), 'missing:', missing_count, 'valid:', len(values))
print('bin_counts:', counts.tolist())
print('zero_fraction:', round(float(np.mean(values == 0)), 3))
print('fraction_le_2:', round(float(np.mean(values <= 2)), 3))
print('fraction_gt_10:', round(float(np.mean(values > 10)), 3))
把图上的高度和比例算出来
all_rows: 9 missing: 1 valid: 8
bin_counts: [6, 1, 0, 0, 1]
zero_fraction: 0.25
fraction_le_2: 0.625
fraction_gt_10: 0.125
直方图的五个区间是 [0,10)、[10,20)、[20,30)、[30,40)、[40,50]。除最后一箱包含最右边界外,其余都是左闭右开;五个箱的样本数依次为 6、1、0、0、1,总数仍是 8;10 落在第二箱,50 被最后一箱包含。
有效 8 条中有 2 条是零,所以零值占比为 25%;小于等于 2 的有 5 条,占 62.5%;大于 10 的只有 50 这一条,占 12.5%。打开右侧 ECDF 图,x=2 处累计高度应到 0.625,x=10 处应到 0.875,最大值 50 处到 1。
ECDF 在相同数值处会累积多条记录,检查曲线数据时应取该重复值最后一个累计高度。程序的断言会比较 x=2 的高度与手算 5/8;不是只检查图片文件是否存在。图中英文轴标签用于减少未安装中文字体导致的缺字问题。
分箱变化为什么会改变观感
把箱宽改小,主体区域的细节会更多;改大后细节会合并。单个数值特征不因为换了箱数就改变原始数据,所以比较两批数据时应使用同一组边界、单位和样本口径。ECDF 不需要选箱数,适合补充核对固定阈值的占比。
本文直方图纵轴是样本数,未启用 density=True。密度的面积归一化,不等于每根柱子就是样本占比;尤其不等宽区间不能把高度、面积与数量随意混用。若把上界截到 20,50 会落在范围外,应另外报告被排除数量,不能把漏掉的长尾当作不存在。
接入真实训练表格的下一步
先核对零值是否是合法观测、是否被源系统用作缺失替代,再保留真实的缺失数和有效分母。若有正负无穷大,本例会拒绝;需要查明除零、溢出或编码问题,再按字段规则处理。不要先把所有异常数字替成零后再看分布。
看到长尾不要直接删除最大值。先回查该样本的来源、单位、测量过程和业务意义;本图不判断它是错误还是有效少数样本。如果以后将分布检查用于确定训练变换,阈值和变换只从训练部分确定,并在独立验证数据上检查结果。
想看尾部,可以直接用对数坐标吗?
可以考虑,但零和负值不能直接放在普通对数轴上。本例包含零,先单独报告零值占比;需要对正值使用对数坐标时,明确筛出的范围和分母,不把去掉零之后的曲线仍称为全体分布。
官方资料与版本范围
本文于 2026 年 10 月 1 日读取下列官方文档;示例的实际版本见正文。
Ai菜鸟网。发布者:AI小管家,转载请注明出处:https://www.alyyhw.com/32544.html