零代码应用开发·第 11 章·3 学时

处理数据

合并、清洗、分组统计、导出。算得出结果不算完,还要能核对。

本章精髓 只要数据能摆成一行一条、一列一项的形状,程序就能替你筛、算、排、汇总。但程序算得快,算错也一样快,而且错得整齐,不容易看出来。所以这一章真正要学的是:算完之后怎么核对。

学完本章后,你应该能够:

  1. 用记录与字段说清一份表的结构,并指出统计依据的是哪几个字段;
  2. 认出一批表里的常见脏数据:全空行、首尾空格、写法不一致、重复、缺失;
  3. 说清缺失值的三种处理方式对结果的影响,选定一种、写进说明、报告受影响的条数;
  4. 把多份格式相同的表合并、分组统计并导出为新文件,原始文件一个不动;
  5. 从汇总结果里抽三条记录,顺着来源列回到原始表逐项核对。
配套演示文件 第11章演示_三种算法.html 第11章演示_脏数据放大镜.html 第11章演示_生成测试数据.py 第11章演示_合并汇总.py 第11章演示_合并汇总_仅标准库.py

十份表合成一张

成品 · 数据的形状 · 五段处理链

先看结果,再看数据要长成什么样,程序才处理得了。

1.1本章成品:十份表合成一张

十位同学各交一份格式相同的成绩表,其中若干份有空行、有空格、有漏填。成品脚本把十份合并成一张,清掉这些问题,按班级统计人数与平均分,导出为一个新文件,十份原始表一个都不改动

命令提示符 · 实际运行结果
…\第11章> python 第11章演示_合并汇总.py 找到 10 份表格 (逐份读取,此处省略十行) 合并后共 99 行 去掉全空行后剩 97 行 平时分为空的记录共 6 条,已按 0 计入 班级 人数 总评平均分 数据2301 28 73.34 数据2302 36 75.45 数据2303 33 80.10 已导出 汇总.xlsx ,原始表格未作任何改动

99 行减到 97 行,以及那句"共 6 条",都是清洗过程留下的交代

标出来的那一行值得停一下。程序把六条漏填的记录按 0 分计入了,并且主动说了出来。它要是不说,这张汇总表看上去同样正常,只是平均分整体偏低两分左右,而且看不出来。

1.2数据要长成什么样,程序才处理得了

十份表能被合并,前提是它们的形状一致:

学号
姓名
班级
平时分
期末分
2023001
朱欣怡
数据2301
71
65
2023002
陈嘉伟
数据2303
98
80
2023003
陈子涵
数据2302
67
80

第一行是表头,规定了有哪些字段;以下每一行是一条记录

十份表的表头必须一字不差,合并才能对齐。名称多一个空格、括号用了全角、英文大小写不同,程序都会当成两个不同的字段,合并之后就会多出一列,而且两列都不全。收表之前先发一份统一的模板,比事后清洗省事得多。

1.3从十份表到一张汇总,中间做了五件事

第一步
读取
逐份读入并标注来源
第二步
合并
纵向摞成一张
第三步
清洗
空行、空格、缺失值
第四步
统计
按字段分组计算
第五步
导出
写入新文件

五步里最花时间的是第三步,本章的多数知识点也集中在这一步

五步之外还有一件事不在链条上,却决定这张表能不能用:拿到结果之后回原始表抽查几条。程序算得快,算错也一样快,而且错得整齐,不容易看出来。

讨论十份表如果用复制粘贴手工合并,出错的形式通常是漏掉一份、粘错一列、或者中途串行。程序合并出错的形式与这些有什么不同?哪一种更容易在事后被发现?

1.4本章新技巧:贴样本数据,不要描述数据

说"表里有班级和成绩",大模型只能自己猜:列名叫什么、顺序怎么排、分数是整数还是带小数、空值长什么样。猜错的部分要等到运行报错才暴露,而报错往往出现在第三步以后,回头改一遍成本不低。

正确的做法是把真实的表头与前三行原样贴进提示词。三行数据提供的信息比三段描述准确得多:列名的确切写法、字段的先后、数值的形式、空值出现在哪一列,全都一目了然。涉及个人信息时,把姓名一列换成占位内容即可,表头与其余字段保持原样。

三行真实数据能确定的事,三段文字描述往往说不清楚,而且说的人自己也常常记错列名。

