本章精髓 只要数据能摆成一行一条、一列一项的形状,程序就能替你筛、算、排、汇总。但程序算得快,算错也一样快,而且错得整齐,不容易看出来。所以这一章真正要学的是:算完之后怎么核对。
学完本章后,你应该能够:
- 用记录与字段说清一份表的结构,并指出统计依据的是哪几个字段;
- 认出一批表里的常见脏数据:全空行、首尾空格、写法不一致、重复、缺失;
- 说清缺失值的三种处理方式对结果的影响,选定一种、写进说明、报告受影响的条数;
- 把多份格式相同的表合并、分组统计并导出为新文件,原始文件一个不动;
- 从汇总结果里抽三条记录,顺着来源列回到原始表逐项核对。
成品 · 数据的形状 · 五段处理链
先看结果,再看数据要长成什么样,程序才处理得了。
1.1本章成品:十份表合成一张
十位同学各交一份格式相同的成绩表,其中若干份有空行、有空格、有漏填。成品脚本把十份合并成一张,清掉这些问题,按班级统计人数与平均分,导出为一个新文件,十份原始表一个都不改动。
99 行减到 97 行,以及那句"共 6 条",都是清洗过程留下的交代
1.2数据要长成什么样,程序才处理得了
十份表能被合并,前提是它们的形状一致:
第一行是表头,规定了有哪些字段;以下每一行是一条记录
1.3从十份表到一张汇总,中间做了五件事
五步里最花时间的是第三步,本章的多数知识点也集中在这一步
五步之外还有一件事不在链条上,却决定这张表能不能用:拿到结果之后回原始表抽查几条。程序算得快,算错也一样快,而且错得整齐,不容易看出来。
1.4本章新技巧:贴样本数据,不要描述数据
说"表里有班级和成绩",大模型只能自己猜:列名叫什么、顺序怎么排、分数是整数还是带小数、空值长什么样。猜错的部分要等到运行报错才暴露,而报错往往出现在第三步以后,回头改一遍成本不低。
正确的做法是把真实的表头与前三行原样贴进提示词。三行数据提供的信息比三段描述准确得多:列名的确切写法、字段的先后、数值的形式、空值出现在哪一列,全都一目了然。涉及个人信息时,把姓名一列换成占位内容即可,表头与其余字段保持原样。
1.5可直接复制的提示词
主提示词里的几条要求,分别对应后面要讲的知识点:
1.6大模型的常见偏差
按它想象的列名写代码。 没有贴样本时,它会写出 df['成绩'] 或 df['score'] 这样的列名。运行时报 KeyError,而且要等程序跑到那一行才报。贴表头是成本最低的预防办法。
直接覆盖原始文件。 它常把结果写回其中一份原表,或者用同名文件覆盖。原始数据一旦被覆盖,清洗规则就再也没法重调。"原始文件一个都不要改动"这句必须写进要求。
把空值按 0 计入却不作说明。 这是本章最需要警惕的一种。程序不报错,结果看上去完全正常,只是平均分整体偏低。要求里必须写明"统计这样的记录共有几条,并打印出来"。
清洗只做半套。 它通常会处理空行,但未必会处理首尾空格,更不会主动想到全角空格。已知的脏数据情况要在提示词里逐条列出,列几条它处理几条。
第11章演示_合并汇总_仅标准库.py。它读取生成脚本一并产出的 原始表格_CSV 文件夹,处理逻辑与 pandas 版完全相同,两者算出的人数与平均分逐项一致,不需要安装任何第三方库。完全跑不起 Python 的机器,可以先开 第11章演示_三种算法.html 与 第11章演示_脏数据放大镜.html,本章最要紧的两件事都在这两个网页里。结构化数据 · CSV · JSON · 脏数据
本章的核心内容集中在这一部分和下一部分。
这八个术语会原样出现在数据文档、统计报告与技术规范里。前四个在本部分讲解,后四个在第三部分讲解。
2.1四个知识点,一个一个看
横看是一条记录,竖看是一个字段
现象:十份表打开来长得一样:第一行是列名,下面每一行是一个人。
概念:记录(record)指表里的一行,对应一个具体对象;字段(field)指一列,对应这个对象的一项属性。行列都对齐的数据叫结构化数据(structured data)。数据能被程序自动处理的前提,就是它具备这个形状。
类比:花名册。每人占一行,每项信息占一列,谁排在第几行不影响这一行说的是谁。
CSV 本身就是纯文本,逗号就是列的分界
现象:同一份数据存成 .csv,用记事本能直接看;存成 .xlsx,用记事本打开是一堆乱码。
概念:CSV(Comma-Separated Values,逗号分隔值)是最朴素的表格文件,一行一条记录,字段之间用逗号隔开,任何程序都能读。.xlsx 是压缩过的二进制格式,能保存公式、格式与多个工作表,但必须由专门的库来解析。只做数据交换时优先用 CSV,需要保留格式时才用 xlsx。
类比:CSV 是毛坯房,xlsx 是精装修。搬运的时候毛坯省事。
认这两种括号,就能看懂大半个 JSON
现象:从网站或者接口拿回来的数据,常常是一堆花括号和方括号,不是表格。
概念:JSON 是另一种通用的数据格式。花括号 { } 表示一个对象,里面是"名字:值"的成对内容;方括号 [ ] 表示一个列表,里面是同类的若干项。两者可以互相嵌套,所以 JSON 装得下表格装不下的层级结构,例如一个人对应多次成绩。null 表示这一项没有值,也就是缺失。
类比:表格是一张登记表,JSON 是一叠可以夹着子文件的档案袋。
这里有三处问题:全空行、首尾空格、缺失值
现象:合并之后总行数是 99,去掉全空行只剩 97。
概念:不符合预期形状的数据统称脏数据(dirty data)。常见的有五类:全空行、单元格首尾的空格(含全角空格)、同一字段写法不一致、重复记录、缺失值。清洗指按事先定好的规则逐类处理,规则必须写下来,否则同一批数据两次处理会得到不同的结果。
类比:收上来的登记表总有人乱填,正式录入之前要先统一一遍。
2.2三类脏数据的处理口径
清洗不是逐条去改,是先定口径,再让程序按口径统一处理。三类最常见的,口径分别是:
打开 第11章演示_脏数据放大镜.html,那里把看不见的空格显形出来,并且把清洗前后的分组结果并排放着。看完你会记住一件事:肉眼看不出来,不代表它不影响结果。
2.3本章的完整代码
下面这份代码由大模型生成,对应五段处理链,另加一段抽查用的输出。
from pathlib import Path
import pandas as pd
# ===== 设置区:只需要改下面四项 =====
FOLDER = '原始表格' # 存放原始表的文件夹
OUTPUT = '汇总.xlsx' # 导出的文件名
GROUP_BY = '班级' # 按哪一个字段分组统计
WEIGHT = (0.4, 0.6) # 总评权重:平时分 40%,期末分 60%
# ==================================
folder = Path(__file__).with_name(FOLDER)
# ① 列出全部表格文件并排序,让每次合并的顺序一致
files = sorted(folder.glob('*.xlsx'))
print('找到', len(files), '份表格')
# ② 逐份读取并合并。额外记下每条记录来自哪份表,供抽查时溯源
tables = []
for one_file in files:
one = pd.read_excel(one_file)
one['来源文件'] = one_file.name
tables.append(one)
data = pd.concat(tables, ignore_index=True)
print('合并后共', len(data), '行')
# ③ 清洗一:去掉五个字段全为空的行
data = data.dropna(subset=['学号', '姓名', '班级', '平时分', '期末分'], how='all')
print('去掉全空行后剩', len(data), '行')
# ④ 清洗二:去掉文本字段首尾的空格,全角空格同样会被去掉
for col in ['姓名', '班级']:
data[col] = data[col].str.strip()
# ⑤ 缺失值:先数清楚有几条,再统一填 0
missing = int(data['平时分'].isna().sum())
data['平时分'] = data['平时分'].fillna(0)
print('平时分为空的记录共', missing, '条,已按 0 计入')
# ⑥ 计算总评
data['总评'] = data['平时分'] * WEIGHT[0] + data['期末分'] * WEIGHT[1]
# ⑦ 分组统计:按班级计算人数与总评平均分
summary = data.groupby(GROUP_BY).agg(
人数=('学号', 'count'),
总评平均分=('总评', 'mean'),
).round(2).reset_index()
print(summary.to_string(index=False))
# ⑧ 抽查:随机取三条,打印来源与原始数值,供人工回原表核对
for _, row in data.sample(3).iterrows():
print(' ', row['来源文件'], row['学号'], row['姓名'],
'平时', row['平时分'], '期末', row['期末分'])
# ⑨ 导出:写入新文件,原始表格一个都不改动
out = Path(__file__).with_name(OUTPUT)
with pd.ExcelWriter(out) as writer:
data.to_excel(writer, sheet_name='合并明细', index=False)
summary.to_excel(writer, sheet_name='按班级汇总', index=False)
input('按回车键结束')
下面这张表的第三列还是反例:把这一行改掉或者删掉,会发生什么。
how='all' 表示全空才删,只空一两项的记录保留。
写成 how='any' 时,凡有一项漏填的记录都会被删掉,人数直接少一截count 数的是学号非空的条数。
归堆字段没清洗干净时,同一个班会被拆成两行三种算法三个平均分 · 错得整齐 · 先抽查三条
这一部分讲的是:同一批数据,为什么能算出不同的结论。
3.1另外四个知识点
同一批数据,三种做法差 2.16 分
现象:平时分那一列有六个空格子。
概念:缺失值(missing value)指某条记录的某个字段没有数据。常见处理有三种:记为 0、按其余字段折算、整条记录剔除。三种做法算出的结果互不相同,所以必须选定一种、写进说明、并报告受影响的条数。把空值悄悄按 0 计入而不加说明,平均分会偏低,而且从结果上看不出偏低。
类比:统计月考成绩时,缺考按 0 分算、按不计入算、还是整个人不统计,班级平均分是三个不同的数。
去重之前要先回答:这份数据里,哪一列能唯一确定一个对象
现象:汇总出来的人数比实际报名人数多了三个,翻明细才发现有人交了两次。
概念:去重(deduplicate)指把指向同一个对象的多条记录合成一条。关键不在于怎么删,而在于依据哪个字段判断"是同一个"。学号、身份证号、订单号这类唯一标识才靠得住。按整行完全相同去重看似保险,实际上只要有人第二次填时改了一个字,两条就都留下了。去重之后要打印去掉了几条。
类比:合并两份通讯录时,按姓名合会把重名的人并成一个,按手机号合才不会。
97 条记录归成三堆,每堆输出一行
现象:汇总表里一个班占一行,后面跟着人数与平均分。
概念:分组统计(group by)指先按某个字段把记录归堆,再对每一堆分别计算。归堆所依据的字段必须是干净的,否则"数据2301"与带空格的"数据2301"会被分成两个班,两行人数都不对。归堆之后先看组数:组数比你预期的多,多半就是这一列还没洗干净。
类比:把货按产地分开,再分别称重。产地标签写乱了,称出来的重量就归错了地方。
同样是"多了十倍数据",代价差别很大
现象:一百行的表瞬间跑完,换成一万行的表,同一个脚本要等好几分钟。
概念:算法(algorithm)就是完成一件事的固定步骤。筛选、排序、去重、分组各有各的算法,区别不只在快慢,更在于数据量翻倍时代价怎么涨。逐条看一遍的做法,数据翻十倍就慢十倍;而"每一条都和其余每一条比一遍"的做法,翻十倍要慢一百倍。写规则的人不必会实现算法,但要知道自己提的要求属于哪一类。
类比:在一叠卡片里找一张,是从头翻一遍;把每两张卡片都比一次看有没有重复的,工作量完全不是一个量级。
3.2六条缺失值,三种算法,三个平均分
测试数据里平时分有六条空白。下面三行是同一批数据用三种方式处理后的真实结果:
| 处理方式 | 规则 | 参与人数 | 全体平均 | 数据2301 | 数据2302 | 数据2303 |
|---|---|---|---|---|---|---|
| 记为 0 | 空的平时分按 0 分参与计算 | 97 | 76.42 | 73.34 | 75.45 | 80.10 |
| 按期末分折算 | 缺平时分的记录,总评直接取期末分 | 97 | 78.22 | 77.15 | 77.33 | 80.10 |
| 整条剔除 | 缺平时分的记录不参与统计 | 91 | 78.58 | 78.88 | 76.91 | 80.10 |
三种做法都不算错。差别在于它们回答的不是同一个问题。全体平均分相差 2.16 分。数据2303 那一列三行完全相同,因为这个班没有缺失值;受影响的只有另外两个班,而且受影响的程度还不一样。这说明缺失值的处理方式不只改变总数,还会改变各组之间的相对高低。看第一行与第二行:按"记为 0",数据2301 的 73.34 低于数据2302 的 75.45;按"整条剔除",数据2301 的 78.88 反而高于数据2302 的 76.91。换一条规则,两个班的高低就掉了个个儿,而这条规则并没有写在表上。
打开 第11章演示_三种算法.html,切换三种规则,看这三行数字怎么动。盯住数据2303 那一列,它一动不动。
3.3错得整齐
前面几章的错误都会让你看见:页面破了、程序报错、文件数量少了。这一章的错误不会。
所以本章的收尾动作是固定的三步,缺一步这张汇总表就不能交:
第三步能做成,全靠合并时多写的那一列来源文件。合并之后行的先后已经看不出来源,这一列是抽查时唯一的线索。没有它,你发现一条数字不对,也找不回是哪份表填错的。
3.4原始数据只读
还有一条纪律贯穿整章:程序只读取原始文件,结果一律写进新文件。
清洗规则往往要调七八次,每次都要从原始数据重跑一遍
这也是脚本不该提供"就地修改"这个选项的原因。底稿要留着。誊写出错可以重抄,底稿改了就没有依据了。
装库 · 合并 · 清洗 · 抽查
从十份带毛病的表开始,走到一张能交出去的汇总表。
4.1先把两个库装上
4.2演示步骤
4.3前沿三分钟:清洗占掉的那部分时间
每章固定栏目 · 一条与本章内容相关的行业动态
数据行业流传多年的一个说法是,一个数据项目里,真正用于分析与建模的时间只占一小部分,其余大半花在收集、清洗与核对上。具体比例各家统计不一,但从业者对"清洗占大头"这一点基本没有分歧。本章五段处理链里,第三步的篇幅明显长于其他四步,就是这个结构的缩影。
由此产生的应对办法有两个方向。一是把清洗规则固定下来、写成可以重复执行的脚本,而不是每次在表格软件里手工改一遍,这样规则可以被检查、可以复用,也可以交给别人执行。二是把问题往前推,在收数据的环节就用统一模板、限定填写格式,从源头减少脏数据。第二种办法省下来的工作量,通常比第一种多得多。
对本章的实际影响是:不要把清洗当成杂活。清洗规则就是这份数据的定义,规则不写下来,结果也就说不清楚。
三选一 · 六步 · 抽查三条
上机环节由此开始,本次上机的成果就是作业 3,当堂完成并提交。
5.1三个题目,任选其一
多份报名表合并、按学号去重,按项目统计报名人数。
通用多份问卷合并,统计单选题各选项的占比与有效答卷数。
偏文科多批次数据合并,按批次统计均值与标准差。
偏理工三个题目都必须包含合并、清洗、分组统计、导出新文件四个环节,并且要在结果里报告缺失或无效记录的条数。数据可以自行准备,也可以改造生成脚本产出。允许更换题材。
5.2上机六步
5.3共同验收标准
5.4常见错误与处理
pip install pandas openpyxl;仍不行时改用仅标准库版本-i 指定国内镜像源,重新安装data.columns 看真实列名,再把表头补进提示词重新生成how='all'5.5提交内容
提交方式:按教师提供的作业模板填写,文档开头注明所选题目。提交渠道见课堂通知,当堂提交。
5.6再进一步(选做)
把分组统计从一个字段扩展到两个字段,例如同时按班级与性别分组,看每一格的人数与平均分。
做完会碰到一个新情况:格子变多之后,某些格子里只有一两个人,平均分随之出现极端值。样本量小到什么程度时,平均分就不该再单独拿出来看,需要由你自己定一个下限并写进说明。
本章五条结论
- 结构化数据就是一张表,一行一条记录,一列一个字段。十份表能合并的前提是表头一字不差。
- 清洗规则就是这份数据的定义。规则不写下来,同一批数据两次处理会得到不同的结果,而结果本身看不出差别。
- 缺失值怎么处理,直接决定统计出来的数字。必须选定一种规则、写进说明、并报告受影响的条数。
- 程序算得快,算错也一样快,而且错得整齐。行数、分组之和、抽查三条,这三步是这一章的收尾动作。
- 合并时给每条记录标注来源。合并之后行的先后已经看不出来源,这一列是抽查时唯一的线索。