十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

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

Pandas数据分析全流程:从数据清洗到可视化实战 想聊一个很实际的问题Pandas在数据分析里到底怎么用很多朋友学了一堆函数打开真实数据还是一脸懵。这篇文章我会从数据清洗讲到可视化用一套完整的流程串起Pandas的核心操作包括环境配置、类型转换、分组聚合、绘图定制以及那些文档里不会写但实际一定会踩的坑。适合刚入门Python数据分析、准备用Pandas做实际项目或者从Excel迁移过来的朋友参考。1. 为什么数据分析的第一站要选Pandas1.1 从Excel到Pandas一张大表引发的迁移我最早处理业务数据也是用Excel后来遇到一张几百万行的订单明细表Excel直接卡死筛选一下要好几分钟透视表转两圈风扇就起飞。那时候就意识到Excel的瓶颈不在功能而在内存和算法效率。Pandas干脆把数据加载到内存里用Python的向量化运算来处理速度完全不在一个量级。更重要的是清洗、转换、分析的每一步都可以写成脚本下次拿到新数据直接跑不用再重复点鼠标。对于周期性的月度报表、日报统计这个优势特别明显。1.2 DataFrame和Series先搞懂这两个概念Pandas的两个核心对象一个是Series一个是DataFrame。Series可以理解成一列带有索引的数据结构很像一个字典加一个顺序DataFrame则是一张二维表由多个Series按列组成自带行索引和列名。import pandas as pd # 创建一个Series s pd.Series([100, 200, 300], name销售额) # 创建一个DataFrame df pd.DataFrame({ 门店: [A店, B店, A店], 销售额: [100, 200, 150], 成本: [60, 110, 80] })实际处理数据时90%的操作都是围绕DataFrame展开的。你只需要记住行是记录列是字段索引是行标签。后面所有操作——筛选、分组、透视、绘图——都是在跟这张表打交道。2. 环境准备Pandas安装与PyCharm里的常见坑2.1 PyCharm中安装Pandas包的两种方式搜索热度很高的pycharm怎么安装pandas包说明这是很多新手的第一个坎。其实很简单两个途径任选。第一种在PyCharm里操作打开File → Settings → Project → Python Interpreter在右侧点击号搜索pandas点Install Package。这种方法最直观适合刚接触虚拟环境的朋友。第二种用命令行在PyCharm底部的Terminal里执行。pip install pandas这里有个特别容易踩的坑如果电脑里装了多个Python环境命令行里的pip可能指向的是全局Python而PyCharm项目用的是虚拟环境两边互不相通。常见表现为命令行里import pandas正常但PyCharm里报ModuleNotFoundError。解决办法就是看PyCharm右下角或Settings里选中的Interpreter路径在命令行用对应路径下的pip安装。如果你在国内网络环境pip下载慢可以加国内镜像源pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple2.2 AttributeError: module pandas has no attribute core 排查链路这个报错在网上检索量非常高我实际排查过一次分享一下完整思路你以后遇到类似问题可以照着走。当时一个同事跑代码import pandas后访问pd.core直接抛了AttributeError: module pandas has no attribute core。第一步我先看了pandas版本pip show pandas发现版本是2.0按道理core是内部模块不应该没有。第二步检查是否安装了多个pandaspip list | findstr pandas发现有两个路径下各有pandas。这通常意味着环境污染。第三步也是最常见的原因项目目录下存在pandas.py文件或者用户自定义模块命名为pandas.py。Python在导入时会优先搜索当前工作目录如果当前目录下有个pandas.pyimport pandas导入了这个文件而不是真正的库。解决办法很简单把自定义的pandas.py改个名字删掉缓存目录__pycache__再重启解释器。排查这种东西切记不要一上来就卸载重装。先看报错来自哪一行导入的是哪个文件再看项目中是否有同名文件最后才考虑环境问题。这个顺序能省下很多时间。3. 数据清洗从脏数据到干净DataFrame的四个关键步骤3.1 读取数据CSV、Excel到DataFrame数据清洗的第一步是读数据。日常最常见的就是CSV和Excel。# 读CSV df pd.read_csv(sales.csv, encodingutf-8) # 读Excel df pd.read_excel(sales.xlsx, sheet_name订单明细)读文件时有两个容易被忽视的参数一个是encoding另一个是parse_dates。CSV文件经常因为编码问题乱码一般用utf-8或gbk交替试如果文件里有日期列最好在读入时指定parse_dates让Pandas自动转成datetime类型而不是事后用字符串处理。Excel读取也有坑。如果你的Python环境没装openpyxl或xlrdread_excel会报错。手动补一下pip install openpyxl正式跑项目前建议先读一个nrows做抽样pd.read_csv(sales.csv, nrows100)看一眼列名和数据类型是否符合预期再全量读取。对于千万级的大文件这一步能避免读入后发现结构不对、重新加载的尴尬。3.2 缺失值与重复值先统计策略再动手清理拿到数据后先别急着dropna。我见过很多人上来就把含空值的行删掉结果把有效数据也删了一半。正确做法是先做统计# 查看每列缺失值数量 print(df.isna().sum()) # 查看重复行数量 print(df.duplicated().sum())然后根据业务场景决定策略关键字段比如订单号为空直接删除因为后续无法使用数值字段为空可以用均值/中位数填充或者用前后填充法分类字段为空可以填未知或众数整行重复直接drop_duplicates()去重。# 删除订单号为空的记录 df df.dropna(subset[订单号]) # 销售额空值用中位数填充 df[销售额] df[销售额].fillna(df[销售额].median()) # 删除完全重复的行 df df.drop_duplicates()填充缺失值的逻辑要写清楚理由。比如销售额用中位数是因为销售额分布往往右偏用均值会被极值拉高中位数更稳健。3.3 数据类型转换让每一列都各归其位Pandas里最烦人的错误之一就是类型不匹配。看数据明明全是数字但一算sum就报错因为存储类型是字符串。所以读取后第一件事就是检查dtypes。print(df.dtypes)看到object类型但内容其实是数字的列要转换类型df[销售额] pd.to_numeric(df[销售额], errorscoerce) df[日期] pd.to_datetime(df[日期], format%Y-%m-%d) df[门店] df[门店].astype(category)用to_numeric而不是直接astype(float)是因为它会给你一个兜底errorscoerce能把无法转换的值变成NaN之后再统一处理。如果直接astype一个脏字符就能让整个程序崩溃。这里有个非常重要的顺序问题先转换类型再处理缺失值。因为转换过程中可能产生新的NaN比如字符串里混入了null或暂无如果先fillna再转换新产生的缺失就漏掉了。日期类型的转换也值得单独说。有时候日期列是2024/03/15或20240315这种格式直接to_datetime可能会解析失败建议指定format参数既提速又避免歧义。3.4 列名清洗与索引重置列名的问题在真实数据里特别常见比如列名带着空格、大小写不统一、有的叫门店有的叫门店名称。如果不统一列名后面写筛选条件时非常容易踩坑。# 去掉列名首尾空格统一小写 df.columns df.columns.str.strip().str.lower() # 重命名个别列 df df.rename(columns{门店名称: 门店})索引经过各种筛选和清洗后会变得很乱通常复位一下df df.reset_index(dropTrue)reset_index有两个作用一是把旧的索引变成普通列二是把连续但不齐整的索引恢复成0开始的顺序。dropTrue表示不用保留旧索引。这一步看起来不起眼但对后续分组和拼接操作影响很大。4. 数据分析实操筛选、分组聚合与透视表4.1 条件筛选与布尔索引清洗干净之后终于可以开始分析数据了。最常用的操作是条件筛选。# 筛选销售额大于5000的记录 high_sales df[df[销售额] 5000] # 多条件筛选 target df[(df[门店] A店) (df[品类] 数码)]这里有两个容易出错的地方。第一多个条件必须用括号括起来否则Python的运算符优先级会出错第二逻辑与是逻辑或是|不是Python里的and和or。Pandas的布尔索引要求的结果是数组级别的逻辑运算所以用位运算符。筛选之后不要忘了reset_index。4.2 groupby分组聚合Excel透视表的平替从按门店统计销售额这种需求开始就要用到groupby了。# 按门店分组计算销售额总和 sales_by_store df.groupby(门店)[销售额].sum()这个操作的结果是一个Series门店变成了索引。通常我希望把它变成规整的DataFramesales_by_store df.groupby(门店)[销售额].sum().reset_index()groupby之后reset_index几乎是每个数据分析师都写的代码目的就是让分组字段恢复成普通列方便后续操作或者导出到Excel。如果要对多个维度同时聚合可以用列表summary df.groupby([门店, 品类]).agg({ 销售额: sum, 成本: sum, 订单号: count }).reset_index()agg函数允许用字典指定每个字段的聚合方式非常灵活。除了内置的sum、mean、count、max、min之外还可以传lambda自定义函数。4.3 用pivot_table实现更复杂的透视分析groupby能解决大部分问题但如果你要做一个行列交叉的透视表比如行是门店、列是品类、值是销售额用pivot_table会更直观。pivot pd.pivot_table( df, index门店, columns品类, values销售额, aggfuncsum, fill_value0 )pivot_table相比groupby有个明显优势会自动把缺失的组合填成NaN再通过fill_value参数填0这样透视表看起来和Excel里的一模一样。加一行reset_index()可以把它转回长格式数据。这里有一个业务上的细节透视表方便人看但机器处理时往往需要长表每一行是一个唯一的组合。所以做分析时我倾向于先groupby搞出长表最后展示时再pivot。数据和展示分离代码更清晰。5. 可视化从Pandas内置绘图到Matplotlib定制5.1 为什么先学Pandas自带的绘图Pandas的DataFrame有plot方法底层是Matplotlib但语法更简洁。对于快速查看数据分布、做个临时报表完全够用。import matplotlib.pyplot as plt # 设置中文字体避免乱码 plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 折线图 df.groupby(日期)[销售额].sum().plot(kindline, figsize(10, 5)) # 柱状图 df.groupby(门店)[销售额].sum().plot(kindbar) # 饼图 df.groupby(品类)[销售额].sum().plot(kindpie, autopct%.1f%%) plt.show()Pandas的plot会根据数据类型自动设置x轴比如日期字段会自动排列Series的索引变成x轴值变成y轴这个默认行为非常适合做时序分析。5.2 常用图表的场景选择很多人做可视化时经常纠结该用哪种图。我用下面的表做了一个归纳也就是Excel数据分析里经常提到的那批常用图表Pandas和Matplotlib基本都能覆盖。图表类型适用场景Pandas实现折线图连续时间序列趋势df.plot(kindline)柱状图分类对比df.plot(kindbar)条形图分类名称较长时横向对比df.plot(kindbarh)饼图占比结构df.plot(kindpie)直方图数值分布df[销售额].plot(kindhist, bins20)箱线图离群点和分位数分布df.boxplot(column销售额)散点图两个数值变量的关系df.plot(kindscatter, x成本, y销售额)面积图累积趋势df.plot(kindarea)选图表的核心逻辑是看清比较关系用柱状看趋势用折线看分布用直方图看占比用饼图看相关性用散点图。不要在饼图里塞超过5个品类否则视觉上一团糟。5.3 中文字体和可视化大屏的边界用Pandas绘图时最让人抓狂的就是中文字体乱码。设置方式就是前面写过的两行rcParams。注意SimHei是Windows自带的黑体Mac和Linux上没有需要改为PingFang SC或者下载一个中文字体文件注册进去。还有一个常见问题负号在默认字体下会显示成方块。所以必须把axes.unicode_minus设为False。热搜词里出现了可视化大屏。这里要明确一点Pandas不是做大屏的工具Pandas负责出数据和基础图表大屏通常用专门的Web可视化方案比如ECharts从Pandas导出数据或JSON前端渲染。所以如果你要做指挥中心那种炫酷大屏把精力放在数据格式转换和接口设计上而不是在Pandas里硬画。6. 一个完整实战从清洗到图表的全流程6.1 场景设定与数据说明为了把前边的内容串起来我用一个模拟销售数据来讲。假设有文件sales.xlsx字段包括订单编号、销售日期、门店、品类、销售额、成本、数量。初始数据存在几个典型问题销售日期有多行是文本格式如2024/3/15 0:00销售额列混入了NaN字符串存在完全重复的订单行门店列有一些前后带空格的值。6.2 完整清洗与数据分析代码下面是一段可直接运行的脚本import pandas as pd import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False # 读取数据 df pd.read_excel(sales.xlsx, sheet_name订单明细, parse_dates[销售日期]) # 查看数据概览 print(数据形状:, df.shape) print(缺失值分布:\n, df.isna().sum()) print(重复行数:, df.duplicated().sum()) # 数据清洗 df df.dropna(subset[订单编号]) df[销售日期] pd.to_datetime(df[销售日期], errorscoerce) df[销售额] pd.to_numeric(df[销售额], errorscoerce) df[成本] pd.to_numeric(df[成本], errorscoerce) df[数量] pd.to_numeric(df[数量], errorscoerce) df[门店] df[门店].str.strip() df[品类] df[品类].str.strip() df df.drop_duplicates() # 再处理清洗过程中产生的新缺失值 df df.dropna(subset[销售日期, 销售额]) # 新增毛利率列 df[毛利率] (df[销售额] - df[成本]) / df[销售额] # 分组统计 store_summary df.groupby(门店).agg({ 销售额: sum, 成本: sum, 订单编号: count }).reset_index() store_summary.columns [门店, 总销售额, 总成本, 订单数] # 透视表门店 x 品类 pivot_sales pd.pivot_table( df, index门店, columns品类, values销售额, aggfuncsum, fill_value0 ) print(store_summary) print(pivot_sales) # 可视化 fig, axes plt.subplots(1, 2, figsize(14, 5)) # 柱状图门店总销售额 store_summary.plot(kindbar, x门店, y总销售额, axaxes[0], legendFalse) axes[0].set_title(各门店总销售额) axes[0].set_ylabel(销售额) # 按日期的销售额趋势 daily_sales df.groupby(销售日期)[销售额].sum() daily_sales.plot(kindline, axaxes[1]) axes[1].set_title(销售趋势) axes[1].set_ylabel(销售额) plt.tight_layout() plt.show()6.3 结果解读与业务价值跑完这段代码你就能得到三个核心产出清洗后的完整DataFrame、各门店经营汇总、按门店和品类的透视矩阵以及两张可以直接放进周报的图。从业务角度看柱状图能直观看见哪个门店贡献最高趋势图能看出是否存在明显的周末效应或节假日前后的需求爬升透视矩阵能帮你发现某个品类在特定门店卖得好。这些结论如果用Excel手动透视每换一个维度都要拖半天脚本化之后只需要改一下参数重跑。有一点要提醒Pandas输出的图是静态图适合做分析报告但不适合做实时监控。如果需要周报自动化可以把plt.savefig保存图片再用脚本拼接到Word或PDF里。这里用到的是一个All in One的思路清洗、分析、图表全部跑一遍数据更新后只需要重新运行脚本。7. 我踩过的坑和一些实用建议7.1 链式赋值和SettingWithCopyWarning新手经常写这类代码df[df[门店] A店][销售额] 100Pandas会报一个SettingWithCopyWarning意思是说你在一个临时副本上做修改不会真正写回原DataFrame。这个警告非常坑它不报错只是警告但你的修改实际上不生效。我的建议是尽量用.loc做条件赋值。df.loc[df[门店] A店, 销售额] 100.loc接受两个参数第一个是行筛选条件第二个是列名这样修改是直接在原数据上进行的不会触发警告。7.2 中文CSV导出乱码清洗完数据要导出时直接df.to_csv(result.csv, indexFalse)在Excel里打开大概率乱码。原因是Excel默认用GBK编码打开CSV文件而Pandas默认用UTF-8写出。解决办法是换成utf-8-sig这个编码会在文件开头带上BOM标记Excel识别后就能正常显示。df.to_csv(result.csv, indexFalse, encodingutf-8-sig)7.3 大文件内存不足时怎么办Pandas读大文件容易把内存撑爆。我处理几个G的日志文件时最常用的方式是分块读取chunk_iter pd.read_csv(big.csv, chunksize100000) result [] for chunk in chunk_iter: # 对每个块做清洗 chunk chunk[chunk[金额] 0] result.append(chunk) df pd.concat(result, ignore_indexTrue)另一个技巧是只读取需要的列通过usecols参数过滤掉无关字段减少内存开销。如果数据量到了几十GPandas就比较吃力了这时候要考虑Spark那套分布式方案但那是另一个故事了。7.4 代码可维护性把清洗流程写成函数我个人的一个体会是如果你只写一次脚本那无所谓但如果这个数据每周都要分析一次建议把清洗流程封装成函数输入文件路径输出干净DataFrame。def load_and_clean(file_path): df pd.read_excel(file_path) df[销售额] pd.to_numeric(df[销售额], errorscoerce) df df.dropna(subset[订单编号]) df df.drop_duplicates() return df这样别人接手你的代码时只需要看函数名和docstring就知道在干什么而不是在一堆中间变量里找逻辑。数据分析脚本很容易变成一坨屎山但如果每步都配合清晰的重命名和函数拆分代码会好维护很多。最后再分享一个小技巧也是我写数据分析项目时最受益的习惯每清洗完一步记得print一下当前数据的shape和dtypes。不要等到最后才输出结果否则中间某个操作把数据类型弄脏了你根本不知道是哪一步搞坏的。借助这些小输出排查问题时会非常有底。
返回列表