尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Pandas数据清洗与预处理:从脏数据到可分析数据

Pandas数据清洗与预处理:从脏数据到可分析数据 干数据分析这几年我越来越确定一件事真正决定一个项目顺利与否的往往不是后面那个看起来很高端的模型而是最开始那几步——用 Pandas 清洗数据、做预处理的过程。很多朋友拿着 Excel 里导出的“初始数据”直接开始跑分析结果要么报错要么统计出来的数字自己都不敢信。这还真不是 Python 的问题而是数据在进入分析流程之前没有经过一次系统性的预处理。这篇文章会完整走一遍 Pandas 清洗数据和预处理的流程。从读取文件时怎么提前规避脏数据到诊断数据结构、处理缺失值、转换数据类型、清理重复值和异常值再到文本字段的定向清洗最后用一个模拟项目把这些步骤串成一条可复用的流水线。适合刚接触 Pandas 的人建立整体思路也适合有一定基础、但总觉得清洗步骤“差点意思”的朋友查漏补缺。1. 原始数据带回来的那些“脏东西”清洗前先想清楚这四件事很多人对数据清洗有个误解觉得它就是“删掉空行、去掉重复值”这么简单。真上了项目你会发现脏数据的形态远比想象中丰富。1.1 真实的脏数据长什么样我接过一个电商订单导出的 CSV字段有订单编号、用户ID、下单时间、商品名称、金额、城市、渠道。打开一看问题全来了金额列里混着中文逗号和货币符号比如1,200还有1.200这种欧洲写法直接 to_numeric 会报错下单时间有三种格式2024/1/5、2024-01-05 14:32:11、20240105排序和分组时各排各的城市字段里“北京市”“北京”“BJ”都有分组统计时被当成三个城市缺失值不在同一列而是散落在十几个字段里没有规律可循部分订单出现了完全相同的两行但是它们的索引不同。这些还不是最离谱的。我还见过商品备注字段里带着换行符和制表符导入数据库后把表结构都搞乱了。所以说数据清洗不是一个“锦上添花”的步骤而是保证后续分析可信度的地基。1.2 动手清洗之前先想清楚的四个问题每次拿到数据我建议你别急着一顿操作先回答四个问题答案清楚了清洗路径自然就出来了。第一数据口径是什么。比如“销售额”到底是含税还是不含税“登录次数”是去重后的用户数还是总点击数如果口径不统一你后面做再多统计都是白费功夫。这个往往需要和业务方反复确认Pandas 层面能做的是先把字段名和含义整理成对照表。第二清洗的边界在哪里。不是所有字段都需要洗。有的字段对当前分析目标毫无用处比如研究销售趋势时用户备注字段根本不影响结论那就别浪费时间在它上面。清洗的优先级应该从分析目标倒推先洗核心字段有时间再处理锦上添花的部分。第三清洗顺序怎么定。我的习惯是先转换类型再处理缺失然后去重和异常最后做文本清洗。为什么类型转换会揭示出一批“伪缺失”比如空字符串、空格、nan这种字符串形态的缺失值先把它们暴露出来fillna 的时候才不会被骗。重复值删除也应该尽量放在类型统一之后因为只有字段标准化了重复判断才准确。第四原始备份不能省。不管你的清洗脚本写得多自信都要保留一份原始数据的副本。我见过不止一次清洗后发现规则写错了或者老板说“这个字段的口径变了”只能从头再来。处理办法很简单读取之后立刻df_backup df.copy()这一行看着不起眼但能让你在后面的任何一次“后悔”中全身而退。1.3 复杂在读取阶段就处理掉一部分很多人不知道pd.read_csv和pd.read_excel本身就带了一堆“预清洗”参数用好了能省掉后面的大量工作。import pandas as pd import numpy as np df pd.read_csv( order_data.csv, encodingutf-8-sig, dtype{user_id: str, order_id: str}, # ID类字段按字符串读防丢失前导0 parse_dates[下单时间], # 读进来就转成datetime usecols[订单编号, 用户ID, 下单时间, 金额, 城市], # 只留需要用的列 thousands,, # 自动处理千分位逗号 )三个容易被忽略的点encodingutf-8-sig如果你用 Excel 打开过导出文件再另存文件头可能带 BOM用这个参数能避免第一列列名出现\ufeff乱码dtype 指定字符串用户ID、订单号这类字段如果按数字读18位以上的长ID会被转成科学计数法或者丢失尾数parse_dates读取阶段就把日期列解析成 datetime64 类型后面排序、分组、画图都顺手。如果处理的是文本文件比如日志或者行为数据pd.read_csv()同样适用只需要在sep参数里换成对应的分隔符比如\t制表符或者正则表达式\s匹配连续空白。E文不好没关系记住sep和encoding这两个参数就能应付绝大多数文本文件读取场景。2. 数据体检怎么做结构性诊断与缺失值处理实操数据读进来以后先别急着洗先“体检”。体检的目的是弄清楚数据到底长什么样、哪里有毛病、严重到什么程度。2.1 用 info、dtypes 和 describe 给数据来一次全面体检我习惯拿到数据后先打一套组合拳df.info()、df.dtypes、df.describe()再加一个df.head()。这一步不是走形式是真的能看出很多门道。print(df.info())info()会输出每个列的名字、非空值数量、数据类型和总内存占用。这里有一个关键判断如果一个列显示为object类型但它实际内容是数值那你这列就必须进行类型转换。Pandas 的object类型是个“大筐”什么都能装但一旦装进“大筐”性能会下降很多数值运算也不支持。如果数据本身不大但列数多我会再手动创建一个 DataFrame 骨架来看结构这种方式在搭建特征工程时尤其好使feature_template pd.DataFrame({ feature_name: [], dtype: [], missing_rate: [], n_unique: [] })先把要用的特征列名、预期类型写进去再和实际数据做比对。这个“先搭骨架、后填数据”的习惯能让你在字段多的时候不至于抓瞎。接着看描述统计print(df.describe(includeall))describe()默认只统计数值列加上includeall以后对象类型列也能看到计数、唯一值数量、出现频次最高的取值。这一步的价值在于你能在几秒钟内发现“金额列的最小值为什么是负数”“城市列唯一值为什么有 87 个但业务上只有 30 多个城市”这类明显异常。体检阶段我还会顺带看一眼内存占用df.memory_usage(deepTrue)。如果跑的是大数据量文本光一个字段就吃了好几个G那你在后面做 groupby 之前就得先考虑降采样或者只保留必要列。2.2 缺失值到底要不要删先算清楚缺失率和业务容忍度Pandas 里检查缺失值有两套等价的方法isnull()和isna()。我习惯用isna()因为拼写顺一点功能完全一样。missing_count df.isna().sum() missing_rate df.isna().mean().round(4) missing_info pd.DataFrame({缺失数量: missing_count, 缺失率: missing_rate}) print(missing_info[missing_info[缺失率] 0])这样一筛哪个字段缺了多少、缺的比例多高一目了然。根据我自己的经验缺失率的处理大致可以分成三档缺失率低于 5%多数情况下可以直接丢行或者用中位数/众数填补影响不大缺失率在 5%-30%需要看缺失是否与某些业务状态有关。比如“支付时间”缺失很可能是订单未支付那这个缺失本身就是有效信息不能随便填缺失率超过 30%如果字段不是分析核心建议删除整列。强行填补超过三分之一的缺失填出来的值基本是编的对模型和统计都是污染。理论上有 MCAR、MAR、MNAR 这些统计概念但实际上做项目时没那么讲究。核心原则就一句话缺失本身就是信息先搞清楚缺失的原因再决定怎么处理。2.3 dropna 与 fillna 的用法和取舍缺失值处理有两大流派删和填。先说说删。dropna()最基础的用法是删掉带缺失的整行但这样操作后遗症很大。如果一张表有 20 列单行只要有一列缺失就会被删最后能留下的行数可能只剩一半。这里有一个非常实用但经常被忽略的参数thresh。df_clean df.dropna(thresh3)这个参数的含义是保留那些在整行中非空值数量不少于 3 的行。换句话说一行如果有超过 2 个字段是空的就整行删掉否则保留。这比dropna()的“零容忍”策略温和得多适合字段多、缺失分散的场景。再说填。fillna()的填法有很多种不是简单填 0 就完事。我按列类型整理了常用的填补策略列类型推荐策略理由连续数值列中位数填充均值容易被极端值拉偏中位数更稳健分类型列众数填充保留分布特征时间序列列ffill / bfill用前值或后值填补符合时间趋势业务有上下限的列上下限值或业务默认值比如“库存”缺失按0填符合业务逻辑明确表示“无”的列填unknown保留缺失状态不让数值污染统计光说不练假把式来两个典型实操场景。数值列如果存在较多的极端值我不用均值而是用分组中位数填充df[金额] df.groupby(渠道)[金额].transform( lambda x: x.fillna(x.median()) )为什么分组因为不同渠道的消费水平可能差异很大统一填一个中位数会掩盖渠道差异。分组之后的填充更贴近真实分布。时间序列字段的填充则要谨慎我去填补订单的“送达时间”缺失时会用ffill先看看情况df[送达时间] df[送达时间].ffill()但要注意如果数据不是严格按时间排序的ffill会产生严重误导必须确认索引或之前已经 sort_values 过才可以用时间序列填充。还有一类“伪缺失”其实是空字符串或者一堆空格。isna()检测不出来但实际内容和缺失没区别。这种情况我会先统一转成真正的缺失df[备注] df[备注].str.strip() df[备注] df[备注].replace(, np.nan)先把空格清掉再批量转换比逐个字段手工处理高效多了。3. 数据类型转换字符串、数值、时间之间来回折腾的坑与技巧类型转换是整个预处理里最容易踩坑、也最体现“预处理”价值的一步。热搜里有人搜“pandas 数据类型转换”说明这块确实是高频卡点。3.1 为什么类型转换是数据预处理的核心Pandas 里的数据类型直接决定了你能对列做什么操作。数值列和字符串列执行sum()一个给数字一个把字符串拼在一起字符串列排序是按字典序排的100会排在99前面datetime 类型的列才能做时间差计算、按月重采样、绘制时间序列图。换句话说一个列的类型错了后面所有操作都可能是错的。类型转换的目的就是把“机器理解不了”的数据变成“机器能正确处理”的数据。3.2 数值转换to_numeric 和 astype 的正确姿势数值转换的最佳工具是pd.to_numeric()不是astype()。虽然两个都能转数值但to_numeric有一个核心杀招errorscoerce。df[金额] pd.to_numeric(df[金额], errorscoerce)coerce的意思是把转不过去的值强制变成 NaN。比如一个字符串列表[1200, N/A, 1,500]转换之后会得到[1200.0, NaN, 1500.0]。这一步的价值是它不报错而是把脏数据暴露为缺失值让你后续可以统一处理。相比之下astype(float)一旦遇到不能转的值会直接抛异常整个脚本中断。要我说astype只适合在数据已经确定干净的情况下做“确认性转换”。尤其是把字符串123转成int这种完全可预测的操作才能放心用。注意一个高危操作如果列里有 NaN你直接写df[列名].astype(int)大概率会得到一个ValueError。正确的绕行方式是先fillna再astype或者用 Pandas 2.0 之后新增的Int64类型来保存带缺失的整型df[数量] df[数量].astype(Int64)这个Int64第一个字母是大写的 I它能在保留缺失值的同时仍然按整数类型存储。很多人不知道这个类型在线上一搜全是astype(int)然后踩坑的帖子。这里重点标记一下。实际项目里最经典的就是“金额字段带货币符号、千分位、前后空格”的清洗链。我会这样一口气处理完# 1. 去掉货币符号和千分位逗号 df[金额] df[金额].str.replace(r[¥,\s], , regexTrue) # 2. 转数值转不过去的变成NaN df[金额] pd.to_numeric(df[金额], errorscoerce) # 3. 用中位数填补转换失败导致的缺失 df[金额] df[金额].fillna(df[金额].median())第 1 步用了正则表达式一次把中文符号、半角逗号、全角逗号、空格全删了。如果字符串里还有括号注释之类的干扰就再扩展正则规则。这三步跑下来金额列基本就干净了。3.3 时间转换to_datetime 和 parse_dates 的配合时间字段的清洗核心工具是pd.to_datetime()。我在读取时如果已经指定了parse_dates那这一步可以简化但如果你在项目中途才想起来日期列还没转换那就手动处理df[下单时间] pd.to_datetime( df[下单时间], format%Y-%m-%d %H:%M:%S, errorscoerce )这里有三个容易被忽略的点。format 参数显式指定格式很重要。如果数据里有2024/01/05、2024.01.05、20240105三种写法混在一起不指定formatto_datetime虽然多半也能猜出来但速度慢几个数量级而且遇到歧义比如01/02/2024这种月日/日月混合的可能猜错。显式指定格式后不匹配的值会变成 NaN这样你能立刻发现哪些数据格式异常。errorscoerce 同样好使。时间字段里偶尔混入NULL、unknown之类的垃圾值不报错、转成 NaN后续统一填充或者删行比脚本中断要可控得多。年月日分列的情况也别慌。数据表里不一定有完整的日期列可能只有年、月、日三列比如日志表就是这种结构。可以拼回去df[日期] pd.to_datetime( df[年].astype(str) - df[月].astype(str) - df[日].astype(str), errorscoerce )如果表里只给了时间戳Unix 时间戳或者毫秒时间戳那就更简单了pd.to_datetime(df[ts], units)就行。唯一的坑是单位不统一有的是秒、有的是毫秒少写一个unit参数日期会差出几十倍。3.4 自定义拆分转换单列拆多列有一种数据类型转换不是“字符串转数字”而是“一列拆成多列”。实际业务里的典型代表是“规格”字段比如500ML/瓶、1000件/箱。这种字段不拆你就没法做单位维度的统计分析。df[[规格数值, 规格单位]] df[规格].str.extract( r(\d(?:\.\d)?)\s*([A-Za-z/]) )str.extract()配合正则表达式可以直接把匹配到的分组变成新列。上面的规则把500ML/瓶拆成500和ML/瓶。我建议拆完之后把数值列再做一次pd.to_numeric因为 extract 出来的永远是字符串。字符串的批量操作也是同样道理str.split配合expandTrue也能实现类似效果df[[一级类目, 二级类目]] df[类目].str.split(/, expandTrue)4. 重复值、异常值和文本脏数据的定向清理结构和类型问题处理完之后就到了“定向清理”环节。这一步针对的是具体的脏数据形态重复值、异常值、文本乱码。4.1 重复值处理哪些重复该删哪些不该删先上基础操作df df.drop_duplicates()这一行会删掉“整行完全一致”的重复记录。真实场景里整行重复通常来自数据源重复导出或者网络重试删掉基本没风险。但大多数重复不是整行重复而是业务主键重复。比如同一用户在同一秒点击了两次“提交订单”可能是手抖也可能是合法操作。这时候你不能单纯按所有列去重而是应该指定业务主键df df.drop_duplicates(subset[订单编号, 用户ID], keepfirst)subset指定判断重复的列keepfirst保留第一条。重点来了到底保留哪条要看你分析目标是什么。如果分析订单金额保留第一条还是最后一条对总额没影响如果分析用户修改记录的轨迹那你可能一条都不能删反而要保留所有记录。还有一个常见误区不去重直接分组统计行数看着没问题其实全部被翻倍。我习惯在清洗流程里加一个断言式检查assert df[订单编号].nunique() len(df), 存在重复订单编号nunique()和len()不相等说明主键有重复代码直接中断报警。这样比事后发现统计数字离谱要舒服得多。4.2 异常值判定统计方法给线索业务规则做决策异常值比缺失值和重复值都棘手。缺失和重复至少“长得明显”异常值却混在正常数据里一不小心就把均值、方差拉到离谱的水平。我的处理逻辑分两层先统计找线索再业务定去留。第一层统计线索。最快的方式是看describe()的 min 和 max。比如金额列的 min 是 -500这大概率有问题正常的交易金额不应该为负。如果数据列很多你还可以画个箱线图辅助判断Pandas 的df.plot(kindbox)配合 matplotlib 一两行就能出图比干看数字直观得多。定量判断常用两个方法3σ 原则数据近似正态分布时超过均值±3倍标准差的值算异常。但真实业务数据很少是标准正态这个方法的误杀率不低IQR 四分位距法以 Q1 和 Q3 的 1.5 倍为边界超出边界的值视为离群点。这个方法不依赖正态假设更稳健我对绝大多数业务字段都优先用它。Q1 df[金额].quantile(0.25) Q3 df[金额].quantile(0.75) IQR Q3 - Q1 lower Q1 - 1.5 * IQR upper Q3 1.5 * IQR df_outlier df[(df[金额] lower) | (df[金额] upper)] print(f疑似异常值 {len(df_outlier)} 条)第二层业务决策。统计算出来的“异常”只是一个信号不能直接当成“脏数据”删掉。比如一个卖高端家电的店铺客户单笔订单 10 万元可能就是正常消费而一个奶茶店的单笔订单 10 万元基本可以断定是测试单或者刷单。所以我的处理框架是先看异常值数量占总体比例如果低于 0.1%多半是录入错误可以删如果比例偏高可能是业务本身长尾分布比如收益、销量这类数据不要删尝试用ewm这类指数加权方法做平滑或者直接进行对数变换如果异常值恰好落在业务规则的禁区如金额必须大于 0那不需要统计方法直接用业务规则过滤。ewm函数在这个场景很实用它的核心参数是span和adjust。比如对店铺的每日销售额做平滑减少偶发大单对趋势判断的干扰df[销售额_平滑] df[销售额].ewm(span7, adjustTrue).mean()span7的意思是近似按 7 天窗口做指数加权越近的日期权重越大。adjust参数一般保持默认就行实际项目里我很少去动它。注意ewm是为了“平滑抖动”不是为了“删掉异常”没有特殊需求别用它来做异常值过滤。4.3 文本脏数据strip、大小写统一、替换和正则文本字段是最让新人头疼的部分因为它的“脏”没有固定模式。但只要掌握四板斧大部分问题都能处理。第一板斧去空格和特殊符号。df[城市] df[城市].str.strip() df[商品名称] df[商品名称].str.replace(r[\t\n\r], , regexTrue)str.strip()只能去掉首尾的空格中间的空格要用str.replace()配合正则才能处理干净。制表符和换行符这种隐形脏字符也靠这一步解决。第二板斧统一大小写。分类字段经常出现大小写混用比如GZIP、gzip、Gzip在语义上完全一样。统一大小写之后再分组统计结果就整齐了df[文件格式] df[文件格式].str.lower()第三板斧批量替换做同义词归一。城市字段写“北京市”“北京”“BJ”的问题我一般用replace配合字典做归一化city_map { 北京市: 北京, BJ: 北京, Beijing: 北京, 上海市: 上海, SH: 上海, Shanghai: 上海, } df[城市_标准化] df[城市].replace(city_map)这个字典可以维护在外部配置文件里新增映射随时追加不用改主代码。第四板斧正则提取和匹配。文本里藏信息是常态。商品备注里可能写着“备注急件”评论里可能带着电话号码这些都可以用str.extract和str.contains提取。热搜里有“pandas 正则表达式”这个词说明大家确实用得多这里给一个典型例子df[是否急件] df[备注].str.contains(急件, naFalse).astype(int) df[联系电话] df[评论].str.extract(r(1[3-9]\d{9}))str.contains返回布尔值拼上.astype(int)就能生成 0/1 标签列非常方便做特征工程。str.extract则把匹配的唯一分组抽出来作为新列提取联系电话和身份证号这类结构化信息时尤其好用。文本清洗的顺序也很重要。我一般把步骤定为先strip清空格再replace做规则替换包括去掉换行符之类然后lower统一大小写最后str.extract提取信息。顺序乱了可能你先提取了字段结果后面替换时把提取结果里的内容也改了导致信息错乱。5. 组合操作让数据真正可用筛选、排序、分组与合并清洗完成不等于数据能用。数据要真正被分析和模型吃进去还得经过筛选、排序、分组、合并这些组合操作。这一步最能体现 Pandas 的“数据处理”功力。5.1 按条件筛选布尔索引、isin 和 query筛选数据最自然的方式是布尔索引。原理简单粗暴df[金额] 100产生一列布尔值把True对应的行留下False的丢掉。df_filtered df[(df[金额] 100) (df[渠道] APP)]多个条件叠加时注意要用和|不能用 Python 的and和or这两个会让 Pandas 报“模糊真值”的错误。新手在这里没有少踩坑。如果要筛的字段值是一个列表用isin更清爽target_cities [北京, 上海, 广州] df_filtered df[df[城市].isin(target_cities)]条件表达式写得多且复杂比如大于、小于、包含、不等于组合在一起时我改用query可读性高很多df_filtered df.query( 金额 100 城市 in target_cities 是否急件 1 )query里的变量名可以引用外部 Python 变量这个技巧很多人不知道但非常实用。筛选之后经常出现索引不连续的情况后续如果用groupby或者按位置取数会出问题顺手重置一下索引df_filtered df_filtered.reset_index(dropTrue)dropTrue是必要的否则原来的索引用会被当成新的一列加进来白白多出一个字段。5.2 groupby 聚合预处理阶段就看数据长什么样groupby是 Pandas 数据处理里使用频率最高的操作之一本质就是“按某个字段分组然后对每个组做统计”。在预处理阶段我用它来做分组填补缺失在探索阶段用来看各维度数据分布。grouped df.groupby(渠道).agg( 订单数(订单编号, count), 总金额(金额, sum), 平均金额(金额, mean), 最大金额(金额, max), )agg可以同时聚合多个字段、多个统计指标上面这个写法可读性最强左边是结果列名右边是(来源列, 聚合函数)的元组。如果你做时间序列分析groupby还能结合时间维度做重采样比如按月统计monthly df.set_index(下单时间).groupby(渠道)[金额].resample(M).sum()这条链式操作是先按渠道分组再对每个渠道按月份汇总输出是一个 MultiIndex 的 Series展开后用reset_index()恢复成普通表格。分组之后还有一类操作叫transform它能把分组计算结果广播回原数据的每一行。前面提到的分组填充就是标准案例df[金额] df.groupby(渠道)[金额].transform(lambda x: x.fillna(x.median()))transform与agg最大的区别在于agg把多个组的结果压缩成统计表行数变少transform保持行数不变把统计结果贴回到每一行。涉及“求每个用户自己的平均消费然后和全表平均消费做对比”这类需求时transform是核心工具。5.3 merge 和 concat多表合并之前的必要准备多表合并是数据清洗最容易踩大坑的地方尤其当你处理的是订单表用户表渠道表这种结构化数据。pd.merge()的核心参数就三个left、right、on或者left_on/right_on。先上一个中规中矩的例子df_order pd.merge( df_order, df_user, howleft, onuser_id )howleft的意思是保留左侧表的所有行右侧匹配不到的字段填 NaN。这是业务分析里最常用的合并方式因为订单一般不会因为缺了用户信息就丢失。合并最容易踩的坑不在函数用法而在合并键的脏数据。举个例子订单表里的用户ID是A001用户表里的用户ID是A01两边看着是一个人但字符串不相等merge 之后要么匹配不上要么重复匹配产生大量笛卡尔积。所以我在多表合并之前一定会做键的预处理三件事必做df_order[user_id] df_order[user_id].str.strip().str.upper() df_user[user_id] df_user[user_id].str.strip().str.upper()去空格、统一大小写有时候还要统一类型。object类型和category类型合并时也可能出问题最好先把键统一转成字符串再合并。为了在合并前发现键的问题我会用isna()统计合并结果的缺失情况merged pd.merge(df_order, df_user, onuser_id, howleft) print(merged[user_name].isna().sum())如果左侧有订单但右侧匹配不到用户名说明两边键数据不齐需要回头看是不是 ID 格式不一致。concat的使用场景不一样它主要用来纵向堆叠结构相似的表。纵向堆叠时最大的坑是两边列名不一致一列叫金额、一列叫总金额堆叠以后变成两列各剩一半空白。所以concat之前先确认列名完全一致或者用ignore_indexTrue去掉原来的索引干扰。如果左右列名确实不一样但含义相同先用rename统一再 concat。6. 一条完整的数据预处理流水线从 CSV 到可分析数据集知识点散着讲容易飘最后用一个模拟项目把前面的操作串成一条完整流水线。这个例子的场景是拿到一份电商订单明细 CSV字段包括订单编号、用户ID、下单时间、商品名称、类目、城市、金额、渠道、备注目标是清洗出一个可以直接做统计分析和建模的数据集。先制造一批带脏数据的模拟数据这一步也顺便演示了 Pandas 数据结构的基本创建方式import pandas as pd import numpy as np df pd.DataFrame({ 订单编号: [A001, A002, A003, A004, A005, A006], 用户ID: [U001, u001, U002, U003, None, U004], 下单时间: [2024-01-05 10:21:00, 2024/1/5 11:30, 20240105, 2024-01-06 09:15, 2024-01-07 08:00, 2024-01-08 14:45], 金额: [1,200, 800, 1.500, 250, N/A, 3000], 城市: [ 北京市, 北京, BJ, 上海, 广州, shanghai], 渠道: [APP, APP, 网页, 小程序, APP, 网页], 备注: [急件, , 客户指定顺丰, 急件\n请尽快, np.nan, 无], 类目: [手机/数码, 手机/数码, 家用/电器, 图书/教育, 服装/男装, 家用/电器], })然后按顺序执行清洗流程。第 1 步备份原始数据。保证后面任何时候反悔都能一键恢复。df_raw df.copy()第 2 步统一用户 ID 并补缺失标记。用户ID是合并和去重的重点键先 strip、转大写再检查重复。这里的u001和U001可视化层面很像实际是两个字符串必须先归一。df[用户ID] df[用户ID].astype(str).str.strip().str.upper() df[用户ID] df[用户ID].replace({NONE: np.nan, : np.nan, NAN: np.nan}).astype(str)这一步很关键因为列里有 NaN直接.str会出错。注意如果列里混着 Noneastype(str)会把None变成字符串None所以后面那次replace就必须跟上把NONE、NAN、空字符串这类伪缺失重新变回 NaN。第 3 步金额清洗链。先正则清掉货币符号和空格再to_numeric转数值无法转换的变成 NaN最后统一用中位数填补。这个顺序不能乱先清洗再转换再填补。df[金额] df[金额].astype(str).str.replace(r[¥,.\s], , regexTrue) df[金额] pd.to_numeric(df[金额], errorscoerce) df[金额] df[金额].fillna(df[金额].median())这里我把1.500里的.也替换掉了替成1500避免欧洲数字格式的干扰。具体怎么写正则要看你数据里点号是小数点还是千分位不能照搬。第 4 步时间格式统一。三套时间写法通过to_datetime统一成 datetime64 类型。这一步不需要先拆分再拼接用errorscoerce让无法解析的变成 NaN 即可。df[下单时间] pd.to_datetime( df[下单时间], errorscoerce, formatmixed )formatmixed是 Pandas 2.0 之后才有的参数允许日期字段混合多种格式统一解析。如果你的 Pandas 版本较老去掉这个参数让 Pandas 自行推断或者干脆先处理成同一种格式再转换。第 5 步城市字段归一化。去掉首尾空格然后批量替换同义词。df[城市] df[城市].str.strip() df[城市] df[城市].replace({ 北京市: 北京, BJ: 北京, shanghai: 上海 })第 6 步缺失值处理。在替换伪缺失之后再看一遍缺失情况逐个字段选择策略。这里用户ID的缺失直接删掉对应行备注的缺失包含空字符串填充为无备注。df df.dropna(subset[用户ID]) df[备注] df[备注].str.strip().replace(, np.nan).fillna(无备注)先strip再replace再fillna一套下来备注字段的空值状态最干净。dropna(subset[用户ID])只针对用户ID这一列做删行不会误伤其他字段。第 7 步去除重复和异常。订单编号加用户ID组合键去重金额字段按 IQR 法过滤极端值同时结合业务规则保证金额大于 0。df df.drop_duplicates(subset[订单编号, 用户ID], keepfirst) 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)] df df[df[金额] 0]这里金额低于 0 的行业务上可以直接判为无效订单不需要再做统计层面的验证。两种情况结合过滤规则才完整。第 8 步文本提取。从备注里提取“是否急件”标签从类目字段里拆分一级和二级类目。df[是否急件] df[备注].str.contains(急件, naFalse).astype(int) df[[一级类目, 二级类目]] df[类目].str.split(/, n1, expandTrue)实际操作里这一步的str.split经常遇到分割后两列数量对不齐的情况用n1可以只分割第一个/避免二级类目里再带斜杠导致结果变三列。第 9 步汇总验证。清洗完不是直接收工先做一次验证性统计。比如按城市分组的销售汇总看结果是否和业务认知一致。result df.groupby(城市).agg( 订单数(订单编号, count), 总金额(金额, sum), 平均金额(金额, mean), ).reset_index() print(result)一步到位之后的结果应该是一个字段类型正确、无重复无缺失、异常值已过滤、文本字段已归一化的干净表格。最后输出的时候按你的需要选择to_excel还是to_csv。有个小细节如果是给 Excel 用户看的文件encodingutf-8-sig比默认 UTF-8 更友好避免 Excel 打开出现中文乱码如果是给下游程序读取的用普通 UTF-8 就行。result.to_excel(clean_order_data.xlsx, indexFalse) result.to_csv(clean_order_data.csv, encodingutf-8-sig, indexFalse)这一步跑一遍清晰看到数据从“原始脏数据”到“可分析数据”的完整变化。我也建议把流水线封装成函数每个步骤一个def后续新数据进来只要调同一个函数就能复用整套清洗逻辑而不是每次都复制粘贴改代码。数据清洗和预处理这类工作看似琐碎实际上是在为后面所有分析动作打地基。我自己在跑完一整套流程之后最大的心得是不要迷信某个“万能函数”每一步都要回到业务逻辑上去判断缺失怎么填、异常怎么删、重复怎么去答案都在业务场景里。把 Pandas 的这些基础操作练熟了遇到任何一张乱糟糟的表你都会有一种“心中有数”的底气。
返回列表