Pandas核心技能实战指南:从数据读写到分组聚合的完整工作流

📅 2026/7/30 10:09:14 👁️ 阅读次数 📝 编程学习
Pandas核心技能实战指南:从数据读写到分组聚合的完整工作流

1. 项目概述:为什么你需要这份“纯干货”Pandas指南

如果你正在用Python处理数据,无论是从Excel里倒腾销售报表,还是分析网站的用户日志,Pandas这个名字你肯定绕不过去。它几乎是Python数据分析领域的“普通话”,不会它,很多活儿你根本没法干。但很多教程要么上来就讲一堆抽象的概念,要么就是例子太简单,看完感觉懂了,一上手还是抓瞎。这份教程的目标很明确:让你用最短的时间,掌握Pandas最核心、最常用的技能,直接能上手解决工作中的实际问题。我们不谈虚的,只讲你马上就能用的东西。

这份指南适合谁?如果你是刚接触数据分析的Python新手,或者之前只用过Excel,想用代码提升效率,那这份教程就是为你准备的。即使你有点基础,但总记不住某些函数的参数怎么用,或者想系统地理清Pandas的操作逻辑,这里也能找到答案。我们的核心思路是“任务驱动”,不是按函数手册来教,而是围绕“你想对数据做什么”来展开。比如,你想“读取数据”、“筛选行”、“计算新列”、“分组统计”,我们就针对这些具体动作,告诉你用哪个函数、怎么写、要注意什么坑。这样学起来目标明确,印象也深刻。

2. 核心设计:理解Pandas的“两层世界观”

在动手写代码之前,你得先理解Pandas看待数据的两个基本容器:SeriesDataFrame。这就像盖房子前得知道砖头和墙的区别。

Series你可以把它想象成Excel表格里单独的一列。它有两个核心部分:索引(index)值(values)。索引默认是0, 1, 2, 3...这样的数字,但也可以是日期、字符串等,用来标记每一个值。创建一个Series非常简单:

import pandas as pd # 从列表创建 s1 = pd.Series([10, 20, 30, 40]) print(s1) # 输出: # 0 10 # 1 20 # 2 30 # 3 40 # dtype: int64 # 指定自定义索引 s2 = pd.Series([100, 200, 300], index=['a', 'b', 'c']) print(s2['b']) # 输出:200

DataFrame这就是我们最常用的、完整的表格了。它由多个Series组成,每个Series成为一列,它们共享同一个行索引。你可以把它看作一个二维的、带标签的数据结构,有行有列。理解DataFrame的构成是高效操作的关键。

# 从字典创建DataFrame,字典的键会成为列名 data = { '姓名': ['张三', '李四', '王五'], '年龄': [25, 30, 35], '城市': ['北京', '上海', '广州'] } df = pd.DataFrame(data) print(df)

输出结果就是一个规整的表格:

姓名 年龄 城市 0 张三 25 北京 1 李四 30 上海 2 王五 35 广州

注意:很多新手会混淆“索引”和“列名”。行索引(最左边那列0,1,2)是用于定位行的标签,而“姓名”、“年龄”这些是列名,用于定位列。操作数据时,搞清楚你要动的是行还是列,用的是索引还是列名,能避免很多错误。

2.1 为什么是“两层”结构?

这种设计让操作非常灵活。你可以对整个DataFrame进行操作(如描述性统计),也可以轻松提取某一列(得到一个Series)进行单独处理(如计算平均值)。这种从整体到局部、再从局部组合回整体的能力,是Pandas强大之处。

3. 数据读写:把数据“搬进来”和“送出去”

数据处理的第一步和最后一步都离不开读写。Pandas支持非常多的格式,我们聚焦最常用的几种。

3.1 读取数据:pd.read_*函数家族

最常用的是读取CSV和Excel文件。

读取CSV文件:

df = pd.read_csv('sales_data.csv')