1.5可直接复制的提示词

发给 AI主提示词 · 贴上表头与三行
我有十份格式相同的 Excel 表要合并统计,用 Python 处理,在 Windows 的终端里运行。 下面是其中一份的表头和前三行(真实的样子,姓名已用占位内容替换): 学号,姓名,班级,平时分,期末分 2023001,某某一,数据2301,88,91 2023002,某某二,数据2301,76,83 2023003,某某三,数据2302,,79 已知的脏数据情况: ① 有的表中间夹着全空行; ② 有的"班级"列前后带空格,其中包含全角空格; ③ 有的"平时分"是空的。 请写一个脚本: ① 读取指定文件夹下所有 .xlsx,并给每条记录标注它来自哪份表; ② 合并成一张; ③ 去掉五个字段全为空的行;去掉文本字段首尾的空格; ④ 平时分为空的记为 0,同时统计这样的记录共有几条,并打印出来; ⑤ 总评 = 平时分×40% + 期末分×60%; ⑥ 按班级分组,统计人数与总评平均分,保留两位小数; ⑦ 导出为 汇总.xlsx,分成"合并明细"与"按班级汇总"两个工作表; ⑧ 原始文件一个都不要改动。 文件夹名、导出文件名、分组字段、总评权重四项写在代码最上方的设置区。 每完成一步打印一行进度。每一段代码前加一行中文注释。
发给 AI补充规则 · 发现新的脏数据之后
运行之后我发现还有两种情况没处理: 第一,有两份表的表头写成了"班级 ",末尾多一个空格,合并后多出一列。 第二,有三条记录的学号重复了,是同一个人交了两次。 请在原脚本的基础上补上这两条处理: ① 合并之前先把每份表的表头首尾空格去掉; ② 学号重复时保留最先出现的一条,并打印一共去掉了几条重复记录。 【不要改动】设置区,以及已经写好的清洗与统计部分。 只给出需要新增或修改的那几行,不要重写整个脚本。
发给 AI抽查 · 让结果可以被核对
在现有脚本的末尾加一段抽查代码: 从合并后的明细里随机取三条记录,每条打印出:来源文件名、学号、姓名、平时分、期末分、 按公式算出的总评。格式排整齐,方便我照着回原表逐项核对。 如果某条记录的平时分是被填补过的 0,请在该行末尾标注"(平时分原为空)"。 只给出新增的那几行。

主提示词里的几条要求,分别对应后面要讲的知识点:

贴上真实的表头和前三行
知识点 01:列名的确切写法与字段顺序由数据本身给出,不靠描述
给每条记录标注它来自哪份表
本章方法:合并之后行的来源就看不出来了,抽查全靠这一列
去掉五个字段全为空的行
知识点 04:写明"全为空",避免只漏填一项的记录被一并删掉
统计缺失记录共有几条,并打印出来
知识点 05:只填补不报告时,平均分偏低而无从察觉
导出为新文件,原始文件一个都不改动
清洗规则要反复调整,每次都要从原始数据重跑一遍

1.6大模型的常见偏差

按它想象的列名写代码。 没有贴样本时,它会写出 df['成绩']df['score'] 这样的列名。运行时报 KeyError,而且要等程序跑到那一行才报。贴表头是成本最低的预防办法。

直接覆盖原始文件。 它常把结果写回其中一份原表,或者用同名文件覆盖。原始数据一旦被覆盖,清洗规则就再也没法重调。"原始文件一个都不要改动"这句必须写进要求。

把空值按 0 计入却不作说明。 这是本章最需要警惕的一种。程序不报错,结果看上去完全正常,只是平均分整体偏低。要求里必须写明"统计这样的记录共有几条,并打印出来"

清洗只做半套。 它通常会处理空行,但未必会处理首尾空格,更不会主动想到全角空格。已知的脏数据情况要在提示词里逐条列出,列几条它处理几条。

备用方案 机房网络装不上 pandas 时,改用 第11章演示_合并汇总_仅标准库.py。它读取生成脚本一并产出的 原始表格_CSV 文件夹,处理逻辑与 pandas 版完全相同,两者算出的人数与平均分逐项一致,不需要安装任何第三方库。完全跑不起 Python 的机器,可以先开 第11章演示_三种算法.html第11章演示_脏数据放大镜.html,本章最要紧的两件事都在这两个网页里。

