ARTICLE DETAIL

资讯详情

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

Pandas实战:从数据清洗到可视化的完整流程

Pandas实战:从数据清洗到可视化的完整流程 接手一份数据你首先要做的事绝对不是建模也不是画图而是打开数据看看到底有多脏。我指的是那种典型的实战数据字段名乱写、中文编码报错、金额里混着逗号和货币符号、日期格式五花八门、缺失值散落各处、同一家公司在不同行里名字还不一样……这套流程我已经来来回回跑了无数遍从一开始拿着Excel手工清洗到后来完全切到Pandas数据处理的速度直接提升了一个量级。今天这篇文章就把这套完整流程整理出来——从数据清洗到可视化一个项目级的数据分析是怎么一步步走下来的。Pandas这套工具在数据分析领域的地位不用我多吹了它是Python生态里处理表格数据最顺手的一套方案。这篇文章会带你走一遍真实项目的完整链路读取数据、清洗脏数据、类型转换、正则提取、聚合分析、可视化输出每一步都有代码、有解释、有坑点提醒。不管是刚开始学Pandas的初学者还是接了一个分析任务不知道从哪下手的从业者这份流程都能直接拿来参考。1. 数据分析项目的整体思路拆解1.1 为什么数据分析里最耗时的部分是清洗经常有人问数据分析项目里哪个环节最重要我的答案从来都是清洗。圈子里流传一个说法一个真实的数据分析项目中80%的时间都花在数据准备和清洗上真正建模和画图的时间其实很少。这个比例我亲身验证过保守估计也是七三开。原因很简单现实世界的数据不会乖乖按你想要的格式排列。数据库导出的表可能有命名混乱的列爬虫抓下来的数据可能有HTML标签没剥干净业务方手工维护的Excel里更是藏着无穷无尽的人为惊喜——全角半角混用、数字存成了文本、同一个分类有七八种写法。这些不处理干净后面的统计结果要么报错要么看起来对但其实全错。Pandas之所以在这个环节无可替代是因为它提供了一整套专门处理这些问题的API处理缺失值的dropna和fillna处理重复值的drop_duplicates处理文本的str访问器配上正则表达式处理类型问题的astype和to_datetime。这些功能单个看都不复杂但组合起来就是一套完整的清洗流水线。1.2 一套能直接复用的四步流程我在这篇文章里用一份模拟的招聘数据做演示。这份数据是我按真实场景构造的包含公司名称、岗位名称、薪资范围、工作地点、学历要求、发布时间等字段结构跟从招聘网站抓到的数据很像。整个分析项目我习惯拆成四步走数据读取——把各种格式的数据导入Pandas解决编码、分块、列名问题。数据清洗——处理缺失值、重复值、格式错误、文本噪音这一步是重点。数据变换与分析——分组聚合、多表关联、透视表、时间序列处理挖掘数据里的规律。可视化输出——把分析结果用图表呈现出来让规律被人一眼看懂。这四步每一步都有对应的Pandas核心功能支撑。整篇文章就按这个流程展开你可以直接把代码拷贝下来替换成自己的数据跑一遍。注意清洗和变换的边界在实际项目中往往是交叠的。比如去重算清洗还是算变换类型转换算清洗还是算准备我的建议是不要纠结归类按数据处理的自然顺序去做就好。2. 数据读取先把数据请进门2.1 不同文件格式的读取姿势拿到一份数据第一步永远是看它是什么格式。实际工作中最常见的三种CSV、Excel、文本文件。Pandas对这几种格式都有对应的一行命令。CSV文件用read_csv读取这是我的主力操作。值得花时间研究的是它的参数很多新手只写一个文件路径就完事但真实数据往往需要额外处理import pandas as pd df pd.read_csv( jobs.csv, encodingutf-8, sep,, header0, dtype{薪资: str}, parse_dates[发布时间] )几个关键参数背后的逻辑encoding指定编码中文数据经常遇到gbk和utf-8混乱的问题dtype可以指定列的类型避免数字列被读成文本parse_dates直接把某一列解析成时间类型省得后面再转换header指定表头在第几行有些导出文件前面有两三行说明信息就需要header2跳过前两行。Excel文件用read_excel它比CSV多出一个sheet_name参数可以指定读取哪个工作表也可以传列表一次读取多个sheet再合并。文本文件用read_csv加不同的分隔符就能搞定制表符分隔的传sep\t固定宽度或者自定义分隔符的也都支持。另外read_csv还有个skiprows参数可以直接跳过前面N行不需要的说明文字。读取后的第一件事我强烈建议执行df.info()和df.head()。前者能看到每列的数据类型和非空值数量后者能看到前几行数据长什么样。这两行代码就是在体检数据有没有读对、哪些列需要清洗一眼就能看出个大概。2.2 编码问题与分块读取中文数据读取时最常踩的坑就是编码。UnicodeDecodeError: utf-8 codec cant decode byte这个报错我估计每个做过中文数据的人都见过。应对方案是准备好几种编码轮着试utf-8最通用但不是所有文件都是它编码的。gbk/gb18030国内很多业务系统导出的文件是这俩编码gb18030比gbk覆盖的字符更全。latin1也就是iso-8859-1不容易报错因为它什么字节都能解码但解码出来可能是乱码一般最后再试。实操办法是写一个小循环依次尝试不同编码直到成功而不是一次一次手动试。我自己处理数据是会把原始文件先拷贝一份然后用二进制方式打开看文件头有FF FE之类的字节序标记基本能断定编码类型。当然大多数情况下utf-8和gbk轮一遍就够了。另外一个读取阶段值得注意的问题是超大文件。几百万行的CSV如果一次性全读进来内存很容易爆掉。Pandas的read_csv支持chunksize参数分块读取chunk_iter pd.read_csv(large_file.csv, chunksize100000) result pd.concat([chunk for chunk in chunk_iter])也可以指定只读某些列usecols[公司名称, 薪资, 发布时间]这个参数在文件列很多但实际用不到的时候非常实用能从源头减少内存占用。还有指读前N行的nrows参数适合先读一小部分看看数据结构确认参数设置对了再全量读取。3. 数据清洗真正花时间的地方3.1 缺失值和重复值处理数据读进来了接下来就进入真正的核心战场。我拿到数据的第一轮必做操作是统计缺失值用isna()配合sum()按列统计missing_stats df.isna().sum() print(missing_stats[missing_stats 0])缺失值处理没有万能答案策略取决于你的数据和业务场景。一般我按三条路走数据量大且缺失比例低直接删掉缺失行缺失列是关键的分类信息但比例不高可以填补占位值缺失列是数值信息可以用均值、中位数或者前后值填充。需要特别提醒的是dropna()默认是只要某个字段为空就把整行删掉这在字段很多时误杀严重。可以加参数subset[关键字段]只针对指定列做缺失判断howall则是只在整行全空时才删除这个在清洗爬虫数据和Excel表格时很实用。重复值处理用drop_duplicates()这个函数默认是保留第一次出现的那一行。实际操作中我几乎总是指定subset参数因为完全重复的行本身就不多真正的坑是看起来不同其实重复df df.drop_duplicates(subset[公司名称, 岗位名称, 薪资], keepfirst)比如一家公司发布的同一个岗位薪资和岗位相同只是发布时间因爬虫抓取批次不同有细微差异这就应该视为重复。keepfirst保留第一次出现的那条keeplast保留最后一次都能在去重后调整行的顺序。3.2 数据类型转换数字不能是文本日期不能是字符串类型转换是清洗环节最容易出细节问题的地方。真实数据里最典型的状况金额列存的是12,000这种带千位分隔符的字符串或者8000-12000这种区间表示的薪资还有把数字存成了浮点数但实际应该是整数的情况。Pandas里astype()是基础的类型转换方法但直接astype(int)遇到文本混数字会直接抛异常。更稳妥的方法是先用pd.to_numeric()处理df[薪资下限] pd.to_numeric(df[薪资下限], errorscoerce)errorscoerce很关键它的作用是把不能转换的值变成NaN而不是报错这样后面的缺失值处理流程就能接上。我曾经处理过一份数据薪资列里有几千行数据混入了面议、电聊这种文本如果没有coerce参数程序早就崩了。日期处理是我个人踩坑最多的一块。日期字段用pd.to_datetime()转换时才真的是一个深坑不断的地方——格式五花八门2023/01/01、2023-01-01、2023年1月1日、01/02/2023还有可能是Excel导出的那种莫名数字序列号。to_datetime在这方面做了很多自动推断大多数情况下直接传一列进去就能识别但偶尔需要加format参数指定格式df[发布时间] pd.to_datetime(df[发布时间], format%Y-%m-%d, errorscoerce)日期转换完成后就解锁了一套时间相关的操作按月份聚合、计算时间差、提取星期几、筛选某段时间的数据。这些在分析环节是高频操作所以清洗阶段把日期处理干净后面会顺很多。3.3 文本清洗与正则表达式真实数据的文本列是最脏的。公司名称前后有空格、岗位名称里混着多余符号、薪资描述里有HTML标签残留、地址信息里塞了一堆冗余词。文本清洗的第一梯队是str.strip()、str.replace()、str.upper()、str.lower()这几个基础方法df[公司名称] df[公司名称].str.strip() df[岗位名称] df[岗位名称].str.replace(急聘, ).str.replace(高薪, )第二梯队就是正则表达式了。数据分析场景里正则表达式最常用的三个场景提取str.extract、替换str.replace配合正则、判断匹配str.contains。以这份招聘数据为例薪资字段是8000-12000元/月这种格式我想把它拆成下限和上限两列df[[薪资下限, 薪资上限]] df[薪资].str.extract(r(\d)-(\d)) df[薪资下限] pd.to_numeric(df[薪资下限], errorscoerce) df[薪资上限] pd.to_numeric(df[薪资上限], errorscoerce)(\d)-(\d)这个正则表达式的意思是抓出纯数字-纯数字这两段str.extract会自动把括号里的内容拆成对应的列。这种操作在真实分析中用得非常多比如从地址里提取城市、从证件号里提取出生年月、从文本描述里提取数字指标。匹配操作str.contains()经常配合条件筛选使用比如把所有带销售字样的岗位筛出来sales_jobs df[df[岗位名称].str.contains(销售, naFalse)]正则表达式的学习曲线确实有点陡但我建议数据分析从业者至少掌握这些基础\d匹配数字、\w匹配字母数字下划线、.*匹配任意字符、[abc]匹配字符集合、^和$表示开头结尾。这五个掌握了对付大部分文本提取场景足够。3.4 异常值处理缺失值、重复值、格式问题都处理完了还剩一类脏数据是异常值。数值列的异常值通常用描述性统计就能发现df[薪资下限].describe()describe()会输出均值、标准差、四分位数和最大最小值。如果薪资下限的min是负数或者max达到了几百万基本可以断定有异常值。更通用的办法是使用IQR四分位距方法超过上下边缘的值视为异常Q1 df[薪资下限].quantile(0.25) Q3 df[薪资下限].quantile(0.75) IQR Q3 - Q1 outliers df[(df[薪资下限] Q1 - 1.5 * IQR) | (df[薪资下限] Q3 1.5 * IQR)]这里1.5 * IQR是统计学里常用的经验阈值没有严格的数学推导但实践中很好用。异常值的处理方式跟缺失值类似要么删掉要么用边界值做截断替换具体取决于分析目的。如果这个异常值是个别录入错误删掉无妨如果它是真实存在的极端情况做分析的时候单独讨论可能更有价值。4. 数据分析让数据产生价值4.1 GroupBy分组聚合数据规律的挖掘机清洗完毕数据总算可以进入实质分析阶段。整个分析过程里用最多的是groupby()它的逻辑就是SQL里的GROUP BY按某个字段分组然后对每组做统计。用这份招聘数据演示我想知道不同城市发布的岗位数量一行代码就能搞定。city_counts df.groupby(工作地点)[岗位名称].count().sort_values(ascendingFalse)先按城市分组对岗位名称计数再降序排列。这是最标准的聚合操作。agg()函数更灵活可以同时对多个列做不同的聚合grouped df.groupby(学历要求).agg( {薪资下限: [mean, median], 岗位名称: count} )这里统计的是不同学历要求的岗位对应的薪资均值、中位数和岗位数量。聚合结果会生成多层索引看着有点复杂用reset_index()或者直接在agg后用droplevel()把多余的层级去掉就能得到一个干干净净的表格。这一步做得好不好直接决定后面可视化环节能用什么数据喂给图表。4.2 多表关联与透视表实际项目中很少只拿到一张表。公司维度的信息行业、规模、融资阶段和岗位维度的信息岗位名、薪资、要求往往是分开的。结构类似的数据用pd.concat()按行堆叠比如多个月份的数据文件。关联关系的数据用pd.merge()连接——这对应的就是SQL里的JOINmerged pd.merge(df_jobs, df_companies, on公司名称, howleft)on指定关联的键how指定连接方式。left保留左侧数据的全部行匹配不到的填NULLinner只保留两边都匹配上的行实际工作中我80%的情况用left。透视表pivot_table()是分组聚合的另一种形式它把一列的唯一值变成新的列让数据形成一个矩阵。比如我想看不同城市、不同学历要求下的平均薪资用一个pivot_table就能搞定pivot pd.pivot_table( df, values薪资下限, index工作地点, columns学历要求, aggfuncmean )这个表格非常适合可视化因为行列结构刚好对应热力图的坐标轴。Pandas还有melt()和stack()/unstack()这类表结构工具用于长表宽表的互换在数据标准化场景很常用。4.3 时间序列与滚动计算如果数据里有日期字段时间序列分析就是不可绕过的环节。Pandas的日期时间索引是一个强大的功能把日期列设为索引后可以用.dt访问器提取年、月、日、星期几等属性也可以进行时间维度的重采样。df[月份] df[发布时间].dt.to_period(M) monthly_count df.groupby(月份)[岗位名称].count()按月份统计岗位发布数量能看出招聘需求的季节性规律。滚动计算用rolling()比如计算薪资的7天移动平均在分析趋势时能平滑掉短期波动df[薪资下限].rolling(window7).mean()热词里提到的ewm函数也是这类场景的常客它是指数加权移动平均和rolling的区别在于近期的数据权重更高。时间序列分析这块内容比较深但对于大部分数据分析任务掌握按时间分组、重采样、滚动计算这三板斧就已经能应付大多数需求了。5. 可视化讲清楚数据背后的故事5.1 快速出图的三种方式清洗和统计分析完成后可视化是把分析结果翻译给人看的关键一步。Pandas内置了基于Matplotlib的绘图接口DataFrame.plot()一行代码就能画图city_counts.head(10).plot(kindbar, figsize(10, 6))kind参数可以指定bar柱状图、line线图、pie饼图、hist直方图等。plot接口的优势是快适合探索阶段快速看数据形态。更精细的图表通常交给Matplotlib和Seaborn。Seaborn是建立在Matplotlib之上的高级绘图库它的优势是默认主题就很好看统计图表的类型也更丰富import seaborn as sns import matplotlib.pyplot as plt sns.set_theme(stylewhitegrid) plt.figure(figsize(12, 6)) sns.boxplot(datadf, x学历要求, y薪资下限) plt.title(不同学历要求的薪资分布) plt.show()箱线图在这个场景下比柱状图更能说明问题因为它同时展示了中位数、四分位距和异常值分布。视觉呈现上我通常是Pandas的plot做快速探索Seaborn做正式图表Matplotlib的pyplot做精细化调参。5.2 实战招聘数据可视化的完整案例把前面的分析结果串起来做一个完整案例。诉求是看看数据分析这个岗位在各城市的薪资分布情况。用str.contains筛出数据分析相关的岗位按城市分组算薪资均值然后分别用柱状图看数量、箱线图看分布da_df df[df[岗位名称].str.contains(数据分析|数据运营|数据专员, naFalse)] city_salary da_df.groupby(工作地点)[薪资上限].mean().sort_values(ascendingFalse) plt.figure(figsize(12, 6)) city_salary.head(10).plot(kindbar, color#4C72B0) plt.title(数据分析岗位平均薪资 Top10 城市) plt.xlabel(城市) plt.ylabel(平均薪资上限元/月) plt.xticks(rotation45) plt.show()另一张图用箱线图看不同规模公司给数据分析岗位的薪资差异plt.figure(figsize(12, 6)) sns.boxplot(datada_df, x公司规模, y薪资上限, order[0-50人, 50-150人, 150-500人, 500-2000人, 2000人以上]) plt.title(不同规模公司的数据分析岗位薪资分布) plt.xticks(rotation30) plt.show()最终把多张图表拼在一起用plt.subplots()创建子图或者用plt.savefig()保存为高清图嵌入到报告里。我做分析报告的惯例是探索阶段多画图、多角度看数据正式交付时只保留能够支撑结论的那几张图避免信息过载。5.3 从静态图表到数据看板热词里反复出现可视化大屏可视化界面这类词说明现在数据分析的交付形态已经从静态图往动态看板演变。Pandas本身不做看板但它是整个看板体系的数据供给层。不管是直接用Plotly做交互图还是用Flask/FastAPI封装成Web服务再到企业级的BI工具底层的数据准备和聚合逻辑都离不开Pandas。做看板项目时我的建议是分析阶段先用Pandas把数据处理好、把关键指标算好再把结果输出成后端接口或导出为csv/parquet文件供前端使用。把Pandas从绘图工具定位成数据准备和中台工具它在看板生态里的价值反而更大。顺带一提Pandas的to_csv()和to_excel()输出功能在对接各种看板系统的时候是最高频的操作。6. 高频踩坑与排查手册6.1 编码与中文显示问题中文乱码是数据分析里出现频率最高的问题它有两个层面。读文件的编码问题前面已经说过这里说绘图的乱码Matplotlib默认字体不包含中文字符直接画图会出现一堆小方框。解决方案是在绘图代码前加plt.rcParams[font.sans-serif] [SimHei, Microsoft YaHei] plt.rcParams[axes.unicode_minus] False第一行指定中文字体第二行解决负号显示成方框的问题。这个配置每次新建脚本都要写一遍我已经习惯把它放进自己的配置文件里了。6.2 性能与内存优化数据量超过百万行之后一些平时没感觉的操作会变得很慢。最常见的性能杀手是逐行遍历。用for循环加iterrows()逐行处理数据是新手最容易犯的性能错误。正确思路是尽量向量化操作能用整列运算就不要逐行处理。另一个内存优化技巧是类型压缩。Pandas默认用64位整型和浮点型但很多数据用32位甚至16位就足够了。一个简单的方案是自动把每一列的类型降到最小编号for col in df.select_dtypes([int64, float64]).columns: if df[col].dtype int64: df[col] pd.to_numeric(df[col], downcastinteger) else: df[col] pd.to_numeric(df[col], downcastfloat)这个操作在列数多但每列取值有限的场景下内存占用能减少一半以上。另外read_csv阶段就指定dtype和usecols、提前完成筛选再处理都是从源头瘦身的好思路。6.3 索引与链式赋值的坑Pandas的链式赋值问题是个经典陷阱。df[df[薪资] 10000][薪资] 0这种写法在Pandas里会触发SettingWithCopyWarning表面上看执行成功但实际可能改的是副本原数据根本没变。正确做法分两种需要条件赋值时用df.locdf.loc[df[薪资] 10000, 薪资] 0先筛选出数据并确实要修改原表时先加.copy()明确生成副本再操作sub_df df[df[薪资] 10000].copy()养成copy()的习惯能少掉很多莫名其妙的bug。还有个索引的坑做筛选和聚合后索引是乱的等下探数据和拼接之前先reset_index(dropTrue)让索引归一。6.4 一些经验清单最后整理一份我在实操中沉淀下来的检查清单按流程顺序过一遍能帮你避开大部分常见问题读取数据后用df.info()和df.isna().sum()做体检别急着分析。中文列名建议改英文列名能省掉很多引号的麻烦也方便后续做可视化对接。处理任何一列数据前先看它的dtype类型不对先转换再做操作。用to_csv导出时记得加encodingutf-8-sig这样用Excel打开不会乱码。做大规模合并前先检查关联键是否有重复值否则合并结果的行数会让你怀疑人生。每一段清洗操作都单独执行、单独验证不要一口气把清洗代码写完再回头调bug。关键结果导出前打印head()检查一眼数据到手直接用的翻车概率远高于自查一遍。我个人在实际操作中最大的体会是Pandas这套工具的复杂度不在API而在思维习惯。刚学的时候总是想着一个函数搞定所有问题用久了才发现数据分析的正确姿势是不断地切分问题——读取的问题、清洗的问题、分析的问题、可视化的问题每一步用最合适的几个函数解决流程走下来数据自己就会开口说话。最后再分享一个小技巧每次分析的完整代码我会整理成一份可复用的流水线脚本下次遇到同类数据只需要改改字段名和正则表达式就能直接跑。数据分析跟写代码一样能复用的东西一定要沉淀下来这才是效率提升最快的路径。
返回列表