ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Pandas数据清洗全流程实战:从原始数据到结构化输出

Pandas数据清洗全流程实战:从原始数据到结构化输出 这次我们直接基于 Pandas 把一套完整的数据清洗流程跑通。平时工作里最常见的脏数据无非是空值、重复值、格式不一致、异常值、文本乱码这几种很多同学拿到数据先df.head()看一眼就开始写处理代码结果处理到一半发现字段是字符串、某几行本来就是重复的、日期格式全乱最后清洗结果根本不敢直接拿去建模或出报表。这篇文章会从读取原始数据开始一直做到输出规范、结构化数据中间覆盖缺失值处理、重复值清理、类型转换、异常值过滤、文本与日期格式化、长宽表转换、批量文件处理以及大数据量下的性能优化思路。这套流程对硬件没有特殊要求普通办公电脑就能跑Pandas 安装用 pip 一条命令Windows、macOS、Linux 都支持。核心能力可以归纳成几句话缺失值能补、重复数据能去、类型能转、异常值能筛、文本能清洗、日期能统一、批量任务能跑、结果能导出成 CSV、Excel、Parquet 等格式清洗逻辑还能封装成函数供 API 服务调用。无论你是做数据分析、数据开发还是正在准备 Python 数据相关岗位面试这套流程都值得照着跑一遍。下面直接从环境准备开始。1. 核心能力速览与适用边界能力项说明技术栈Python Pandas 2.x底层依赖 NumPy生态成熟核心功能缺失值处理、重复值清理、异常值过滤、数据类型转换、文本清洗、日期标准化、结构化输出输入数据CSV、Excel、JSON、Parquet、Feather、数据库查询结果输出数据清洗后的 DataFrame可导出为 CSV、Excel、Parquet、Feather硬件要求普通办公电脑即可内存 8G 以上处理中等数据量更稳操作系统Windows / macOS / Linux安装方式pip 或 conda批量任务支持通过脚本循环处理多个文件API 对接清洗逻辑可封装成函数供 FastAPI、Flask 等服务调用适合人群数据分析师、数据开发工程师、业务运营、Python 学习者、数据岗位面试准备者适用场景非常明确拿到一份不规范的原始数据需要整理成能用于统计分析、可视化、机器学习建模的结构化表格。比如从业务系统导出的订单表、用户表从第三方平台下载的报表或者爬虫抓下来的非结构化文本。这类场景的共性问题是字段错位、格式乱、有缺失和重复需要一套可复用的清洗流程。边界也要说清楚。Pandas 不是大数据引擎单表数据量达到几千万行时内存占用会明显上升如果数据规模达到 TB 级应该考虑 Spark、DuckDB 或数据库侧预处理而不是硬塞进 Pandas。另外涉及用户手机号、身份证、地址等敏感信息时处理前必须确认数据来源合法、已获得授权在演示和分享时也要先脱敏。数据清洗本身不产生数据只修正数据动手之前先备份原始文件永远是第一原则。2. 环境准备Python 与 Pandas 安装清洗流程基于 Python 3 运行推荐使用 3.9 及以上版本当前 Pandas 2.x 稳定版安装和使用都很简单不需要手动编译。如果你之前没有安装过 Python 环境这里给一套完整的准备步骤。2.1 检查 Python 环境打开终端先确认本机 Python 和 pip 是否可用python -V pip -V如果系统同时装了多个 Python 版本建议使用虚拟环境避免不同项目依赖冲突python -m venv venvWindows 激活虚拟环境venv\Scripts\activatemacOS 或 Linux 激活虚拟环境source venv/bin/activate2.2 安装 Pandas 及相关依赖库数据清洗除了 Pandas通常还会用到 NumPy读取 Excel 文件需要 openpyxl。用 pip 一次性安装pip install pandas numpy openpyxl如果速度慢可以换国内镜像源pip install pandas numpy openpyxl -i https://pypi.tuna.tsinghua.edu.cn/simple验证安装是否成功python -c import pandas as pd; print(pd.__version__)看到版本号输出说明环境就绪。在 PyCharm 里操作时需要确认当前项目解释器选的是这个虚拟环境在 VSCode 里则要选择对应的 Python 解释器。Jupyter Notebook 也可以直接使用交互式分步清洗时更方便观察中间结果。3. 原始数据读取与数据体检清洗的第一步不是处理脏数据而是把数据读进来做一次“体检”。只有知道数据哪里脏、脏到什么程度才能决定后续用哪种策略。3.1 读取常见数据文件import pandas as pd # 读取 CSV 文件 df pd.read_csv(raw_data.csv, encodingutf-8)如果 CSV 文件是中文且编码不是 utf-8常常会报UnicodeDecodeError。可以尝试指定其他编码或者用 try except 自动探测try: df pd.read_csv(raw_data.csv, encodingutf-8) except UnicodeDecodeError: df pd.read_csv(raw_data.csv, encodinggbk)读取 Excel 文件df pd.read_excel(raw_data.xlsx, sheet_name订单, engineopenpyxl)读取 JSON、Parquet、Feather 也很常用尤其 Parquet 和 Feather 格式在列式存储、压缩率和读取速度上比 CSV 更适合做中间结果df pd.read_json(raw_data.json) df pd.read_parquet(raw_data.parquet) df pd.read_feather(raw_data.feather)3.2 数据体检三件套# 1. 看整体结构和非空情况 print(df.info()) # 2. 看数值列分布 print(df.describe()) # 3. 看缺失值和重复值 print(df.isnull().sum()) print(df.duplicated().sum())df.info()会输出每个列名、非空数量、数据类型能快速发现列名是否规范、字段类型是否合理、哪些列存在缺失。df.describe()针对数值列输出均值、标准差、最小值、四分位数、最大值用于初步判断是否存在超出合理范围的异常值。df.isnull().sum()和df.duplicated().sum()则直接给出缺失样本数和重复样本数。真实业务里可能拿到几百列的表这时候先不要无脑全看。优先关注唯一键列、时间列、金额列、手机号或身份证这类核心标识列判断这几类字段是否可用其他列可以后面按需处理。为了演示完整流程下面构造一个包含多种脏数据的模拟订单表后续所有清洗操作都基于这个数据import pandas as pd df pd.DataFrame({ 订单号: [A001, A002, A002, A003, A004, A005], 用户ID: [U_001, U_002, U_002, U_003, U_004, U_005], 下单日期: [2024/1/3, 2024/01/05, 20240108, 2024-02-14, 2024年3月2日, 2024-02-30], 手机号: [13800138000, 13912345678, 13912345678, 021-12345678, hello, 13711112222], 金额: [100.5, 200,000, 300, None, 五十, 400], 地区: [ 上海 , 北京, 北京, 广州, 深圳, 未知] }) print(df)这个数据包含的问题有订单号 A002 重复手机号列混入了座机号和字符串金额列有字符串、数字和空值日期列存在多种格式地区列有空格和“未知”分类。接下来进入正式修复环节。4. 脏数据修复缺失值、重复值、异常值与类型转换这一节是整套数据清洗流程的核心解决的问题覆盖日常场景里八成以上的脏数据。每类问题先说明判断思路再给出可直接套用的代码。4.1 缺失值处理处理缺失值之前先明确需求这个字段能否为空、缺失比例多少、业务上能不能用其他字段推算。盲目fillna(0)会把缺失和真实为零混在一起影响统计口径。# 查看缺失情况 print(df.isnull().sum()) # 方案一行内有关键字段缺失直接删除 df_dropna df.dropna(subset[订单号]) # 方案二数值列缺失用均值或中位数填充 df[金额] pd.to_numeric(df[金额], errorscoerce) df[金额] df[金额].fillna(df[金额].median()) # 方案三时间序列数据用插值填充 # 示例df[数值] df[数值].interpolate()在模拟数据里可以演示金额列的空值用中位数填充因为金额通常右偏分布均值容易受极端值影响中位数更稳健。对于时间序列数据比如按小时采集的指标缺失值用interpolate()做线性插值往往效果更好。注意如果某个字段缺失比例超过 50%且不是关键字段直接删除该列通常是更合理的选择。4.2 重复值处理重复数据会导致统计结果被放大。Pandas 的判断逻辑是按行比较也可以用subset指定部分列判断是否重复。热搜词里有一句很典型“如果指定两列的值均相同则取第一条数据即可”这就是drop_duplicates(subset[...], keepfirst)最常用的场景。# 查看全行重复 print(df.duplicated().sum()) # 基于订单号和用户ID两个字段判断重复保留第一条 df df.drop_duplicates(subset[订单号, 用户ID], keepfirst) # 如果想保留最后一条 # df df.drop_duplicates(subset[订单号, 用户ID], keeplast)这里要特别说明去重之前必须先确认业务语义。两个订单号相同是否一定意味着重复下单如果订单号本身可以重复只是用户操作记录就不能盲目删除。所以指定subset列的时候要选能真正唯一标识一条记录的字段组合。4.3 数据类型转换Pandas 中常见的类型问题有三个数值列被读成字符串、日期列被读成字符串、分类列被读成 object。直接astype在数据干净时可以用但一旦遇到200,000、五十这种值就会报错或被转成错误结果更稳妥的做法是使用带errors参数的方法。# 金额列先去逗号再转数值无法转换的置为 NaN df[金额] ( df[金额] .astype(str) .str.replace(,, , regexFalse) .str.replace(元, , regexFalse) ) df[金额] pd.to_numeric(df[金额], errorscoerce)pd.to_numeric(errorscoerce)会把五十这类无法解析的字符串转成 NaN之后可以按缺失值策略再处理不会让整个程序中断。这个方法是数据清洗里最值得记住的类型转换技巧之一。日期列的统一处理也属于类型转换会在下一节单独展开因为它涉及格式问题比较多。4.4 异常值处理异常值的判断要看业务含义。比如订单金额一般不会为负数也不会高到离谱年龄字段理论上应该在 0 到 120 之间。常见做法有三种业务阈值过滤、四分位数 IQR 法、Z-Score 标准差法。# 方式一业务阈值过滤 df df[(df[金额] 0) (df[金额] 1000000)] # 方式二IQR 四分位距法 Q1 df[金额].quantile(0.25) Q3 df[金额].quantile(0.75) IQR Q3 - Q1 df df[(df[金额] Q1 - 1.5 * IQR) (df[金额] Q3 1.5 * IQR)] # 方式三Z-Score 法一般选择 |z| 3 视为异常 import numpy as np df[金额_zscore] (df[金额] - df[金额].mean()) / df[金额].std() df df[np.abs(df[金额_zscore]) 3]IQR 法对非正态分布的数值列更稳健适合金额、响应时间这类偏态分布字段。阈值处理之后最好再做一次describe()确认清洗后的分布是否符合预期。5. 文本与格式类脏数据治理很多脏数据不是缺、不是重复而是格式不规范。常见问题包括字符串首尾有空格、中英文大小写不统一、电话和日期格式混乱、同一分类有多种叫法。这部分用 Pandas 的字符串方法可以批量解决。5.1 清理空格与统一大小写# 去掉首尾空格 df[地区] df[地区].str.strip() # 去除字符串中间的多余空格 df[地区] df[地区].str.replace(r\s, , regexTrue) # 统一大小写例如对英文用户名 # df[用户名] df[用户名].str.lower()处理后的地区列不会再有 上海 这种前面带空格的值后续分组统计时结果更准确。5.2 手机号和电话格式清洗手机号列出现座机号、字母甚至完全无关的文本是用户表里很常见的脏数据。处理思路是先用正则把 11 位手机号提取出来同时把明显无效的值标记为缺失最后对手机号做脱敏。def clean_phone(value): s str(value).strip() if len(s) 11 and s.isdigit() and s.startswith(1): return s match re.search(r1[3-9]\d{9}, s) return match.group(0) if match else None import re df[手机号] df[手机号].apply(clean_phone) # 脱敏保留前三位和后四位中间四位用星号代替 df[手机号] df[手机号].str.replace(r(\d{3})\d{4}(\d{4}), r\1****\2, regexTrue)这里用了re.search从包含座机号、字母的原始值里尽量提取手机号提取不到就返回 None再走缺失值处理流程。这个函数可以直接复用加注释后放进项目工具模块。5.3 日期格式统一日期清洗的核心目标是让数据中的日期统一成datetime64类型再按需求格式化为字符串或提取年月日字段。Pandas 的to_datetime会自动识别多种格式但遇到2024-02-30这种不存在的日期时会失败用errorscoerce把解析失败的置为 NaT。df[下单日期] pd.to_datetime(df[下单日期], errorscoerce) # 查看解析结果 print(df[下单日期]) # 统一格式化为字符串 df[下单日期] df[下单日期].dt.strftime(%Y-%m-%d) # 提取年、月、日到单独字段 df[年] pd.to_datetime(df[下单日期]).dt.year df[月] pd.to_datetime(df[下单日期]).dt.month清洗之后2024/1/3、20240108、2024年3月2日都会被统一成2024-01-03这样的标准格式后续做时间序列分析、按月份汇总时非常方便。5.4 分类字段的别名统一同一个值在业务表里可能有多种写法比如“北京”、“北京市”、“beijing”都表示北京。建议先看唯一值再映射成标准分类。print(df[地区].value_counts()) region_map { 北京: 北京, 北京市: 北京, beijing: 北京, 上海: 上海, 上海市: 上海, 广州: 广州, 深圳: 深圳 } df[地区标准化] df[地区].map(region_map).fillna(未知)映射时用fillna(未知)兜底避免新出现的分类值变成 NaN 后丢失信息。完成这一步分类字段就变成真正可控的结构化字段了。6. 结构化数据整理、批量处理与结果输出数据修完问题之后还需要把表格整理成方便分析和建模的结构化形态。这个环节包括字段拆分与合并、长宽表转换、分组聚合以及批量处理多个数据文件最后导出成不同格式。6.1 字段拆分与合并如果原始数据里有“张三_13800138000”这种合并字段可以用str.split拆开。如果原始数据把省市区放在一个地址字段里也可以按分隔符拆分。# 示例将 姓名_手机号 拆成两列 df[姓名], df[联系电话] df[用户信息].str.split(_, expandTrue) # 示例将地址按“省市区”拆分 # df[[省, 市, 区]] df[地址].str.extract(r(.?省)?(.?市)?(.?区)?)反过来如果需要把分散字段拼成一个字段可以用字符串相加或cat方法df[完整地址] df[省].fillna() df[市].fillna() df[区].fillna()6.2 长表与宽表转换业务数据经常需要在这两种结构之间切换。宽表适合人看长表适合机器分析和可视化库输入。Pandas 提供melt和pivot_table两个核心方法。# 长表转宽表按用户ID统计各地区金额合计 tooth df.pivot_table(index用户ID, columns地区标准化, values金额, aggfuncsum, fill_value0) # 宽表转长表 long_df tooth.reset_index().melt(id_vars用户ID, var_name地区, value_name金额)pivot_table是结构化数据建模过程中非常常用的聚合操作能把明细数据变成特征矩阵。比如做用户画像时按用户 ID 做行、行为类型做列、行为次数或金额做值得到的数据可以直接进入 sklearn 建模流程。6.3 分组聚合统计清洗后的数据可以正常做各种聚合summary ( df.groupby(地区标准化)[金额] .agg([count, sum, mean, std]) .round(2) ) print(summary)6.4 批量处理多个数据文件批量任务不复杂核心就是用glob或os.listdir拿到文件列表循环读取、清洗、写出。真实项目中通常把清洗流程抽成一个函数脚本只负责任务调度。import glob import pandas as pd def clean_data(df): df df.copy() df df.drop_duplicates(subset[订单号, 用户ID], keepfirst) df[金额] pd.to_numeric(df[金额].astype(str).str.replace(,, , regexFalse), errorscoerce) df[下单日期] pd.to_datetime(df[下单日期], errorscoerce) return df for file_path in glob.glob(data/raw/*.csv): df pd.read_csv(file_path, encodingutf-8) df_clean clean_data(df) file_name file_path.split(/)[-1].replace(.csv, _clean.csv) df_clean.to_csv(fdata/clean/{file_name}, indexFalse, encodingutf-8-sig) print(f处理完成: {file_name}, 清洗前 {len(df)} 行, 清洗后 {len(df_clean)} 行)批量处理建议每完成一个文件就打印一行日志记录清洗前后行数方便后面核对问题。文件输出目录和原始目录分离避免覆盖原始文件。6.5 导出多种结构化格式# 导出 CSVutf-8-sig 便于 Excel 打开不乱码 df.to_csv(clean_data.csv, indexFalse, encodingutf-8-sig) # 导出 Excel df.to_excel(clean_data.xlsx, indexFalse, sheet_name清洗结果, engineopenpyxl) # 导出 Parquet列式存储后续读取快且占用空间小 df.to_parquet(clean_data.parquet, indexFalse) # 导出 Feather跨语言共享速度快 df.to_feather(clean_data.feather)从工程稳定性考虑中间结果建议用 CSV 让人能直观检查最终要交给下游模型或大数据平台的数据用 Parquet 或 Feather 更合适。Pandas 读取 Parquet 的速度比 CSV 快不少而且文件更小。6.6 清洗逻辑对接 API 服务清洗函数封装好之后可以很方便地接到 Web 接口服务上。FastAPI 是当前比较常用的轻量方案示例代码如下# app.py from fastapi import FastAPI import pandas as pd from cleaner import clean_data app FastAPI() app.post(/clean) async def clean(records: list[dict]): df pd.DataFrame(records) df_clean clean_data(df) return df_clean.to_dict(orientrecords)这个示例需要根据实际项目结构调整比如是否校验字段、是否需要鉴权、是否限制请求体大小。启动服务后可以用 curl 或 Postman 发送 JSON 数组接口返回清洗后的 JSON 结果适合对接数据上报、文件预处理等场景。7. 性能观察与大数据量处理思路Pandas 处理几十万行数据一般没有问题但数据量达到千万行级别时内存占用和运行时间会成为瓶颈。这一节讲怎么观察资源占用以及有哪些可以立刻落地的优化思路。7.1 观察内存占用# 查看每列内存占用 print(df.memory_usage(deepTrue)) # 查看整个 DataFrame 内存占用 print(df.memory_usage(deepTrue).sum() / 1024 / 1024, MB)memory_usage(deepTrue)能看出来哪些列占内存最大。object 类型列通常比数值列更占空间尤其是包含较长字符串时。7.2 降低内存占用的实用手段第一是压缩数据类型。地区、性别、状态这类字段数量有限可以转成 category 类型df[地区] df[地区].astype(category) print(df.memory_usage(deepTrue).sum() / 1024 / 1024, MB)第二是只读需要的列。如果原始 CSV 有 200 列而清洗只需要其中 30 列读取时直接指定usecolsdf pd.read_csv(large_file.csv, usecols[订单号, 用户ID, 下单日期, 金额, 地区])第三是分块读取。chunksize把一个大文件拆成多个小批次逐块处理避免一次性把全部数据加载进内存chunk_iter pd.read_csv(large_file.csv, chunksize50000) result_chunks [] for chunk in chunk_iter: chunk_clean clean_data(chunk) result_chunks.append(chunk_clean) df_all pd.concat(result_chunks, ignore_indexTrue)分块处理时要注意去重和缺失值填充这些全局操作可能会跨块失效。比如去重需要记住已经出现过的订单号可以在循环里维护一个集合只保留集合中没见过的数据。7.3 向量化操作优先Pandas 执行性能差距最大的操作就是「遍历行」。能用内置向量化方法解决的问题尽量不要用for循环逐行处理。# 性能差遍历修改 # for i, row in df.iterrows(): # df.at[i, 金额] clean_money(row[金额]) # 性能好向量化 apply 结合 df[金额] pd.to_numeric(df[金额].astype(str).str.replace(,, , regexFalse), errorscoerce)字符串处理、条件筛选、缺失值填充、数值运算都尽量用 Pandas 自带方法只有遇到非常复杂的自定义业务规则时才用apply且先用df.sample(1000)做小数据量验证再跑全量。8. 常见问题与排查方法问题现象可能原因排查方式解决方案读取 CSV 报 UnicodeDecodeError文件编码不是 utf-8用记事本打开文件查看编码指定 encodinggbk 或 gb18030读取 Excel 报缺少 openpyxl未安装 Excel 读取引擎查看报错信息pip install openpyxl金额列 astype 转换报错列中存在逗号、货币符号、中文文本先打印 df[金额].unique()先清理符号再用 pd.to_numeric(errorscoerce)日期解析失败日期格式过多或包含非法日期打印无法解析的值用 errorscoerce 把失败值置为 NaT字段是字符串但想当数字算读取时类型推断错误查看 df.dtypes用 to_numeric 或 astype 转换内存不足数据量过大或列数过多memory_usage 查看占用分块读取、只读关键列、类型转 categorydrop_duplicates 后行数异常指定的 subset 列没有唯一标识性核对业务字段更换判断字段组合批量处理卡住某个文件读取失败或数据量过大查看循环日志增加 try except 和超时观察导出 CSV 后 Excel 中文乱码编码用了默认 utf-8用文本编辑器打开观察使用 encodingutf-8-sig排查问题最重要的是先定位是「读入阶段」问题还是「清洗阶段」问题。读入阶段看报错信息清洗阶段用unique()、value_counts()、isnull().sum()逐步缩小范围。建议把每个阶段的中间结果打印或导出这样能快速定位是哪一步把数据搞坏了。9. 最佳实践与使用建议到这里整套数据清洗流程已经跑通。最后补几条工程实践建议这些才是实际项目里最值钱的部分。第一清洗前备份原始数据。不管多熟练清洗都是有损操作原始文件必须保留。批量处理时建议把原始文件放在data/raw/清洗后放在data/clean/目录分层清晰避免误覆盖。第二清洗过程尽量有日志。打印或输出每个步骤前后的行数、字段数、缺失值数量。这样后期数据出问题可以回溯是哪一步引入的。第三清洗逻辑模块化。不要把所有处理代码堆在一个脚本里建议拆成read_data.py、cleaners.py、output.py这样的模块。clean_data()这种核心函数单独放方便测试和复用。第四小数据验证再全量跑。先用df.sample(100)或df.head(1000)验证整个流程确认逻辑正确后再处理全量数据。批量任务更要先拿一个文件试跑再循环所有文件。第五涉及敏感数据必须脱敏。手机号、身份证、地址、银行卡号这类信息在展示、分享、提交代码到外部平台之前一律做脱敏处理。数据来源要合法处理权限要明确这是数据工作者的基本底线。第六保留可复现环境。把依赖版本固定在requirements.txt里比如pandas2.2.2、numpy1.26.4方便别人或未来的自己复现结果。如果你想继续深入下一步建议按这个顺序走先用 NumPy 补充数组运算能力然后学习用 Matplotlib、Seaborn 做清洗后的数据可视化接着用 Scikit-learn 把清洗后的结构化数据喂给机器学习模型最后了解 DuckDB 或 PySpark 来处理 Pandas 撑不住的大数据量场景。数据清洗的价值不在代码量而在流程的稳定性和可维护性。先把本文这套“读取 - 体检 - 修复 - 结构化 - 批量 - 性能优化”的流程练熟再用自己的真实数据跑一遍会比硬背几十个 API 有效得多。建议把文中的代码整理成自己的清洗工具模块下次拿到新数据直接复用即可。
返回列表