数据的形状与脏数据 本章重点

结构化数据 · CSV · JSON · 脏数据

本章的核心内容集中在这一部分和下一部分。

本章要学的术语 · 八个
01结构化数据structured data
02CSV 与 XLSXcsv / xlsx
03JSONjson
04脏数据dirty data
05缺失值missing value
06去重deduplicate
07分组统计group by
08算法algorithm

这八个术语会原样出现在数据文档、统计报告与技术规范里。前四个在本部分讲解,后四个在第三部分讲解。

2.1四个知识点,一个一个看

01结构化数据:一行一条记录,一列一个字段
学号
姓名
期末分
2023001
朱欣怡
65
2023002
陈嘉伟
80

横看是一条记录,竖看是一个字段

现象:十份表打开来长得一样:第一行是列名,下面每一行是一个人。

概念:记录(record)指表里的一行,对应一个具体对象;字段(field)指一列,对应这个对象的一项属性。行列都对齐的数据叫结构化数据(structured data)。数据能被程序自动处理的前提,就是它具备这个形状。

类比:花名册。每人占一行,每项信息占一列,谁排在第几行不影响这一行说的是谁。

02CSV 与 XLSX:两种表格文件的差别
成绩表_01.csv用记事本打开
学号,姓名,班级,平时分,期末分 2023001,朱欣怡,数据2301,71,65 2023002,陈嘉伟,数据2303,98,80 ……

CSV 本身就是纯文本,逗号就是列的分界

现象:同一份数据存成 .csv,用记事本能直接看;存成 .xlsx,用记事本打开是一堆乱码。

概念:CSV(Comma-Separated Values,逗号分隔值)是最朴素的表格文件,一行一条记录,字段之间用逗号隔开,任何程序都能读。.xlsx 是压缩过的二进制格式,能保存公式、格式与多个工作表,但必须由专门的库来解析。只做数据交换时优先用 CSV,需要保留格式时才用 xlsx。

类比:CSV 是毛坯房,xlsx 是精装修。搬运的时候毛坯省事。

03JSON:表格装不下的数据长什么样
一条记录的 JSON 写法花括号是对象,方括号是列表
{ "学号": "2023001", "姓名": "朱欣怡", "成绩": [71, 65], "备注": null }

认这两种括号,就能看懂大半个 JSON

现象:从网站或者接口拿回来的数据,常常是一堆花括号和方括号,不是表格。

概念:JSON 是另一种通用的数据格式。花括号 { } 表示一个对象,里面是"名字:值"的成对内容;方括号 [ ] 表示一个列表,里面是同类的若干项。两者可以互相嵌套,所以 JSON 装得下表格装不下的层级结构,例如一个人对应多次成绩。null 表示这一项没有值,也就是缺失。

类比:表格是一张登记表,JSON 是一叠可以夹着子文件的档案袋。

04脏数据:不符合预期形状的那些记录
学号
班级
平时分
2023061
数据2302
83
(空)
(空)
(空)
2023073
␣数据2301␣
(空)

这里有三处问题:全空行、首尾空格、缺失值

现象:合并之后总行数是 99,去掉全空行只剩 97。

概念:不符合预期形状的数据统称脏数据(dirty data)。常见的有五类:全空行、单元格首尾的空格(含全角空格)、同一字段写法不一致、重复记录、缺失值。清洗指按事先定好的规则逐类处理,规则必须写下来,否则同一批数据两次处理会得到不同的结果。

类比:收上来的登记表总有人乱填,正式录入之前要先统一一遍。

2.2三类脏数据的处理口径

清洗不是逐条去改,是先定口径,再让程序按口径统一处理。三类最常见的,口径分别是:

第一类
重复记录
先说清哪个字段是唯一的(学号、身份证号、订单号),按这个字段去重,保留最先出现的一条并打印去掉了几条。
按"整行完全一样"去重,改过一个字的重复行就留下了
第二类
空值
记为 0、按其余字段折算、整条剔除,三选一,全表一致,并报告条数。
这一列填 0、那一列剔除,同一张表两套口径
第三类
写法不一致
先把这一列出现过的取值全部列出来看一遍,再定一张映射表,把同义的写法归到一处。
直接凭印象替换,漏掉自己没想到的那几种写法

