ML-For-Beginners 回归篇:用 Pandas 清洗 USDA 南瓜数据,并用 Matplotlib/Seaborn 构建数据可视化
在 ML-For-Beginners 的回归(Regression)模块中,第 2 课(2-Data)解决的是建模前的第一个关键问题:如何对一份“脏”的真实数据提出正确的回归问题,并用 Pandas 把它清洗成可直接建模的结构,再用 Matplotlib 与 Seaborn 验证数据的形态。读完本篇,你能完整复现南瓜价格数据从原始 CSV 到月度均价、再到散点图/柱状图/相关性热图的全流程,理解“先看图选模型”的探索式分析(EDA)思路。
案例背景:南瓜市场原始数据
本课程的素材是 US-pumpkins.csv,位于仓库的 2-Regression/data/ 目录。它源自美国农业部(USDA)发布的 Specialty Crops Terminal Markets 终端市场标准报告,课程作者把按城市分散下载的多个文件合并成了单个电子表格。
通过实际解析该文件可以确认它的“脏”程度:
- 全文件共 1757 行数据、26 列(含两个空表头列,即
Unnamed: 24、Unnamed: 25); - 数据覆盖 13 个城市,日期跨 2016 至 2017 年(美国格式
MM/DD/YYYY); - 仅
Package(包装规格)一列就有 15 种取值:24 inch bins(551 行)、36 inch bins(663 行)、1/2 bushel cartons(234 行)、1 1/9 bushel cartons(117 行)、50 lb sacks、each等混杂值; - 多列大量缺失:
Type缺失 1712 行、Grade全列缺失(1757 行),而Package、Date、Low Price、High Price四列无缺失。
原始数据长这样(文件前几行):
City Name,Type,Package,Variety,Sub Variety,Grade,Date,Low Price,High Price,...
BALTIMORE,,24 inch bins,,,,4/29/17,270,280,270,280,MARYLAND,,lge,...
BALTIMORE,,1 1/9 bushel cartons,PIE TYPE,,,9/24/16,15,15,15,15,DELAWARE,...
可以看到字符串、数字、空值、以及 Package 列中 “bushel”(蒲式耳,体积单位)与 “bin”“sack”“each” 混杂的奇异值,都需要处理。这正是课程想传达的核心观点:几乎没有人会直接把一份“开箱即用”的数据集送给你,数据准备本身就是 ML 工作的一部分。
向数据提出正确的回归问题
在动手之前,先明确问题类型决定算法选型。课程给出的回归问题是:“预测某个月份在售南瓜的价格”。要把它变成回归任务所需的“特征 + 标签”结构,需要对原始列做三项改造:
Date(字符串日期)→ 只保留月份;Low Price与High Price→ 取平均得到一个Price标签;Package→ 过滤并归一化到“每蒲式耳”口径。
配套练习在 notebook.ipynb(空模板,供学员填写),完整可运行的参考实现在 solution/notebook.ipynb,注意答案 notebook 中数据路径是 ../../data/US-pumpkins.csv,因为它是从 solution/ 子目录运行的;若在 2-Data/ 目录下运行则为 ../data/US-pumpkins.csv。
数据准备:Pandas 四步清洗
第一步:加载数据并检查缺失值
import pandas as pd
pumpkins = pd.read_csv('../data/US-pumpkins.csv')
pumpkins.head() # 查看前 5 行;查看最后 5 行可用 tail()
pumpkins.isnull().sum() # 每列缺失值计数
head() 展示前五行,课程留的自测题是“查看最后五行用哪个函数”(答案是 tail())。isnull().sum() 会返回每列的空值数量。实际运行确认:Type 缺 1712、Grade 缺 1757(整列为空),但日期与两列价格完全没有缺失——这意味着缺失值对“按月预测均价”这个特定任务无碍,不必填充,直接忽略即可。这是数据准备的实用原则:先定任务,再决定要不要处理缺失值。
第二步:用 loc 只保留需要的列
loc 的语义是“按 [行选择器, 列选择器] 从原 DataFrame 提取子集”,其中 : 表示所有行:
columns_to_select = ['Package', 'Low Price', 'High Price', 'Date']
pumpkins = pumpkins.loc[:, columns_to_select]
26 列被裁到 4 列,后续的清洗和绘图都在精简结构上进行。
第三步:构造标签列 Price 与特征列 Month
price = (pumpkins['Low Price'] + pumpkins['High Price']) / 2
month = pd.DatetimeIndex(pumpkins['Date']).month
new_pumpkins = pd.DataFrame({
'Month': month,
'Package': pumpkins['Package'],
'Low Price': pumpkins['Low Price'],
'High Price': pumpkins['High Price'],
'Price': price
})
Price取低、高价的算术平均,作为该条报价的代表价格;pd.DatetimeIndex(pumpkins['Date']).month把MM/DD/YYYY字符串日期转成 1–12 的整数月份;- 最后用
pd.DataFrame({...})把新列组合成一个干净、规整的new_pumpkins。
第四步一:按 “bushel” 过滤包装规格
Package 列的问题在于南瓜的售卖单位极不统一:有按整蒲式耳、半蒲式耳、1/9 蒲式耳、按个(each)、按磅、按箱子宽度卖的记录。值得注意的是,原始数据中 Unit of Sale 为 EACH 或 PER BIN 的行,其 Package 也恰是 per inch / per bin / each 一类(实际数据中此类行共 127 行),说明南瓜很难被一致地称重。为了口径统一,课程选择只保留 Package 中包含 'bushel' 子串的行:
pumpkins = pumpkins[pumpkins['Package'].str.contains('bushel', case=True, regex=True)]
这行过滤应加在读取 CSV 之后的最早位置(即整个清洗链的最前面)。过滤后恰好剩 415 行(与课程文档给出的“约 415 行”一致),具体构成为:
| Package 取值 | 行数 |
|---|---|
1/2 bushel cartons |
234 |
1 1/9 bushel cartons |
117 |
1 1/9 bushel crates |
17 |
bushel cartons |
37 |
bushel baskets |
10 |
第四步二:把价格归一化到“每蒲式耳”
剩下的问题是同一行里的“bushel 量”并不一样——有的报价是一整蒲式耳,有的是 1 1/9 蒲式耳、有的是半蒲式耳。价格必须除以对应数量才可比:
new_pumpkins.loc[new_pumpkins['Package'].str.contains('1 1/9'), 'Price'] = price/(1 + 1/9)
new_pumpkins.loc[new_pumpkins['Package'].str.contains('1/2'), 'Price'] = price/(1/2)
注意两处细节:new_pumpkins.loc[条件, 'Price'] 是“按行条件选中后改写某一列”的写法;条件字符串用的是 str.contains 的模糊匹配,天然覆盖了 1 1/9 bushel cartons 与 1 1/9 bushel crates 两种包装。课程还留了一个思考题:蒲式耳是体积单位,不同农产品对应重量差别很大(如西红柿一蒲式耳约 56 磅),南瓜更是难以标准化,因此这里不换算成磅价,统一按“每蒲式耳”计价——课程借此强调:不理解数据的业务性质,清洗就做不对。另一个自测点是:为什么半蒲式耳南瓜明显更贵?提示是小南瓜单个更便宜但一蒲式耳装得下更多颗,均价自然被抬高。
清洗完成后,实际计算出的 new_pumpkins 月度均价为:8 月约 24.4、9 月约 28.5、10 月约 28.4、11 月约 25.1、12 月约 15.4,价格区间 10.8–51.5。可以看到季节性峰值确实落在 9、10 月,与后面图表的结论互相印证。
用 Matplotlib 做第一组可视化
数据科学家的职责之一是把数据的“质量与性质”可视化出来:散点、折线、柱状图能暴露出关系与空洞。而且可视化还能帮你选算法——例如散点图若近似落在一条直线上,说明数据是线性回归的好候选。
在 notebook 顶部导入后重新运行全部单元格:
import matplotlib.pyplot as plt
散点图:先看“有没有用”
price = new_pumpkins.Price
month = new_pumpkins.Month
plt.scatter(price, month)
plt.show()
得到的是价格(x 轴)对月份(y 轴)的散点。坦率说,这张图信息量有限——只是把每个月的数据点铺开而已。结论是:图表要产生洞察,通常需要对数据做某种分组聚合。
分组柱状图:让结论浮现
new_pumpkins.groupby(['Month'])['Price'].mean().plot(kind='bar')
plt.ylabel("Pumpkin Price")
这行链式调用做了三件事:groupby('Month') 按月分组、['Price'].mean() 求组内均值、.plot(kind='bar') 画柱状图。
这张图明显更有用:南瓜均价在 9 月和 10 月达到峰值,12 月骤降。课程让你判断这是否符合直觉(秋季正是南瓜收获季,供给充足、价格本应更低,而这里反而是峰值——值得结合数据年份和样本量思考)。
用 Seaborn 提高统计可视化的表达力
Matplotlib 强大但啰嗦;Seaborn 构建在 Matplotlib 之上,专为统计可视化设计:直接接收 Pandas DataFrame、内置美观默认样式、用更少的代码产出信息更丰富的图,且返回的仍是 Matplotlib 对象,可用已知的 Matplotlib API 继续微调。若尚未安装:pip install seaborn。
import seaborn as sns
散点图看变量关系(relplot)
建模前探索的核心是找变量间关系:散点若近似一条线,说明两变量可能相关,是线性模型可行的信号。用 relplot()(关系图)重绘价格-月份散点:
sns.relplot(x="Price", y="Month", data=new_pumpkins)
对比 Matplotlib 版本,注意两点差异:你传递的是列名字符串加 DataFrame,Seaborn 自动处理轴标签;图形的统计语义(散点 = 关系展示)由函数名直接表达。
切换成折线图只需加一个参数,Seaborn 还会自动画出围绕折线的置信区间带:
sns.relplot(x="Price", y="Month", kind="line", data=new_pumpkins)
由于该数据噪声很大,折线图在这里并不是最清晰的选择——但它演示了 Seaborn 用 kind 参数在图表类型间一键切换的低成本。
柱状图看分布(catplot)
之前柱状图是你手动 groupby + mean 后交给 Matplotlib 的;catplot()(分类图)可以把分组聚合一步完成。kind="bar" 默认显示每类均值,并用黑线标出置信区间:
sns.catplot(x="Month", y="Price", data=new_pumpkins, kind="bar")
它确认了 Matplotlib 版本看到的“9、10 月价格峰值”,同时额外展示了每个月内部价格的波动幅度——这是手动均值柱状图丢失的信息。
热图看全局相关性(heatmap)
散点图一次只能比较两个变量。当有多个数值列时,相关性热图能一次性展示任意两列之间的相关强度,是选特征前的常用手段(分类篇之后会用同类图表展示混淆矩阵):
correlations = new_pumpkins[['Month', 'Low Price', 'High Price', 'Price']].corr()
sns.heatmap(correlations, annot=True, cmap="coolwarm")
DataFrame.corr()由 Pandas 算出相关系数矩阵;annot=True在每个格子里打印数值,cmap="coolwarm"指定红蓝发散色带。
读这张图有一条重要教训:接近 1(或 -1)的格表示两列线性强相关——Low Price 与 High Price 几乎完全相关(这符合预期,二者就是同一报价的上下界);而 Month 与 Price 的线性相关很弱,尽管柱状图明明显示出 9、10 月的季节性峰值。原因在于相关系数只度量直线关系,对季节性、台阶式等非线性模式是“盲”的。所以课程的自测题问:为什么选特征前要同时看热图和柱状图这类图表?——因为二者捕捉的是数据的不同侧面。
Matplotlib 还是 Seaborn?
两者都值得掌握:
- Matplotlib:对图形的每个元素都有细粒度控制,是几乎所有 Python 绘图库的地基;
- Seaborn:面向统计图表的高层函数与美观默认值,直接吃 DataFrame,探索性分析更快。
常见的组合工作流:先用 Seaborn 快速探索数据,需要定制细节时再下沉到 Matplotlib。
挑战与课后作业
🚀 Challenge:探索 Matplotlib 和 Seaborn 提供的不同可视化类型,思考哪几类最适合回归问题(提示:散点图用于判断线性关系,分组图用于看分布与季节性)。
课后作业 Exploring visualizations 要求你:用本课程(本仓库 2-Regression/2-Data/ 目录下的)南瓜数据,在示例 notebook 中用 matplotlib 和 seaborn 各做可视化实验,并提交一个包含至少一个探索性可视化的 notebook(评分标准:2 个为优秀、1 个为合格、未提交为不合格),同时思考“哪个库更好用”。
小结
这一课在 2-Regression/2-Data/README.md 中给出完整教程,配套材料齐备:空模板 notebook.ipynb、可运行答案 solution/notebook.ipynb、原始数据 US-pumpkins.csv 以及 assignment.md 作业。核心收获可以浓缩为三步:
- 先定问题再清洗:回归问题“按月预测价格”决定了只需处理
Date、价格两列和Package口径; - 口径归一化是回归的前提:把混装的售卖单位过滤到 bushel、再除以每行的 bushel 数量,才得到可比的
Price标签; - 图表即模型选择器:散点判断线性、柱状图暴露季节性、热图量化线性相关——三者结合才能避免“高相关列冗余、非线性模式漏判”两类典型错误。
完成数据准备与可视化后,下一课将基于这份干净的月度价格数据正式训练线性回归模型。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00






