ARTICLE DETAIL

资讯详情

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

Pandas数据清洗与可视化实战:从脏数据到洞察的完整链路

Pandas数据清洗与可视化实战:从脏数据到洞察的完整链路 咱们整天聊数据分析很多人一上来就抱着“我要用机器学习建模”的心态结果卡在第一步就懵了数据拿到手根本没法直接用。作为常年和数据打交道的人我越来越觉得真正决定一个分析项目成败的环节往往不是后面那个花哨的模型而是前期的数据清洗工作。Pandas在这块儿就是一把锋利的瑞士军刀从读取杂乱无章的原始数据到整理成规规矩矩的表格再到最后画出能讲出故事的图表全链路打通。这篇文章我就结合自己实际干活的经验从数据清洗一路聊到可视化把那些文档里不常写的取舍逻辑和踩过的坑一并倒出来希望能给正在这条路上摸索的朋友一些参考。先放一句我个人的总结数据分析百分之八十的时间都花在清洗和整理数据上这很正常也不要觉得枯燥。把清洗这关过了你会发现连画图都顺手很多。1. 为什么数据清洗比建模更值得花时间一个反直觉的结论大多数人提起数据分析第一反应是那个高深莫测的模型训练过程似乎只有敲出一行行算法代码才算得上“技术活”。但真正在项目里摸爬滚打过的朋友心里都清楚数据清洗才是整个分析链路里最耗时、最磨人、也最决定成败的环节。数据科学家圈子里流传着一句老话叫“垃圾进垃圾出”你再牛的分析算法喂进去一堆格式混乱、缺胳膊少腿、充满重复和异常的数据输出的结果也毫无价值可言。这个阶段的工作虽然看起来平平无奇但恰恰是这种“不起眼”才让后面的分析能够顺理成章地展开。Pandas作为一个专门为数据处理而生的Python库之所以在数据分析领域地位如此稳固靠的并不是什么高深莫测的模型算法而是它提供了极其丰富且高效的数据清洗与整理工具。从读入各种杂乱的源数据开始Pandas就能帮你把不同格式的数据统一成结构化表格把缺失值、重复值、异常值这些“顽疾”逐一揪出来处理掉再把字段类型、业务口径通过转换对齐。可以说Pandas替你把“数据厨艺”里的洗菜、切菜、配菜功夫全都包揽了等你真正要“下锅炒菜”也就是做分析、建模、可视化的时候手里的料已经是干干净净、整整齐齐的了。这种体验上的差异说个很直白的例子你们就明白了。我曾经接过一个电商平台的月度销售数据原始文件是从业务系统导出的里面日期字段有的是“2024/08/15”有的是“20240815”还有的是“15-Aug-2024”下单金额有的带人民币符号有的带千分位逗号甚至还有几条记录的用户ID直接是空字符串。如果不做清洗光是统一这个日期格式就够你喝一壶的更别提后续按日、按月做趋势分析了。而用Pandas几行to_datetime配合自定义格式解析就能把这些五花八门的字符串一股脑儿转成标准的日期类型后续所有时间维度的聚合都变得顺理成章。所以我的建议是无论你的项目目标是做一个简单的描述性统计还是打算上机器学习模型都请先正视数据清洗这个环节。它不酷炫但它是地基。你愿意在地基上花多少功夫直接决定了你的分析结果能盖多高的楼。这篇文章之所以从一开始就聊这个就是想帮大家把这个容易被低估的环节放到它应有的核心位置上来。2. 开始之前Pandas的数据结构认知与安装避坑聊清洗之前得先把手里的工具摸清楚。Pandas有两个核心的数据结构一个是Series一个是DataFrame。很多人一开始学的时候容易混淆我这里用个大白话解释一下Series就是带标签的一维数组像一列数据配上它每行的名字DataFrame就是一张二维表格既有行索引又有列名你平时在Excel里看到的那种表它的形态就非常接近DataFrame。为什么要强调这两个概念因为后面所有的事情——筛选、清洗、聚合、合并——本质上都是围绕这两种结构来展开的。DataFrame可以看作是由多个Series拼接而成的一张表所以你对一列数据进行清洗操作本质上就是在操作一个Series。搞清楚了这一层关系你在写代码的时候思路就会清晰很多。在实际写代码的第一步往往是安装Pandas。这件事看着简单但很多人第一次装就栽了跟头尤其是用pip install pandas的时候速度慢到令人发指不说还经常中途报错超时。这里分享一个可靠的办法直接用清华的镜像源下载速度飞快还能绕开不少网络问题。命令行下执行pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple另外你要是用的PyCharm其实不需要单独跑到命令行里去敲命令。直接在PyCharm的设置里找到Project Interpreter点加号搜索pandas点安装就能搞定它会自动帮你处理依赖关系。不过我个人经验是如果你已经开始用PyCharm了尽量用虚拟环境来装避免不同项目之间的包版本冲突。那种“我明明装好了pandas怎么跑代码还是报ModuleNotFoundError”的情况多半就是装到了系统环境而PyCharm用的是虚拟环境两边没对上。装好之后习惯上都会给它起一个别名也就是那句经典代码import pandas as pd这行代码基本可以看作是Pandas世界的入场券。需要注意的是有些教程会顺带让你装一个叫numpy的库别嫌多Pandas底层运算依赖它你后面用Pandas做条件筛选、向量化运算时很多结果返回的也是numpy类型装好无害且有用。还有一个小提示如果你的数据文件比较大比如有几百MB那种在读取的时候会有明显等待时间。这时候可以考虑用read_csv的chunksize参数分块读取或者直接用dtype参数指定某些列的数据类型减少内存消耗。别小看这个处理真实项目数据的时候光是一个memory error就能让你心态崩掉提前用这些参数做优化比事后补救强太多。3. 数据清洗的四个核心关卡缺失值、重复值、异常值与类型乾坤大挪移数据清洗本身是个细碎活但如果把它拆解开来核心无非四个关卡缺失值、重复值、异常值、数据类型。每一关都有它特定的处理手法和考量逻辑。接下来我逐一展开把我平时的处理思路和代码一并放出来。3.1 缺失值处理先判断“为什么缺失”再谈“怎么补”缺失值是数据清洗里最常遇到的关卡。拿到一张表第一件事别急着dropna先搞清楚这些缺失值到底是怎么来的。它们可能是业务上的天然缺失也可能是数据采集时的遗漏这两种情况的处理逻辑是完全不同的。我习惯先通过isnull()方法配合sum()统计一下列的缺失情况大致有个底import pandas as pd df pd.read_csv(sales_data.csv) missing_stats df.isnull().sum() print(missing_stats[missing_stats 0])这里有个容易忽略的细节isnull()对空字符串是识别不出来的它只认NaN、None这类Pandas标准缺失值标记。所以很多时候你扫一眼数据觉得某些字段明明是空的但统计出来的缺失值却是0。这也算是一个常见的坑吧处理的时候可以先用strip()去掉空格再把空字符串统一替换成NaNdf[customer_name] df[customer_name].str.strip().replace(, pd.NA)替换之后再重新统计缺失情况就会准确很多。真正处理缺失值的时候面对不同的列有不同的策略。数值型字段比如销售额、点击量如果你的数据分布比较均匀缺失比例也不高直接用均值或者中位数填充是可行的但如果数据波动很大或者存在明显的离群值用均值填充就容易干扰整体分布这时候用中位数会更稳健。如果缺失的字段是类别型的比如用户等级、所属区域用众数填充或者单独标记一个“未知”类别通常是不错的选择。还有一类情况这个字段本身对业务决策没有多大意义比如某个人工备注列缺失率高达八成保留着对后续分析也没帮助那直接drop掉整列反而是最干净的处理方式。判断依据就是一句大白话留着它能不能为后面的分析提供有效信息不能就删。至于彻底删除含有缺失值的行也就是dropna()则需要谨慎再谨慎。如果缺失的行数占总体比例很小比如不到5%而且这些行分布较随机删掉对整体结果影响不大那可以删。但如果缺失比例高或者缺失集中在某些关键字段上删了会导致样本严重偏差那还是得老老实实走“填充”这条路线。3.2 重复值处理别无脑去重先看清业务语义重复值这个话题听起来特别简单好像drop_duplicates()一行代码就完事了。但实际项目里这里面的门道并不比缺失值少。首先你需要搞清楚什么叫“重复”。在Pandas里默认的去重逻辑是按行判断也就是说如果两行数据的每一列都一模一样才会被判定为重复。但真实业务中这种全列重复的记录其实不多见更常见的是“关键字段重复”。比如用户行为日志同一用户在同一时间点产生了两条记录时间、用户ID完全相同只有某个非核心属性略有差异这时候你就得想清楚到底该按哪些字段判断重复才算合理我一般会先看业务主键如果明确知道哪几列组合起来应该唯一比如订单号、用户ID加下单时间那就按这些列去重。drop_duplicates方法在这方面给了足够的灵活性df df.drop_duplicates(subset[order_id, customer_id, order_time], keepfirst)keep参数也有讲究。keepfirst会保留重复组里的第一条记录keeplast保留最后一条而keepFalse则是把所有重复记录全删掉。具体用哪个取决于你的业务场景如果数据是按时间顺序追加的那最后一条往往状态最新保留last更合适如果几条记录之间没有先后语义差别保留first也没问题。千万别一上来就keepFalse那很可能会把你本来想保留的那条有效记录也给误删了。另外还有一个高频操作就是先查一下到底有多少重复行再做决定duplicate_count df.duplicated(subset[order_id]).sum() print(f发现重复订单数: {duplicate_count})这个输出可以让你对数据集的质量有个直观判断。如果重复比例很高说明上游数据链路可能存在问题不只是清洗的问题了得更深一层去排查根源。3.3 异常值识别用好描述性统计和箱线图异常值处理也是清洗环节里的一大重头戏。所谓异常值就是明显偏离正常取值范围的数据点。比如订单金额出现负数比如用户年龄显示200岁这些显然都是不合理的。对于这类异常处理方法相对简单粗暴——直接过滤掉或者替换成缺失值再走填充逻辑df df[df[age].between(0, 120)]但有些异常值比较隐蔽单靠肉眼和经验看不出问题。这时候就要借助统计学工具了。我惯用的做法是先用describe()一键生成描述性统计看一下各个数值列的min、max、四分位数注意力重点放在那些极值上。numeric_cols df.select_dtypes(include[number]).columns print(df[numeric_cols].describe())比如你可以看到某列数值的75%分位数是80但最大值却是10000这种巨大的落差背后往往藏着异常值。进一步确认的方法可以用箱线图也叫箱型图。它通过四分位距IQR来判断离群点用代码画出来很简单借助matplotlib或者seaborn都可以import matplotlib.pyplot as plt import seaborn as sns plt.figure(figsize(10, 6)) sns.boxplot(datadf[sales_amount]) plt.show()箱线图的好处在于它不依赖对数据的先验认知而是纯粹从数据分布本身出发识别出那些落在上下须之外的点。这些点到底是不是异常还得结合业务来判断。有些时候某个“异常点”恰恰是业务上的重要信号比如大促期间的成交量暴增。这时候你不应该粗暴地把它删掉而应该单独标记它们在分析中的权重。关于异常值我的原则是先识别再分类最后才能决定处理方式。可修正的修正可剔除的剔除需要保留的信号就单独处理。千万别一看到“异常”就下意识删除那会让你错过很多有价值的信息。3.4 数据类型转换让每一列都变成它该有的样子数据类型转换是清洗过程中一个看似琐碎但影响重大的环节。Excel读进来的数据经常会出现“看似数值其实是字符串”的状况比如金额列带着货币符号ID列被读成了数字导致前面的0丢失日期列变成了各种格式混杂的对象类型。这些如果不加处理后面做聚合计算、排序、时间序列分析的时候各种诡异的报错就会接踵而至。Pandas里的核心方法是astype()它可以帮你把列从字符串转换成数值、从数值转换成字符串等。比如订单ID如果是013开头的读进来会被当成整数1去掉前导零这时候你就需要先进性字符串填充df[order_id] df[order_id].astype(str).str.zfill(6)这里的zfill(6)是把位数不够的ID前面补0补到6位。日期处理就更常见了。pd.to_datetime()是处理日期列的神器它能自动识别大部分常见的日期格式。但对于那种格式特别混乱的列建议显式指定格式参数避免它自作聪明解析错误df[order_date] pd.to_datetime(df[order_date], format%Y-%m-%d)%Y代表四位年份%m代表两位月份%d代表两位日期这个格式串你可以根据实际数据的样式灵活调整。转换之后你还可以顺手用它提取年、月、周、星期几等新字段为后续的时间维度分析做好准备。还有个值得留意的小点是astype(category)可以把某些取值有限的字符串列转换成Pandas的类别类型。这样做除了语义上更清晰还能大幅减少内存占用尤其是对那些有几百万行的数据集来说效果非常可观。反正我处理那种“省份”“性别”“支付方式”这类列时只要有性能顾虑就会优先考虑转成category类型。4. 从清洗走向分析筛选、聚合、合并的实战思路清洗工作做到位之后接下来就要开始真正的数据处理和分析操作了。这一阶段的核心工作简单说就是三件事筛选出你需要的数据子集、聚合出有价值的统计信息、把多张表按关键字段拼接起来。这三板斧用熟了大部分业务分析需求你都能稳稳接住。4.1 条件筛选用好布尔索引与query方法筛选数据直观的做法是布尔索引。比如我想筛选出订单金额大于500且支付成功的所有记录paid_orders df[(df[sales_amount] 500) (df[payment_status] paid)]这里的关键点是多个条件必须用括号括起来条件之间用与、|或、~非来组合。很多人第一次写这段很容易漏掉括号结果报错或者结果不对。所以每次写完条件筛选我都建议立刻打印一下shape看看行数有没有变化以及head()看看结果是否符合预期。如果你觉得一堆df[列名]写起来又长又不直观Pandas还提供了一个叫query的接口paid_orders df.query(sales_amount 500 and payment_status paid)这个写法和SQL语感很接近方便你直接在字符串里写筛选逻辑。这里唯一要注意的是列名如果有特殊字符或者空格需要用反引号引起来。总体来说query方法在列数多、条件复杂的情况下可读性更好也更容易维护。筛选操作不仅是简单的行过滤也可以配合列选择一起使用。比如我只需要订单号、金额和日期三列筛选完再按列名提取即可result paid_orders[[order_id, sales_amount, order_date]]列选择还有一个高级用法filter(regex...)可以根据正则表达式匹配列名比如你想一次性选出所有以“_amount”结尾的列这个方式就会非常高效。4.2 分组聚合从groupby到透视表的层层递进分组聚合是业务分析里出镜率最高的操作。想要知道每个区域的销售额总和每个产品类别的平均订单金额每种支付方式的使用次数这些需求问法不同但底层都是同一个套路——groupby。region_summary df.groupby(region)[sales_amount].sum().reset_index()这里的写法拆开来看是先按region分组然后对sales_amount列做sum求和最后reset_index把分组字段从索引变回普通列方便后续查看和处理。如果你不需要索引变回列可以直接用as_indexFalse这个参数效果一样但更简洁region_summary df.groupby(region, as_indexFalse)[sales_amount].sum()分组之后能做的聚合函数五花八门sum、mean、count、max、min、median、std等等都是常用操作。如果你想同时算多个指标可以用agg方法传一个字典进去product_stats df.groupby(product_category).agg( total_sales(sales_amount, sum), avg_sales(sales_amount, mean), order_count(order_id, count) ).reset_index()这样一张表里既能看到总销售额又能看到平均客单价和订单量后续做业务复盘时一张表就能看明白很多问题。如果你的分析需求更复杂比如行列都要做交叉分类统计就可以考虑用pivot_tablepivot pd.pivot_table(df, valuessales_amount, indexregion, columnsproduct_category, aggfuncsum, fill_value0)透视表在Excel里大家都很熟悉了Pandas里的用法逻辑其实差不多values指定需要汇总的数值列index指定行索引columns指定列索引aggfunc指定聚合方式。有一个小细节fill_value0能把表格中因为无数据而产生的NaN填充为0这样后续无论是导出还是做可视化都不会被缺失值干扰。4.3 多表合并理解merge与concat的不同场景处理真实业务数据你不会总在一张表里打转。用户信息是一张表、订单明细是一张表、商品信息是第三张表这时候就需要把表按某些关键字段连接起来。Pandas里最常用的两个方法一个是merge一个是concat。merge比较适合做“SQL风格”的连接操作。比如我有订单表orders和用户表users两张表通过customer_id关联我想要在订单表上补充用户所在城市、会员等级等信息就可以这么写merged_df pd.merge(orders, users, oncustomer_id, howleft)参数的含义需要重点说说。on指定连接键how决定连接方式left是保留左表全部记录右表没有匹配到的数据用NaN填充right相反inner是只保留两边都能匹配上的记录outer是全连接保留两边所有记录。分工协同上我90%的场景用的都是left因为主分析表通常就一张其他的表只是靠上去“附赠”附加字段用一个left join最稳妥。concat则更像是纯粹的“拼接”。两种常见情况一种是按行拼接多张结构相同的表比如1到12月的月度数据每个月一张表把12张表上下堆叠成全年数据另一种是按列拼接把两张列不同的表左右并排。前者用axis0后者用axis1year_data pd.concat([jan_df, feb_df, mar_df], axis0, ignore_indexTrue)这里ignore_indexTrue很关键因为上下拼接时索引容易冲突加上这个参数重新生成连续索引能避免后续很多莫名其妙的索引问题。多表合并有一个高频出现的坑连接键的数据类型不一致。比如订单表里的customer_id是数值类型用户表里却是字符串类型直接合并时会发现匹配不上结果merge出来一堆NaN。遇到这种情况先用astype(str)统一两边键的数据类型再做合并问题就能顺利解决。5. 让数据开口说话Matplotlib与Seaborn可视化实战数据整理完了统计指标也出来了但一堆数字放在那里人眼很难快速找出规律。这时候就需要把数据“画”出来用图表讲清楚数据背后的故事。Python生态里最常用的两把可视化利器就是Matplotlib和Seaborn。Matplotlib底子扎实、自定义能力极强Seaborn则是站在Matplotlib肩膀上的高级接口画统计图表既简单又好看。两者配合使用几乎能覆盖日常所有可视化需求。5.1 绘图的第一步设置全局风格与中文字体很多人第一次用Matplotlib画图第一个撞上的问题就是图表里的中文全部变成了小方块。这是因为Matplotlib默认字体不支持中文。解决办法很简单提前设置中文字体import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第一行指定了无衬线字体优先使用黑体或微软雅黑第二行是解决负号显示异常的问题——如果不设置这个坐标轴上的负号经常会变成一个莫名其妙的方块。这两行代码建议放在脚本最开头一劳永逸后面所有画图都不会再被字体问题困扰。在颜值方面可以用Seaborn的全局设置快速统一风格import seaborn as sns sns.set_theme(stylewhitegrid, palettemuted)这一行就能让所有图表的背景变成带有网格线的浅色底配色也变得柔和统一图表整体质感立刻就上来了。具体绘图的时候还可以再微调坐标轴标签、图例位置等细节。5.2 单变量与多变量图表的选型逻辑面对不同的数据形态和分析目的选对图表类型是可视化成功的关键。我在这里把最常用的几种场景和相应的图表列一下方便你直接对号入座。数值分布画直方图hist或核密度图kde用来了解单列数据的分布形态。plt.figure(figsize(10, 6)) sns.histplot(df[sales_amount], bins30, kdeTrue) plt.xlabel(订单金额) plt.ylabel(频数) plt.title(订单金额分布) plt.show()bins控制柱子的数量kdeTrue会在直方图上方叠加一条平滑的密度曲线让你更容易看出分布是否偏态、是否有多个峰值。类别对比用柱状图barplot或者箱线图boxplot。柱状图适合对比不同类别的总量或平均值箱线图则适合展示不同类别下的数值分布差异。plt.figure(figsize(12, 6)) sns.barplot(dataregion_summary, xregion, ysales_amount) plt.title(各区域销售额对比) plt.show()如果不同组别的数据量差异大柱状图上还可以加置信区间误差条这也是barplot默认带上的一项好处在做A/B测试场景时格外有用。时间趋势最常用的是折线图lineplot。但这里有个前提时间列必须先转成datetime类型并且排序好否则画出来的折线会乱成一团。daily_sales df.groupby(order_date)[sales_amount].sum().reset_index() plt.figure(figsize(14, 6)) sns.lineplot(datadaily_sales, xorder_date, ysales_amount) plt.xticks(rotation45) plt.title(每日销售趋势) plt.tight_layout() plt.show()rotation45让横轴日期标签旋转45度避免重叠tight_layout()自动调整间距防止标签被裁剪这两个细节会让图表看起来专业很多。多变量关系散点图scatterplot是起步首选可以用颜色映射第三维比如会员等级、地区等plt.figure(figsize(10, 8)) sns.scatterplot(datadf, xunit_price, ysales_amount, huemembership_level, alpha0.6) plt.xlabel(单价) plt.ylabel(销售金额) plt.title(单价与销售金额关系) plt.show()alpha0.6设置了点的透明度当数据点多、重叠严重时这个参数能让密集区域的分布状态清晰可见。hue参数按照会员等级给点上了色可以直观看到不同等级用户的消费模式差异。选图核心逻辑就一句话想清楚你要展示的是“分布”“对比”还是“趋势”然后选对应的图表。别为了炫技硬上一些花哨图能用柱状图讲清楚的事用雷达图反而容易让人觉得刻意。5.3 布局与保存让你的图表既有面子又有里子在实际汇报或者写文档时你往往不会只画一张图而是需要把多个图组合在一起对比。Matplotlib提供的subplots方法就可以实现多子图布局fig, axes plt.subplots(2, 2, figsize(14, 10))这行代码创建了一个两行两列的画布axes是一个二维数组你可以通过axes[0][0]这样定位到第一个子图然后在每个子图里绘制想要的内容。用完了如果不希望子图之间相互干扰还能配合plt.tight_layout()自动调整子图间距。图表绘制好以后保存成图片也是常规操作。但这里有个细节直接用plt.show()之后保存图片往往是带弹窗的交互界面不方便后续嵌入报告。更好的方式是直接savefigplt.savefig(sales_dashboard.png, dpi150, bbox_inchestight)dpi150是分辨率参数数字越高图片越清晰bbox_inchestight会自动裁剪掉图片周围的空白边缘让内容区域占满整张图。保存时注意先savefig再show或者确保没有开启交互阻止模式否则显示出来的图跟你保存下来的图可能不太一样。这里还想额外提一个经验保存图表文件时文件名尽量语义清晰比如用“202408_区域销售对比.png”这种带时间维度的命名方式时间长了找图的时候方便而且不容易覆盖同名文件。6. 别只会跑通代码一个设备健康度看板的实战复盘之前的章节多少有点“知识点罗列”的感觉这一部分我想用一套完整的小项目串一遍全流程让大家看看清洗、分析、可视化是怎么在实际场景里咬合起来的。这个项目的背景是我之前做过的一个小任务基于一批物联网设备上报的历史运行数据分析设备健康状态并输出一个可视化看板。6.1 原始数据的“脏乱差”现场我拿到的原始数据是系统导出的CSV文件将近10万行列包括设备ID、上报时间、温度读数、CPU使用率、内存占用率、网络延迟、状态码等。光看几个样本问题就不少上报时间格式有的是2024-07-21T08:30:00Z这种UTC标准格式有的是2024/07/21 08:30还有个别行根本就是个空值温度读数有极个别记录达到99.9明显是传感器异常CPU使用率一列里混着“85%”和“0.73”两种语义完全不同的写法内存占用率存在部分负值这种不用说肯定是采集程序有bug。这些脏乱差的点如果不处理后面画时间趋势图根本没法看算平均负载的时候也会被异常值带偏。这也正是清洗环节价值最好的印证。6.2 清洗逻辑落地一行行代码把问题掰正面对这份数据我首先做了一次全量扫描print(df.dtypes) print(df.isnull().sum())然后分步处理。第一统一时间格式。把UTC时间字符串和普通北京时间字符串全部转成datetime类型df[report_time] pd.to_datetime(df[report_time], errorscoerce, utcTrue) df[report_time] df[report_time].dt.tz_convert(Asia/Shanghai)这里errorscoerce表示解析不了的直接转成NaN后续统一处理。utcTrue先按UTC解析再统一转换到北京时间保证时区一致性。第二处理温度异常值。先画个箱线图看一下分布确认哪些点落在合理范围之外再用95%分位数做上下限过滤Q1 df[temperature].quantile(0.25) Q3 df[temperature].quantile(0.75) IQR Q3 - Q1 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR df df[(df[temperature] lower_bound) (df[temperature] upper_bound)]第三把CPU使用率统一成小数形式。凡是带%符号的先去掉百分号再除以100原来的小数形式维持原值def parse_cpu(value): if isinstance(value, str) and value.endswith(%): return float(value.strip(%)) / 100 return float(value) df[cpu_usage] df[cpu_usage].apply(parse_cpu)第四内存负值直接替换为NaN再用列均值做填充df.loc[df[memory_usage] 0, memory_usage] pd.NA df[memory_usage] df[memory_usage].fillna(df[memory_usage].median())把这些步骤走完再跑一次df.describe()会发现各个字段的分布都变得合理了,数据质量有了质的提升。6.3 分析重点与看板呈现数据清洗干净之后后续的分析思路就比较顺了。我先按设备ID分组计算每个设备的平均CPU使用率、平均温度、内存平均占用率然后把高于一定阈值的设备标记为“高负载设备”。再按天聚合看整体集群的负载随时间的变化趋势尝试找出是否存在固定的高峰时段。最终的可视化看板我做了一个多子图的组合包括三块内容左上角是整体CPU使用率的时间趋势折线图右上角是温度分布的箱线图下方左右分别展示负载最高的前10个设备柱状图和内存占用分布的直方图。整个看板用subplots排布输出成PNG文件方便直接嵌入周报里。做这个项目的过程中我最大的体会是清洗工作做得越细致后面分析和展示就越顺滑。很多你觉得“画出来怎么这么丑”的图表往往不是绘图代码的问题而是数据本身没处理好比如时间轴乱序、有缺失段、有极端值拉高坐标轴。数据干净了哪怕用最简单的图表类型效果也能立得住。7. 我踩过的坑希望你别再踩一遍最后这部分聊聊我在实际使用Pandas做数据清洗和可视化时踩过的一些至今记忆犹新的坑。这些点都非常细节但每一个都可能让你白折腾好几个小时。第一个坑链式赋值操作。刚学Pandas的时候我习惯写df[df[age] 18][age] 99这样的代码想对筛选出来的子集做修改。结果运行完发现原表根本纹丝不动甚至有时候还会收到一堆警告。原因在于“链式索引”有时候看到的是DataFrame的副本而不是视图你在副本上改了等于白改。正确的做法是用loc进行条件赋值df.loc[df[age] 18, age] 99记住这个原则要修改DataFrame时优先用loc而不是链式索引。可以少踩很多坑。第二个坑读取CSV时默认把某些列解析成了错误类型。比如订单号的数值过大或者包含前导零被Pandas默认读成整数后会丢失精度。解决方法是read_csv时显式指定dtype把订单号按字符串读取df pd.read_csv(orders.csv, dtype{order_id: str})早年间我为了这个丢精度的问题排查了一个下午才发现原来几千几万条订单号后面几位全都变成了0。这类问题如果不在读取这一步就把类型锁死后面再怎么清洗也补不回来。第三个坑合并时索引没对齐产生大量NaN。这事常见于把两张索引不同但其实是一一对应的表直接合并时经常会出现明明数据都有结果合并后全都是NaN的情况。关键是要在merge之前先确认连接键的唯一性并且统一连接键的数据类型。再不行就先用reset_index()把索引重置成普通列再合并别让索引掺和进来捣乱。第四个坑绘图时中文字体不生效。有时候你明明设置了font.sans-serif为SimHei但某个图表里的中文还是方框。这种情况多半出在使用了seaborn的主题覆盖或者局部修改了rcParams。解决办法是在绘图之前重新设置一次全局字体并且确保设置代码在任何plt.figure()或者sns.set_theme()之前执行。第五个坑groupby之后忘了reset_index()导致后续处理逻辑混乱。分组聚合后得到的结果按分组字段做了索引看起来没什么大不了但你再想去按列筛选或者绘图索引层级就会带入图中经常导致横纵坐标显示异常。所以我平时只要不是专门要做层级索引操作一律顺手加上reset_index()把结果拉平回普通的二维表结构。这些坑说来说去本质都是对Pandas内部的数据视图、索引机制、类型推断理解不够深入造成的。不做这一行的人是体会不到的只有亲手踩一遍再看一眼错误提示才会真正记住。我把它们写在这里就是希望大家能在看这篇文章时就建立起这个意识少浪费一些无谓的调试时间。说到底Pandas数据分析这条链路从数据清洗到可视化每一步都像是在给数据“梳妆打扮”。你梳得越细致它给你的反馈就越惊艳。如果你正在为手里的脏数据发愁不妨耐下心来按这篇文章的思路一步一步来试试。清洗过了坎后面的分析之路会顺畅很多。
返回列表