打开 第11章演示_脏数据放大镜.html,那里把看不见的空格显形出来,并且把清洗前后的分组结果并排放着。看完你会记住一件事:肉眼看不出来,不代表它不影响结果。

2.3本章的完整代码

下面这份代码由大模型生成,对应五段处理链,另加一段抽查用的输出。

第11章演示_合并汇总.py正文 · 可直接运行
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('按回车键结束')

下面这张表的第三列还是反例:把这一行改掉或者删掉,会发生什么。

one['来源文件'] = one_file.name
给每条记录标注它来自哪份表。合并之后行的先后已经看不出来源,这一列是抽查时唯一的线索。 没有这一列时,发现一条数字不对也找不回是哪份表填错的
pd.concat(tables, ignore_index=True)
把十张表纵向摞成一张。依据的是字段名,字段名对不上的会各占一列。 表头有一份写成"班级 "时,合并结果会多出一列,且两列都不全
data.dropna(… how='all')
去掉五个字段全为空的行。how='all' 表示全空才删,只空一两项的记录保留。 写成 how='any' 时,凡有一项漏填的记录都会被删掉,人数直接少一截
data[col].str.strip()
去掉文本首尾的空格。全角空格也在其中,这一步做完,带空格的班级才会与正常的归为一堆。 不做这一步,分组结果里会多出一个看上去一模一样的班级
isna().sum() … fillna(0)
先数清楚有几条缺失,再统一填 0。顺序不能颠倒,填完就数不出来了。 只填不数时,平均分偏低而没有任何提示
data.groupby(GROUP_BY).agg(…)
按班级归堆,每堆算人数与平均分。count 数的是学号非空的条数。 归堆字段没清洗干净时,同一个班会被拆成两行

缺失值、去重、分组与算法 本章重点

三种算法三个平均分 · 错得整齐 · 先抽查三条

这一部分讲的是:同一批数据,为什么能算出不同的结论。

3.1另外四个知识点

05缺失值:怎么处理,决定了平均分是多少
处理方式
全体平均
记为 0
76.42
按期末分折算
78.22
整条剔除
78.58

同一批数据,三种做法差 2.16 分

现象:平时分那一列有六个空格子。

概念:缺失值(missing value)指某条记录的某个字段没有数据。常见处理有三种:记为 0、按其余字段折算、整条记录剔除。三种做法算出的结果互不相同,所以必须选定一种、写进说明、并报告受影响的条数。把空值悄悄按 0 计入而不加说明,平均分会偏低,而且从结果上看不出偏低。

类比:统计月考成绩时,缺考按 0 分算、按不计入算、还是整个人不统计,班级平均分是三个不同的数。

06去重:先说清哪个字段是唯一的
按整行相同 改过一个字就不算重复重复的还在,人数偏多
按学号相同 同一个学号只留一条人数对得上

去重之前要先回答:这份数据里,哪一列能唯一确定一个对象

现象:汇总出来的人数比实际报名人数多了三个,翻明细才发现有人交了两次。

概念:去重(deduplicate)指把指向同一个对象的多条记录合成一条。关键不在于怎么删,而在于依据哪个字段判断"是同一个"。学号、身份证号、订单号这类唯一标识才靠得住。按整行完全相同去重看似保险,实际上只要有人第二次填时改了一个字,两条就都留下了。去重之后要打印去掉了几条。

类比:合并两份通讯录时,按姓名合会把重名的人并成一个,按手机号合才不会。

07分组统计:先归堆,再算每一堆
班级
人数
总评平均分
数据2301
28
73.34
数据2302
36
75.45
数据2303
33
80.10

97 条记录归成三堆,每堆输出一行

现象:汇总表里一个班占一行,后面跟着人数与平均分。

概念:分组统计(group by)指先按某个字段把记录归堆,再对每一堆分别计算。归堆所依据的字段必须是干净的,否则"数据2301"与带空格的"数据2301"会被分成两个班,两行人数都不对。归堆之后先看组数:组数比你预期的多,多半就是这一列还没洗干净。

类比:把货按产地分开,再分别称重。产地标签写乱了,称出来的重量就归错了地方。

08算法:筛、排、去重都是算法
做的事
数据翻十倍
逐条筛选
大约慢十倍
排序
比十倍略多一些
两两比对找重复
大约慢一百倍

