简介:面向高职与本科院校大数据相关专业学生,也适合转行数据分析的自学者,这份实操数据包聚焦数据清洗环节,覆盖缺失值处理、异常值检测、格式统一、重复值剔除等常见预处理场景。压缩包共含11个文件,包括3个数据库脚本、2个逗号分隔表格、2个文本文档、2个Excel电子表格、1个JSON数据文件和1个XML数据文件,整体体积仅96KB,便于下载和课堂分发。已有528人学习下载,适合教学演示与自主训练。借助这套多格式数据源,读者可配合Python的Pandas库、数据库查询或ETL工具,完整经历从原始数据导入、清洗到输出可用数据集的流程,理解不同文件格式下的编码问题、缺失值填充与一致性检查要点,并体会数据质量对后续分析结论的影响。同时,多类型数据还能用于模拟异构数据合并、清洗规则编写与效果验证等实战任务,为真实项目中的数据预处理奠定扎实基础。
1. 拿到“数据清洗数据源.zip”之后,先别急着解压
做数据分析这行,隔三差五就会收到这样的压缩包:名字叫“数据清洗数据源.zip”,里面可能是几个CSV、Excel文件,也可能夹着JSON、TXT,甚至还有子文件夹。刚入职那会儿我也踩过坑,拿到压缩包直接双击解压,然后拖进编辑器就开始跑,结果数据量一大直接内存爆炸,字段类型全乱套,日期格式五花八门,中文字段名乱码……光清洗就花了三天。
实际上,“数据清洗数据源.zip”这个标题背后藏着一个典型的数据处理场景:你拿到的是一个多文件、多格式、需要统一处理的原始数据集,第一步不是写清洗逻辑,而是先把“数据源”这层皮扒干净。也就是要做好解压、文件结构摸底、编码识别、字段探查这几件基础工作,后面再谈清洗。
这篇文章按我自己的实操路径来写,适用场景是:本地或邮件收到一个zip压缩包,里面是多张表,需要合并清洗后输出给下游分析或建模。涉及的核心工具是Python的pandas加标准库zipfile,也会穿插一些命令行技巧。
2. 解压与数据源摸底:这一步做得好,后面省一半事
2.1 用Python解压而不是双击
双击解压当然方便,但在数据工程场景里,我建议用脚本解压,原因有三个:
- 可复现:下次拿到同样结构的压缩包,跑一遍脚本就行。
- 可处理异常:比如压缩包损坏、编码异常、密码保护等,脚本里能捕获。
- 可自动登记:解压后直接生成文件清单,方便后续做数据字典。
zipfile是Python标准库,不需要额外安装。基础用法如下:
import zipfile from pathlib import Path zip_path = Path("数据清洗数据源.zip") extract_dir = Path("./data_raw") extract_dir.mkdir(exist_ok=True) with zipfile.ZipFile(zip_path, "r") as zf: # 先打印压缩包里的文件清单 for info in zf.infolist(): print(f"{info.filename} | 原始大小: {info.file_size} | 压缩后: {info.compress_size}") zf.extractall(extract_dir)这里有个细节:直接用extractall()解压,如果压缩包里文件名包含中文,在部分Windows系统上会出现乱码。原因在于zipfile库默认用cp437编码解析文件名,而中文Windows环境下很多压缩包是用GBK编码写入的。解决办法是手动重命名:
import zipfile with zipfile.ZipFile(zip_path, "r") as zf: for info in zf.infolist(): # 尝试用gbk解码文件名,失败则保持原样 try: decoded_name = info.filename.encode("cp437").decode("gbk") except (UnicodeDecodeError, UnicodeEncodeError): decoded_name = info.filename zf.extract(info, extract_dir) # 如果解码后的名字和原始名字不同,重命名文件 if decoded_name != info.filename: (Path(extract_dir) / info.filename).rename(Path(extract_dir) / decoded_name)注意:以上重命名写法要求文件在压缩包内是平铺的,如果有嵌套目录,需要先拼接完整路径再做rename。更稳妥的做法是遍历
zf.infolist(),对每个entry构建目标路径后再移动。
2.2 用命令行unzip快速摸底
如果你在Linux服务器或macOS终端上操作,unzip -l这个命令我几乎每次都用,它在不真正解压文件的情况下列出压缩包内容,效率极高:
unzip -l 数据清洗数据源.zip输出类似:
Archive: 数据清洗数据源.zip Length Date Time Name --------- ---------- ----- ---- 1024 2024-01-15 09:30 order_202401.csv 2048 2024-01-15 09:31 user_info.xlsx 512 2024-01-15 09:32 readme.txt通过这个清单,你能快速判断:有几张表、什么格式、大概多大。如果发现里面有readme.txt或数据字典.docx这类文件,先读它,往往比你自己猜字段含义高效得多。
2.3 压缩包损坏?先排查再干别的
如果你解压时遇到invalid zip archive: could not find eocd这类报错,先别急着怀疑代码。这个错误的意思是“找不到中央目录结束标记(End of Central Directory)”,常见原因有两个:
- 文件下载不完整,zip文件被截断了。
- 文件本身不是zip格式,只是扩展名改了。
排查方式:
file 数据清洗数据源.zip如果输出显示Zip archive data,说明格式没问题;如果显示data或HTML document,说明文件就不是zip,硬解压肯定失败。另外也可以用unzip -t测试压缩包完整性:
unzip -t 数据清洗数据源.zip输出末尾如果显示No errors detected in compressed data of 数据清洗数据源.zip,说明文件完整。
3. 读数据别一把梭:多源格式的读取策略
3.1 先看扩展名,再选读取函数
解压完之后,你手里可能是这样一个文件集合:
| 文件 | 格式 | 预计用途 |
|---|---|---|
| order_202401.csv | CSV | 订单表 |
| user_info.xlsx | Excel | 用户信息表 |
| product_detail.json | JSON | 商品明细 |
| readme.txt | 文本 | 字段说明 |
pandas提供了read_csv、read_excel、read_json等函数分别对应不同格式,但实际工作中最常见、也最容易出问题的是CSV。我见过太多人在read_csv上栽跟头,核心原因就两个:分隔符不对和编码不对。
import pandas as pd # 读取CSV时建议显式指定编码和分隔符 df_order = pd.read_csv( "data_raw/order_202401.csv", encoding="utf-8-sig", # 带BOM的utf-8也能读 sep=",", dtype={"order_id": str}, # 防止订单号被读成科学计数法 )encoding="utf-8-sig"比encoding="utf-8"多一个好处:它能自动处理带BOM头的文件。如果你不确定文件编码,推荐用chardet或charset-normalizer检测一下:
from charset_normalizer import from_path result = from_path("data_raw/order_202401.csv").best() print(result.encoding)Excel文件的读取相对省心,但要注意.xls和.xlsx的差异——read_excel底层依赖openpyxl(针对.xlsx)和xlrd(针对.xls,新版本xlrd只支持.xls,不支持.xlsx),如果环境里没装对应的库,读取会直接报错。建议提前安装好依赖:
pip install pandas openpyxl xlrd3.2 字段类型推断:pandas的“好心”有时候是帮倒忙
pandas读取数据时会自动推断字段类型,这看起来很方便,但实际使用中坑很多。举个例子,订单号如果只有数字组成,且超过15位,pandas会自动读成int64,后面做字符串匹配时就容易出问题;如果没有数据超过15位但字段本身应该保留前导零(如“00123”),pandas会直接去掉前导零。
所以在读取阶段就统一指定dtype,是一门必修课。我通常是这样处理的:
dtype_mapping = { "order_id": str, "user_id": str, "product_id": str, "amount": float, "quantity": int, } df = pd.read_csv("data_raw/order_202401.csv", dtype=dtype_mapping)注意:如果你指定了dtype为str,但原字段里有空值,pandas读出来的空值会变成NaN(float类型)而不是None或空字符串。这一点在后续清洗时要特别留意。
3.3 多数据源的表结构对齐
如果压缩包里的多张表需要合并,强烈建议先打印每个表的columns和dtypes,形成一张“字段对比表”。别急着merge,先确认哪些字段是公共键、哪些字段同名不同义、哪些字段同义不同名。
for name, df in [("order", df_order), ("user", df_user)]: print(f"=== {name} ===") print(df.columns.tolist()) print(df.dtypes) print(f"行数: {len(df)}")这一步能帮你发现很多问题:比如订单表里的user_id是字符串,用户表里的user_id是整数,后面merge时明明看起来一样的值却匹配不上,十有八九就是类型不一致。我遇到过的真实案例中,这种问题占比非常高。
3.4 数据量大的时候,别一次性全读进内存
如果你的CSV有几百MB甚至几个GB,pd.read_csv()默认会全部读入内存,很容易导致内存不足。更合理的做法是分块读取或只读取需要的列:
# 只读取需要的列 df = pd.read_csv( "data_raw/order_202401.csv", usecols=["order_id", "user_id", "amount", "created_at"], ) # 分块读取,适合做清洗预览 chunk_iter = pd.read_csv( "data_raw/order_202401.csv", chunksize=50000, # 每次读5万行 ) for chunk in chunk_iter: # 对chunk做清洗,然后增量写入目标文件 process(chunk)对于真正的大文件,我还会考虑用polars替代pandas,它的惰性计算和内存效率在多数场景下优于pandas。不过这是另一个话题,今天先不展开。
4. 清洗实操:从无到有建一套可复用的处理流水线
4.1 缺失值处理:先问“为什么缺失”,再决定怎么填
缺失值处理是数据清洗里最核心的环节,但很多人上来就fillna(0)或dropna(),这是比较危险的做法。我的原则是:先搞清楚缺失的机制。缺失分为三种:完全随机缺失、随机缺失、非随机缺失。不同的缺失机制对应的处理策略完全不同。
举个例子,订单表中的pay_time字段如果大量缺失,一种可能是用户下单后未支付(业务原因),另一种可能是上游系统没记录(技术原因)。前者不需要填充,后者可能需要从其他表补数据。
实操上,第一步是摸清缺失情况:
import pandas as pd def missing_report(df): missing = df.isnull().sum() missing_pct = missing / len(df) * 100 report = pd.DataFrame({ "缺失数量": missing, "缺失占比(%)": missing_pct.round(2), }) return report[report["缺失数量"] > 0].sort_values("缺失数量", ascending=False) print(missing_report(df_order))然后根据业务含义分情况处理:
| 场景 | 处理方式 |
|---|---|
| 数值型字段缺失,且业务上不允许为空 | 用中位数或均值填充,但必须记录填充逻辑 |
| 分类型字段缺失 | 单独给一个“未知”类别,不要强行填众数 |
| 时间字段缺失 | 如果同表有下单时间,可以用下单时间近似 |
| 不重要且缺失占比>70% | 直接删列,但要在清洗报告中记录 |
提示:任何填充动作都要在清洗报告中留痕。这也是专业数据工作者和业余选手的分水岭。
4.2 重复值处理:不是所有重复都要删
很多重复值处理教程一上来就教你drop_duplicates(),但实际业务里的“重复”需要自己定义——是全部字段重复才算,还是关键字段重复就算?
比如订单表里,同一个订单号可能因为系统重推而出现两行记录,这两行中有一个字段(如update_time)不同。这时候如果你用drop_duplicates(subset=["order_id"]),默认保留第一个出现的记录,你可能会丢掉最新更新的那一版。
正确做法是先按时间排序,再删除重复:
df_order = df_order.sort_values("update_time", ascending=False) df_order = df_order.drop_duplicates(subset=["order_id"], keep="first")这段代码的含义是:按更新时间倒序排,然后对每个order_id保留最新的记录。要注意的是,keep="first"是保留排序后的第一条,所以先排倒序,再保留“第一条”,取到的就是最新记录。
如果你想看清楚重复的规模,可以先分组统计:
dup_count = df_order.groupby("order_id").size() dup_records = dup_count[dup_count > 1] print(f"重复订单数: {len(dup_records)}")4.3 格式统一与异常值清洗
格式统一是数据清洗里最琐碎、也最体现耐心的部分。常见问题包括:
- 日期格式不统一(2024/01/01、2024-01-01、20240101共存)
- 字符串字段里有不可见字符(全角空格、换行符、\u3000)
- 数值字段里混入千分位逗号或货币符号
- 手机号、身份证号等字段被Excel科学计数法“改造”过
日期格式统一是我最常用的操作,推荐用pd.to_datetime(),并指定errors参数:
df["created_at"] = pd.to_datetime( df["created_at"], format="mixed", # pandas 2.0+支持自动混合格式解析 errors="coerce", # 解析失败转成NaT,而不是直接报错 )注意:解析失败转成NaT后,你需要回头检查这些无法解析的值,它们往往隐藏着数据质量问题的线索。
字符串清洗方面,我习惯用.str系列方法:
# 去首尾空白(包括全角空格) df["user_name"] = df["user_name"].astype(str).str.strip() # 去掉中间的全角空格 df["user_name"] = df["user_name"].str.replace("\u3000", "") # 统一大小写(比如邮箱) df["email"] = df["email"].str.lower()4.4 中文乱码的终极大法
处理中文数据源,最怕的就是乱码。解决思路分三步:
- 用
charset_normalizer检测原始编码。 - 读取时明确指定编码。
- 输出时统一用
utf-8-sig,方便Excel直接打开不乱码。
df_order.to_csv("output/order_cleaned.csv", index=False, encoding="utf-8-sig")如果数据源已经乱码了,想“还原”几乎不可能,因为信息已经在编码转换中丢失了。这种情况下只能回到源头,重新获取正确编码的文件。这也是为什么我一直在强调:读文件时先检测编码,不要用默认参数。
5. 数据源合并:多表关联的正确打开方式
5.1 merge前的“三查”
多数据源清洗的最后一步通常是合并,而合并之前的准备工作决定了合并的质量。我每次merge前都会做三件事:
- 查键类型:关联字段类型必须一致,见4.3中提到的user_id例子。
- 查键重复:如果关联键在左表或右表有重复,merge后会产生笛卡尔积,行数会暴增。所以先检查重复。
- 查键覆盖率:用
isin或merge(how="left", indicator=True)确认左表有多少记录能匹配到右表。
df_merged = df_order.merge( df_user, on="user_id", how="left", indicator=True, ) # 检查匹配情况 print(df_merged["_merge"].value_counts())_merge列的值有三种:both(两边都有)、left_only(只在左表)、right_only(只在右表)。如果left_only占比过高,说明很多订单对应的用户信息缺失,这种数据在下游分析时是要特别标注的。
5.2 concat追加多表时的字段对齐
如果压缩包里是多个结构相同的月度文件(比如order_202401.csv、order_202402.csv),用pd.concat纵向拼接:
df_all = pd.concat([df_jan, df_feb, df_mar], ignore_index=True)这里有个隐藏坑:如果某个月份文件新增了一列,其他月份没有,直接concat后缺失的列会自动补NaN。这本身不是问题,但如果新列的业务含义需要统一,就要在合并前对齐列名。
一种更稳妥的做法是先统一列顺序:
common_cols = df_jan.columns.tolist() df_feb = df_feb.reindex(columns=common_cols)5.3 多数据源的“数据血缘”记录
数据清洗做完之后,我强烈建议你顺手生成一份“数据血缘说明”,至少包含:每个字段来自哪个源文件、做了哪些清洗操作、清洗前后的行数和关键指标对比、输出文件路径。这份说明不仅是给同事看的,也是给未来的自己看的——三个月后你回过头来用这批数据时,如果没有这份记录,几乎等于重新踩一遍坑。
6. 常见问题排查:打包好的避坑指南
6.1 zip解压相关的报错
| 报错信息 | 可能原因 | 解决办法 |
|---|---|---|
could not find eocd | 文件下载不完整或不是zip | 用file命令确认格式,重新获取文件 |
not all files were readable | 压缩包内某个文件损坏 | 用unzip -t逐文件检查,单独提取损坏文件 |
| 文件名乱码 | 编码问题(GBK vs UTF-8) | 用cp437重编码再解码,或改解压库 |
| 缺少分卷 | zip分卷压缩后只拿到一部分 | 确认所有.z01、.zip文件在同一目录 |
6.2 pandas读数据报错
| 报错信息 | 可能原因 | 解决办法 |
|---|---|---|
ParserError: Error tokenizing data | 分隔符不是逗号 | 指定sep,常见的有\t、;、` |
UnicodeDecodeError | 编码不对 | 用charset_normalizer检测编码 |
MemoryError | 文件太大 | 分块读取、只读必要列、换polars |
Excel file format cannot be determined | 文件扩展名和实际格式不符 | 用file命令确认,改用正确读取方式 |
6.3 清洗过程中容易“翻车”的三个细节
- 不要在原DataFrame上直接改:建议操作前先
df_copy = df.copy(),避免后续代码出错时数据已经被污染。 - 不要用循环逐行清洗:pandas的向量化操作比逐行
iterrows()快几个数量级,数据量大时差距尤其明显。 - 不要忽视索引:合并或分组后索引可能乱掉,需要时用
reset_index(drop=True)。
根据我自己的实操经验,数据清洗这件事,80%的时间花在“处理数据源本身的问题”上,只有20%的时间真正花在写清洗逻辑上。很多人一上来就写清洗函数,结果被乱七八糟的数据源折磨得欲哭无泪。这个数据清洗数据源.zip的案例正好说明:从解压、摸底、读取、探查到清洗、合并,每一步都有章可循。把这套流程跑熟了,后面遇到任何“新压缩包”都不会慌。
本文还有配套的精品资源,点击获取