第 15 章 数据可视化与探索
文章目录
- 第 15 章 数据可视化与探索
- 🌈 关于《AI 零基础 36 讲》
- 先问一个能用现有字段回答的问题
- 把运行环境和输入先核对一遍
- 问题一,已知时长落在哪些区间
- 问题二,不同标签完成了多少条
- 问题三,时长与完成状态怎样对应
- 没有相关系数时别补成零
- 从特别大的数回到原始记录
- 把图和分析一起保存
- 留一道独立练习
- 资料来源
第 14 章把八行学习记录清洗成了五行。表格已经可以读,问题还没问完。已知时长通常是多少?两个标签的完成情况怎样?花了更多时间的记录,在这份表里是不是更常显示完成?
这一章用同一份数据回答这三个问题,分别生成直方图、柱状图和散点图。你会得到一份已经执行的分析 Notebook、三张可发布的原创图表,以及一份保留统计范围的分析报告。前置条件是读完第 14 章,能读取 CSV、选择行列并处理缺失值。
下面始终使用上一章清洗结果的快照,不修改原表。
🌈 关于《AI 零基础 36 讲》
🧠零基础也能开始学 AI
《AI 零基础 36 讲》是一套面向初学者的 AI 入门教程。课程从认识和使用 AI 开始,逐步学习
Python、数据处理、机器学习与深度学习,再进入大模型应用开发,最后完成个人知识库助手的综合实践。📖本篇是正式讲解文章
通过具体例子、原创图示和操作结果讲清概念。涉及编程的章节提供可运行代码,方便你边读边试。
🎯读完以后,动手检验
建议按专栏顺序学习,并完成对应的课后训练,检查自己能否独立运用本章知识。
博客专栏:《AI 零基础 36 讲》
先问一个能用现有字段回答的问题
本章表格有记录 ID、主题、完成状态、分钟数与标签。没有测验分数,也没有日期。用它回答“学得久是不是考得好”,就会缺少结果字段;把 source_row 当日期,又会把文件行号误读成学习时间。
我们先把问题收在已有字段的范围内。时长分布看 minutes,不同标签的完成情况看 tag 与 done,时长与完成状态的关系则要两列配对。先确认问题需要哪些字段,再决定画什么图,这样图形才有可核对的内容。
| 当前问题 | 使用字段 | 图形 | 纳入的记录 |
|---|---|---|---|
| 已知时长落在哪些区间 | minutes | 直方图 | 时长已知的三条 |
| 不同标签完成了多少条 | tag、done | 柱状图 | 全部五条 |
| 时长与完成状态怎样对应 | minutes、done | 散点图 | 两项都已知的三条 |
探索性分析就是先查看现有数据的数量、分布和关系,再找值得继续检查的问题。它可以帮助发现漏填、单位不一致或某条特别大的数值。探索结果需要带着数据范围阅读,不能把看见的形状直接扩展成普遍结论。
把运行环境和输入先核对一遍
本章新增 Matplotlib 3.10.7,继续使用 NumPy 2.3.5 与 Pandas 3.0.1。在本章目录运行下面的命令。
python-m venv.venv.venv\Scripts\python.exe-m pip install-r requirements.txt.venv\Scripts\python.exe examples/explore_records.py第三条命令会生成全部图表与统计文件。第一次可以先运行,让自己知道完整结果长什么样,再回到下面逐步理解。macOS 与 Linux 使用.venv/bin/python。
先读数据,核对总行数与缺失数量。
importpandasaspd frame=pd.read_csv("data/learning_records.csv",dtype={"done":"boolean","minutes":"Float64"})print(len(frame))print(frame["minutes"].notna().sum())print(frame["minutes"].isna().sum())结果是 5、3、2。notna 表示当前值不是缺失,isna 表示当前值缺失。两个数量相加等于五条总记录,这个检查很小,却能解释后面为什么某些图只有三个点。
两条未知时长仍然保留在表里。L03 的状态是完成,L04 的状态是未完成。它们可以参与完成数量统计,不能参与时长均值或时长散点。一个字段缺失,不会自动让同一行的其他字段失效。
问题一,已知时长落在哪些区间
直方图把一个数值字段分成连续区间,再数每个区间有多少项。它适合观察时长的分布。柱子的位置对应数值范围,宽度也带着区间含义。
我们按十分钟分一组,边界为 0、10、20、30、40、50。先算数量,再画图。
importnumpyasnpimportpandasaspd frame=pd.read_csv("data/learning_records.csv",dtype={"minutes":"Float64"})values=frame["minutes"].dropna().to_numpy(dtype=float)counts,edges=np.histogram(values,bins=[0,10,20,30,40,50])print(values.tolist())print(counts.tolist())print(values.mean())print(np.median(values))已知值为 20、15、40,区间计数是[0, 1, 1, 0, 1]。平均值是 25,中位数是 20。中位数就是把值排序后取中间位置,本例排序为 15、20、40,中间那个值是 20。
直方图区间一般左端包含、右端不包含,最后一个区间包含右端。15 落在 10 到 20 之前,20 落在 20 到 30 之前,40 落在 40 到 50 这一组。改变区间宽度,柱子的形状也会改变,比较两张分布图时要先看边界是否相同。
图的纵轴是记录条数,没有画成百分比,也没有使用密度。密度会按样本数量和区间宽度调整柱高,它与每个区间有几条记录不同。入门阶段先看计数图,能直接把柱高和原表对起来。
制作这张图的核心动作如下。
importpandasaspdfromplot_styleimportconfigure_style,new_figure,save_figure configure_style()frame=pd.read_csv("data/learning_records.csv")fig,ax=new_figure("已知学习时长的分布","学习时长(分钟)","记录数量(条)","虚构教学数据;只使用三条已知时长记录。")ax.hist(frame["minutes"].dropna(),bins=[0,10,20,30,40,50])save_figure(fig,"results","practice-histogram")print("已保存 practice-histogram.png 与 SVG")这段可以在本章目录运行,辅助模块在 examples 中。若单独另建脚本,把它放在 examples 目录,或一并复制 plot_style.py。hist 执行分组计数,标题和轴标签让读者知道数字单位,save_figure 把图存下来。
现在只有三个已知数,图能准确展示它们落在哪些区间。不能凭三根柱子宣布学习时长符合某种总体分布。平均值高于中位数,也只说明当前三项的数值结构,40 把平均值向上拉了一些。
问题二,不同标签完成了多少条
柱状图适合比较类别。python 和 ai 是两个标签,先按标签分组,再统计各组记录数和完成数。
importpandasaspd frame=pd.read_csv("data/learning_records.csv",dtype={"done":"boolean","minutes":"Float64"})groups=frame.groupby("tag").agg(records=("record_id","size"),completed=("done","sum"))groups["completion_rate"]=groups["completed"]/groups["records"]print(groups.to_dict("index"))groupby 把标签相同的记录放在一组,agg 指定每组怎样汇总。size 数全部行,布尔列的 sum 把 True 计为一、False 计为零,所以得到完成条数。
python 组有三条、完成两条,比例是 2/3,约 66.7%。ai 组有两条、完成一条,比例是 1/2,也就是 50%。比例的分母必须是本组全部记录,不能因为要画时长图而提前删掉未知时长行。
图中每个类别并排放两根柱子,一根表示全部记录,一根表示完成记录。纵轴从零开始,数量差异可以直接看。完成比例写在完成柱附近,它和柱高的“条数”使用不同单位,图里已经明确标注。
ai 与 python 的完成比例确实不同,但数据分别只有两条和三条。每组多加入一条记录,比例就可能明显变化。标签还对应不同学习主题,难度没有控制,所以这里仅描述这五条记录,不据此判断哪类知识更容易学。
问题三,时长与完成状态怎样对应
散点图把两个字段放在横纵轴上,每条有效记录画一个点。横轴使用分钟数,完成状态先编码为 0 与 1,0 表示未完成,1 表示完成。这个编码只表示两个状态,不代表完成质量高低。
importpandasaspd frame=pd.read_csv("data/learning_records.csv",dtype={"done":"boolean","minutes":"Float64"})paired=frame.dropna(subset=["minutes","done"])print(paired[["record_id","minutes","done"]].to_dict("records"))points=pd.DataFrame({"minutes":paired["minutes"].astype(float),"done_code":paired["done"].astype(int)})print(points.corr().loc["minutes","done_code"])三个点分别对应 L01 的 20 分钟且完成、L02 的 15 分钟且未完成、L05 的 40 分钟且完成。相关系数约为 0.655。
相关系数在这里衡量两个数值字段的线性关联程度。能定义时,它介于 -1 与 1 之间,正号表示在当前样本中较大的分钟数倾向于对应较大的完成编码,负号表示相反方向。接近零表示线性关联较弱,复杂关系仍可能存在。
纵轴只有两个取值,所以散点只能排在两条水平位置上,这是状态编码本身造成的。我们没有给状态人为加小数来美化图,也没有把三个点连成趋势线。点旁边保留记录 ID,可以回到原表逐条检查。
这个正相关值不能说明“多学五分钟就会完成”。记录涉及不同主题,还可能有任务难度、原有基础等差异,当前数据没有提供这些信息。样本又只有三条,其中两条完成;补上未知时长后,数值关系也可能变化。
没有相关系数时别补成零
两列中任意一列没有变化,相关系数就不能按通常方式定义。比如三个完成状态全部为 True,完成编码没有差异;时长都一样也会出现相同问题。只有一对记录时,同样不足以计算通常的 Pearson 相关系数。
本章统计函数会检查有效点数和各列不同值数量,无法计算时返回 None,并在 JSON 中保存为 null。零表示算出来的线性关联接近零,null 表示这一项没有可定义的结果,两种情况不能混为一谈。
还要注意配对。不能分别删去横纵轴的缺失值,再假定两组数组仍然对应同一批记录。本章先在整张表上筛选有效行,然后从同一份 paired 表取得两列。横纵长度不一致的实际报错与修复输出见 error-samples.md。
从特别大的数回到原始记录
这份表里 40 比 15 与 20 大,但没有足够证据把它判为录错。先查看 L05 的来源,核对单位、字段和任务,再决定是否需要处理。看到离其他点远的数字,可以把它作为待查线索,删除要有根据。
如果把单位“秒”误写成“分钟”,数值也可能仍在 0 到 1440 的合法范围内。第 14 章的类型与范围检查能发现一部分问题,业务含义需要另外核对。本章保留记录 ID、来源行号与分钟数质量标记,就是为了让这些检查能回到具体记录。
绘图范围也可能藏掉数据。当前直方图边界只到 50,未来加入 80 分钟的记录就要扩展边界。统计程序会检查所有已知值是否都被分箱计数,落在范围之外时明确报错,不静默少画一条。
把图和分析一起保存
explore_records.py 输出三张 1600 × 1000 PNG 和三份保留文本的 SVG,同时写入 analysis_summary.json 与 group_summary.csv。JSON 记录参与分析的数量、分箱边界、分组结果、散点配对与相关系数,还保存输入文件的校验值,方便重复运行时确认数据未换。
使用 exploration.ipynb 可以逐格查看同样的统计和图。安装 requirements-notebook.txt 后,在第 9 章的编辑器中选择本章环境,从头运行所有单元格。交付 Notebook 已有真实内核输出,独立分析结论见 analysis-report.md。
如果中文变成方框,先核对绘图字体。本章辅助模块优先寻找系统中的微软雅黑或 Noto Sans CJK SC 等中文字体,找不到时会提示安装或修改候选名字。
留一道独立练习
把直方图区间换成 0、20、40、60,先在纸上判断 15、20、40 各在哪一组,再算区间计数。预期三个区间各有一项,不能把 20 同时算进左右两组。随后分别对两个标签统计缺失分钟数,预期各有一项。完整答案见 【零基础学AI】第 15 章课后练习与答案。
下一章会用具体数字和图形解释方差、概率与梯度下降,继续练习怎样从一次计算里读出含义。
资料来源
- 【零基础学AI】第 15 章课后练习与答案
- Matplotlib 直方图与分箱
- Matplotlib 柱状图
- Matplotlib 散点图
- Pandas 相关系数与缺失处理