同样是"多了十倍数据",代价差别很大

现象:一百行的表瞬间跑完,换成一万行的表,同一个脚本要等好几分钟。

概念:算法(algorithm)就是完成一件事的固定步骤。筛选、排序、去重、分组各有各的算法,区别不只在快慢,更在于数据量翻倍时代价怎么涨。逐条看一遍的做法,数据翻十倍就慢十倍;而"每一条都和其余每一条比一遍"的做法,翻十倍要慢一百倍。写规则的人不必会实现算法,但要知道自己提的要求属于哪一类。

类比:在一叠卡片里找一张,是从头翻一遍;把每两张卡片都比一次看有没有重复的,工作量完全不是一个量级。

本章方法 先抽查三条,再信全表。程序算得快,算错也一样快。

3.2六条缺失值,三种算法,三个平均分

测试数据里平时分有六条空白。下面三行是同一批数据用三种方式处理后的真实结果:

处理方式规则参与人数全体平均 数据2301数据2302数据2303
记为 0空的平时分按 0 分参与计算9776.42 73.3475.4580.10
按期末分折算缺平时分的记录,总评直接取期末分9778.22 77.1577.3380.10
整条剔除缺平时分的记录不参与统计9178.58 78.8876.9180.10
最后一列三行完全相同,因为数据2303 这个班没有缺失值

三种做法都不算错。差别在于它们回答的不是同一个问题。全体平均分相差 2.16 分。数据2303 那一列三行完全相同,因为这个班没有缺失值;受影响的只有另外两个班,而且受影响的程度还不一样。这说明缺失值的处理方式不只改变总数,还会改变各组之间的相对高低。看第一行与第二行:按"记为 0",数据2301 的 73.34 低于数据2302 的 75.45;按"整条剔除",数据2301 的 78.88 反而高于数据2302 的 76.91。换一条规则,两个班的高低就掉了个个儿,而这条规则并没有写在表上。

打开 第11章演示_三种算法.html,切换三种规则,看这三行数字怎么动。盯住数据2303 那一列,它一动不动。

三种做法都要写进说明。 交出去的汇总表如果只有数字没有规则,看表的人无从判断这个平均分意味着什么。规范的做法是在结果里同时给出受影响的记录条数与所采用的规则,本章代码里那句"平时分为空的记录共 6 条,已按 0 计入"就是这个作用。
讨论如果这份成绩要用于评奖学金,你会选哪一种处理方式?如果只是给任课教师看班级整体情况呢?同一批数据、同样三种算法,为什么用途不同会导致选择不同?

3.3错得整齐

前面几章的错误都会让你看见:页面破了、程序报错、文件数量少了。这一章的错误不会。

程序算得快,算错也一样快,而且错得整齐:每一行的格式都对,小数点都保留两位,表格排得整整齐齐,只是数字是错的。整齐会让人放松警惕,这正是它比报错危险的地方。

所以本章的收尾动作是固定的三步,缺一步这张汇总表就不能交:

1
核对行数的变化
合并后多少行、清洗后剩多少行、缺失几条,三个数字要能互相解释。清洗前后的差说得清,才算清洗完
2
核对分组之和
各组人数加起来应当等于参与统计的总条数。对不上,说明有记录既不属于这一组也不属于那一组,多半是归堆字段没洗干净
3
抽查三条回原表
顺着来源列打开那一份原表,找到这个学号,把各项原始数值与按公式手算的结果逐个对一遍。三条都对得上,才认这张汇总表

第三步能做成,全靠合并时多写的那一列来源文件。合并之后行的先后已经看不出来源,这一列是抽查时唯一的线索。没有它,你发现一条数字不对,也找不回是哪份表填错的。

3.4原始数据只读

还有一条纪律贯穿整章:程序只读取原始文件,结果一律写进新文件。

原始表格 只读取,不写入修改时间不变
汇总.xlsx 每次运行重新生成可以随便删
覆盖原表 清洗规则一改就要重来无法复原

清洗规则往往要调七八次,每次都要从原始数据重跑一遍

这也是脚本不该提供"就地修改"这个选项的原因。底稿要留着。誊写出错可以重抄,底稿改了就没有依据了。

完整演示与一条行业共识

装库 · 合并 · 清洗 · 抽查

从十份带毛病的表开始,走到一张能交出去的汇总表。

