数据清洗数据源.zip实战:从脏数据到可复用清洗流水线

📅 发布时间:2026/10/12 7:57:34
数据清洗数据源.zip实战:从脏数据到可复用清洗流水线
简介这份数据清洗数据源压缩包面向大数据应用学习者与数据分析初学者提供可直接上手练习的原始数据素材帮助解决真实数据中缺失值、异常值、重复记录与格式不一致等常见问题。包内共11个文件涵盖sql、csv、txt、xlsx、xls、json、xml等多种格式分别对应数据库表结构、结构化表格、文本日志与半结构化数据等典型数据源压缩包整体约96KB体积轻便便于快速导入与反复实验。目前已有528人学习下载说明其在数据清洗入门练习中具有一定参考价值。读者可借助这些素材练习缺失值填充、异常值检测、一致性校验与数据类型转换等操作并结合Pandas、SQL或ETL工具完成从多源数据提取到清洗落地的完整流程为后续数据分析与挖掘打下基础。1. 数据清洗数据源.zip一个压缩包背后藏着多少条能跑通的清洗流水线拿到「数据清洗数据源.zip」这个标题多数人第一反应是去找下载链接但真正做过数据工程的人会先问一句这个包里到底装的是原始脏数据还是已经配好的清洗脚本加样例数据两种东西的价值差着数量级。前者是素材后者是能直接复现的流水线。我见过太多团队把「数据源」理解成「一堆 CSV 扔进文件夹」结果接手的人打开一看字段名带空格、日期三种格式混排、空值用NULL、null、-、无四种写法清洗脚本根本没法通用。这个标题真正指向的需求是拿到一批有代表性的脏数据样本配上一套可迁移的清洗逻辑让新手能照着跑通让熟手能直接改参数上生产。适合谁做数据分析前期准备的人、搭 ETL 管道的后端、以及被「数据质量」反复折磨的算法工程师。下面按「先看清数据源长什么样再动手写清洗最后避开那些让脚本白写的坑」这条线走。2. 拆开数据清洗数据源.zip之前先搞清脏数据的六种典型形态2.1 为什么不能拿到文件就写dropna()数据清洗最怕的不是脏而是不知道脏在哪。一个压缩包解压后你面对的可能是一张订单表、一份用户行为日志、或者几个爬虫抓下来的 JSON。直接df.dropna()或者df.fillna(0)是最常见的翻车起点——把有意义的缺失当成噪声删掉或者把「未填写」硬填成 0后面聚合出来的均值全是错的。我一般会先做一轮「数据体检」不写任何清洗逻辑只输出每个字段的非空率、唯一值数量、前 10 个高频值、以及最值。这一步花十分钟能省掉后面两小时的返工。import pandas as pd def data_health_check(df, namedataset): 输出每个字段的基础体检报告不做任何修改 report [] for col in df.columns: s df[col] report.append({ 字段: col, 类型: str(s.dtype), 非空率: round(s.notna().mean(), 4), 唯一值数: s.nunique(dropnaTrue), 高频值Top3: s.value_counts(dropnaTrue).head(3).to_dict(), 样例: s.dropna().head(2).tolist() }) return pd.DataFrame(report) # 用法先读进来别急着改 df pd.read_csv(raw_orders.csv, dtypestr, keep_default_naFalse) print(data_health_check(df).to_string())这段代码的关键在dtypestr和keep_default_naFalse。前者强制所有列按字符串读避免 pandas 自动把「00123」变成 123、把「2024-01-01」猜成 datetime 后又在后续操作里丢精度后者让空字符串保持空字符串而不是被自动转成NaN这样你才能区分「字段缺失」和「字段填了空」。体检报告里如果某列非空率低于 60%先别删去问数据来源方这个字段是不是选填如果唯一值数等于行数大概率是主键或 ID清洗时要保护。2.2 六种脏数据形态与对应识别信号把常见脏数据归成六类每类都有可观测的信号不用靠猜。脏数据形态典型信号常见来源缺失值非空率低、空字符串与NaN混用表单选填、接口字段缺失格式不一致同一字段出现多种日期/电话/金额写法多系统对接、人工录入重复记录唯一值数远小于行数但主键不重复多次导入、日志重放异常值最值超出业务合理范围如年龄 200录入错误、单位混用编码/乱码出现\ufffd、Ã¥等字符文件编码不统一逻辑矛盾结束时间早于开始时间、金额为负但状态是「已支付」业务规则未校验识别信号比清洗动作更重要。比如「格式不一致」这一条如果你只看到日期列有2024/1/5和2024-01-05两种直接pd.to_datetime可能报错但更隐蔽的是05/01/2024这种日月顺序歧义——美式还是欧式这时候不能靠代码猜要去问数据提供方或者看同一行其他字段的上下文。我一般会在体检报告里加一列「格式模式」用正则粗提取把明显不同的模式标出来。2.3 从压缩包到 DataFrame读取阶段的三个参数决定后面顺不顺解压后常见格式是 CSV、Excel、JSON、Parquet。读取阶段有三个参数最容易被忽略但直接影响清洗难度。# CSV指定编码和分隔符别让 pandas 猜 df_csv pd.read_csv( raw_data.csv, encodingutf-8-sig, # 处理带 BOM 的 UTF-8避免第一列名带 \ufeff sep,, # 如果实际是 \t 或 |这里必须改 dtypestr, # 统一按字符串读后续按需转换 keep_default_naFalse, # 保留空字符串区分缺失与空值 on_bad_lineswarn # 遇到列数不对的行先警告不直接崩 ) # Excel指定 sheet 和表头行避免把标题行当数据 df_excel pd.read_excel( raw_data.xlsx, sheet_nameSheet1, header0, # 如果前两行是说明改成 header2 dtypestr ) # JSON如果是嵌套结构先 json_normalize 再进 DataFrame import json with open(raw_data.json, r, encodingutf-8) as f: raw json.load(f) df_json pd.json_normalize(raw, sep_) # 嵌套字段用下划线连接encodingutf-8-sig是血泪经验Windows 导出的 CSV 经常带 BOM用utf-8读第一列名会多一个不可见字符后面df[订单号]死活匹配不上。on_bad_lineswarn在数据量大时很有用先跑通再回头处理坏行不要一上来就error中断。JSON 的json_normalize把嵌套字典展开成扁平列sep_让user.address.city变成user_address_city后续引用方便。3. 清洗流水线的四步骨架从原始表到可用表3.1 第一步列名与类型标准化别让中文空格毁掉 merge列名标准化是清洗的第一道工序也是最容易被跳过的一步。常见问题列名带前后空格、大小写混用、中文列名里夹英文括号、重复列名 pandas 自动加.1。这些在单表操作时没事一旦要merge或写 SQL就是灾难。import re def standardize_columns(df): 列名标准化去空格、转小写、替换特殊字符、去重 new_cols [] seen {} for col in df.columns: c str(col).strip().lower() c re.sub(r[\s\-/\\], _, c) # 空格和斜杠转下划线 c re.sub(r[^\w\u4e00-\u9fa5], , c) # 保留字母数字下划线和中文 if c in seen: seen[c] 1 c f{c}_{seen[c]} else: seen[c] 0 new_cols.append(c) df.columns new_cols return df df standardize_columns(df)逻辑说明先strip去首尾空格再lower统一大小写然后把空格、斜杠、反斜杠替换成下划线接着用正则去掉除字母、数字、下划线、中文以外的字符。最后用seen字典处理重名第二个同名列变成_1后缀。参数上\u4e00-\u9fa5是中文 Unicode 范围如果你的数据有日文或韩文列名需要扩展这个范围。这一步做完后面所有引用都用标准化后的列名不再出现df[ 订单号 ]这种写法。类型标准化紧随其后。字符串列统一astype(str)并strip数值列先用pd.to_numeric(errorscoerce)转转失败的置为NaN后续单独处理日期列用pd.to_datetime(errorscoerce, formatmixed)formatmixed让 pandas 逐行推断格式比统一格式更宽容但速度慢数据量大时建议先抽样确定格式再指定。3.2 第二步缺失值与重复值的处理策略按字段语义分治缺失值处理没有万能公式核心原则是「按字段语义分治」。我一般把字段分成三类标识类ID、主键、维度类地区、品类、度量类金额、数量。标识类缺失直接标记为无效行维度类缺失填「未知」并保留因为「未知」本身可能是信息度量类缺失要看业务能回算的回算不能回算的标记为缺失不要填 0。def handle_missing(df, id_cols, dim_cols, measure_cols): 按字段语义处理缺失值 # 标识类缺失整行标记不删除留待人工确认 df[_invalid] df[id_cols].isna().any(axis1) # 维度类缺失填「未知」 for col in dim_cols: df[col] df[col].fillna(未知) # 度量类缺失保留 NaN额外加标记列 for col in measure_cols: df[f{col}_missing] df[col].isna() return df df handle_missing( df, id_cols[order_id, user_id], dim_cols[province, category], measure_cols[amount, quantity] )重复值处理要区分「完全重复」和「业务重复」。完全重复用df.drop_duplicates()直接删业务重复比如同一用户同一秒提交两次订单需要按业务规则保留最新或最早一条。我一般先看重复组的数量如果重复率超过 5%先别删去查数据来源是不是有重放或多次导入。# 完全重复 before len(df) df df.drop_duplicates() print(f删除完全重复: {before - len(df)} 行) # 业务重复同一 order_id 保留更新时间最新的一条 df df.sort_values(update_time).drop_duplicates( subset[order_id], keeplast )keeplast配合先按时间排序保留最新记录。如果时间列也有缺失排序会把 NaN 放最后需要先处理时间缺失再排序。3.3 第三步异常值检测与逻辑校验用 IQR 和业务规则双保险异常值检测分统计法和业务法。统计法常用 IQR四分位距对偏态分布比 3σ 稳健业务法靠硬规则比如年龄 0-120、金额非负、结束时间不早于开始时间。两者结合统计法发现可疑点业务法做最终判定。def detect_outliers_iqr(series, factor1.5): IQR 法标记异常值返回布尔 Series q1 series.quantile(0.25) q3 series.quantile(0.75) iqr q3 - q1 lower q1 - factor * iqr upper q3 factor * iqr return (series lower) | (series upper) # 对金额列做 IQR 检测 df[amount_outlier] detect_outliers_iqr(df[amount].astype(float)) # 业务规则校验 df[logic_error] ( (df[amount].astype(float) 0) | (df[end_time] df[start_time]) )factor1.5是标准 IQR 系数调大到 3 会减少标记数量适合数据本身波动大的场景。amount_outlier和logic_error都只做标记不直接删或改因为异常值可能是真实的高价值订单也可能是录入错误需要人工或下游规则判断。这一步的输出是一张带标记列的表而不是一张「干净」的表——保留标记比直接清洗更安全后悔药就在这些标记列里。3.4 第四步输出与版本记录让清洗过程可回溯清洗完不记录版本等于白洗。我一般输出三样东西清洗后的主表、清洗日志每步删了多少行、改了多少值、以及字段字典每个字段的含义、类型、取值范围。清洗日志用 JSON 存方便后续对比不同批次。import json from datetime import datetime clean_log { source_file: raw_orders.csv, clean_time: datetime.now().isoformat(), steps: [ {step: standardize_columns, rows: len(df)}, {step: handle_missing, invalid_rows: int(df[_invalid].sum())}, {step: drop_duplicates, removed: before - len(df)}, {step: detect_outliers, amount_outliers: int(df[amount_outlier].sum())} ], output_rows: len(df) } with open(clean_log.json, w, encodingutf-8) as f: json.dump(clean_log, f, ensure_asciiFalse, indent2) df.to_parquet(clean_orders.parquet, indexFalse)输出用 Parquet 而不是 CSV因为 Parquet 保留类型信息下次读取不用再猜编码和分隔符体积也更小。clean_log.json里记录每一步的行数变化下次数据更新时对比日志能快速定位是哪一步导致行数异常波动。4. 避坑与排查数据清洗数据源.zip落地时最容易翻车的五件事4.1 现象pd.to_datetime报错OutOfBoundsDatetime原因日期超出 pandas 时间戳范围解决先筛出异常年份再转换pandas 的Timestamp默认范围是 1677 到 2262 年。如果数据里有9999-12-31这种「永久有效」的占位日期pd.to_datetime会直接抛OutOfBoundsDatetime。解决方法是先用字符串截取年份把明显超出范围的筛出来单独处理。# 先提取年份筛出可疑行 df[year_str] df[date_col].astype(str).str[:4] suspicious df[~df[year_str].str.match(r^(19|20)\d{2}$)] print(f可疑日期行数: {len(suspicious)}) # 对正常范围的行做转换 df[date_parsed] pd.to_datetime( df.loc[~df.index.isin(suspicious.index), date_col], errorscoerce )4.2 现象merge后行数暴涨原因关联键有重复值解决merge 前先检查关联键唯一性两个表merge后行数从 1 万变成 5 万八成是右表的关联键不唯一。merge默认是内连接一对多会展开成多行。解决merge 前对关联键做value_counts确认唯一再合并。# 检查关联键唯一性 right_dup df_right[key].duplicated().sum() if right_dup 0: print(f右表关联键有 {right_dup} 个重复merge 会膨胀) # 先聚合或去重 df_right df_right.drop_duplicates(subset[key], keepfirst) df_merged df_left.merge(df_right, onkey, howleft, validateone_to_one)validateone_to_one让 pandas 在键不唯一时直接报错比事后查行数更早发现问题。4.3 现象中文列名在groupby后消失原因列名含不可见字符解决统一用standardize_columns处理从 Excel 复制过来的列名经常带\xa0不换行空格或\u200b零宽空格肉眼看不出来但df[订单号]就是匹配不上。standardize_columns里的正则[^\w\u4e00-\u9fa5]会把这些不可见字符去掉。如果已经进了流程可以用df.columns df.columns.str.replace(r\s, , regexTrue)应急。4.4 现象fillna(0)后均值失真原因缺失被当成真实 0解决度量类字段保留 NaN用标记列区分金额、数量、时长这类度量字段缺失和 0 是两回事。缺失代表「没记录」0 代表「记录为 0」。填 0 后算平均分母变大均值被拉低。正确做法是保留 NaNdf[amount].mean()会自动跳过 NaN同时加一列amount_missing标记缺失位置后续分析可以按缺失与否分组对比。4.5 现象清洗脚本在样本上跑通全量数据报内存错误原因dtypestr读全量后内存翻倍解决分块读取或指定列类型dtypestr在样本上没问题但全量数据读进来后字符串列的内存占用远高于数值列。如果数据超过内存一半就会 OOM。解决用chunksize分块处理或者读之前先指定每列类型数值列用float32/int32而不是默认的float64/int64。# 分块读取每块 10 万行 chunk_list [] for chunk in pd.read_csv(big_file.csv, chunksize100000, dtypestr): chunk standardize_columns(chunk) chunk_list.append(chunk) df pd.concat(chunk_list, ignore_indexTrue)分块时注意standardize_columns要在每块上单独做因为列名标准化不依赖跨块信息。如果清洗逻辑涉及全局统计比如 IQR 的上下界需要先扫一遍算全局分位数再分块应用。5. 把清洗脚本变成可复用资产参数化与自动化校验的两个技巧清洗脚本写完不是终点能复用到下一批数据才是。我一般做两件事把硬编码的列名、阈值、文件路径抽成配置字典在脚本末尾加一段自动化校验用断言检查清洗后的表是否满足基本约束。# config.yaml 或 config.py CONFIG { id_cols: [order_id, user_id], dim_cols: [province, category], measure_cols: [amount, quantity], iqr_factor: 1.5, date_col: create_time, output_path: clean_orders.parquet } # 自动化校验 def validate_clean(df, config): 清洗后校验不通过直接抛异常 assert df[config[id_cols]].notna().all().all(), 标识列仍有缺失 assert df[amount].dropna().ge(0).all(), 金额出现负值 assert df[create_time].dropna().between( 2020-01-01, 2030-01-01 ).all(), 日期超出合理范围 print(校验通过) validate_clean(df, CONFIG)配置化的好处是换一批数据只改CONFIG不动清洗逻辑。自动化校验用assert把「隐性假设」变成「显性检查」——比如你默认金额非负那就写出来下次数据里出现负金额时脚本直接报错而不是悄悄算出一个负的总额。校验规则不用多覆盖标识列、关键度量、时间范围这三类就够。另一个技巧是给清洗脚本加--dry-run参数只输出日志不写文件。这样在正式跑之前可以先看一遍每步删了多少行、标记了多少异常确认无误再落盘。我自己的习惯是任何清洗脚本第一次跑全量数据必须--dry-run过一遍看日志里的行数变化是否符合预期再正式输出。这个习惯帮我拦下过好几次「关联键重复导致行数膨胀」的事故。数据清洗这件事工具和代码都是次要的核心是「先看清再动手保留标记不硬删日志留全可回溯」。希望帮到你。本文还有配套的精品资源点击获取