1. 内容整体设计与思路拆解
1.1 为什么是Pandas,而不是Excel或原生Python
拿到一份数据,不管是几万行的销售记录、用户行为日志,还是从数据库导出的明细表,第一件事永远是把它变成“能分析的样子”。这个环节里,Pandas是绕不开的选择。它的核心数据结构DataFrame可以理解成一张常驻内存的二维表,有行有列,操作起来比Excel灵活得多:Excel处理几万行数据就开始卡,Pandas处理百万行也还算轻松;原生Python处理数据往往要用一堆循环,代码繁琐且性能差,而Pandas把大部分常用操作都封装成了向量化的API,一行命令顶写十行循环。
我见过不少刚接触数据分析的人,一上来就追求复杂的机器学习模型,结果数据本身一塌糊涂——重复项、缺失值、类型错乱、异常值满地都是。清洗这部分工作通常占整个分析流程的60%以上,Pandas就是这把最顺手的刀。这篇文章我会从零开始,带着你走一遍完整的流程:读取数据、清洗整理、分组聚合、最终可视化,每个环节都有可直接复现的代码。
1.2 整体流程的四个环节
整个数据分析链路可以拆成四块:数据获取、数据清洗、数据加工、数据可视化。数据获取阶段,Pandas能读Excel、CSV、数据库甚至剪贴板;数据清洗阶段,处理缺失值、重复值、类型转换、异常值;数据加工阶段,做字段拆分、条件筛选、分组聚合;可视化阶段,借助Pandas内置的绘图接口或者Matplotlib生成图表。
这篇文章也会按这个顺序展开。每个阶段我都会用一份示例数据边说边做,你可以把代码保存成.py文件或直接在Jupyter Notebook里逐行跑,跟着动手比只看不练的效率高太多。
1.3 一个关键认知:清洗比建模更影响结果
很多人会低估清洗环节的重要性。模型选得好不好,决定了效果的天花板;但数据洗得干不干净,决定你能不能碰到那个天花板。脏数据里最常见的情况就是类型不统一,比如同一列里既有数字又有字符串“2,000”,或者日期有的写成“2024-01-01”、有的写成“2024/1/1”。这些不处理好,后续画图、算均值、建分组全部会出问题。Pandas的价值正在于此:它不是帮你做某个具体分析,而是把各种乱象的数据捋顺,让你能用统一的方式高效处理。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与数据读取
2.1 安装Pandas及相关依赖库
如果你还没装好环境,先用最基础的方式检查一下。打开命令行输入:
bash复制python -m pip install pandas
如果你要读写Excel文件,还需要安装openpyxl,这是Pandas背后的Excel解析引擎:
bash复制python -m pip install openpyxl
可视化部分需要Matplotlib:
bash复制python -m pip install matplotlib
安装完成后可以在Python环境里验证版本:
python复制import pandas as pd
print(pd.__version__)
2.2 关于Python版本适配的一个常见问题
很多人问Python 3.10到底应该装哪个版本的Pandas。我在实际使用中的建议是:不要手动指定老版本,直接用新版本即可。从Pandas 1.5开始就完整支持Python 3.10了,现在主流的Pandas 2.x对Python 3.10和3.11的支持都很稳定。如果你用的是更古老的Python 3.7或3.8,那就要留意别装到Pandas 2.x,因为它要求Python至少3.9。最简单的原则是:你是什么Python版本,就让pip自己决定装哪个Pandas,只要Python不是太老,一般不会出问题。
提示:如果你在安装或导入Pandas时看到了“ModuleNotFoundError: No module named 'pandas'”,先确认你是在哪个Python环境里跑的。很多新手踩过这个坑——系统里装了多个Python,pip装到了一个版本,运行代码用的却是另一个版本。用
pip list看看当前环境装了哪些包,第一时间定位问题。
2.3 读取CSV和Excel文件
准备好了环境,接下来看怎么把数据装进DataFrame。最常用的是CSV:
python复制import pandas as pd
df = pd.read_csv("sales.csv")
print(df.head())
读取Excel也一样简单:
python复制df = pd.read_excel("sales.xlsx", sheet_name="Sheet1")
读进来之后,不要立刻埋头分析,先做三个基本动作:
python复制# 1. 看数据集规模,几行几列
print(df.shape)
# 2. 看每列的数据类型
print(df.dtypes)
# 3. 看前几行数据长什么样
print(df.head())
这三个动作能让你在几秒钟内了解数据的整体面貌,包括字段数量、记录数、各列类型。如果发现某个字段应该是日期却显示成了object,或者应该是个数字却混进了字符串,这就是后面清洗的重点。
2.4 手动构造一个小示例数据
没有现成数据的情况下,可以直接在代码里构造一个小表来练习。下面这段代码我会用在全文的演示中:
python复制import pandas as pd
import numpy as np
data = {
"姓名": ["张伟", "李娜", "王强", "刘洋", "张伟", "赵敏", "孙丽", "周杰"],
"城市": ["北京", "上海", "北京", "广州", "北京", "上海", "上海", "南京"],
"销售额": ["12,000", "8,500", "9,000", "2,000", "12,000", "15000", "6,200", "7,800"],
"日期": ["2024-01-05", "2024/1/12", "2024-01-18", "2024-01-20", "2024-01-05", "2024-02-01", "2024-02-03", "2024-02-08"],
"退货": [None, "否", "是", None, "否", "否", "是", "否"]
}
df = pd.DataFrame(data)
这份数据已经埋了不少坑:有重复行、有缺失值、销售额列混着千分位逗号、日期格式不统一。接下来我就会用这份数据演示完整的清洗过程。
3. 数据清洗实操:从脏数据到干净数据
3.1 重复值识别与删除
先看重复数据。日常导出的数据里,最常见的重复情况是整行完全一样,比如数据被重复同步了两次;另一种更隐蔽,只有关键几列重复,其他列可能有微小的差异。用duplicated()可以判断哪些行是重复的:
python复制print(df.duplicated())
这个函数会返回一个布尔序列,标记每一行是否是前面出现过的重复行。整行删除用drop_duplicates():
python复制df = df.drop_duplicates()
指定列去重的技巧
热搜词里有一条很典型:“如果指定两列的值均相同,则取第一条数据即可”。这个场景在订单数据里特别常见,比如同一个用户在同一天下了好几单,你想只保留他当天的第一条记录。实现方式很简单:
python复制df = df.drop_duplicates(subset=["姓名", "日期"], keep="first")
这里的subset参数是关键,它指定了按哪些列去判断重复;keep="first"表示保留第一次出现的行。如果想留最后一次,就改成keep="last"。实际业务里我更常用的是以唯一ID判断去重,比如订单号、用户ID,而不是用姓名这种可能有同名问题的字段。
注意:
drop_duplicates()默认不会修改原DataFrame,而是返回一个新对象。如果不加inplace=True,记得把结果重新赋值给变量。
3.2 缺失值处理
缺失值在真实数据里几乎是无法避免的。我的这份示例数据里,退货列有两个None。处理策略取决于缺失比例和分析需求:
python复制# 查看每列缺失值数量
print(df.isnull().sum())
如果缺失的是少量记录,且删除后不影响整体代表性,可以删除:
python复制df = df.dropna(subset=["退货"])
如果不想删除整行,更稳妥的做法是填充。比如退货列对于缺失值可以理解为“未退货”,补充成“否”:
python复制df["退货"] = df["退货"].fillna("否")
数值列缺失,常用均值或中位数填充:
python复制df["销售额"] = df["销售额"].fillna(df["销售额"].median())
但注意填充之前必须先让这一列变成数值类型,否则median()会报错。这就是下面要讲的类型转换。
3.3 数据类型转换与脏格式清洗
数据清洗里最磨人的就是类型不对。我们这份示例的“销售额”列是字符串,还带着千分位逗号,直接astype(float)会报错。正确做法是先把逗号去掉,再转成数值:
python复制df["销售额"] = df["销售额"].str.replace(",", "", regex=False)
df["销售额"] = pd.to_numeric(df["销售额"], errors="coerce")
errors="coerce"的意思是:转换不了的值直接变成NaN,不报错终止。这个参数在处理混杂数据时非常实用,比如一列里混着数字和“未知”,转换后“未知”会变成缺失值。
日期列的问题也一样,我们的数据里同时有“2024-01-05”和“2024/1/12”两种格式,用pd.to_datetime统一转换:
python复制df["日期"] = pd.to_datetime(df["日期"])
转换后再确认一下各列类型:
python复制print(df.dtypes)
正常情况下就会看到销售额变成float或int,日期变成datetime64,姓名、城市、退货保持object。到这一步,数据基本上具备了进一步分析的基础。
提示:
astype()适合简单转换,比如把整数列转成字符串;pd.to_datetime()和pd.to_numeric()是更强大也更宽容的转换方式,尤其是处理格式不规整的数据时,我会优先用后者。
3.4 异常值识别
异常值不等于错误值,它可能是极端值,也可能是录入错误。快速识别最简单的方法是看描述性统计:
python复制print(df.describe())
describe()会输出数值列的计数、均值、标准差、最小值、四分位数、最大值。如果销售额最大值远远偏离均值,或者最小值出现负数,就要重点关注了。比如销售额为负,如果不是退款记录,很可能就是录入错误。
筛选出异常值可以用条件表达式。比如只想看销售额大于平均销售额3倍标准差的记录:
python复制mean = df["销售额"].mean()
std = df["销售额"].std()
outliers = df[df["销售额"] > mean + 3 * std]
print(outliers)
发现异常值后,不要急着删除。先判断是真实业务场景(比如大客户订单),还是明显错误。我的原则是:能确认错误的才删,拿不准的先标记保留,免得误删有效数据。
4. 数据加工与分析:从明细到结论
4.1 列的筛选、排序与条件过滤
清洗完成后,就可以开始正经分析了。先从最简单的筛选说起。只保留销售额大于10000的记录:
python复制high_sales = df[df["销售额"] > 10000]
对结果排序,按销售额从高到低:
python复制df = df.sort_values("销售额", ascending=False)
按多列排序也很方便,比如先按城市排,再按销售额降序:
python复制df = df.sort_values(["城市", "销售额"], ascending=[True, False])
如果你想用类似SQL的写法,Pandas也有query接口,可读性更好:
python复制high_sales = df.query("销售额 > 10000 and 城市 == '北京'")
4.2 新增列与条件逻辑
分析时经常需要根据已有字段生成新字段。最直接的做法是赋值。比如计算含税销售额:
python复制df["含税销售额"] = df["销售额"] * 1.13
如果逻辑更复杂,比如判断销售额是否达标,可以用numpy.where:
python复制import numpy as np
df["是否达标"] = np.where(df["销售额"] >= 10000, "达标", "未达标")
这个写法等价于Excel里的IF函数,在Pandas里处理条件判断时比用apply循环高效得多。
4.3 分组聚合:groupby的经典玩法
分组聚合是Pandas最核心的能力。比如按城市统计总销售额和订单数:
python复制result = df.groupby("城市").agg(
总销售额=("销售额", "sum"),
订单数=("销售额", "count"),
平均销售额=("销售额", "mean")
).reset_index()
这段代码里的agg函数接收一个字典或命名元组,指定每一列用哪种聚合方式。可以直接在括号里用列名=(原列, 聚合函数)的方式命名,结果数据的可读性很强。最后的reset_index()会把分组索引变回普通列,方便后续保存或进一步处理。
4.4 一个完整的小案例:按城市统计销售额并排序
把前面的操作连起来,看一个完整的分析流程。假设我们要得到“各城市总销售额排名”:
python复制city_stats = (
df.groupby("城市")["销售额"]
.agg(["sum", "mean", "count"])
.sort_values("sum", ascending=False)
.reset_index()
)
print(city_stats)
运行后你能看到每个城市的销售额合计、平均单笔销售额、订单笔数。这个结果可以直接给业务方作为决策依据。再进一步,如果想看每个城市每个月的销售趋势,可以拆出月份字段再分组:
python复制df["月份"] = df["日期"].dt.to_period("M")
monthly = df.groupby(["城市", "月份"])["销售额"].sum().reset_index()
print(monthly)
到这里,数据已经从“一张很乱的表”变成了“一组有价值的统计结果”,接下来就看怎么把这些结果用图形表达出来。
5. 可视化:把分析结果讲清楚
5.1 为什么先清洗再做可视化
可视化本身不会修正数据的问题,只会把问题原样放大。我见过很多初学者的图,横轴日期乱成一团、纵轴出现奇怪的跳点,原因不在绘图代码,而是上游数据没洗干净。所以一定要养成习惯:先做前面几节的清洗加工,再进入画图环节。
5.2 Pandas自带的绘图接口
Pandas内置了基于Matplotlib的绘图方法,一行代码就能出图:
python复制import matplotlib.pyplot as plt
city_stats.plot(kind="bar", x="城市", y="sum", title="各城市总销售额")
plt.show()
这里kind参数可以换成“line”、“pie”、“barh”等。比如想看销售额按日期的时间变化趋势,可以先按月分组,再画折线图:
python复制monthly_line = df.groupby("月份")["销售额"].sum()
monthly_line.plot(kind="line", title="月度销售额趋势")
plt.show()
5.3 中文乱码问题怎么解决
用Matplotlib画图,中文乱码几乎人人都会遇到。这是因为Matplotlib默认字体不支持中文。一个比较省心的解决办法是在画图前设置全局字体:
python复制plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei", "Arial Unicode MS"]
plt.rcParams["axes.unicode_minus"] = False
Mac系统上一般用“Arial Unicode MS”就能生效,Windows用“SimHei”或“Microsoft YaHei”。如果字体设置后还没效果,可能是缺少对应字体文件,需要单独安装一个中文字体。
5.4 调整图表细节,让图能直接拿去汇报
直接把Pandas默认图交出去,有点过于朴素。稍微改几个参数会好很多。下面这段代码加上了图例、网格和数据标签:
python复制fig, ax = plt.subplots(figsize=(8, 5))
bars = ax.bar(city_stats["城市"], city_stats["sum"], color=["#4C72B0", "#DD8452", "#55A868", "#C44E52"])
ax.set_title("2024年1月各城市销售额汇总", fontsize=14)
ax.set_xlabel("城市")
ax.set_ylabel("销售额(元)")
ax.grid(axis="y", linestyle="--", alpha=0.7)
for bar in bars:
height = bar.get_height()
ax.annotate(f"{height:.0f}", xy=(bar.get_x() + bar.get_width() / 2, height),
xytext=(0, 3), textcoords="offset points", ha="center", fontsize=10)
plt.tight_layout()
plt.show()
这样生成的图,标题、坐标轴、标签都齐全,保存下来放到汇报材料里没什么问题。
5.5 导出处理后的数据
分析结果总不能只在屏幕上看,导出成文件同样重要。保存成CSV:
python复制df.to_csv("clean_sales.csv", index=False, encoding="utf-8-sig")
注意encoding="utf-8-sig"很关键,不加这个用Excel直接打开CSV时中文会乱码。保存成Excel就用:
python复制df.to_excel("clean_sales.xlsx", index=False, sheet_name="清洗后数据")
多个结果还能写到同一个Excel的不同Sheet里:
python复制with pd.ExcelWriter("analysis_result.xlsx", engine="openpyxl") as writer:
city_stats.to_excel(writer, sheet_name="城市汇总", index=False)
df.to_excel(writer, sheet_name="清洗明细", index=False)
6. 常见问题与排查技巧实录
6.1 高频报错速查表
我在过往项目里遇到的Pandas相关报错,大多数都能归到下面这几类。整理成一张表,遇到问题可以直接查:
| 常见报错 | 出现原因 | 快速排查方法 |
|---|---|---|
| ModuleNotFoundError: No module named 'pandas' | 环境里没装Pandas,或装到了别的Python环境 | 用与运行脚本相同的解释器执行pip install pandas;在IDE里确认当前解释器路径 |
| KeyError: '列名' | 列名写错,或该列在DataFrame中不存在 | 先执行df.columns查看所有列名的精确写法,注意中文列名是否有空格 |
| ValueError: cannot convert float NaN to integer | 列里有缺失值,无法直接从float转int | 先fillna()或dropna(),再做类型转换 |
| SettingWithCopyWarning | 链式赋值,Pandas在提示你可能改了副本而不是原数据 | 用.loc[]或先.copy()得到明确副本 |
| ParserError: Error tokenizing data | 读取CSV时行数或列数不匹配 | 检查CSV是否用错分隔符,可用sep参数指定;检查文件里有没有奇怪的引号或转义字符 |
6.2 关于SettingWithCopyWarning的补充
这个警告几乎每个Pandas用户都会遇到。它本身不是错误,而是一个提醒:你写的代码可能是拿一个切片视图去赋值,结果没有真正写入原DataFrame。我用一个简单例子说明:
python复制# 这种写法容易触发警告
filtered = df[df["销售额"] > 10000]
filtered["新列"] = "高"
# 更稳妥的写法
df.loc[df["销售额"] > 10000, "新列"] = "高"
第一种写法中,filtered可能是原数据的一个视图,对它的修改未必生效;而第二种写法直接用.loc[]指定位置写入,语义明确,不会出问题。日常开发里我基本都改用df.loc[condition, column] = value这种模式。
6.3 读取CSV时编码问题
另一个高频问题是用Python读CSV时报编码错误。文件是GBK编码,但read_csv默认用UTF-8解析,就会出现UnicodeDecodeError。解决办法是显式指定编码:
python复制df_zh = pd.read_csv("销售数据.csv", encoding="gbk")
不确定文件编码时,可以先试encoding="gbk"、encoding="gb18030"、encoding="utf-8"几种常见编码。如果是别人发给你的文件,直接问一句对方用的什么编码最省事。
6.4 个人实操中的一点心得
我的习惯是:拿到任何数据,先写三行代码——df.head()、df.dtypes、df.describe(),这三行能帮我在10秒内判断出大部分数据问题。然后不管数据多干净,都先把重复项和缺失值处理掉,再开始提取字段和分组。前期多花两分钟,后面能少踩一堆坑。
另外,处理比较大的数据集时,尽量少用循环,多用Pandas的向量化操作。比如要对一列做逻辑判断,np.where比逐行for循环快几十倍;要对字符串批量清洗,直接用.str方法就好。写Pandas代码的正确思路是“描述你想做什么,而不是怎么一步一步做”,这样既快又不容易出错。
这篇内容覆盖了从环境搭建、数据读取、清洗加工到可视化输出的完整链路。如果你正拿着Pandas在啃一份真实数据,照着这个流程走一遍,应该能应对大部分日常分析需求。后面我还会继续分享一些关于数据透视表、时间序列处理、大数据分批读取之类的实战内容,到时再细聊。