4.1先把两个库装上

pip install pandas openpyxl
一次装两个。pandas 负责表格计算,openpyxl 负责读写 .xlsx。装一次即可,之后写多少脚本都能用。
pip install pandas openpyxl -i 镜像地址
默认源超时时改用国内镜像。镜像地址请自行查询学校或公开镜像站的说明。
pip list
列出已装的库,用来确认这两个确实装上了。
python -c "import pandas"
没有任何输出就表示能正常导入;有报错说明装的位置不对。

4.2演示步骤

1
运行生成脚本,得到十份带毛病的原始表
脚本会同时生成 xlsx 与 CSV 两套。脏数据是事先安排好的:第 07 份中间有两个全空行,第 09 份班级列含全角空格,第 03、05、08 份平时分有空值
产出:两个原始数据文件夹
2
打开其中一份,抄下表头与前三行
这三行要原样贴进提示词。姓名一列用占位内容替换,其余保持原样
产出:一段样本数据
3
用主提示词生成脚本,先只读一份、打印前五行
在合并十份之前,先确认单份读得对:列名与表头一致吗,数字有没有被读成文本。这一步不通过就不要往下走,后面所有的数字都建立在读得对的基础上
产出:单份读取正确的确认
4
合并十份,观察行数的三次变化
合并后 99 行、去掉全空行后 97 行、缺失值 6 条。三个数字都要对得上,对不上就回去查是哪一步
产出:一张合并明细
5
抽三条记录,回原表逐项核对
照着来源文件名打开那一份表,找到对应学号,把平时分、期末分与总评三项逐个对一遍。三条都对得上,才认这张汇总表
产出:三条核对记录
6
换一种缺失值处理方式,再跑一次
把"记为 0"改成"整条剔除",对比两次的平均分。这一步用来说明为什么规则必须写进说明
产出:两组可对比的结果
7
导出汇总表,确认原始文件未被改动
查看十份原始表的修改时间,应当与第 1 步生成时一致
产出:汇总.xlsx 与只读验证

4.3前沿三分钟:清洗占掉的那部分时间

每章固定栏目 · 一条与本章内容相关的行业动态

数据行业流传多年的一个说法是,一个数据项目里,真正用于分析与建模的时间只占一小部分,其余大半花在收集、清洗与核对上。具体比例各家统计不一,但从业者对"清洗占大头"这一点基本没有分歧。本章五段处理链里,第三步的篇幅明显长于其他四步,就是这个结构的缩影。

由此产生的应对办法有两个方向。一是把清洗规则固定下来、写成可以重复执行的脚本,而不是每次在表格软件里手工改一遍,这样规则可以被检查、可以复用,也可以交给别人执行。二是把问题往前推,在收数据的环节就用统一模板、限定填写格式,从源头减少脏数据。第二种办法省下来的工作量,通常比第一种多得多。

对本章的实际影响是:不要把清洗当成杂活。清洗规则就是这份数据的定义,规则不写下来,结果也就说不清楚。

讨论与其事后写脚本清洗,不如在收表时就统一模板、限定填写格式。既然如此,为什么清洗脚本仍然不可缺少?在什么情况下你无法控制数据的来源?

上机实验 作业 3

三选一 · 六步 · 抽查三条

上机环节由此开始,本次上机的成果就是作业 3,当堂完成并提交。

5.1三个题目,任选其一

题目 A
报名表自动汇总

多份报名表合并、按学号去重,按项目统计报名人数。

通用
题目 B
问卷结果自动出结论

多份问卷合并,统计单选题各选项的占比与有效答卷数。

偏文科
题目 C
实验数据批量算均值

多批次数据合并,按批次统计均值与标准差。

偏理工

三个题目都必须包含合并、清洗、分组统计、导出新文件四个环节,并且要在结果里报告缺失或无效记录的条数。数据可以自行准备,也可以改造生成脚本产出。允许更换题材。

5.2上机六步

