ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

多数据源清洗实战:从脏数据到可用表的完整链路

2026/10/6 10:05:15 拓冰建站 浏览量
多数据源清洗实战:从脏数据到可用表的完整链路 简介这份数据清洗数据源压缩包面向大数据应用方向的学习者与培训学员用于练习从原始数据中识别缺失值、异常值与不一致记录并完成类型转换与一致性校验等清洗流程。包内共11个文件涵盖sql、csv、txt、xlsx、xls、json、xml等多种格式分别对应数据库查询、表格数据、文本抽取与结构化配置等典型数据源场景压缩包整体约96KB体量轻便便于快速导入本地环境反复演练。目前已有528人学习下载说明其在数据清洗入门与实战训练中具有一定参考价值。借助这些多源异构素材读者可结合Pandas、dplyr或ETL工具练习缺失值填充、异常值检测、编码处理与跨源比对等操作理解不同数据源在读取与清洗上的差异为后续数据分析与挖掘项目积累可复用的处理经验。1. 数据清洗数据源.zip一份能直接跑通的多源清洗素材包做数据分析的人大概都有过这种经历拿到手的原始数据五花八门CSV 里混着 Excel、JSON 里嵌着脏字符串、日期格式能凑出七八种写法光是把它们统一成一张能用的表就耗掉大半天。数据清洗数据源.zip这个资源包解决的正是这个前置环节——它把多数据源清洗场景下常见的原始样本、字段结构和脏数据形态打包在一起让你不用自己造数据就能把清洗流程跑一遍。适合两类人一是刚接触 pandas 数据清洗、想找真实脏数据练手的新手二是需要给团队搭一套可复用的多数据源清洗模板、但懒得从零构造测试集的工程师。包里是数据源素材不是成品代码库所以你得自己写清洗逻辑但这也意味着它不会限制你的技术选型。2. 拆包看结构多数据源到底长什么样2.1 解压后的目录与文件类型拿到 zip 包第一步是解压。Windows 上右键解压即可Linux 或 macOS 下用命令行更可控# 解压到当前目录下的 data_clean 文件夹 unzip 数据清洗数据源.zip -d data_clean # 查看解压后的目录树确认文件类型分布 find data_clean -type f | head -50解压后通常会看到按数据源类型分组的文件常见形态包括.csv、.xlsx、.json、.txt几类。不同来源的字段命名风格往往不统一比如用户 ID 在一份文件里叫user_id另一份里叫uid还有一份直接写成用户编号。这正是多数据源清洗最典型的痛点结构对齐比值清洗更费精力。提示解压前先确认 zip 包完整性。如果遇到invalid zip archive: could not find eocd这类报错说明文件下载不完整或传输损坏重新获取即可不要用修复工具硬改容易丢数据。2.2 用 pandas 做首轮探查在写任何清洗逻辑之前先让 pandas 把每个数据源的底细交代清楚。下面这段代码做三件事遍历目录、按扩展名读取、输出每个文件的行列数和前几行样本。import pandas as pd import os import json data_dir data_clean for root, dirs, files in os.walk(data_dir): for fname in files: fpath os.path.join(root, fname) ext os.path.splitext(fname)[1].lower() try: if ext .csv: # 常见坑编码可能是 gbk 或 utf-8-sig df pd.read_csv(fpath, encodingutf-8) elif ext .xlsx: df pd.read_excel(fpath) elif ext .json: df pd.read_json(fpath) else: continue print(f {fname} | shape{df.shape} ) print(df.head(3)) print(df.dtypes) print() except Exception as e: print(f[FAIL] {fname}: {e})逻辑说明os.walk保证子目录也能扫到按扩展名分派读取函数避免用统一接口硬读导致格式报错。参数上encoding是最容易翻车的地方——国内不少导出文件默认 GBK直接read_csv会抛UnicodeDecodeError。如果报错把encoding换成gbk或utf-8-sig再试。df.dtypes的输出要重点看数值列被读成object通常意味着混入了非数字字符日期列被读成object说明格式不统一这两类问题后面都要专门处理。2.3 字段对齐多源合并前的必经步骤探查完之后你会发现不同数据源的列名和列数都不一样。直接concat会得到一堆 NaN所以要先做字段映射。常见做法是维护一张映射字典把各源的列名统一到目标 schema。# 各数据源列名到标准字段的映射 col_mapping { source_a: {uid: user_id, reg_time: register_date, amt: amount}, source_b: {用户编号: user_id, 注册日期: register_date, 金额: amount}, source_c: {user_id: user_id, register_date: register_date, amount: amount}, } def align_columns(df, source_name): mapping col_mapping.get(source_name, {}) df df.rename(columnsmapping) # 只保留标准字段多余的列先丢弃 standard_cols [user_id, register_date, amount] for col in standard_cols: if col not in df.columns: df[col] None # 缺失字段补空后续统一处理 return df[standard_cols]这段代码的关键在于映射字典按数据源名称区分避免不同源的相同列名被错误覆盖缺失字段补None而不是直接报错保证合并时列结构一致。参数上standard_cols就是你的目标 schema实际项目中应该从业务需求倒推确定不要贪多求全。3. 清洗流水线从脏数据到可用表的完整链路3.1 缺失值与异常值的处理策略多数据源合并后缺失值几乎不可避免。但缺失值的处理不能一刀切用dropna()得先判断缺失模式。下面这段代码统计每列缺失率并按阈值决定填充还是丢弃。def handle_missing(df, drop_threshold0.5): # 统计缺失率 missing_rate df.isnull().mean() print(缺失率\n, missing_rate) # 缺失率超过阈值的列直接丢弃 cols_to_drop missing_rate[missing_rate drop_threshold].index.tolist() df df.drop(columnscols_to_drop) print(f丢弃高缺失列{cols_to_drop}) # 数值列用中位数填充类别列用众数填充 for col in df.columns: if df[col].isnull().sum() 0: continue if df[col].dtype in [float64, int64]: df[col] df[col].fillna(df[col].median()) else: df[col] df[col].fillna(df[col].mode()[0] if not df[col].mode().empty else unknown) return df逻辑说明drop_threshold0.5意味着缺失超过一半的列没有保留价值这是经验值实际可以按业务调整。数值列用中位数而非均值是因为中位数对异常值不敏感——金额字段里混进一个 999999 的记录均值会被拉偏中位数不会。类别列用众数填充是保守做法如果类别分布本身不均匀也可以填unknown保留缺失信息。异常值检测用 IQR 方法比较稳妥def detect_outliers(df, col): Q1 df[col].quantile(0.25) Q3 df[col].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR outliers df[(df[col] lower) | (df[col] upper)] print(f{col} 异常值数量{len(outliers)}范围[{lower:.2f}, {upper:.2f}]) return outliers参数1.5是 IQR 的标准系数偏严格如果数据本身波动大可以放宽到 3.0。检测到异常值后不要急着删先看是不是录入错误——比如金额为负数可能是退款记录删了就丢业务信息。3.2 日期与字符串的标准化日期格式混乱是多数据源清洗里最烦人的环节。同一批数据里可能同时存在2024-01-15、2024/1/15、15-Jan-2024、20240115四种写法。统一用pd.to_datetime配合formatmixed处理def standardize_date(df, col): # formatmixed 让 pandas 自动推断每行的格式 df[col] pd.to_datetime(df[col], formatmixed, errorscoerce) # 转换失败的置为 NaT统计数量 fail_count df[col].isnull().sum() print(f{col} 日期解析失败{fail_count} 条) # 统一输出为 YYYY-MM-DD df[col] df[col].dt.strftime(%Y-%m-%d) return dferrorscoerce是关键参数遇到无法解析的值不报错而是置为NaT这样你能一次性看到所有解析失败的行而不是被第一条脏数据打断。字符串标准化则重点处理首尾空格、全角半角、大小写def clean_string(df, col): df[col] df[col].astype(str).str.strip() # 去首尾空格 df[col] df[col].str.replace( , , regexFalse) # 全角空格转半角 df[col] df[col].str.lower() # 统一小写 return df全角空格这个问题在从 Excel 复制的数据里特别常见肉眼看不出来但做分组统计时会导致同一个值被分成两组属于典型的玄学 bug。3.3 去重与合并多源数据的最终对齐多数据源合并后同一个用户可能在多个源里都有记录需要按主键去重。去重策略取决于业务保留最新记录、保留最完整记录、还是合并字段。def dedup_by_key(df, key_col, keeplast): before len(df) df df.sort_values(register_date).drop_duplicates(subset[key_col], keepkeep) after len(df) print(f去重{before} - {after}删除 {before - after} 条) return df # 完整流水线 df_a align_columns(pd.read_csv(data_clean/source_a.csv), source_a) df_b align_columns(pd.read_excel(data_clean/source_b.xlsx), source_b) df_c align_columns(pd.read_json(data_clean/source_c.json), source_c) df_all pd.concat([df_a, df_b, df_c], ignore_indexTrue) df_all handle_missing(df_all) df_all standardize_date(df_all, register_date) df_all dedup_by_key(df_all, user_id, keeplast) df_all.to_csv(cleaned_output.csv, indexFalse, encodingutf-8-sig)keeplast配合按日期排序效果是保留每个用户最新的那条记录。encodingutf-8-sig是给 Excel 看的——不加-sig用 Excel 打开中文 CSV 会乱码这个坑我踩过不止一次。4. 避坑与排查多数据源清洗的五个血泪教训4.1 编码报错UnicodeDecodeError 反复出现现象pd.read_csv读取时报UnicodeDecodeError: utf-8 codec cant decode byte...换gbk后部分文件正常、部分又报错。原因不同数据源的导出工具编码设置不同有的用 UTF-8有的用 GBK还有的用 UTF-8 with BOM。一个编码参数打天下必然翻车。解决写一个探测函数按utf-8-sig→gbk→utf-8顺序尝试哪个能读通用哪个。或者用chardet库自动检测。别手动一个个试文件多了会疯。4.2 日期解析静默失败现象pd.to_datetime没报错但转换后大量NaT后续按日期分组统计结果为空。原因errorscoerce把解析失败的值静默转成了NaT如果不主动统计失败数量根本发现不了。有些日期字符串看着像日期实际是2024年13月45日这种非法值。解决转换后强制打印isnull().sum()失败率超过 5% 就停下来检查原始值分布用df[col].value_counts().head(20)看看到底是哪些格式没被覆盖。4.3 合并后行数暴涨现象三个源各 1000 行concat后变成 3000 行但去重后只剩 800 行说明大量重复。原因多数据源之间本身就有重叠用户且主键列在合并前没做类型统一——A 源的user_id是整数B 源的是字符串00123pandas 视为不同值去重失效。解决合并前统一主键类型全部转成字符串并去空格。df[user_id] df[user_id].astype(str).str.strip()这行看着简单但不做的话去重就是自欺欺人。4.4 Excel 文件读取慢或内存溢出现象.xlsx文件不大但read_excel卡很久或者直接MemoryError。原因openpyxl引擎会加载整个工作簿到内存文件里如果有大量格式、合并单元格、隐藏 sheet开销远超数据本身。解决指定sheet_name只读需要的表用usecols只读需要的列。如果还是慢先用 Excel 另存为 CSV 再读别跟 xlsx 死磕。4.5 清洗后数值列变成字符串现象金额列清洗完dtype是object做sum()变成字符串拼接。原因清洗过程中某一步引入了非数字字符比如货币符号¥、千分位逗号pandas 自动降级为 object 类型。解决数值列清洗后强制pd.to_numeric(df[col], errorscoerce)转换失败置 NaN 再走缺失值处理。这一步放在流水线最后作为类型兜底。5. 进阶技巧把清洗流程封装成可复用管道5.1 用管道模式组织清洗步骤上面每个清洗函数都是独立的实际项目中更推荐用管道模式串起来这样加步骤、调顺序、做 A/B 对比都方便。class CleaningPipeline: def __init__(self): self.steps [] def add_step(self, name, func, **kwargs): self.steps.append((name, func, kwargs)) return self def run(self, df): for name, func, kwargs in self.steps: before df.shape df func(df, **kwargs) after df.shape print(f[{name}] {before} - {after}) return df # 使用示例 pipeline (CleaningPipeline() .add_step(缺失值处理, handle_missing, drop_threshold0.5) .add_step(日期标准化, standardize_date, colregister_date) .add_step(去重, dedup_by_key, key_coluser_id, keeplast)) df_clean pipeline.run(df_all)这个模式的好处是每步的输入输出形状都打印出来哪一步把数据搞没了、哪一步行数暴涨一眼就能定位。参数通过kwargs传入改阈值不用动函数体。5.2 清洗结果验证三个必查指标清洗完不能直接交付至少验证三件事。第一主键唯一性df[user_id].duplicated().sum()必须为 0。第二关键字段缺失率核心字段缺失率超过阈值就要回退检查。第三数值范围合理性金额不能为负除非业务允许、日期不能晚于今天。把这三个检查写成函数每次清洗后自动跑def validate(df): assert df[user_id].duplicated().sum() 0, 主键存在重复 assert df[register_date].isnull().mean() 0.01, 日期缺失率过高 assert (pd.to_numeric(df[amount], errorscoerce) 0).all(), 金额存在负值 print(验证通过)用assert而不是if-print是因为验证失败应该直接中断流程而不是打印个警告继续跑——带着脏数据往下走后面出的报表全是错的回头排查成本更高。5.3 参数速查表参数常用值适用场景注意encodingutf-8-sig/gbkCSV 读取中文 Excel 导出优先试 gbkerrorscoerce类型转换失败置 NaN需配合缺失统计drop_threshold0.5缺失列丢弃按业务调整核心字段不适用IQR 系数1.5 / 3.0异常值检测1.5 严格3.0 宽松keeplast/first去重保留配合排序决定保留哪条从那以后我每次拿到新的多数据源包都强制先跑一遍探查脚本再动手写清洗逻辑——跳过这步直接写代码十有八九要返工。希望帮到你。本文还有配套的精品资源点击获取