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,需要完成以下几件事:

任务描述:

  1. 读取订单表。
  2. 删除“订单金额”为空的行。
  3. 把“下单日期”转为真正的日期时间字段。
  4. 新增一列“月份”,从下单日期里提取出来。
  5. 按“月份+城市”分组求订单总额。
  6. 筛选出月订单总额超过10万的城市。
  7. 把结果保存到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做数据分析项目,总体的经验就是:基础函数就那么几十个,真正重要的不是背函数,而是建立起“拿到数据之后先做什么、再做什么”的流程感。数据体检、清洗、筛选、聚合、输出,这一套流程走熟了,任何领域的数据处理工作你都能快速上手。

希望这篇内容对你有实实在在的帮助。如果你在练习过程中遇到了让人崩溃的报错,不妨回头看看这篇教程里的避坑清单——很可能你踩的坑,我早就踩过了。

更多推荐