不是吧?"上机实践"真不是给个题目让你自己敲代码
先说明一下,我这里的"第2.12节上机实践",是教学大纲中安排的一次集中动手环节,目标指向 Python 数据清洗与可视化。很多同学在听到"上机实践"这四个字的时候,第一反应就是"老师会给个题目,然后我自己敲代码,最后交个结果"。这个理解不能说全错,但偏差挺大。真正的上机实践,尤其是第二节之后的这一场,考察的绝对不是"会不会调用库",而是你能不能从一堆乱糟糟的数据里完成一次完整的、可复用的数据任务。
这篇文章适合这么几类人:正在上一门带实践环节课程的学生,准备自己组织学习笔记的自学者,以及需要带学生做实验、设计实训方案的老师或助教。我会把整个第2.12节从设计意图、环境准备、环节拆解到踩坑全过程都梳理一遍,把我实际带这节实践课遇到的各种问题、处理方式、背后原因,以及最后总结出来的经验教训,全部写出来。
1. 上机实践的核心,不是"敲代码"而是"做决策"
1.1 先把实践目标讲清楚,很多人在这一步就走偏了
我每年带这节实践课之前,都会先问自己一个问题:学生上完这节课,到底应该带走什么能力?如果答案只是"会写几行 pandas 代码",那这节课完全可以取消,让大家看教程自学就行。实际上,第2.12节要培养的是三层能力。
第一层是操作能力,也就是环境搭建、库的导入、数据读取、基本清洗这些手熟动作。第二层是拆解能力,拿到一个原始数据集,能快速判断需要哪几步处理,先做哪一步后做哪一步,为什么是这个顺序。第三层是呈现能力,数据处理的最终结果不是跑一遍print就结束了,你要能用图表把结论讲清楚。
所以我在实践开始前反复跟学生强调:这节上机实践,你花的时间分配应该大概是20%写代码,40%想清楚处理逻辑,40%看结果然后反推调整。如果哪个同学从头到尾一直在"写代码",大概率是连题目要解决什么问题都没想明白。
1.2 为什么这一节选了"数据清洗与可视化"这个主题
熟悉我的读者知道,我经常强调一个观点:数据清洗不是什么高深的技术,但它是整个数据分析链条里最耗时、最影响结果质量的环节。第2.12节安排在这个时间点,恰好是在课程前面已经讲过基础语法、pandas 常用操作和 matplotlib 基本绘图之后,学生对工具已经不陌生,但还没有真正面对过"脏数据"。
所谓脏数据,就是那些包含缺失值、重复记录、异常格式、明显错误值的真实数据。教科书里的示例数据通常干干净净,但真实场景完全不是这样。所以这一节实践课的目标非常明确——让学员在模拟真实场景的数据集上,走完读取、清洗、分析、可视化的全过程,并在这个过程中学会做判断。数据读到一半报错了,是直接忽略还是回填?某列出现明显异常值,是删除整行还是做插值?这些判断能力,靠听课是听不出来的,必须亲手做一遍才能理解。
我在这节课开始前,通常会给学生讲一个类比:厨师拿到一块带泥的萝卜,不会直接切了进锅,得先洗、去皮、看看有没有坏的部分。数据清洗就是厨师备菜环节,不好好备菜,后面的菜做得再花哨,端上桌也是没法吃的。
2. 环境准备与数据集选择,直接影响实践课成败
2.1 五个最容易出问题的环境细节
很多实践课翻车,不是题太难,是环境没搞定。我的经验是,第2.12节一开始,必须留出15到20分钟让所有人把运行环境确认一遍。别觉得这个时间浪费,后面你会发现,这20分钟能帮你省掉一个小时的处理报错时间。
第一个需要确认的是 Python 版本和 pip 环境。我要求学员统一使用 conda 创建的独立环境,版本建议3.9以上,低于3.8的版本在很多库的新版API上会有兼容问题。第二个是 pandas、matplotlib、numpy 的版本。我见过太多诡异的问题最后定位到就是版本不一致,比如某个函数老版本不生效,或者新版本改了默认参数。第三个是中文显示问题,matplotlib 默认字体是不支持中文的,第2.12节的可视化环节肯定要画中文标签的图,不提前设置好字体,图一出来全是方块,所有人都会懵。第四个是数据文件路径问题。我强烈建议在实践开始前就约定好,把数据集放在和 notebook 同一个目录下,并且用相对路径读取。Windows 环境下直接复制绝对路径容易遇到反斜杠转义,初学者处理起来很烦。第五个是 Jupyter Notebook 的 kernel 选择,有人不小心选错了 kernel,导致 pandas 导入版本和命令行不一样,很容易出现"我这里能跑那里不能跑"的情况。
2.2 数据集应该怎么选:难度梯度非常关键
给实践课选数据集,最忌一上来就丢一个10万行、几十列、来自真实业务系统的原始数据。学员会直接被吓住,然后整节课都在处理集成的烦琐问题,完全顾不上理解数据本身。我自己常用的处理方式,是准备三个难度梯度的数据集。
第一梯度是训练用的小数据集,几百行,5列以内,缺失值和重复值不多,目的是让学员熟悉基本流程。第二梯度是正式实践用的模拟真实数据集,3000行左右,包含姓名、时间、地区、销售额、类别等常见字段,掺杂了明显的缺失、重复、格式错误和极端值,这是第2.12节的主体数据。第三梯度是拓展数据集,给学有余力的学员准备的,包含多表关联和明显的录入错误,需要学员自己写逻辑才能处理干净。这个梯度设计,能让不同基础的人都在一节课内获得成就感,同时又能被真正难住一会儿。
数据集的主题上,我一般选电商销售记录,因为这个场景大家熟,不需要额外解释业务背景。字段包括订单编号、用户ID、购买日期、商品类别、单价、数量、总金额、地区、支付方式等。我会有意制造几个问题:部分总金额和单价乘以数量对不上,某些用户ID是空的,日期格式既有2024-01-05又有2024/1/5,还有一列有重复记录。这些坑都是将来真实工作中会遇到的,提前在上机实践里踩一遍,比以后在项目里被坑要好得多。
3. 核心操作流程拆解:从原始数据到可读图表
3.1 第一步:读取数据和初步摸底,别急着清洗
我要求学生打开数据文件后,第一件事不是立刻去fillna或者drop_duplicates,而是先做三件看起来"很没用"的事。第一,用 shape 看一下数据规模;第二,用 info() 查看每列的类型和非空数量;第三,用 head() 和 sample() 各看几行,感受一下数据长什么样。这三件事做完,你应该对这份数据"脏在哪"有个初步判断。
我为什么特别强调这个顺序?因为清洗动作必须建立在了解数据结构的基础上。你连哪一列有缺失都不知道,就开始fillna,填进去的值大概率是错的。比如"用户ID"列如果缺失,那说明这一行可能是游客下单,不应该填充,反而应该保留缺失并单独标记。"支付方式"列如果缺失,可能意味着订单未完成。这两种缺失的处理逻辑完全不同,必须先看数据分布,才能决定策略。
初步摸底的时候,我还会让学员顺手做一个小练习:用列表推导式把所有列名找出来,看看有没有空格、大小写不一致的情况。列名清洗是很多人容易漏掉的一步,但真实数据里列名经常是乱七八糟的,比如有的列叫"Price",有的叫"price",还有的带个奇怪后缀。第2.12节我要求大家把这个操作固化成习惯:在真正清洗数据之前,先把列名统一成小写加下划线的风格,你后面的代码会舒服很多。
3.2 第二步:缺失值处理,核心是"不要乱填"
缺失值处理是最能拉开学员水平差异的环节。我能看到两组截然相反的操作。第一组同学一遇到缺失,无脑用dropna把所有含缺失的行全删了。我看了看,其实有些字段的缺失,例如"总金额"缺失,是可以根据"单价*数量"算出来的,直接删掉是巨大的浪费。第二组同学则是无脑用fillna填0,结果就是所有统计指标全部失真。
正确做法是你必须先给每个字段的缺失值分个类。对于"订单编号"缺失,通常说明记录无效,应该删除。对于"用户ID"缺失,可能只是匿名用户,不影响金额统计,可以保留并单独标记。对于"日期"缺失,如果是某些行,无法可靠推断,删除更稳妥;但如果只是个别单元,也许可以结合相邻记录推断。对于"总金额"缺失,完全可以计算,不需要填充。把这些判断写成四个函数,然后用 apply 或者条件索引去执行,保证每一类缺失都用了正确的策略。
我还要求学员记录每一列缺失值处理前后的数量变化,最后写进报告。为什么要记录?因为数据清洗报告的核心就是可追溯性。你删了多少行、填了多少值、为什么这么处理,这些都要有据可查。这是职业习惯,不是课程要求。
3.3 第三步:重复值处理与维度检查
重复值这块,难点不在调用 drop_duplicates,而在定义"什么算重复"。第2.12节的数据里,我故意放了两类重复:完全重复的行,以及订单编号相同但其他字段略有差异的行。前者直接用默认参数就能去掉,后者需要判断保留哪一条。
我引导学生先按"订单编号"分组,看每个组的行数。如果一行完全一样,那直接去重。如果同一订单编号下多行数据在金额、商品类别上不一致,那就说明可能存在数据录入错误,这时候不能简单去重,要先检查是什么原因造成的。在实际教学中,我发现最快的方式是写一个 groupby + filter 的函数,把重复组都筛出来单独检查,你看了真实内容再决定怎么处理,比纯靠逻辑去猜测高效得多。
处理完重复值,还应该顺手做一次"维度检查"。所谓维度检查,就是确认数据的取值有没有明显异常。比如"支付方式"列应该只有三到四种取值,如果出现了一个奇怪的拼写,大概率是录入错误。比如"地区"列如果有一堆近似的写法,像"北京"、"北京市"、"北京巿"这种,你需要做归一化。这一步我称为常识性校验,不需要什么算法,靠的就是对业务的理解和对数据的敏感度。这也是上机实践比普通练习题多出来的价值。
3.4 第四步:统计分析,明确这次实践要回答的问题
数据处理完成之后,就进入了统计分析的环节。第2.12节的实践题目是固定的:分析不同地区的销售额分布、不同商品类别的销售量对比、以及支付方式的使用比例。这三个问题的设定是有讲究的。地区分布考察的是数据聚合能力,类别对比考察的是分组和排序能力,支付方式比例考察的是透视表的灵活运用。
写统计代码本身不难,groupby 加 sum,然后 sort_values,几行就能完成。但我会额外要求学员在跑出指标之后,多问一句"数字是否符合直觉"。比如各地区销售额排名里突然冒出一个特别小的地区,你要去看是不是这个地区的数据量太少。比如某种商品类别的销售额和销售量排名完全不一致,那可能说明它的客单价特别高。这种追问,才是数据分析真正有意思的地方。
我还要求大家用 pivot_table 做一个"地区×类别"的交叉汇总表,看看哪些类别在哪些地区卖得好。这一步的价值是让学员体验到同一个数据集,从不同维度切进去,能回答的问题是完全不一样的。第2.12节的统计环节不是考你能不能算出数,而是考你会不会从数里找出"可以讲给别人听的故事"。我在带课的时候经常提醒学员:上机实践的报告里,不要只放一堆数字和代码输出,那只是半成品。需要加上你的直观判断,比如"华东地区销售额高,但客单价低,说明走量"这种结论,才是有价值的分析输出。
3.5 第五步:可视化,别让图表变成"自嗨"
可视化环节是第2.12节的高潮,也是最容易出"看起来挺好看但一点没用"的图表的地方。这里有个核心原则:图中的每种元素,都必须为表达结论服务,否则就是噪音。我在这个环节会给出三个建议。
第一,用柱状图展示地区销售额,但要对柱子按从大到小排序,让读者一眼看到头部地区是哪些。第二,用饼图展示支付方式比例,但要限制分片数量,如果有个类别占比只有0.6%,建议合并到"其他"里,否则饼图会显得很碎。第三,用配对图或者箱线图来检查金额是否存在极端异常值,这类图才是发现数据问题的利器。
实际写代码时,matplotlib 和 pandas 内置绘图都够用,没必要非要上 seaborn,除非你要画更复杂的统计图。不过有个坑必须提前给大家排掉:中文字体。Windows 上大概率是 SimHei,Mac 上是 PingFang SC 或 Heiti TC。我一般用 plt.rcParams['font.sans-serif'] = ['SimHei'] 直接指定,然后还要加上 plt.rcParams['axes.unicode_minus'] = False 避免负号显示异常。这个是中文数据可视化最容易踩的坑,没有之一。
我还特别强调,可视化结果展示出来后,要和之前的统计结论互相验证。比如统计表里说"华东地区第一",图中柱状图第一根柱子也应该是华东,如果图跟表对不上,说明你的数据处理出了问题,得回头查。这种"表图互检"的习惯,能帮你抓住很多隐蔽的数据处理错误。
3.6 第六步:整理报告,让过程可以被别人复现
最后一步不是关掉 notebook 走人,而是写一份简短的数据处理报告。报告不需要长篇大论,但必须包含三个部分:数据概览,说明原始数据大小、字段数、缺失和重复情况;处理逻辑,逐条列出你做了哪些清洗步骤以及理由;结果分析,用图表加一两句话呈现三个核心问题的结论。
为了培养复现意识,我要求整个 notebook 从上到下运行一次,不能中断,而且必须保证每一次 read_csv 之后立刻接上处理步骤。如果你的 notebook 里有哪个 cell 运行顺序是乱的、缺了前置变量,这就说明你不在一个干净的逻辑流程里。第2.12节上机实践的最终产出,是一个能从头跑到尾、别人拿过去也能跑通的 notebook,加一份300字左右的小结,而不是一页 "报告.docx"。
4. 常见问题与现场排障实录
4.1 这几个报错几乎每个班都会出现一次
我每次带实践课,都会提前把一些常见的坑拿出来讲一遍,但该踩的坑一个都不会少。这里我把出现频率最高的问题整理成了一张速查表,大家做实验的时候可以对照着查。
| 报错/问题现象 | 主要原因 | 快速解法 |
|---|---|---|
| ImportError: No module named 'pandas' | 当前 kernel 用的 Python 环境不对 | 检查 Jupyter kernel 是否选到了 conda 环境,或重新 pip install pandas |
| DataFrame 中列名里带空格 | 数据文件表头不规范 | 读取后用 df.columns = df.columns.str.strip() 清理 |
| 日期格式无法排序 | 日期列是 object 类型 | 用 pd.to_datetime,指定格式参数,errors='coerce' |
| 图表中文全部是方块 | matplotlib 默认字体不含中文 | 设置 rcParams 的字体为中文字体,如 SimHei |
| drop_duplicates 后行数没变 | 行间并非完全相同,有小差异 | 先制定"去重键",再使用 subset 参数 |
| 填充缺失值后统计数据对不上 | fillna(0) 误用 | 给每个字段单独指定缺失值处理策略,而不是统一填0 |
| 读取 CSV 报 UnicodeDecodeError | 文件编码不是 UTF-8 | read_csv 时指定 encoding='gbk' 或 'utf-8' |
| 柱状图高度太接近,看不出差距 | 纵轴范围太大 | 不一定是问题,可改用表格展示精确值或调整坐标轴范围 |
这张表是我多年实践课的真实积累,几乎每个问题我都亲手帮学生解决过。比如那个 UnicodeDecodeError,用 Excel 导出的 CSV 默认极可能是 GBK 编码,Python 里默认按 UTF-8 读,不报错才怪。遇到这种问题,先别急着改代码,看文件编码是什么,比到处搜报错信息来得快。
4.2 一个让我印象深刻的现场排查案例,分享给你
有一次实践课,有个学员跑完数据清洗之后,惊讶地发现"总金额"列的和,怎么算都跟原始文件里的月度汇总对不上。他排除了自己算错的可能,就开始怀疑 pandas 是不是有 bug。我过去看他的 notebook,第一眼就发现了问题:他在做缺失值处理的时候,把"总金额"为空的某些行直接删掉了,而删除之前,并没有把"单价×数量"的可计算记录先补回来。结果自然是总金额少了。
这个案例特别典型,它说明了一个核心问题:很多人把"清洗"理解成一个独立的、机械的流程,不去思考清洗动作和后续统计之间的关联。删除还是填充,不是一个"哪个更干净"的问题,而是"哪个更符合分析目标"的问题。我当时让他回去统计一下被删掉的行里有多少是可以通过"单价×数量"算出来的,结果发现大约有30多行的总金额是能算的,删掉这些行,直接导致华东地区的月销售额少了好几千。后来他把处理顺序改成"先推导缺失金额,再删无法推导的缺失行",统计数字就恢复正常了。这个教训比我做十分钟的PPT讲"缺失值处理原则"有用得多。
4.3 现场排障的三个通用思路
最后总结三个我在现场排障时反复使用的通用思路。
第一个思路,把大问题拆成小问题。报错了不要慌,先在出错的那一行代码前面加一个临时输出,看看数据State是什么样。比如"为什么 groupby 报 keyError",十有八九是列名打错了,或者列名里有隐藏空格。第二个思路,"看数据永远比猜原因快"。很多学员报错后第一反应是回忆代码语法,但我更建议直接打印出当前 DataFrame 的前几行,亲眼看一下这一列长什么样,往往一眼就能看出是格式问题还是取值问题。第三个思路,回到最小可复现样例。如果你整个清洗流程写了一大堆函数,不知道问题出在哪里,那就拿一个只有三行数据的小DataFrame,一步一步跑,问题很快就能定位。
5. 从这节实践课里,我总结出的三点教学心得
第一,上机实践的本质不是"教工具",而是"练判断"。工具操作只是基础,学员真正需要的是学会在复杂的真实数据面前做选择。如果你的实践课只是让大家把一段示例代码抄一遍,那这节实践课不如不上。学员离开课堂后遇到新数据,依然不知道从哪下手。
第二,必须给学员留足"思考时间"。我见过很多实践课时间安排得太满,从上课到下课每一分钟都在赶进度,完全没有容错空间。结果是学员一直在跟着老师的节奏跑代码,根本没时间停下来想为什么。第2.12节我每次都会预留至少20分钟的"自由摸索时间",不安排任何新内容,让大家把前面遇到的问题重新看一遍,或者自己改一改参数试试效果。这段时间看起来"浪费",实际上效率最高。
第三,让学员自己讲一遍,比老师总结十遍都管用。在实践课最后,我会随机请两三名学员用两三分钟展示自己的 notebook,讲一讲自己这一步做了什么、踩了什么坑、怎么解决的。这个环节会带来一个神奇的效果:发言的人会重新梳理自己的思路,听讲的人会发现"原来不是我一个人遇到这个问题",课堂氛围一下子就活跃起来了。我发现能清晰讲出自己处理逻辑的人,几乎不会在实践结束以后忘掉这节课的内容,因为"讲一遍"本身就是一个主动回忆和加深理解的过程。
第2.12节的上机实践,看起来只是一节课,但它背后隐含的是一个完整的学习闭环:从设计合理的目标开始,到环境准备、数据处理、分析呈现,再到总结复盘。这个闭环不管是学生还是从业者,都值得反复练习。试着自己动手把这个流程走完,等你遇到真正的一手数据时,会特别感谢自己当初在这节课上花掉的那些时间和踩过的坑。
