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

资讯详情

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

Pandas数据分析全流程实战:从数据清洗到可视化

Pandas数据分析全流程实战:从数据清洗到可视化 直接上手Pandas数据分析全流程实战做数据分析这些年Pandas一直是我工具箱里使用频率最高的库。不管是几十万行的业务报表还是只有几百条记录的小样本数据只要是表格形态的数据Pandas几乎都能一站搞定。今天想和你分享一套完整的处理流——从拿到原始数据开始经过清洗、转换、分析到最后用图表把结论呈现出来。这套流程我在多个项目里反复打磨过今天拆开揉碎讲给你。先说你读完能拿到什么一是搞清楚为什么Pandas能在数据清洗阶段大幅提效二是掌握一套从read_csv到df.plot的完整链路三是在遇到缺失值、重复值、类型错乱这些高频脏数据问题时有直接用得上的解法。这篇内容更适合数据分析的初中级使用者如果你是刚开始接触Python数据分析建议手边备一份Pandas的官方文档配合下面的案例同步操作效果会好很多。为了让整个流程有落脚点我选了一份模拟的白酒销售数据来演示。这个数据集里有门店信息、品牌、度数、销量、销售额、订单日期等字段基本覆盖了日常销售数据分析的所有典型场景。我会从0到1把它洗成可分析的状态再做几个有业务含义的可视化图表每一步都会当场给出代码和说明。1. 整体设计思路为什么把清洗放在可视化前面很多新手拿到数据后第一件事就是画图这个习惯其实埋了不少雷。数据在采集、导出、合并过程中大概率会带上各种问题——缺失值、重复记录、格式不一致、异常数值。这些问题不去处理画出来的图表就是误导。我见过一张销售额趋势图因为日期字段里有几条是字符串格式的“2024/01/01”另一部分是时间戳格式直接导致折线图的X轴错乱结论完全跑偏。所以我把整套流程固定成四个阶段探查数据、清洗数据、转换数据、可视化输出。探查阶段用info()、describe()、head()这些方法快速摸清数据的长相和健康状况清洗阶段针对缺失、重复、异常这三类老问题逐一下手转换阶段处理类型、创建衍生指标可视化阶段才真正开始做图表。这个顺序在业务项目中屡试不爽它保证了你做出来的每一张图底子都是干净的。用生活里的事情来类比这就像做饭之前先摘菜洗菜切菜。你不能把一棵带泥的青菜直接下锅炒。数据也一样不进过清洗就直接上图表等于吃带沙子的菜。尤其是做给业务方看的数据报告他们对细节非常敏感一个重复门店的销售额被算了两次整张报表的可信度都会被打折扣。2. 数据清洗实操解决三大高频脏数据问题2.1 先探查再动手读取数据后的第一件事拿到一份数据我习惯先跑三个命令df.head()看前几行长什么样df.info()看每一列的类型和非空计数df.describe()看数值列的分布大致情况。import pandas as pd df pd.read_csv(wine_sales.csv, encodingutf-8) print(df.head()) print(df.info()) print(df.describe())info()的输出信息量很大。哪一列有缺失缺失多少个一列看得很清楚。describe()则会暴露数值列的异常苗头——比如销量列里出现负数促销退回单被错误地录成了负数这些都需要在分析前处理掉。这一步的关键不是执行完就行而是要有意识地记下三个清单:哪几列有缺失值、哪几列的字段类型可能需要调整、哪些列的数值范围不合理。带着这几张“问题清单”进到下一步清洗过程就是精准打击而不是盲目处理。2.2 缺失值处理dropna和fillna怎么选缺失值是数据清洗里最普遍的问题。处理方式无外乎两条路直接丢弃带空值的行或者用合理的值填充空缺。具体怎么选取决于两个因素——缺失比例和业务含义。# 查看每列的缺失情况 print(df.isnull().sum()) # 缺失比例很小的行可以直接丢弃 df_cleaned df.dropna(subset[门店名称, 品牌]) # 数值列缺失用中位数填充更稳健 df_cleaned[销量] df_cleaned[销量].fillna(df_cleaned[销量].median())如果一个维度字段比如门店名称、品牌缺失率超过了30%直接丢弃反而是正确的选择因为靠猜测填充一个从未见过的门店名字对后续分析没有任何帮助。而数值字段缺失时中位数填充通常比均值更安全因为中位数不受极值影响。比如一个订单金额偶然出现了几万块的团购大单均值会被拉高但中位数依然稳定用中位数填充完能保留数据的整体形状。还有个小提示时间序列数据里的空值更适合用methodffill或methodbfill做前向/后向填充。销售额某天没记录上用前一天的数值顶上是常见做法但这只适用于短窗口连续缺失很多天就别硬顶了。2.3 重复值排查drop_duplicates的两个参数重复值的坑在于它不像缺失值那么好察觉。业务上最常见的重复有两种完全相同的整行重复以及某个关键维度下有多行但只有其中一行该保留。第二种情况在销售数据里尤其多——同一个订单号可能出现两条记录其中一条是原始录入一条是修正后的数据。# 完全相同的行去重 df_cleaned df_cleaned.drop_duplicates() # 按订单号去重保留最后一条通常是修正过的 df_cleaned df_cleaned.drop_duplicates(subset[订单编号], keeplast)drop_duplicates有两个关键参数第一个是subset用来指定按哪些列判断重复第二个是keep决定保留第一行还是最后一行。很多情况下修正后的数据往往追加在原始记录后面这时候keeplast更靠谱。我在实操中还会先看一眼重复量级。如果重复行数占整体数据的比重极大说明源头可能进行了重复导出这时候光靠pandas去重还不够得回过头检查数据导出逻辑。2.4 类型转换astype和to_datetime的精修艺术字段类型错误是隐蔽性很强的数据问题。最典型的就是日期列读进来是object字符串销售额读进来是字符串格式。这类数据不转换类型后续算不了均值也排不了时间序。# 日期列统一转为时间类型 df_cleaned[订单日期] pd.to_datetime(df_cleaned[订单日期], format%Y-%m-%d) # 字符串数字转为数值类型 df_cleaned[销售额] pd.to_numeric(df_cleaned[销售额], errorscoerce) # 固定宽度的编号转为字符串 df_cleaned[门店编号] df_cleaned[门店编号].astype(str)这里有三个细节值得多说。第一to_datetime里指定format参数能显著提高解析速度尤其是数据量大时不指定格式Pandas会逐条猜测很慢。第二to_numeric的errorscoerce会把无法解析的内容转成NaN这样后续可以用fillna做统一处理不会因为一条脏数据中断整个流程。第三有些字段看起来像数字但实际上应该是字符串——门店编号、订单编号这类数据如果转成int类型后面对比、拼接时反而会出错。2.5 异常值识别用describe和条件筛选揪出“野值”异常值处理是清洗环节里最有技术含量的一步因为它没有绝对的标准得结合业务经验。销售额不能为负数、度数应该在10到60之间、客单价不应该突然出现一个小数点错位的数字——这些判断都需要对业务有基本了解。# 找出销量为负数的记录检查是否录入错误 negative_sales df_cleaned[df_cleaned[销量] 0] print(negative_sales.head()) # 用分位数筛选极值 q99 df_cleaned[销售额].quantile(0.99) print(df_cleaned[df_cleaned[销售额] q99].head())绝大多数情况下我不会粗暴地把超过99分位数的数据全部删掉。真实业务里确实存在大额团购订单这些是有价值的信号不能被当作异常清洗掉。我的操作习惯是先标记出极值看上下文判断是录入错误还是真实业务然后区别对待。录入错误就修正或删除真实业务则保留。这个环节我强烈建议养成一个习惯做任何清理操作之前先备份一份原始数据。df_raw df.copy()这行代码只需要几毫秒却能让你在误删数据后轻松回滚不至于重头再来。3. 数据分析与可视化让清洗后的数据开口说话3.1 数据分组聚合groupby与agg的配合清洗完成后数据终于“能用”了。接下来的分析环节groupby大概是出现频率最高的方法。很多新手刚接触时容易把groupby之后的聚合操作写得非常冗余——循环按品牌、按门店、按月份各算一遍代码又臭又长。实际上用groupby和agg组合可以一条语句算多个指标。# 各品牌的销量和销售额汇总 brand_stats df_cleaned.groupby(品牌).agg( 总销量(销量, sum), 总销售额(销售额, sum), 订单数(订单编号, nunique), 平均客单价(销售额, mean) ).reset_index() print(brand_stats.sort_values(总销售额, ascendingFalse))这里比较值得玩味的是订单数用了nunique。字段名是(订单编号, nunique)意味着统计的是不同订单的数量排除了重复值的影响。这是聚合计算里容易踩的一个小坑——如果直接用count会把同一个订单的多条行记录全部算进去数字虚高。nunique才能代表真实的业务单数。如果你需要同时按品牌和区域分组直接在groupby参数里传入列表即可region_brand df_cleaned.groupby([区域, 品牌]).agg( 销售额(销售额, sum) ).reset_index()经过这一步数据已经从明细粒度汇总到了分析所需要的中粒度层面。这是整个分析流程中承上启下的关键步骤后面做的所有图表几乎都基于这一阶段产出的汇总表。3.2 图表选择逻辑不同分析目标配不同的图可视化不是把图画出来就行而是每一张图都要回答一个具体的问题。常见组合有三组。时间趋势分析用折线图。月度销售额变化、季度同比表现这类有时间维度的数据用plot(kindline)最直观。结构占比分析用饼图或横向柱状图。各品牌销售额占比、各品类分布饼图适合占比结构清晰的场景但如果分类超过六七个饼图就挤得没法看换成横向柱状图更清晰。相关性分析用散点图或矩阵。销量和销售额之间是否有线性关系度数高低和销量之间是否存在关联散点图能快速展示变量间的分布特征。import matplotlib.pyplot as plt # 设置中文字体 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 折线图月度销售额趋势 monthly_sales df_cleaned.groupby(df_cleaned[订单日期].dt.to_period(M))[销售额].sum() monthly_sales.plot(kindline, figsize(10, 5), title月度销售额趋势) plt.xlabel(月份) plt.ylabel(销售额) plt.show()这里必须提醒一句中文字体问题简直是Pandas可视化的第一拦路虎。在Windows上SimHei或Microsoft YaHei可用在macOS系统上通常是PingFang SC或Arial Unicode MS。如果不设置字体图表里的中文就会显示成方框图表直接报废。Pandas的plot方法基于Matplotlib胜在写法简单快捷。当你需要更精细的图表控制时可以直接切到Matplotlib的面向对象接口。上面钉起来的细节是对类型的判断——groupby括号里用dt.to_period(M)就把时间维度按月切分了这是Pandas里处理时间序列聚合的经典写法。3.3 关联分析用散点图发现隐形关系除了常规的业务汇报图表相关性分析往往能带来意外的洞察。比如酒精度数和销量之间的关系。# 散点图查看销量和销售额的分布关系 df_cleaned[[销量, 销售额]].plot( kindscatter, x销量, y销售额, alpha0.3, figsize(8, 5), title销量与销售额散点分布 ) plt.show()把alpha调成0.3的好处是当数据点大面积重叠时能通过点颜色的深浅看出密度分布避免黑压压一片什么都看不清。如果你希望把多列两两关系一次性看全pd.plotting.scatter_matrix()可以生成散点图矩阵效果相当直观from pandas.plotting import scatter_matrix scatter_matrix(df_cleaned[[销量, 销售额, 度数]], figsize(10, 10), alpha0.3) plt.show()一眼扫过去如果发现两个变量之间斜向分布明显就能很快锁定相关性强的关系。分析报告里有这样一张图比单纯堆数据表格要有说服力得多。4. 常见问题与排查技巧实录4.1 中文乱码和字体问题这是Pandas画图最普遍的痛。plt.rcParams[font.sans-serif]不急设置出来的图表中文全会变成方框。不同操作系统字体名不同换环境跑代码时常常忘了同步修改。我自己的习惯是第一行就写上字体设置代码并在代码注释里标明当前系统是Windows还是macOS。另外encoding参数在read_csv里也很容易踩坑gbk和utf-8是两种最常见的编码拿到的数据文件编码不确定时就用encodinggbk和encodingutf-8分别试一遍或者用errorsignore跳过异常字符。4.2 to_datetime解析时间慢几年前我在处理一个十几万行的日志文件时时间列解析跑了好几秒觉得不对劲。后来给format参数加上之后速度提高了一个量级。原因在于不指定格式时Pandas会尝试多种解析策略来推断时间格式这是开销很大的操作。如果日期格式统一指定format%Y-%m-%d %H:%M:%S这类参数能让解析速度明显提升。4.3 读取大文件的内存压力当数据文件大到几百MB甚至几个GB时read_csv会直接把整个文件读进内存如果你的笔记本只有8GB内存很容易卡死。一个实用的变通方法是read_csv里加usecols参数只读取需要的列。另一个方法是使用chunksize参数分批读取。# 只读取需要的列减少内存占用 df pd.read_csv(large_data.csv, usecols[订单日期, 销售额, 品牌]) # 分批读取处理 chunk_list [] for chunk in pd.read_csv(large_data.csv, chunksize50000): chunk_filtered chunk[chunk[品牌] 白酒A] chunk_list.append(chunk_filtered) df pd.concat(chunk_list, ignore_indexTrue)这个思路在内存紧张的机器上能救急。数据量再大的时候就该考虑上dask或polars了不过那又是另一个话题本篇暂且不展开。4.4 处理链式赋值警告很多Pandas新手都遇到过SettingWithCopyWarning这个警告。它本质上是因为你对一个切片出来的副本赋值pandas无法确定你写的是原数据还是临时副本。稳妥的写法是每次操作都手动加上.copy()或者直接对df.loc[条件, 列]赋值而不是先切片再赋值。# 避免链式赋值的写法 df.loc[df[销量] 0, 销量] 0 # 需要改副本时显式拷贝 sub_df df[df[品牌] 白酒A].copy() sub_df[销售额] sub_df[销售额] * 1.1这个习惯养成之后你能少很多莫名其妙的bug。5. 数据可视化进阶从占位图到能讲故事的图表5.1 双轴图的技巧分析销售数据时经常需要把销量和销售额放同一张图里对比。但两个量纲完全不同——销量以瓶为单位销售额以元为单位前者是几十几百的数值后者可能是几万几十万。放进同一个坐标轴时销量曲线会趴在底部几乎看不出趋势。fig, ax1 plt.subplots(figsize(12, 6)) ax1.bar(monthly.index.astype(str), monthly[销量], color#4C72B0, alpha0.7, label销量) ax1.set_ylabel(销量, color#4C72B0) ax1.tick_params(axisy, labelcolor#4C72B0) ax2 ax1.twinx() ax2.plot(monthly.index.astype(str), monthly[销售额], color#C44E52, markero, label销售额) ax2.set_ylabel(销售额, color#C44E52) ax2.tick_params(axisy, labelcolor#C44E52) plt.title(月度销量与销售额对比) plt.show()双轴图的技巧在于twinx()——让两条曲线共用一个X轴拥有各自独立的Y轴。这样既避免了量纲差异带来的视觉误导又能让两个指标的波动同步呈现业界大量产销、营收与利润的对比报告都在用这个写法。5.2 定制化样式让图表更像“作品”默认的Matplotlib样式只能算中规中矩放进报告里缺点质感。其实只需要调三个地方图表观感就能提升一大截设置图表基础样式、调整颜色板、控制图例位置。plt.style.use(seaborn-v0_8-whitegrid) fig, ax plt.subplots(figsize(10, 5)) bars ax.barh(brand_stats[品牌], brand_stats[总销售额], color#1f77b4) ax.set_xlabel(销售额) ax.set_title(各品牌销售额排名, fontsize14, weightbold) # 在柱状图末端标注具体数值 for bar in bars: width bar.get_width() ax.text(width * 1.02, bar.get_y() bar.get_height() / 2, f{width:,.0f}, vacenter, fontsize9)这里用barh画了横向柱状图比纵向更适合品牌名称这类文字较长的分类。在柱子末端加数值标注省去了读者看图时还要对照坐标轴的麻烦信息传达效率高很多。5.3 聚合数据透出更多层次用pivot_table做交叉分析如果你需要一个展示“品牌在每个月销售额”的二维表格pivot_table非常好用。这是groupby的一个补充形态适用于行列交叉的场景。pivot df_cleaned.pivot_table( index品牌, columnsdf_cleaned[订单日期].dt.to_period(M), values销售额, aggfuncsum, fill_value0 ) print(pivot)填上fill_value0之后没有销售记录的月份不再显示为NaN而是显示为0。这种交叉表画成热力图很容易看到哪些品牌在哪些月份有销售高峰比单独逐品牌画折线图更直观。import seaborn as sns plt.figure(figsize(12, 6)) sns.heatmap(pivot, cmapYlOrRd, linewidths0.5, annotTrue, fmt.0f) plt.title(各品牌月度销售额热力图) plt.show()用YlOrRd这个暖色系色板时颜色越深代表销售额越高销售峰谷一眼就能辨认。6. 工具链扩展从Pandas到完整分析生态6.1 导出数据清洗完如何交付清洗和分析做完最终交付的形式多半是带格式的表格或可视化大屏。Pandas导出Excel时df.to_excel()默认情况下样式几乎为零。如果你的报告需要直接交到业务方手上可以配合xlsxwriter做一些简单的格式优化——设置列宽、加表头加粗、冻结首行这些都能省掉手动整理的功夫。with pd.ExcelWriter(销量分析报告.xlsx, enginexlsxwriter) as writer: brand_stats.to_excel(writer, sheet_name品牌汇总, indexFalse) pivot.to_excel(writer, sheet_name月度交叉, indexTrue) workbook writer.book worksheet writer.sheets[品牌汇总] worksheet.set_column(A:E, 12)ExcelWriter配合xlsxwriter引擎可以在代码层面控制列宽、加粗表头等细节交付出去的数据表观感专业很多。当遇到海量明细数据分析时还能考虑用pandas配合polars提速或者用spark处理真正超出单机内存的数据集。6.2 可视化大屏场景把Pandas的产出接到前端在数据可视化大屏的项目里Pandas通常不直接负责图表的最终呈现而是负责在后台把数据处理成前端可消费的JSON数据。它把清洗后的数据聚合计算好再通过df.to_json()输出给前端的图表组件比如ECharts从而实现大屏的实时数据更新。我做过的一个门店销售看板后端就是这个模式十几张图的数据源全部由Pandas在Python脚本里算好再输出到统一的JSON接口上游系统只要按字段约定推数据过来就能刷新大屏。这套流程把数据处理和前端展示解耦灵活性和可维护性都更好。如果对高并发、秒级实时更新要求不高完全够用。6.3 配套软件生态不要只盯着Pandas做数据分析不可能只用Pandas一个库。我平时在项目中常搭配这几个工具NumPy做底层数值计算Matplotlib和Seaborn做静态度图Plotly做交互式图表SQL和Excel负责外部数据交换。这套组合各有分工配合起来非常顺手。环境管理上如果你在PyCharm里安装Pandas包遇到版本冲突我习惯用conda或者venv建独立虚拟环境pip install pandas或者conda install pandas一把装上避免全局环境的依赖打架。最近几年AI辅助数据分析也逐渐成为日常。用ChatGPT或Copilot生成Pandas的清洗代码初稿再人工检查业务逻辑效率高了不少。但这里有个经验提一下AI生成的代码只做参考业务校验和边界情况必须自己把关特别是对数据做删改这类不可逆操作时务必要理解每一步在做什么。7. 个人实操经验与扩展建议回到最初那个问题——一套完整的数据分析流程关键不是某个单独环节有多熟练而是整条链路能否打通。从我踩过的坑里总结几条对你可能更受用的建议。第一每一步清洗操作之后尽量打印一行信息确认结果。比如print(删除重复行后:, df.shape)这样能在早期就发现数据量异常变化不至于做到最后一步才发现前面出了问题。第二清洗过程的脚本写成一个按顺序执行的函数输入端是原始路径输出端是清洗后的文件。这样数据源一旦更新重新跑一次脚本就能拿到最新分析结果不用每次从头手动点。第三可视化的配色和样式尽量统一。一套报告的颜色如果五花八门观感会差很多。提前定义好颜色板和字体风格所有图表共用一套配置报告的整体感会好很多。入行数据分析这些年我最大的体会其实是清洗数据的时间永远比画图的时间多但正是被清洗干净的底子才让后面的分析有了意义。Pandas这个库你用得越熟就越能体会到它的逻辑之美——每一个API的设计几乎都对应着实打实的业务场景。希望这篇文字对你接下来的实战有所启发。
返回列表