1
准备至少六份格式相同的数据,其中两份带脏数据
可以改造生成脚本。脏数据要自己安排并记下来,验收时要说明处理了哪几类
产出:原始数据文件夹
2
抄下表头与前三行,写进主提示词
连同已知的脏数据情况一起写进去,有几类写几类。不要用文字描述代替真实数据
产出:一份完整的提示词
3
先只读一份,打印前五行,确认列名与类型正确
这一步不通过就不要往下走,后面所有的数字都建立在读得对的基础上
产出:单份读取截图
4
合并全部数据,完成清洗与分组统计
屏幕上要能看到行数的变化过程与缺失记录的条数,这些数字是验收依据
产出:运行截图与汇总结果
5
抽三条记录回原表逐项核对,并记录核对过程
写明每条来自哪份文件、原始各项数值是多少、按公式手算的结果与程序输出是否一致。没有这一步的作业不予验收
产出:三条核对记录
6
组内互测:把脚本与数据发给同组同学,在他的机器上跑一遍
重点确认路径没有写死,缺失值的处理规则在说明里写清楚了
产出:一行互测记录

5.3共同验收标准

原始文件未被改动 结果一律写入新文件 查看原始文件的修改时间,应与准备数据时一致
行数的变化过程被打印出来 合并后、清洗后各是多少行 运行截图里能看到这两个数字,且与数据实际情况相符
缺失或无效记录的条数有报告 不只是处理掉,还要说有几条 屏幕输出或汇总表中能找到这个数字
分组字段已清洗 同一个组不会出现两行 查看汇总结果的组数,与实际组数比对;各组人数之和等于参与统计的总条数
抽查三条全部对得上 来源、原始值、手算结果三项一致 照文档里的记录回原表复核任意一条
处理规则写进了说明 缺失值怎么处理、重复怎么处理 文档里有一段文字明确写出规则,而不只是贴代码

5.4常见错误与处理

ModuleNotFoundError: No module named 'pandas'
第三方库还没装,或者装到了另一个 Python 环境
运行 pip install pandas openpyxl;仍不行时改用仅标准库版本
pip 长时间无响应,最后超时
默认从境外服务器取包
命令后面加 -i 指定国内镜像源,重新安装
KeyError: '成绩'
代码里的列名与表头对不上,多半是没贴样本
先打印一次 data.columns 看真实列名,再把表头补进提示词重新生成
合并后多出一列,两列都不全
某几份表的表头首尾带空格
合并之前先统一去掉表头的首尾空格
分组结果里同一个班出现两行
分组字段没有清洗,带空格与不带空格被当成两个值
对分组字段先去空格,再分组统计
人数比实际少了一大截
去空行时用了"任一字段为空即删"
改成"五个字段全为空才删",即 how='all'
数字列被当成文本,无法求平均
原表里这一列混入了空格或者全角字符
清洗后把该列转成数值类型,转不了的记录单独打印出来
PermissionError: 汇总.xlsx
导出的文件正在 Excel 里打开着
关掉该文件后重新运行

5.5提交内容

脚本与原始数据
.py 文件,以及全部原始数据文件
压缩成一个包
运行截图
看得到行数变化、缺失条数与分组结果
一张截图
汇总结果与核对记录
导出的汇总文件,以及三条抽查的逐项对照
文件加三段文字
脏数据处理说明
处理了哪几类、各按什么规则处理、各影响多少条
一段文字

提交方式:按教师提供的作业模板填写,文档开头注明所选题目。提交渠道见课堂通知,当堂提交。

作业 3 评分构成占比
数据结构理解与字段设计
30%
处理链路完整可复现
40%
结果正确性与核对过程
30%

5.6再进一步(选做)

把分组统计从一个字段扩展到两个字段,例如同时按班级与性别分组,看每一格的人数与平均分。

做完会碰到一个新情况:格子变多之后,某些格子里只有一两个人,平均分随之出现极端值。样本量小到什么程度时,平均分就不该再单独拿出来看,需要由你自己定一个下限并写进说明。

本章小结

本章五条结论

  1. 结构化数据就是一张表,一行一条记录,一列一个字段。十份表能合并的前提是表头一字不差。
  2. 清洗规则就是这份数据的定义。规则不写下来,同一批数据两次处理会得到不同的结果,而结果本身看不出差别。
  3. 缺失值怎么处理,直接决定统计出来的数字。必须选定一种规则、写进说明、并报告受影响的条数。
  4. 程序算得快,算错也一样快,而且错得整齐。行数、分组之和、抽查三条,这三步是这一章的收尾动作。
  5. 合并时给每条记录标注来源。合并之后行的先后已经看不出来源,这一列是抽查时唯一的线索。
本章脉络