pandas数据处理全指南:从入门到实战
1. 写在前面:为什么你一定要学pandas
如果你刚接触Python,或者已经写了几天代码准备往数据分析方向走,那pandas一定是绕不开的那个库。我可以直接告诉你一个判断标准:在Python的数据处理生态里,pandas就是那个“事实标准”。你去看招聘要求也好,看别人做的数据分析项目也好,爬虫之后的数据清洗也好,几乎全部都会出现pandas的身影。
那pandas到底能做什么?说白了,它就解决一个问题:让Python能够像操作Excel表格一样,在代码里处理结构化数据。你在Excel里能做筛选、排序、求和、透视,pandas全都能做,而且是用代码来做,这意味着你可以批量处理、自动重复、一键跑完整个流程。同样的工作,用Excel点点点可能要半小时,写一段pandas脚本三秒钟搞定。这就是它的核心价值——把“人肉操作表格”变成“程序自动处理表格”。
这篇内容我打算完全按照我的实际操作经验来写。我既不会照着官方文档给你念一遍API,也不会像某些教程那样只丢一堆代码让你自己看。我会告诉你:pandas里的每个核心概念到底解决什么问题,每个操作在真实项目中会在什么场景用到,以及我这些年用pandas踩过的坑、养成的习惯,全部一次讲清楚。这篇内容会很长,因为我希望你看完不只是“知道pandas”,而是真的“能上手用pandas”。
内容适合的人群我也说一下:已经会Python基础语法(变量、列表、字典、for循环、函数),想往数据分析、数据清洗或者自动化办公方向走的同学。如果你是完全的编程零基础,建议先花一周把Python基础语法过一遍再回来看这篇,不然你可能会卡在语法上而不是pandas本身。
2. 环境准备:pandas到底怎么装才不出问题
2.1 安装之前必须先搞清楚的几件事
很多新手一上来就卡在装库这一步,而且报错信息五花八门,什么
ModuleNotFoundError
、
Requirement already satisfied
、
pip install pandas
执行之后提示找不到pip……这些问题九成都是环境搞混了。什么叫环境搞混?就是你电脑上有好几个Python,或者你用的Python和pip不是同一个。
我先给你一个最简单的验证方法。打开命令行(Windows按Win+R输入cmd回车,Mac打开Terminal),输入:
python --version
然后再输:
pip --version
你注意看这两个命令返回的信息。正常情况下,它们显示的Python版本应该来自同一个目录。如果python显示的是3.11,但pip显示的是3.9的路径,那说明你的Windows系统里装了多个Python,pip装到了另一个Python里,
import pandas
当然就找不到。这个问题特别常见,尤其是在Windows上自己从官网下载安装包、然后又装了Anaconda、然后又用VSCode选了别的解释器的同学,十个人里面有八个会踩这个坑。
所以我给你的第一条建议非常直接:不要在系统里瞎装一堆Python。如果你只是为了做数据分析,直接装Anaconda全家桶最省事。Anaconda自带Python解释器、自带pandas、numpy、matplotlib等两百多个常用库,你学pandas前期根本不需要额外装任何东西,打开即用。
如果不想用Anaconda,觉得太臃肿,那就要学会使用虚拟环境(venv)。每次新建一个项目就创建一个干净的Python环境,库装在虚拟环境里,不会和系统全局环境冲突。
2.2 三种安装pandas的方式对比
我直接把三种常用方案整理成对比表,你可以根据自己的情况对号入座:
| 安装方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Anaconda自带 | 数据分析新手、不想折腾环境的懒人 | 安装完直接用,集成了pandas、numpy等核心库 | 软件体积大,需要好几个G |
| pip安装 | 已经有Python环境、不想用Anaconda的朋友 | 轻量、灵活,想装什么装什么 | 容易踩环境冲突的坑 |
| 在IDE里安装(PyCharm/VSCode) | 正在用IDE创建项目的新手到进阶用户 | 图形化操作,不用记命令 | 有时候IDE选择的解释器和pip不一致 |
如果你是走pip这条路,我建议你用下面的命令,直接指定国内镜像源下载,速度会快很多:
pip install pandas -i https://pypi.tuna.tsinghua.edu.cn/simple
为什么要指定镜像源?因为pandas以及它的底层依赖numpy等库都比较大,默认从官方源下载速度可能只有几十KB每秒,有时候还会直接超时失败。换成清华源之后,基本上几秒钟就能下完。这是最原始的国内开发者被网络逼出来的经验,实测非常有效。
2.3 验证pandas是否安装成功
装完之后怎么确认成功了?在命令行进入python交互模式,或者直接在IDE里新建一个py文件运行:
import pandas as pd
print(pd.__version__)
如果没有任何报错,并且打印出来一个版本号(比如
2.1.0
),那恭喜你,pandas安装成功了。如果你看到的是
ModuleNotFoundError: No module named 'pandas'
,那就说明pandas没有装到当前这个Python环境里。这时候你再去检查一下:你现在执行python命令的,到底是哪个解释器?pip又是装到了哪里?对照着把我上面说的方法重新走一遍,一般就能解决。
注意:PyCharm里跑代码之前,一定要先看右下角(或设置里的Project Interpreter)当前选中的解释器是不是你安装pandas的那个。这一步很重要,因为PyCharm默认可能会使用它自带的虚拟环境,里面根本没有pandas。我看到过太多人卡在这个地方,代码明明没问题,就是import报错。
3. 核心数据结构:先把Series和DataFrame吃透
3.1 pandas和Excel表格的对应关系
装好环境之后,第一件要做的事情不是急着跑代码,而是先把pandas的两个核心数据结构搞清楚:Series和DataFrame。我直接用Excel来类比:
- DataFrame对应Excel里的一张完整表格,有行索引、有列名,是二维数据。
- Series对应Excel里的某一行或者某一列,是一维数据序列。
可以这样理解:DataFrame是“列的组合”,它的每一列都是一个Series。反过来看,DataFrame可以看作由多个Series在纵向上拼起来的结果。把这一层关系搞清楚,后面所有的操作你都会觉得顺理成章。
我来创建几个最基础的数据结构看看:
import pandas as pd
# 创建一个Series,传入一个列表
s = pd.Series([10, 20, 30, 40])
print(s)
# 创建一个DataFrame,传入一个字典
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五'],
'年龄': [25, 30, 35],
'城市': ['北京', '上海', '广州']
})
print(df)
输出大概是这样:
0 10
1 20
2 30
3 40
dtype: int64
姓名 年龄 城市
0 张三 25 北京
1 李四 30 上海
2 王五 35 广州
你注意到没有?Series的输出左边那一列(0、1、2、3)叫索引,默认从0开始;DataFrame的第一行(姓名、年龄、城市)是列名,左边的一列也是索引。这就是pandas所有操作的基础——通过索引和列名来定位数据。
3.2 为什么Series和DataFrame要分开设计
刚接触pandas的人可能会不理解:既然DataFrame已经能表示表格了,为什么还要单独弄一个Series?直接用DataFrame不就行了吗?
这个设计的核心原因其实是:大量数据分析场景处理的源头就是一维数据。比如你从接口拿回来一个列表,从数据库查出来一列数据,你需要统计这批数据的均值、最大值、缺失值情况——这些操作在一维层面上就完成了,不需要强行套一个二维表格。而且把DataFrame的一列单独取出来做统计,也是一个极其高频的操作。
在实际业务里,最常见的操作模式是这样的:
# 取出姓名这一列,本质是Series
names = df['姓名']
print(type(names)) # pandas.core.series.Series
# 对年龄这一列做统计
print(df['年龄'].mean()) # 30.0
print(df['年龄'].max()) # 35
print(df['年龄'].min()) # 25
只要一个Series,就能直接调用
mean()
、
max()
、
min()
、
sum()
这些统计方法,非常方便。如果你非要用DataFrame去干这件事,反而绕路。所以我的建议是:记不住概念没关系,但一定要记住这两个东西的存在,因为后面所有代码都在跟它们打交道。
3.3 DataFrame的创建方式不只是字典传参
DataFrame的创建方式其实有很多种,除了上面说的传字典之外,在实际项目中你还会遇到另外几种常见情况:
import numpy as np
# 从二维列表创建,同时指定列名
data = [[1, 'Alice', 90], [2, 'Bob', 85], [3, 'Cathy', 92]]
df1 = pd.DataFrame(data, columns=['ID', 'Name', 'Score'])
# 从numpy数组创建
arr = np.array([[1, 2, 3], [4, 5, 6]])
df2 = pd.DataFrame(arr, columns=['A', 'B', 'C'])
# 从多个Series拼成DataFrame
s1 = pd.Series([1, 2, 3], name='x')
s2 = pd.Series([4, 5, 6], name='y')
df3 = pd.concat([s1, s2], axis=1)
这里面你需要特别注意的是
axis=1
这个参数。很多新手刚学时看到
axis=1
就懵,其实可以用一个简单方式理解:
axis=0
表示沿着行的方向往下走,
axis=1
表示沿着列的方向往右走。你要是记不住,就记住这句话——
concat时
axis=0
是纵向拼接(多行合并),
axis=1
是横向拼接(多列合并)
。如果你把方向弄反了,很可能会得到一个稀奇古怪的DataFrame。
4. 读取数据:从Excel、CSV和网页数据说起
4.1 pandas怎么读Excel文件
大多数人的数据分析生涯是从读Excel开始的,毕竟Excel是日常办公里最普遍的数据载体。pandas读Excel用一行代码:
import pandas as pd
df = pd.read_excel('销售数据.xlsx')
就这么简单,文件路径给出,pandas就会把整个表格读进来。但是如果你直接运行这段代码,很可能会遇到一个报错:
ImportError: Missing optional dependency 'openpyxl'
。这个报错的意思是,pandas本身不是一个完全独立的解析器,它读取Excel文件需要依赖
openpyxl
或者
xlrd
这个第三方库。
解决办法也很直接,先安装openpyxl:
pip install openpyxl
然后在使用时指定引擎:
df = pd.read_excel('销售数据.xlsx', engine='openpyxl')
我觉得这个坑值得多说一句。为什么pandas不把openpyxl直接集成到内部?因为pandas的生态策略是保持自身轻量化,把不同的文件解析工作交给更专业的库去做。Excel解析交给openpyxl,CSV解析就是自带的C语言解析器,这样各司其职、维护也方便。理解了这一点,你以后再遇到pandas依赖其他第三方库的情况(比如读取SQL数据库时依赖SQLAlchemy),就不会觉得莫名其妙了。
4.2 读取CSV文件以及常见的字符集坑
CSV文件的读取更常见,因为很多系统导出的数据、网站下载的数据都是CSV格式:
df = pd.read_csv('订单记录.csv')
但是CSV有一个极其恶心的点:中文乱码。你在本地用Excel打开一个CSV完全正常,用pandas一读,出来的全是乱码。这是因为CSV文件本身不强制规定编码方式,有些文件是UTF-8编码,有些是GBK编码,如果pandas默认按UTF-8解码,而文件实际是GBK编码的,那必然乱码。
解决办法是手动指定编码:
df = pd.read_csv('订单记录.csv', encoding='gbk')
如果还是不行,试一下另一个:
df = pd.read_csv('订单记录.csv', encoding='utf-8')
如果还想再保险一点,可以试试
encoding='gb18030'
,这是GBK的超集,兼容性更好。我自己的经验是:如果客户发给我的CSV是从老系统里导出来的,十有八九是GBK编码;如果是Python爬虫保存下来的,基本都是UTF-8编码。你只要遇到乱码就在这两个编码之间切换,基本能覆盖大多数情况。
4.3 快速查看数据质量:head、info、describe三件套
数据读取进来之后,先别急着做处理。我强烈建议你先执行三个方法,这也是所有老手拿到数据后的“第一眼体检”:
# 查看前5行,快速看下长什么样
print(df.head())
# 查看数据总览:有多少行多少列、每列的类型、有没有空值
print(df.info())
# 查看数值列的统计描述:均值、方差、最小值、最大值、分位数
print(df.describe())
这三个方法输出的信息量很大。
head()
让你直观看到表格的结构;
info()
告诉你每列的数据类型(int64、float64、object),如果某列显示
object
但值看起来是数字,那说明这一列被存成了文本,后面需要做类型转换;
describe()
直接给出统计信息,如果你的数据里有异常值,比如年龄列最大值是999,在describe里一眼就能看出来。
我每次做数据分析项目,无论是处理什么数据,第一步永远是这三个方法。养成这个习惯之后,你对数据的“体感”会强很多。很多新手一拿到数据就开始写各种复杂的处理逻辑,结果发现数据本身就有问题,白费半天功夫。先体检,再动手,这是老手和新手的一个关键区别。
5. 数据筛选:loc、iloc和布尔索引这三件套
5.1 loc和iloc到底有什么区别
数据读取进来,最频繁的操作就是按条件筛选数据。pandas里做筛选,最核心的就是
loc
和
iloc
。
很多人一开始分不清这两个东西。我一句话就能说明白:
-
loc是用 标签 (行名、列名)来选取数据,也就是“按名字找”。 -
iloc是用 位置 (第几行、第几列)来选取数据,也就是“按第几个找”。
来看一段代码:
import pandas as pd
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六'],
'年龄': [25, 30, 35, 40],
'城市': ['北京', '上海', '广州', '深圳']
}, index=['a', 'b', 'c', 'd']) # 自定义行索引
# loc按标签选取:取行索引为'a'的这一行
print(df.loc['a'])
# iloc按位置选取:取第0行
print(df.iloc[0])
# loc取连续区域:从行索引'a'到'c',列只取前两列
print(df.loc['a':'c', ['姓名', '年龄']])
# iloc取连续区域:取前3行,前2列
print(df.iloc[:3, :2])
注意一个特别容易出错的点:
loc
的切片是包含结尾的,比如
df.loc['a':'c']
会取到a、b、c三行;而
iloc
的切片和Python原生切片一样,是
左闭右开
的,
df.iloc[:3]
只会取第0、1、2行,不包含第3行。我在刚学的时候就被这个搞晕过,总是记混。后来我就只记一句话:
loc是闭区间,iloc是开区间
。你试试看,这个口诀在实战中真的很管用。
5.2 布尔索引:按条件筛选数据的核心武器
比
loc
和
iloc
更常用的是布尔索引。什么叫布尔索引?就是先得到一个由True和False组成的条件序列,然后把它放进方括号里,pandas就会只保留条件为True的行。
import pandas as pd
df = pd.DataFrame({
'姓名': ['张三', '李四', '王五', '赵六'],
'年龄': [25, 30, 35, 40],
'城市': ['北京', '上海', '广州', '深圳']
})
# 筛选年龄大于30的人
df_30 = df[df['年龄'] > 30]
print(df_30)
# 筛选城市是北京的人
df_bj = df[df['城市'] == '北京']
print(df_bj)
# 多条件筛选:年龄大于25且在北上广
df_multi = df[(df['年龄'] > 25) & (df['城市'].isin(['北京', '上海', '广州']))]
print(df_multi)
这里有几个细节一定要注意:
第一,多条件之间要用
&
(且)、
|
(或),不能用Python的
and
、
or
。原因在于
df['年龄'] > 30
得到的是一个Series,Series之间的逻辑运算只能用
&
、
|
,如果你写
and
会直接报错:
The truth value of a Series is ambiguous
。这是pandas新手最常见的报错之一。
第二,多条件时每个条件要加括号。比如
df[(df['年龄'] > 25) & (df['城市'] == '北京')]
,如果你不写括号,Python会因为运算符优先级问题报错或者得到错误结果。我的建议是:不管有没有必要,每个条件都加括号,保证代码清晰可读。
第三,
isin()
这个方法适合处理“多个可能取值的匹配”,比如你想筛选城市为“北京、上海、广州”三者之一的用户,写成一个列表传进去就行,不用写三个
|
连接的条件。
5.3 新增列和删除列:给表格做“外科手术”
筛选之外,你一定还会经常需要对列做操作:新增一列、删除一列、改列名。这些操作在pandas里都极其简单:
# 新增一列:可以直接用一个列表
df['是否成年'] = df['年龄'] >= 18
# 新增一列:通过计算现有列生成新列
df['年龄加10'] = df['年龄'] + 10
# 删除一列
df = df.drop(columns=['年龄加10'])
# 修改列名
df = df.rename(columns={'姓名': '名字', '城市': '所在城市'})
关于删除列,你可能会在很多老代码里看到
df.drop('列名', axis=1, inplace=True)
这种写法。这里我特别多说一句:
inplace=True
这个参数在pandas社区里一直存在争议。它是说“原地修改”,也就是不返回值,直接改掉原来的df。如果
inplace=False
(默认),
drop
会返回一个新的DataFrame,原df不变。我个人的建议是:
尽量不要用
inplace=True
。因为它的行为容易让人困惑,而且pandas官方已经在多个场合表示未来可能会废弃这个参数。我更推荐你直接用
df = df.drop(columns=['某一列'])
这种写法,显式地把返回值赋给变量,逻辑上清楚得多。
6. 缺失值与数据类型:数据清洗的两座大山
6.1 怎么发现缺失值:info和isnull的组合拳
在真实的数据处理中,最让人头疼的不是数据量大,而是数据不干净。数据不干净的主要表现之一就是缺失值。Excel里的空单元格,导进pandas之后会变成什么?答案是
NaN
(Not a Number)。注意,它不是字符串
"NaN"
,也不是
None
,而是一个特殊的浮点数标记,代表“这个位置没有值”。
发现缺失值有几种办法:
# 一:info会直接告诉你每列有多少个非空值
df.info()
# 二:isnull()把每个位置标记为True/False,sum()统计每列多少缺失
print(df.isnull().sum())
# 三:查看有缺失值的行
print(df[df.isnull().any(axis=1)])
df.isnull().sum()
是我用得最多的一个。它会输出每列有缺失值的个数,比如:
姓名 0
年龄 1
城市 2
dtype: int64
这样你一眼就知道哪些列需要处理。这里有个小技巧一定要掌握:
df.isnull()
得到的结果是和原df同样大小的全是True/False的表格,
sum()
统计的是每一列True的个数。这个思路是pandas里很典型的一种模式——先用布尔值标记,然后聚合统计。你后面学其他操作时也会反复用到这个套路。
6.2 处理缺失值:dropna还是fillna,这是个选择
发现缺失值之后,处理方案无非两种:删掉或者填上。
如果你确定这些缺失值所在的整行数据对分析没有价值,可以直接删:
# 删掉有缺失值的行
df_clean = df.dropna()
# 只删除某一列上有缺失的行
df_clean = df.dropna(subset=['年龄'])
如果你不想删,那就填。填什么样的值,取决于业务场景:
# 用固定值填充
df['年龄'] = df['年龄'].fillna(0)
# 用这一列的平均值填充
df['年龄'] = df['年龄'].fillna(df['年龄'].mean())
# 用前一行或后一行的值填充,适用于时间序列
df['温度'] = df['温度'].fillna(method='ffill') # 前向填充
df['温度'] = df['温度'].fillna(method='bfill') # 后向填充
我之前接过一个做门店销售分析的需求,销售金额列里有一些缺失值。我如果用0去填充,就会把平均值拉低;如果用平均值填充,则会掩盖异常的波动。后来我选择了前向填充
ffill
,用上一个已知值去填补,这在时间序列数据里算是比较合理的策略。所以你看,选择哪种填充方式不是拍脑袋决定的,而是要根据数据的业务含义来判断。
还有一类处理方式是直接不处理。如果缺失值只占数据总量的不到5%,而且这些缺失值不会影响到你具体的统计指标,那可以保留。这也是我常用的策略——不是所有数据都得做到完美无缺,处理方式的性价比也很重要。
6.3 数据类型转换:astype和pd.to_datetime
数据类型的坑非常隐蔽。我举个例子:你用pandas读取Excel里的“编号”列,里面的值明明是数字,但数据量大时pandas可能把它读成整数,也可能读成浮点数;有些“日期”列你以为是日期时间类型,实际读取后是字符串。这些问题如果不提前发现,后面做排序、聚合、画图都会出现莫名其妙的错误。
查看数据类型的方法:
print(df.dtypes)
转换数据类型的核心方法:
# 强制转成int类型
df['年龄'] = df['年龄'].astype(int)
# 转成字符串
df['编号'] = df['编号'].astype(str)
# 转成日期时间类型
df['下单日期'] = pd.to_datetime(df['下单日期'])
# 转成数值类型,如果转换失败会变成NaN
df['金额'] = pd.to_numeric(df['金额'], errors='coerce')
这里我重点说两个参数。第一个是
pd.to_datetime
里的
format
参数。如果你的日期字符串是
2024-05-01 12:30:00
这种标准格式,
pd.to_datetime
可以直接解析;但如果遇到
2024/05/01
或者
01-05-2024
这种非标准格式,最好手写
format='%Y/%m/%d'
来指定,速度会快很多,也不容易解析错。
第二个是
errors='coerce'
。
to_numeric
在遇到无法转换成数字的字符串时,如果设了
errors='coerce'
,就会把转换失败的值变成
NaN
而不是直接报错。这在处理脏数据时特别实用——你先转一遍,把非法值变成NaN,再用前面讲的缺失值处理方法统一处理,数据清洗的过程就能流畅很多。
7. 数据聚合与分组:从“看数据”到“分析数据”
7.1 groupby的用法与底层逻辑
当你的数据量达到几千行几万行,你不可能一行一行去看。你需要的是“分组统计”:比如按城市统计平均销售额、按部门统计人数、按月统计总流水。这些需求在pandas里全靠
groupby
实现。
groupby
的底层逻辑其实很简单,就是“拆分-应用-合并”(Split-Apply-Combine):
import pandas as pd
df = pd.DataFrame({
'城市': ['北京', '上海', '北京', '广州', '上海', '北京'],
'销售额': [100, 200, 300, 150, 400, 250],
'成本': [60, 120, 180, 90, 240, 150]
})
# 按城市分组,计算销售额和成本的总和
result = df.groupby('城市').sum()
print(result)
# 按城市分组,计算每个城市的平均销售额
avg_result = df.groupby('城市')['销售额'].mean()
print(avg_result)
输出大概是:
销售额 成本
城市
北京 650 390
上海 600 360
广州 150 90
你看,
groupby('城市')
把数据先按城市拆成了若干组,然后
.sum()
对每组进行了求和处理,最后pandas自动把所有结果拼成了一整张表。这个过程不需要你写循环,一行代码就完成了。这也是pandas相比纯Excel公式的一大优势——你可以在一个连贯的计算流程里把数据处理好。
7.2 多列分组和多个聚合函数
实际业务中,你经常需要按两个维度同时分组。比如按“城市+月份”统计销售情况:
df['月份'] = ['1月', '1月', '2月', '2月', '1月', '2月']
result = df.groupby(['城市', '月份'])['销售额'].sum()
print(result)
得到的是一个MultiIndex的Series,你可以用
result.unstack()
把内层索引拆成行或列,让它看起来更像Excel透视表。这个操作处理完了再配一个
reset_index()
,就可以很方便地保存到Excel了。
如果你想同时算多个统计指标,比如每个城市的销售额总和、平均值、最大值:
result = df.groupby('城市')['销售额'].agg(['sum', 'mean', 'max', 'count'])
print(result)
agg
这个函数非常灵活,它支持传列表。你可以在一次
groupby
操作里把多个聚合函数全部跑完,而不需要写多个
groupby
。这在实际项目中能省下大量时间。
7.3 pivot_table:Excel透视表在pandas里怎么用
如果你用过Excel的透视表,那pandas里的
pivot_table
你一定觉得非常亲切。
groupby
是按已有的列分组汇总,而
pivot_table
则更像是把一列的唯一值展开成新的列,形成“行列交叉”的矩阵式汇总。比如你要做一张“城市(行)× 月份(列)”的销售额透视表:
pivot = pd.pivot_table(
df,
values='销售额',
index='城市',
columns='月份',
aggfunc='sum',
fill_value=0
)
print(pivot)
这个输出格式就和Excel透视表几乎一模一样了。唯一需要留意的是
aggfunc
参数,默认是
mean
(均值),但你在完全一样的输入下跑出和预期不符的结果时,十有八九是忘了指定
aggfunc
。我一开始用这个函数好几次都忽略了这一点,算出来的数值跟同事对不上,后来才发现是均值而不是求和。
8. 数据合并与输出:把你的处理结果真正落地
8.1 concat和merge:横向拼还是纵向叠
数据处理到后面,经常需要把两个表合并成一个。pandas提供了两个核心方法:
concat
和
merge
。
concat
适合简单的拼接——两张表结构一样或者形状一致:
df_a = pd.DataFrame({'姓名': ['张三', '李四'], '成绩': [90, 85]})
df_b = pd.DataFrame({'姓名': ['王五', '赵六'], '成绩': [92, 88]})
# 纵向拼接(把df_b接到df_a下面)
df_all = pd.concat([df_a, df_b], axis=0, ignore_index=True)
merge
适合按某个关键字段进行关联,就像SQL里的JOIN。比如一张表存了员工基本信息,另一张表存了员工的部门,可以通过员工ID关联起来:
df_info = pd.DataFrame({'ID': [1, 2, 3], '姓名': ['张三', '李四', '王五']})
df_dept = pd.DataFrame({'ID': [1, 2, 4], '部门': ['技术部', '销售部', '财务部']})
# 内连接,只保留两边都有ID的行
merged = pd.merge(df_info, df_dept, on='ID', how='inner')
print(merged)
这里的
on
指定连接的键;
how
指定连接方式,
inner
表示内连接(取交集),
left
表示左连接(以左边表为基准),
right
和
outer
同理。如果你只看
merge
的代码可能觉得抽象,可以先在Excel里用VLOOKUP联想一下——
merge
做的事情就相当于VLOOKUP,但功能更强大、更灵活。
8.2 to_excel和to_csv:把结果保存到文件
数据处理完之后,最终是要输出的。最常见的两个输出就是写回Excel和CSV:
# 保存为Excel
df.to_excel('处理结果.xlsx', index=False, engine='openpyxl')
# 保存为CSV
df.to_csv('处理结果.csv', index=False, encoding='utf-8-sig')
index=False
这个参数我每次都会强调,因为它的作用是不把行索引写进文件。如果你不指定,保存的文件里会多出一列0、1、2……的序号,很多时候这不是你想要的结果。
encoding='utf-8-sig'
这个参数是针对中文用户的。普通的
utf-8
编码在Excel中打开CSV时可能中文乱码,但
utf-8-sig
会在文件头部加一个BOM标记(Byte Order Mark),Excel就能正确识别编码。每当我给别人交付CSV文件时,都会用这个编码。
如果你用Excel打开了CSV却发现中文是乱码,不要怀疑是数据处理错误,先检查保存时是否用了
utf-8-sig
。
8.3 一个完整的“读数-清洗-筛选-聚合-输出”小项目
掌握了上面的所有知识点之后,我们来写一个完整的小案例,把pandas常用的操作全部串起来。假设你是一个电商平台的数据分析实习生,手里有一份订单记录Excel,需要完成以下几件事:
任务描述:
- 读取订单表。
- 删除“订单金额”为空的行。
- 把“下单日期”转为真正的日期时间字段。
- 新增一列“月份”,从下单日期里提取出来。
- 按“月份+城市”分组求订单总额。
- 筛选出月订单总额超过10万的城市。
- 把结果保存到Excel。
完整代码如下:
import pandas as pd
# 1. 读取数据
df = pd.read_excel('订单记录.xlsx', engine='openpyxl')
# 2. 体检
print(df.head())
print(df.info())
# 3. 删除缺失金额的行
df = df.dropna(subset=['订单金额'])
# 4. 日期转换
df['下单日期'] = pd.to_datetime(df['下单日期'], format='%Y-%m-%d')
# 5. 新增月份列
df['月份'] = df['下单日期'].dt.month
# 6. 按月份和城市聚合
result = df.groupby(['月份', '城市'])['订单金额'].sum().reset_index()
# 7. 筛选总额大于100000的城市月份组合
high_result = result[result['订单金额'] > 100000]
# 8. 输出
high_result.to_excel('高销售城市汇总.xlsx', index=False, engine='openpyxl')
print(high_result)
你仔细看整个流程:从读取到体检,从清洗到转换,从分组聚合到条件筛选,最后保存输出,这一整套流程就是数据分析师每天都在重复的“标准操作流程”。你在学pandas时不用每个函数都背下来,但一定要练熟这条主干流程——它能覆盖日常80%以上的数据处理需求。
9. 绕开这些坑:我用pandas这几年总结的避坑清单
9.1 最常报错的几个原因
我见过大量初学者在论坛里求助,翻来覆去就是那几个问题。我直接整理成一个速查表,你自己对号入座:
| 报错信息或现象 | 根本原因 | 解决方案 |
|---|---|---|
ModuleNotFoundError: No module named 'pandas'
| pandas没装到当前环境 / 解释器选错 | 检查解释器,在正确的环境用pip安装 |
The truth value of a Series is ambiguous
|
在条件里用了
and
/
or
|
改成
&
/ `
|
读Excel时报缺
openpyxl
| 缺少Excel解析依赖 |
pip install openpyxl
,或指定
engine='openpyxl'
|
| CSV中文乱码 | 编码不一致 |
读的时候指定
encoding='gbk'
或
utf-8
;保存时用
utf-8-sig
|
| 明明筛选了却没生效,数据没变 | 忘了把返回的新DataFrame赋值回来 |
记住:
df = df[...]
,或者
df = df.drop()
|
9.2 两个真正影响代码质量的习惯
第一个是“链式操作友好”。pandas里很多操作可以连在一起写,不用每步都赋值给新变量。比如:
df_clean = (
df.dropna(subset=['金额'])
.assign(月份=pd.to_datetime(df['日期']).dt.month)
.groupby('月份')['金额']
.sum()
.reset_index()
)
这种写法看着高级,其实底层逻辑和你拆开写是一样的。它有个好处是逻辑清晰,中间不会产生多余的临时变量。但缺点是你不能在中间步骤里打印验证结果。我的建议是:调试阶段拆开写,逻辑确认无误后可以合并成管道式写法,让代码更简洁。
第二个是“复制视图和修改原数据的分寸”。pandas里有
copy()
方法,但很多人用不好。比如筛选出来的子DataFrame,你改了里面的某个值,有时候会带着把原DataFrame的对应值也改了(这是视图和副本机制导致的)。避免这个问题的稳妥办法是:只要你对筛选出来的数据要做修改操作,先
.copy()
一份:
df_sub = df[df['城市'] == '北京'].copy()
df_sub.loc[0, '销售额'] = 999 # 这样才不影响原df
如果你不做copy,直接用变量去修改,翻车只是时间问题。我一个前同事就因为这个机制吃过闷亏,他改了筛选出来的子表,结果原表也被改了,数据源头直接污染,排查了很久才发现问题。从那以后我凡是修改操作都先copy,再也不在这个坑上了。
10. 写在最后:pandas学习的路线建议
看到这里,你已经把pandas的核心主干知识全部过了一遍。从安装环境到DataFrame和Series,从读取数据到数据体检,从筛选索引到缺失值处理,从groupby聚合到合并输出,这些就是在真实项目中最高频使用的操作组合。你说它们简单也好,琐碎也好,但就是这些基础操作,日复一日地组成了数据分析工作的大部分内容。
我个人学习pandas时的一个体会是:学它的最佳方式不是从头到尾完整读一本教程,而是带着一个具体问题去查、去用、去踩坑。你可以找一个自己感兴趣的数据集,比如从网站上下载一份公开的食材价格数据、股票历史数据或者体育比赛数据,然后试着用它回答几个问题:“价格最高的十种食材分别是哪些”、“近五年某只股票的月线走势如何”、“主客场比赛的平均分差有多大”……每解决一个问题,你的pandas水平就会扎实一圈。遇到不会的操作,就查pandas官方文档或者直接搜索,在实践中构成的记忆,远比死记函数API牢固得多。
我自己这些年用pandas做数据分析项目,总体的经验就是:基础函数就那么几十个,真正重要的不是背函数,而是建立起“拿到数据之后先做什么、再做什么”的流程感。数据体检、清洗、筛选、聚合、输出,这一套流程走熟了,任何领域的数据处理工作你都能快速上手。
希望这篇内容对你有实实在在的帮助。如果你在练习过程中遇到了让人崩溃的报错,不妨回头看看这篇教程里的避坑清单——很可能你踩的坑,我早就踩过了。
更多推荐


所有评论(0)