read_csv参数巨多,但掌握几个关键的就够用:

  • encoding: 遇到中文乱码时尝试encoding='gbk''utf-8'
  • header: 指定哪一行作为列名,默认header=0(第一行)。如果文件没有列名,设置header=None
  • index_col: 指定哪一列作为行索引。
  • usecols: 只读取指定的列,例如usecols=[0, 2, 4]usecols=['列名A', '列名C'],对于列很多的大文件非常有用,能节省内存和时间。

读取Excel文件:需要安装openpyxl(用于.xlsx)或xlrd(用于旧版.xls)库。

df = pd.read_excel('财务报告.xlsx', sheet_name='Sheet1') # 指定工作表

实操心得:读取大型文件(几百MB以上)时,如果内存吃紧,可以考虑两个方法:1) 使用usecolsnrows(读取前n行)参数先读一部分看看;2) 使用chunksize参数分块读取,用于迭代处理。例如for chunk in pd.read_csv('huge.csv', chunksize=100000):

3.2 保存数据:to_*方法

处理完的数据要保存,同样简单。

df.to_csv('processed_data.csv', index=False) # 保存为CSV,index=False表示不保存行索引列 df.to_excel('结果.xlsx', sheet_name='汇总') # 保存为Excel

这里的关键参数是index。默认情况下,Pandas会把行索引也作为一列写入文件。如果你不希望这样(通常我们都不希望),务必加上index=False

4. 数据查看与清洗:先看清,再动手

数据读进来后,别急着分析,先“体检”。

4.1 快速了解你的数据

df.head() # 查看前5行 df.tail(3) # 查看后3行 df.shape # 查看维度(行数, 列数) df.info() # 查看列的数据类型、非空值数量,内存占用 df.describe() # 对数值列进行快速统计摘要(计数、均值、标准差、最小值、四分位数、最大值)

df.info()特别有用,它能一眼告诉你哪列有缺失值(Non-Null Count小于总行数),以及每列的数据类型,这是后续清洗的基础。

4.2 处理缺失值:数据中的“空洞”

真实数据常有缺失,Pandas中用NaN表示。处理方式无非三种:删除、填充、保留。

# 1. 删除 df_drop_rows = df.dropna() # 删除任何包含NaN的行 df_drop_cols = df.dropna(axis=1) # 删除任何包含NaN的列(慎用!) df_drop_subset = df.dropna(subset=['年龄', '收入']) # 仅在‘年龄’和‘收入’列同时为NaN时才删除该行 # 2. 填充 df_fill_zero = df.fillna(0) # 用0填充所有NaN df_fill_mean = df['年龄'].fillna(df['年龄'].mean()) # 用该列平均值填充 df_fill_ffill = df.fillna(method='ffill') # 用前一个有效值向前填充

注意事项:盲目删除可能会损失大量数据。填充时,用均值填充数值列是常见做法,但对于分类数据,可能需要用众数(df['列名'].mode()[0])填充。ffill(前向填充)在时间序列数据中很常用。

4.3 处理重复值

df.duplicated() # 检查重复行,返回布尔序列 df.drop_duplicates() # 删除重复行 df.drop_duplicates(subset=['姓名'], keep='last') # 基于‘姓名’列去重,保留最后一条

4.4 数据类型转换

df.info()显示的类型不对时就需要转换。

df['日期列'] = pd.to_datetime(df['日期列']) # 转为日期时间类型 df['金额列'] = df['金额列'].astype('float') # 转为浮点数 df['类别列'] = df['类别列'].astype('category') # 转为分类类型,节省内存,提高速度

5. 数据选择与过滤:精准定位你要的数据

这是Pandas操作中最频繁的部分,方法多样,核心是理解“标签索引”和“位置索引”。

5.1 选择列

最简单,直接用列名。

single_col = df['姓名'] # 返回一个Series multi_cols = df[['姓名', '年龄']] # 返回一个只包含这两列的DataFrame(注意双层括号)

