Pandas进阶:数据清洗与预处理实战全教程(数据分析工程师落地版)
前言:一线数据分析从业者日常总结,摒弃入门级基础语法,聚焦项目落地高频进阶用法,全文结合电商真实订单脏数据案例,所有代码均本地实测可运行,无套话、无AI模板化行文,适合有Pandas基础、需要进阶做项目数据预处理的开发、数据分析师学习。
行业共识:数据分析项目中,60%~80%工时消耗在数据清洗环节,原始CSV、Excel、数据库导出数据普遍存在空值、乱码、重复、格式错乱、异常极值、文本混杂数字等脏数据,直接运算会导致统计失真、机器学习模型效果崩盘,本文按照数据探查→缺失值进阶处理→精准去重→异常值甄别与修正→数据类型标准化→文本字段清洗→日期规范化→特征预处理→封装通用清洗工具类完整业务流程编写。文章目录
- 一、环境配置与实战数据集构造
- 1.1 依赖库导入与全局显示配置
- 1.2 构造仿真电商脏数据集(贴近真实业务)
- 1.3 第一步:数据探查(进阶排查,不止info())
- 二、缺失值进阶处理(摒弃无脑dropna/fillna,分场景业务化填充)
- 2.1 按需删除空值(两种进阶筛选逻辑)
- 2.2 分类字段填充(depart部门、用户姓名)
- 2.3 数值字段高阶填充(分组填充>全局均值/中位数)
- 2.4 时序/连续数据插值填充interpolate()
- 2.5 时间序列前向填充ffill(日志类数据专用)
- 三、重复数据精准去重(进阶多条件去重,项目最高频)
- 3.1 按单一主键去重(user_id唯一,保留最新记录)
- 3.2 多字段联合去重
- 3.3 查看被剔除的重复明细
- 四、异常值处理(IQR四分位+3σ准则+业务阈值三重方案)
- 4.1 业务规则直接剔除(最稳妥,优先执行)
- 4.2 IQR四分位法剔除统计异常(箱线图原理)
- 4.3 Winsor缩尾处理(不想删数据,极值替换为边界值)
- 五、字段格式标准化:数据类型转换+文本清洗
- 5.1 文本字段去空格、统一大小写(user_name)
- 5.2 强制数值转换(pd.to_numeric,非法字符转NaN)
- 5.3 日期字段统一格式化(pd.to_datetime万能转换)
- 六、分类特征编码(建模预处理必备:独热编码+标签编码)
- 6.1 独热编码get_dummies(低基数分类:部门depart)
- 6.2 标签编码(高基数分类,用户姓名等)
- 七、进阶优化:批量字段处理+自定义通用清洗类(项目复用)
- 八、清洗结果导出与数据校验
- 8.1 导出清洗后干净数据
- 8.2 最终校验三步法
- 九、落地避坑总结(多年踩坑经验)
- 十、拓展高阶场景(超大文件分块清洗)
一、环境配置与实战数据集构造
1.1 依赖库导入与全局显示配置
日常处理表格时默认输出会省略行列、浮点数科学计数,提前配置全局参数,实操查看数据更直观,也是项目标准化写法:
importpandasaspdimportnumpyasnp# 全局展示设置pd.set_option('display.max_columns',None)# 展示全部列pd.set_option('display.max_rows',80)# 单次最多展示80行pd.set_option('display.float_format',lambdax:"%.2f"%x)# 浮点保留两位小数pd.set_option('mode.chained_assignment',None)# 关闭链式赋值警告,避免冗余报错弹窗1.2 构造仿真电商脏数据集(贴近真实业务)
模拟电商用户订单原始数据,刻意植入空值、异常年龄、异常薪资、乱格式日期、重复订单、空字符串脏数据,后续全章节基于该DataFrame处理,读者可直接复制代码生成数据练习:
np.random.seed(28)# 固定随机种子,复现结果统一raw_dict={"user_id":list(range(1,101)),"user_name":["小明","小红 ","小李"," 小张"]*25,# 首尾空格脏文本"age":np.concatenate([np.random.randint(20,55,92),[220,-8,0,np.nan,np.nan,190,-12,np.nan]]),# 异常年龄+空值"order_money":np.concatenate([np.random.randint(99,1999,93),[999999,-5000,np.nan,np.nan,np.nan,888888,-999]]),#异常订单金额"pay_date":np.concatenate([pd.date_range("2024-01-01",periods=90).astype(str),["2024/02/30","无日期","2023-13-05",np.nan,"2024.05.22","2025-00-01"]]),#非法日期"depart":np.random.choice(["电商部","运营部","市场部",None,""],size=100)# None空值+空字符串}df=pd.DataFrame(raw_dict)df_origin=df.copy()# 永久备份原始数据,清洗永远不修改源数据(项目硬性规范)1.3 第一步:数据探查(进阶排查,不止info())
新手仅使用df.info(),进阶项目需要批量统计缺失占比、数值字段分布、重复条数,快速定位数据问题:
#1.基础信息print(df.info())#2.缺失值精细化统计(数量+占比%)miss_df=pd.DataFrame({"缺失条数":df.isna().sum(),"缺失占比(%)":round(df.isna().sum()/len(df)*100,2)})print("字段缺失统计:\n",miss_df[miss_df["缺失条数"]>0])#3.数值字段极值探查(快速揪异常值)print("数值字段描述:\n",df[["age","order_money"]].describe())#4.统计全量重复行总数print("全字段完全重复行数:",df.duplicated().sum())实操经验:缺失占比>50%的字段直接整列删除;数值字段最大值/最小值严重违背业务逻辑即为异常字段。
二、缺失值进阶处理(摒弃无脑dropna/fillna,分场景业务化填充)
新手通病:遇到空值直接df.dropna()全删或全填0,极易丢失有效样本,进阶分删除空值、固定值填充、分组特征填充、插值填充、时序填充5种落地方案,按缺失占比、字段类型选用。
2.1 按需删除空值(两种进阶筛选逻辑)
- 关键主键空值直接删除:user_id是用户唯一标识,主键为空无业务意义,直接剔除该行,用
subset精准限定字段,不影响其他字段空值:
df=df.dropna(subset=["user_id"])- 整行空值过多再剔除:设定阈值,单行有效数据不足50%直接删除(列数*0.5),适配大批量残缺脏数据:
#本案例6列,有效字段低于3则删行df=df.dropna(thresh=int(df.shape[1]*0.5))2.2 分类字段填充(depart部门、用户姓名)
分类字段(文本)空值、空字符串统一替换为“未知部门”,先用replace把空字符串转为np.nan再填充,解决数据源""空字符≠NaN漏处理问题:
df["depart"]=df["depart"].replace("",np.nan).fillna("未知部门")2.3 数值字段高阶填充(分组填充>全局均值/中位数)
全局均值容易被异常极值带偏,同部门分组中位数填充是企业数据分析首选方案,同分组业务特征接近,填充误差最小:
#按depart分组,组内中位数填充age年龄空值df["age"]=df.groupby("depart")["age"].transform(lambdax:x.fillna(x.median()))#订单金额用分组均值填充df["order_money"]=df.groupby("depart")["order_money"].transform(lambdax:x.fillna(x.mean()))2.4 时序/连续数据插值填充interpolate()
连续有序数据(销量、流水)不用均值,线性插值拟合空缺值,比简单填充更贴合数据趋势:
#模拟连续序列插值示例ser=pd.Series([120,np.nan,156,np.nan,189])ser=ser.interpolate(method="linear")2.5 时间序列前向填充ffill(日志类数据专用)
用户行为日志、埋点数据常用,缺失数据沿用前一条有效数据:
df["pay_date"]=df["pay_date"].fillna(method="ffill")落地总结:缺失占比<10%→分组填充;10%~40%→中位数/插值;>50%字段直接删列。
三、重复数据精准去重(进阶多条件去重,项目最高频)
drop_duplicates()默认全字段匹配去重,真实业务极少全字段重复,大多单一主键、多字段联合判定重复,配合排序实现保留最新数据,是订单、用户数据清洗刚需。
3.1 按单一主键去重(user_id唯一,保留最新记录)
数据源存在同用户多条重复录入,按user_id去重,先按支付日期倒序,保留最晚一条数据:
#先转日期排序df["pay_date"]=pd.to_datetime(df["pay_date"],errors="coerce")df=df.sort_values("pay_date",ascending=False)#按用户ID去重,保留最后出现(最新)df=df.drop_duplicates(subset=["user_id"],keep="last")#重置索引(去重后索引错乱必做步骤)df=df.reset_index(drop=True)3.2 多字段联合去重
user_id+depart两个字段同时一致才判定重复:
df=df.drop_duplicates(subset=["user_id","depart"],keep="first")3.3 查看被剔除的重复明细
dup_data=df[df.duplicated(subset=["user_id"],keep=False)]print("重复明细数据:",dup_data)四、异常值处理(IQR四分位+3σ准则+业务阈值三重方案)
异常值分两类:统计异常(极值)、业务异常(年龄>150、订单负数),处理方式:剔除、缩尾修正、替换为中位数,禁止无脑全删数据。
4.1 业务规则直接剔除(最稳妥,优先执行)
基于行业常识:人类年龄20~60、订单金额≥0,超出范围判定脏数据:
#筛选合规数据df=df[(df["age"]>=18)&(df["age"]<=65)&(df["order_money"]>0)]4.2 IQR四分位法剔除统计异常(箱线图原理)
适配无明确业务阈值的数值字段,自动识别上下界异常,通用公式:Q1-1.5IQR ~ Q3+1.5IQR
defiqr_filter(data,col):q1=data[col].quantile(0.25)q3=data[col].quantile(0.75)iqr=q3-q1 low=q1-1.5*iqr high=q3+1.5*iqrreturndata[(data[col]>=low)&(data[col]<=high)]#过滤订单金额异常df=iqr_filter(df,"order_money")4.3 Winsor缩尾处理(不想删数据,极值替换为边界值)
部分小样本数据集不能删数据,把超过上下限的异常值改为临界值:
fromscipy.stats.mstatsimportwinsorize df["order_money"]=winsorize(df["order_money"],limits=[0.02,0.02])五、字段格式标准化:数据类型转换+文本清洗
5.1 文本字段去空格、统一大小写(user_name)
原始数据姓名带首尾空格、大小写混杂,str.strip()批量去空格,str.lower()统一小写:
df["user_name"]=df["user_name"].str.strip()#清除首尾空格#如需统一大写 df["user_name"] = df["user_name"].str.upper()进阶:文本混杂数字/特殊符号剔除
#只保留中英文,剔除数字符号importre df["user_name"]=df["user_name"].apply(lambdax:re.sub(r"[0-9\W]","",x))5.2 强制数值转换(pd.to_numeric,非法字符转NaN)
订单金额、年龄字段混入中文、特殊字符报错,errors="coerce"遇到非法值自动转为空值,后续再填充:
df["age"]=pd.to_numeric(df["age"],errors="coerce")df["order_money"]=pd.to_numeric(df["order_money"],errors="coerce")5.3 日期字段统一格式化(pd.to_datetime万能转换)
pay_date存在2024/01/01、2024.01.01、非法文本多种格式,errors="coerce"无法解析日期转为NaT(时间空值):
df["pay_date"]=pd.to_datetime(df["pay_date"],errors="coerce")#进阶:提取年月日、周、季度,特征衍生df["pay_year"]=df["pay_date"].dt.year df["pay_month"]=df["pay_date"].dt.month df["pay_week"]=df["pay_date"].dt.week六、分类特征编码(建模预处理必备:独热编码+标签编码)
清洗完成后进入建模预处理,分类文本无法直接喂入机器学习模型,两种主流编码方案:
6.1 独热编码get_dummies(低基数分类:部门depart)
df_onehot=pd.get_dummies(df,columns=["depart"],prefix="depart")6.2 标签编码(高基数分类,用户姓名等)
fromsklearn.preprocessingimportLabelEncoder le=LabelEncoder()df["user_name_code"]=le.fit_transform(df["user_name"])七、进阶优化:批量字段处理+自定义通用清洗类(项目复用)
企业项目多表重复清洗逻辑,封装类一次编写、全项目复用,减少重复代码,也是Pandas高阶实操重点:
classDataCleanTool:def__init__(self,origin_df):self.df=origin_df.copy()self.log=[]#记录清洗日志,方便回溯deflog_record(self,opt_name,pre,aft):self.log.append(f"{opt_name}:处理前{pre}行→处理后{aft}行,剔除{pre-aft}行")defdrop_key_na(self,key_list):pre=len(self.df)self.df=self.df.dropna(subset=key_list)aft=len(self.df)self.log_record("主键空值删除",pre,aft)returnselfdeffill_group_median(self,num_col,group_col):self.df[num_col]=self.df.groupby(group_col)[num_col].transform(lambdax:x.fillna(x.median()))returnselfdeffilter_outlier(self,col,min_v,max_v):pre=len(self.df)self.df=self.df[(self.df[col]>=min_v)&(self.df[col]<=max_v)]aft=len(self.df)self.log_record(f"{col}业务异常过滤",pre,aft)returnself#调用示例clean_obj=DataCleanTool(df_origin)df_final=clean_obj.drop_key_na(["user_id"]).fill_group_median("age","depart").filter_outlier("age",18,65).df#打印清洗日志foriteminclean_obj.log:print(item)八、清洗结果导出与数据校验
8.1 导出清洗后干净数据
#保存本地csv,中文不乱码用utf-8-sigdf_final.to_csv("clean_order_data.csv",index=False,encoding="utf-8-sig")8.2 最终校验三步法
df_final.isna().sum():核查剩余空值数量df_final.duplicated().sum():核查剩余重复条数df_final[["age","order_money"]].describe():复核数值分布
九、落地避坑总结(多年踩坑经验)
- 永远备份原始数据:所有清洗操作基于副本,禁止直接修改源DataFrame;
- 空字符串≠NaN:CSV导出经常出现""空文本,务必先用replace转为np.nan再填充;
- 去重优先排序:业务需要保留最新数据,先按时间倒序再drop_duplicates;
- 异常值不盲目删除:小样本优先缩尾,大样本按业务阈值剔除;
- 日期统一用pd.to_datetime:拒绝手动字符串切割拆分年月日,兼容性差。
十、拓展高阶场景(超大文件分块清洗)
超10GB大CSV无法一次性读入内存,用pandas分块迭代清洗:
chunk_list=[]#每次读取5000行forchunkinpd.read_csv("big_order.csv",chunksize=5000):#单块内执行清洗逻辑chunk=chunk.dropna(subset=["user_id"])chunk_list.append(chunk)#合并全量清洗后数据big_clean_df=pd.concat(chunk_list,ignore_index=True)