1. 为什么数据分析绕不开Pandas
刚接触Python数据分析那会儿,我最先装的是NumPy。数组运算确实快,但一到处理带标签的表格数据就浑身难受——想按列名取数得自己记索引,想按条件筛选得写一堆布尔运算,缺失值处理更是要手动循环。直到用上Pandas,才真正体会到什么叫“为表格数据而生”。
Pandas是Python生态里做数据分析最核心的库,没有之一。它建立在NumPy之上,但把NumPy那种“面向数组”的思维升级成了“面向表格”的思维。你可以把它理解成Python里的Excel,但能力远超Excel——处理百万行数据毫无压力,支持复杂的行列索引、分组聚合、时间序列、缺失值处理,还能直接读写Excel、CSV、JSON、SQL等十几种数据源。
这篇文章适合谁看?如果你刚开始学Python数据分析,或者从Excel转过来想用代码处理数据,又或者已经用过Pandas但总觉得“会用但没系统搞明白”,那这篇内容就是写给你的。我会从Pandas最核心的两个数据结构讲起,把创建、索引、筛选、类型转换这些基础操作掰开揉碎,配上我实际踩过的坑和总结的技巧。看完之后,你至少能做到:拿到一份Excel或CSV文件,知道怎么读进来、怎么看数据长什么样、怎么按条件筛选、怎么处理缺失值、怎么把结果导出去。
Pandas的官方文档写得很全,但全不等于好用。很多新手打开官方文档就懵了——参数太多,示例太散,不知道从哪下手。我这篇就按“实际干活需要什么”的顺序来组织,不追求面面俱到,但求每个讲到的点都能直接上手用。
2. 两个核心数据结构:Series和DataFrame
2.1 Series:带标签的一维数组
Series是Pandas里最基础的数据结构,你可以把它想成“带索引的一列数据”。普通列表只有位置,Series每个元素都有一个标签(索引),这样你就能用标签来取值,而不是只能靠位置。
创建一个Series很简单:
import pandas as pd s = pd.Series([10, 20, 30, 40], index=['a', 'b', 'c', 'd']) print(s)输出是:
a 10 b 20 c 30 d 40 dtype: int64左边是索引,右边是值。如果不指定index,Pandas会自动生成0到n-1的整数索引。Series的索引不要求唯一,也不要求有序,这给了很大的灵活性。
Series有两个核心属性需要记住:values返回底层的NumPy数组,index返回索引对象。实际用的时候,s.values和s.index能帮你快速看清数据长什么样。
注意:Series的索引可以是字符串、整数、时间戳,甚至混合类型。但混合类型索引在筛选时容易出问题,建议保持索引类型一致。
2.2 DataFrame:带行列标签的二维表格
DataFrame才是Pandas真正的主角。它是一个二维表格,有行索引和列名,每一列可以有不同的数据类型。你可以把它理解成“多个Series拼在一起,共享同一个行索引”。
创建DataFrame最常见的方式是用字典:
data = { '姓名': ['张三', '李四', '王五', '赵六'], '年龄': [25, 30, 28, 35], '城市': ['北京', '上海', '广州', '深圳'], '薪资': [15000, 20000, 18000, 25000] } df = pd.DataFrame(data) print(df)输出:
姓名 年龄 城市 薪资 0 张三 25 北京 15000 1 李四 30 上海 20000 2 王五 28 广州 18000 3 赵六 35 深圳 25000左边那列0、1、2、3是自动生成的行索引。列的顺序默认按字典键的插入顺序,Python 3.7之后字典有序,所以列顺序是稳定的。
DataFrame的shape属性返回(行数, 列数),columns返回列名索引,dtypes返回每列的数据类型。这三个属性是我拿到任何新数据后最先看的——先知道数据多大、有哪些列、每列什么类型,再决定下一步怎么处理。
2.3 从外部文件创建DataFrame
实际工作中,数据很少是手敲进去的,大部分时候是从Excel或CSV读进来的。
读CSV:
df = pd.read_csv('data.csv', encoding='utf-8')读Excel:
df = pd.read_excel('data.xlsx', sheet_name='Sheet1')这两个函数参数很多,但新手只需要关注几个关键的:
| 参数 | 作用 | 常用值 |
|---|---|---|
| encoding | 文件编码 | 'utf-8'、'gbk' |
| header | 表头行号 | 0(默认第一行)、None(无表头) |
| names | 自定义列名 | 列表 |
| usecols | 只读某些列 | 列名列表或索引列表 |
| nrows | 只读前n行 | 整数 |
中文CSV文件经常遇到编码问题。Windows上Excel导出的CSV默认是gbk编码,用utf-8读会报错。我的经验是:先试utf-8,报错就换gbk,再不行用chardet库检测编码。
实操心得:读大文件时先用
nrows=100读前100行看看结构,确认列名和数据类型没问题再全量读。直接读几个G的文件容易把内存撑爆。
3. 数据查看与索引操作
3.1 快速了解数据全貌
拿到DataFrame后,别急着分析,先花30秒把数据摸清楚。我习惯按这个顺序来:
df.head() # 看前5行 df.tail() # 看后5行 df.info() # 看每列类型和非空值数量 df.describe() # 看数值列的统计摘要 df.shape # 看行列数info()特别有用,它会告诉你每列有多少非空值。如果某列非空值数量明显少于总行数,说明有缺失值,后面要处理。
describe()默认只统计数值列,输出count、mean、std、min、25%、50%、75%、max。如果想让类别列也参与统计,加参数include='all'。
3.2 列操作:选取、新增、删除
选一列:
df['姓名'] # 返回Series df[['姓名', '年龄']] # 返回DataFrame注意单方括号和双方括号的区别:单方括号选一列返回Series,双方括号选多列返回DataFrame。这个区别在后续操作中很重要,因为Series和DataFrame支持的方法不完全一样。
新增一列:
df['年薪'] = df['薪资'] * 12删除列:
df.drop('年薪', axis=1, inplace=True)axis=1表示操作列,axis=0表示操作行。inplace=True表示直接在原DataFrame上修改,不返回新对象。我个人的习惯是不用inplace,而是重新赋值:df = df.drop('年薪', axis=1)。这样代码更清晰,也避免链式操作时的警告。
3.3 行操作:loc和iloc
这是Pandas新手最容易混淆的地方。记住一句话:loc用标签,iloc用位置。
df.loc[0] # 取索引为0的行 df.iloc[0] # 取第1行(位置0) df.loc[0:2] # 取索引0到2的行(包含2) df.iloc[0:2] # 取位置0到1的行(不包含2)loc的切片是闭区间,iloc的切片是左闭右开——这和Python列表切片一致,但和loc不一致,容易搞混。
同时选行列:
df.loc[0:2, ['姓名', '年龄']] # 前3行的姓名和年龄列 df.iloc[0:2, 0:2] # 前2行前2列常见坑:如果DataFrame的索引不是默认整数索引,比如是字符串索引,那
df[0]会报错,必须用df.loc['标签']或df.iloc[0]。我建议养成习惯:取行一律用loc或iloc,不用方括号。
3.4 布尔索引:按条件筛选
这是Pandas最强大的功能之一。比如筛选年龄大于28的人:
df[df['年龄'] > 28]多个条件用&(与)、|(或),每个条件必须用括号包起来:
df[(df['年龄'] > 28) & (df['城市'] == '北京')]为什么必须加括号?因为Python中&的优先级比>高,不加括号会先算&再算比较,结果完全不对。这个坑我踩过不止一次。
筛选某列值在某个列表中:
df[df['城市'].isin(['北京', '上海'])]筛选某列值不在列表中,前面加~:
df[~df['城市'].isin(['北京', '上海'])]3.5 设置和重置索引
有时候需要把某一列设为索引,比如用日期做索引方便按时间筛选:
df.set_index('日期', inplace=True)想恢复默认整数索引:
df.reset_index(inplace=True)如果原来的索引有名字,reset后会变成一列。如果不想保留原索引,加drop=True。
4. 数据类型转换与缺失值处理
4.1 查看和转换数据类型
df.dtypes能看到每列的类型。常见类型有int64、float64、object(通常是字符串)、datetime64、bool。
转换类型用astype():
df['年龄'] = df['年龄'].astype('int32') df['薪资'] = df['薪资'].astype('float64')字符串转数字要小心。如果字符串里有非数字字符,astype会报错。这时候用pd.to_numeric(),加参数errors='coerce'把无法转换的变成NaN:
df['薪资'] = pd.to_numeric(df['薪资'], errors='coerce')日期转换用pd.to_datetime():
df['日期'] = pd.to_datetime(df['日期'], format='%Y-%m-%d')不指定format也行,Pandas会自动推断,但数据量大时指定format能快很多。
4.2 缺失值检测与处理
检测缺失值:
df.isnull() # 返回布尔DataFrame df.isnull().sum() # 每列缺失值数量 df.isnull().sum().sum() # 总缺失值数量处理缺失值有几种策略:
删除缺失值:
df.dropna() # 删除任何有缺失值的行 df.dropna(subset=['薪资']) # 只删除薪资列有缺失的行 df.dropna(how='all') # 只删除全为空的行填充缺失值:
df.fillna(0) # 用0填充 df['薪资'].fillna(df['薪资'].mean()) # 用均值填充 df['城市'].fillna('未知') # 用固定值填充向前填充和向后填充:
df.fillna(method='ffill') # 用前一个值填充 df.fillna(method='bfill') # 用后一个值填充实操心得:填充缺失值没有万能方案。数值列用均值或中位数,类别列用众数或“未知”,时间序列用前向填充。关键是填充后要检查分布有没有被扭曲。我一般会对比填充前后
describe()的结果,如果均值变化超过5%,就要重新考虑填充策略。
4.3 重复值处理
检测重复行:
df.duplicated() # 返回布尔Series df.duplicated().sum() # 重复行数量删除重复行:
df.drop_duplicates() # 完全重复的行 df.drop_duplicates(subset=['姓名']) # 姓名列重复的行 df.drop_duplicates(subset=['姓名'], keep='last') # 保留最后一条keep参数默认是'first',保留第一条;'last'保留最后一条;False则全部删除。
5. 数据排序、分组与聚合
5.1 排序
按某一列排序:
df.sort_values('薪资', ascending=False)按多列排序:
df.sort_values(['城市', '薪资'], ascending=[True, False])按索引排序:
df.sort_index()排序后索引会跟着数据走,如果想让索引重新从0开始,加ignore_index=True。
5.2 分组聚合
这是Pandas最核心的分析功能。按城市分组算平均薪资:
df.groupby('城市')['薪资'].mean()按城市分组算多个统计量:
df.groupby('城市')['薪资'].agg(['mean', 'max', 'min', 'count'])对不同列做不同聚合:
df.groupby('城市').agg({ '薪资': ['mean', 'max'], '年龄': 'mean' })groupby的结果是一个GroupBy对象,不直接显示数据,需要接一个聚合函数才会出结果。常用的聚合函数有:mean、sum、count、max、min、std、median、nunique。
5.3 数据透视表
Pandas的pivot_table和Excel的数据透视表功能类似:
pd.pivot_table(df, values='薪资', index='城市', columns='性别', aggfunc='mean')这个功能在交叉分析时特别有用,比如想看不同城市不同性别的平均薪资,一行代码就出来了。
6. 文件读写与导出
6.1 读Excel的常见问题
pd.read_excel()依赖openpyxl库(读xlsx)或xlrd库(读xls)。如果报错说缺少引擎,装一下就行:
pip install openpyxl读多个sheet:
xls = pd.ExcelFile('data.xlsx') print(xls.sheet_names) # 查看所有sheet名 df = pd.read_excel(xls, sheet_name='Sheet2')6.2 写Excel和CSV
导出CSV:
df.to_csv('output.csv', index=False, encoding='utf-8-sig')index=False表示不写行索引。encoding='utf-8-sig'带BOM头,用Excel打开不会乱码。如果对方用WPS或老版本Excel,用gbk编码更稳。
导出Excel:
df.to_excel('output.xlsx', index=False, sheet_name='结果')导出多个sheet:
with pd.ExcelWriter('output.xlsx') as writer: df1.to_excel(writer, sheet_name='Sheet1', index=False) df2.to_excel(writer, sheet_name='Sheet2', index=False)注意:写Excel时如果文件被其他程序占用,会报PermissionError。确保目标文件没有被Excel打开。
7. 常见问题与排查技巧
7.1 SettingWithCopyWarning警告
这个警告几乎每个Pandas用户都遇到过。原因是Pandas无法确定你是在操作原DataFrame的视图还是副本。比如:
df[df['年龄'] > 28]['薪资'] = 30000 # 会触发警告,且可能不生效正确做法是用loc:
df.loc[df['年龄'] > 28, '薪资'] = 30000或者先复制一份:
subset = df[df['年龄'] > 28].copy() subset['薪资'] = 300007.2 中文列名和路径问题
中文列名本身没问题,但在某些操作中容易因为编码问题出错。我的建议是:如果数据要长期维护,列名用英文;如果只是临时分析,中文列名更方便阅读。
文件路径含中文时,Windows上一般没问题,Linux上要注意编码。用pathlib处理路径更稳:
from pathlib import Path file_path = Path('数据') / '销售数据.xlsx' df = pd.read_excel(file_path)7.3 内存不足
处理大文件时内存不够,可以分块读:
chunks = pd.read_csv('big_file.csv', chunksize=100000) for chunk in chunks: process(chunk)或者只读需要的列:
df = pd.read_csv('big_file.csv', usecols=['姓名', '薪资'])还可以指定更小的数据类型:
df = pd.read_csv('big_file.csv', dtype={'年龄': 'int32', '薪资': 'float32'})7.4 常见问题速查表
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 读CSV乱码 | 编码不匹配 | 换encoding参数,试utf-8、gbk、utf-8-sig |
| 读Excel报错 | 缺少引擎 | pip install openpyxl |
| 筛选结果为空 | 条件类型不匹配 | 检查列类型,字符串和数字不能直接比较 |
| 分组后列消失 | 分组列变成了索引 | 加as_index=False或reset_index() |
| 排序后索引乱 | 索引跟着数据走 | 加ignore_index=True |
| 填充缺失值不生效 | 用了inplace但没接收返回值 | 重新赋值或加inplace=True |
8. 我踩过的坑和总结的技巧
第一个坑是链式索引。刚开始用Pandas时,我总喜欢写df[df['A'] > 1]['B'] = 2这种链式操作,结果要么报警告,要么改了没效果。后来养成习惯:只要涉及修改,一律用df.loc[条件, 列名] = 值。
第二个坑是数据类型。从Excel读进来的数字列,如果某一行有个“暂无”之类的文本,整列会变成object类型。这时候做数值计算就会报错。我的做法是:读完数据先df.info()看一眼,发现类型不对就用pd.to_numeric(errors='coerce')转,把无法转换的变成NaN再处理。
第三个坑是索引对齐。两个DataFrame做运算时,Pandas会自动按索引对齐,索引不匹配的位置会变成NaN。这个特性有时候很有用,但如果不注意就会得到一堆NaN。解决办法是运算前先reset_index()或者确认两边索引一致。
第四个坑是inplace参数。早期版本很多函数支持inplace=True,但新版本逐渐在弱化这个参数。我现在基本不用inplace,而是重新赋值,代码更清晰,也不容易出错。
最后分享一个提高效率的技巧:把常用的数据探查代码封装成一个函数,每次拿到新数据先跑一遍。比如:
def explore(df): print(f"形状: {df.shape}") print(f"列名: {df.columns.tolist()}") print(f"类型:\n{df.dtypes}") print(f"缺失值:\n{df.isnull().sum()}") print(f"重复行: {df.duplicated().sum()}") print(df.head())这个函数帮我省了大量重复敲代码的时间,也避免了遗漏检查项。Pandas的内容很多,但核心就是这些:数据结构、索引、筛选、类型转换、缺失值、分组聚合、文件读写。把这些练熟,日常数据分析任务基本都能应付。后面再学合并、时间序列、窗口函数这些进阶内容,就有了扎实的基础。