5.2 选择行

按标签索引选择(.loc:基于行/列的“名称”进行选择。

df.loc[0] # 选择索引为0的单行(返回Series) df.loc[[0, 2]] # 选择索引为0和2的行(返回DataFrame) df.loc[0:2] # 选择索引从0到2(包含2)的行(切片) df.loc[df['年龄'] > 25] # 布尔索引:选择年龄大于25的所有行(最常用!) df.loc[0, '姓名'] # 选择索引为0,列名为‘姓名’的单个值(标量)

按位置索引选择(.iloc:基于行/列的“整数位置”(从0开始)进行选择。

df.iloc[0] # 第0行 df.iloc[0:3] # 第0,1,2行(切片不包含结尾3) df.iloc[0, 1] # 第0行,第1列 df.iloc[:, 0:2] # 所有行,第0列和第1列

核心区别与避坑.loc的切片是包含结束值的([0:2]包含索引0,1,2),而.iloc的切片是不包含结束值的([0:2]包含位置0,1)。这是新手最容易混淆和出错的地方之一。记住:标签用.loc,位置用.iloc

5.3 布尔索引(条件过滤)

这是数据筛选的灵魂,通过条件表达式产生一个布尔序列(True/False),然后用它来索引数据。

# 单个条件 high_age = df[df['年龄'] > 30] # 多个条件(必须用 &(与)、|(或)、~(非),并且每个条件要用括号括起来) complex_filter = df[(df['城市'] == '北京') & (df['年龄'] < 40)] # 使用 isin() 方法筛选特定集合 city_filter = df[df['城市'].isin(['北京', '上海'])]

6. 数据处理与转换:让数据“变形”

6.1 新增或修改列

直接赋值即可,非常直观。

# 基于现有列计算新列 df['出生年份'] = 2023 - df['年龄'] # 使用 apply 方法进行更复杂的行或列运算 def classify_age(age): if age < 30: return '青年' elif age < 50: return '中年' else: return '老年' df['年龄段'] = df['年龄'].apply(classify_age) # 使用 lambda 表达式简化 df['年龄平方'] = df['年龄'].apply(lambda x: x**2)

6.2 重命名列

df.rename(columns={'旧名1': '新名1', '旧名2': '新名2'}, inplace=True) # inplace=True 表示直接修改原DataFrame,否则会返回一个新对象

6.3 排序

df.sort_values(by='年龄') # 按‘年龄’升序 df.sort_values(by=['城市', '年龄'], ascending=[True, False]) # 先按城市升序,同城市按年龄降序

6.4 字符串处理

对于文本列,通过.str访问器调用字符串方法。

df['姓名'] = df['姓名'].str.upper() # 转为大写 df['邮箱域名'] = df['邮箱'].str.split('@').str[1] # 提取邮箱域名 df['是否包含北京'] = df['地址'].str.contains('北京') # 检查是否包含子串

7. 数据分组与聚合:从细分到总结

groupby是Pandas进行数据分析的“王牌”功能,它遵循“拆分-应用-合并”的模式。

7.1 基础分组聚合

# 按‘城市’分组,并计算‘年龄’的平均值 grouped = df.groupby('城市')['年龄'].mean() print(grouped) # 输出: # 城市 # 北京 27.5 # 上海 32.0 # Name: 年龄, dtype: float64 # 同时进行多个聚合计算 agg_result = df.groupby('城市')['年龄'].agg(['mean', 'min', 'max', 'count']) print(agg_result)

7.2 多级分组与多列聚合

# 按‘城市’和‘年龄段’两级分组 multi_group = df.groupby(['城市', '年龄段']) # 对不同列应用不同的聚合函数 complex_agg = df.groupby('城市').agg({ '年龄': 'mean', '收入': ['sum', 'std'], # 对收入求和和求标准差 '姓名': 'count' # 计数,即每个城市的人数 })

实操心得groupby之后的对象,在应用aggmean等函数之前,它只是一个“分组对象”,并没有实际计算。只有当你调用聚合函数时,计算才会发生。这种“延迟计算”的设计对于大数据集很高效。你可以通过.groups属性查看分组详情。

8. 数据合并与连接:整合多张表

类似SQL的JOIN操作,Pandas主要用mergeconcat

8.1pd.merge:基于键连接

df1 = pd.DataFrame({'key': ['A', 'B', 'C'], 'value1': [1, 2, 3]}) df2 = pd.DataFrame({'key': ['B', 'C', 'D'], 'value2': [4, 5, 6]}) # 内连接(默认):只保留两个表都有的键 inner = pd.merge(df1, df2, on='key') # 左连接:以左表(df1)的键为准 left = pd.merge(df1, df2, on='key', how='left') # 外连接:保留所有键 outer = pd.merge(df1, df2, on='key', how='outer')

how参数是关键:inner,left,right,outer

8.2pd.concat:沿轴堆叠

用于简单地将多个DataFrame在行方向或列方向拼接。

# 行方向拼接(上下堆叠),axis=0是默认值 result_row = pd.concat([df1, df2], axis=0, ignore_index=True) # ignore_index重置索引 # 列方向拼接(左右合并) result_col = pd.concat([df1, df2], axis=1)

9. 时间序列处理

Pandas在处理时间数据上异常强大,核心是TimestampDatetimeIndex

9.1 解析时间列

df['日期'] = pd.to_datetime(df['日期字符串列']) df.set_index('日期', inplace=True) # 将日期设为索引,便于时间序列操作

9.2 重采样与频率转换

对于按时间索引的数据,resample是神器。

# 假设df已设置日期索引,且有一列‘销售额’ # 将每日数据降采样为月度数据,计算每月销售额总和 monthly_sales = df['销售额'].resample('M').sum() # 将每日数据上采样为每小时数据,并用前向填充 hourly_data = df.resample('H').ffill()

'M'表示月末,'MS'表示月初,'H'表示小时,'D'表示天。非常灵活。

10. 性能优化与常见问题排查

10.1 避免链式赋值

这是一个经典错误,会导致SettingWithCopyWarning

# 错误示范(可能不生效或产生警告) df[df['年龄']>30]['新列'] = 100 # 正确做法:使用 .loc 一次性完成筛选和赋值 df.loc[df['年龄']>30, '新列'] = 100

10.2 使用向量化操作代替循环

Pandas底层基于NumPy,向量化操作比Python循环快成百上千倍。

# 慢:循环 for i in range(len(df)): df.loc[i, '收益'] = df.loc[i, '价格'] * df.loc[i, '数量'] # 快:向量化 df['收益'] = df['价格'] * df['数量']

10.3 常见错误KeyErrorSettingWithCopyWarning

  • KeyError:通常是因为列名或索引名写错了。用df.columns打印所有列名仔细核对。
  • SettingWithCopyWarning:如上所述,通常是因为链式索引。坚持使用.loc.iloc进行单一步骤的赋值。

10.4 内存优化

对于大型数据集,数据类型很重要。

  • 将字符串列转换为category类型(如果唯一值不多)。
  • 将整数列转换为int32int16(如果数值范围允许)。
  • 使用df.memory_usage(deep=True)查看内存占用。

我个人在实际使用中的体会是,Pandas的熟练度完全取决于“肌肉记忆”。最开始你肯定需要查文档,但核心的df[列名]df.loc[]df.groupby()df.merge()这几个操作,反复用上几十次,自然就记住了。遇到复杂操作时,别急着写代码,先想清楚:我的目标是什么?输入数据是什么样子?输出应该是什么样子?把这个问题想清楚了,再去找对应的函数和方法,往往事半功倍。最后,善用df.head()df.shape在每一步操作后都检查一下中间结果,确保数据在按照你期望的方式变化,这是调试Pandas代码最有效的方法。