Pandas 数据处理入门
Pandas 是 Python 数据分析的瑞士军刀——加载、清洗、变换、分析结构化数据,一个库全搞定。它是数据科学家和机器学习工程师每天使用频率最高的工具之一。前置阅读:数据预处理、NumPy 科学计算入门。
这个库是什么
Section titled “这个库是什么”Pandas 的名字来自 “Panel Data”(面板数据,即沿多个维度观测的数据集),由 Wes McKinney 于 2008 年在金融数据分析工作中创建。它提供了两个核心数据结构——Series(一维带标签数组)和 DataFrame(二维表格),让你能用类似 SQL 或 Excel 的方式在 Python 中操作数据,但灵活性和自动化程度远超两者。
如果说 NumPy 是”纯数学的数组引擎”,那 Pandas 就是”带标签的表格引擎”——每一列有名字、每一行有索引,你可以按列名或条件直接筛选,而非用脆弱的数字下标。它是连接原始数据和机器学习模型之间的桥梁:真实世界的数据又脏又乱,Pandas 负责把它们洗刷整理成模型能吃的干净格式。
版本演进:从 1.x 到 2.x 再到 3.0
Section titled “版本演进:从 1.x 到 2.x 再到 3.0”Pandas 2.0(2023 年发布)是一次重大架构升级,底层引入了对 Apache Arrow(一种跨语言的列式内存格式)的原生支持,大幅提升了字符串处理和 I/O 性能。进入 2025 年,Pandas 正在向 3.0 迈进,最重要的变化包括:
- Copy-on-Write(CoW,写时复制)成为默认行为:以前 Pandas 的很多操作会返回”视图”或”副本”,行为不一致导致
SettingWithCopyWarning频繁出现。CoW 统一了语义——只有当你真正修改数据时才复制,否则共享底层内存,既消除了警告又减少了内存浪费。 - PyArrow 字符串后端:从 2.0 起可用
dtype="string[pyarrow]",3.0 有望将其设为默认,使得大规模文本列的内存占用降低 50%–80%。 - 移除大量已弃用 API:1.x 时代的
DataFrame.append、Series.iteritems等历史包袱被彻底清理。
小贴士:即使你当前用的是 2.x,只要在脚本开头加上
pd.set_option("mode.copy_on_write", True),就能提前启用 CoW 行为,让代码对未来 3.0 平滑过渡。
安装与环境配置
Section titled “安装与环境配置”# 安装 Pandas(通常会自动装上 NumPy 作为依赖)pip install pandas
# 推荐同时安装数据读取和可视化相关的库pip install pandas openpyxl matplotlib pyarrow
# 验证安装python -c "import pandas as pd; print(pd.__version__)"Pandas 读取 Excel 文件需要额外引擎:.xlsx 文件需要 openpyxl,旧版 .xls 需要 xlrd。pyarrow 是可选但强烈推荐的后端库——2.x 以后的 read_csv 可以用 engine="pyarrow" 获得数倍的读取速度。导入约定写为 import pandas as pd。
Series 与 DataFrame
Section titled “Series 与 DataFrame”Series 是一维带标签的数组——你可以把它想象成一列 Excel 数据,每个单元格都有一个行标签(索引)。DataFrame 是二维表格,由多个 Series 组成,每一列可以是不同的数据类型(整数列、字符串列、日期列共存)。
类比理解:Series 是一叠名片,每张名片有名字和内容;DataFrame 是一个名片盒,按类别分了好多格,每格里放一类名片。
从底层看,DataFrame 的每一列本质上是一个 NumPy ndarray(或 PyArrow ChunkedArray)。这意味着:
对列的操作(如 df['薪资'] * 1.1)底层调用的是 NumPy 的矢量化运算(SIMD(Single Instruction Multiple Data,单指令多数据流,CPU 一条指令同时处理多个数据)),因此远比 Python 原生循环快。
索引(Index)
Section titled “索引(Index)”Pandas 的每个 Series 和 DataFrame 都有一个行索引。索引不只是一排数字——它可以是日期、字符串、甚至多级层次结构(MultiIndex)。善用索引能让数据对齐变得极其自然:两个 DataFrame 相加时,Pandas 会自动按索引对齐,而非按位置。
形式化地说,若 DataFrame 有索引 ,DataFrame 有索引 ,则 的结果是索引为 的表,其中 和 行因缺少对应数据而被填为 NaN(Not a Number,缺失值标记)。这种”自动对齐 + 缺失填充”的语义是 Pandas 区别于 NumPy 的核心设计。
分组聚合(GroupBy)
Section titled “分组聚合(GroupBy)”SQL 里的 GROUP BY ... SUM() 在 Pandas 中就是 df.groupby('列名').sum()。思路一致:先按某列的值将数据分组,再对每组施加聚合函数(求和、均值、计数等)。这是数据分析中最常用的操作模式,被称为 split-apply-combine(分组-应用-合并)范式。
其数学本质可以表述为:给定分组映射 和聚合函数 ,输出为:
架构与工作流
Section titled “架构与工作流”例 1:创建 DataFrame 与数据加载
Section titled “例 1:创建 DataFrame 与数据加载”import pandas as pd
# 从字典创建 DataFrame(最直观的方式)data = { '姓名': ['张三', '李四', '王五', '赵六', '钱七'], '年龄': [25, 30, 35, 28, None], # None 表示缺失值 '城市': ['北京', '上海', '北京', '广州', '上海'], '薪资': [15000, 25000, 30000, 18000, 22000]}df = pd.DataFrame(data)print(df.info()) # 查看列名、数据类型、非空数量print(df.describe()) # 数值列的统计摘要(均值、标准差等)
# 从 CSV 文件加载(实际项目中最常见的入口)# Pandas 2.x 起可用 pyarrow 引擎,大文件读取速度提升数倍:# df = pd.read_csv('sales.csv', engine='pyarrow', dtype_backend='pyarrow')# 从 JSON 加载:pd.read_json('data.json')describe() 输出的统计量背后都是经典公式。例如 标准差(衡量数据分散程度)的计算为:
其中 是均值, 是样本数。Pandas 默认计算的是样本标准差(分母用 ,又称 Bessel 校正,即用 代替 来补偿样本对总体方差的低估)。
例 2:数据清洗——缺失值与重复值
Section titled “例 2:数据清洗——缺失值与重复值”import pandas as pdimport numpy as np
df = pd.DataFrame({ '姓名': ['张三', '李四', '王五', '张三'], # 有重复行 '年龄': [25, None, 35, 25], '薪资': [15000, 25000, None, 15000]})
# 处理缺失值print(df.isnull().sum()) # 每列缺失值计数
# 策略一:均值/中位数填充——适合数值型且分布近似对称的列df_filled = df.fillna({'年龄': df['年龄'].mean(), # 年龄用均值填充 '薪资': df['薪资'].median()}) # 薪资用中位数填充(对异常值更稳健)# 策略二:直接删除含缺失的行——适合缺失比例很低的情况df_dropped = df.dropna()
# 处理重复值print(df.duplicated()) # 标记重复行df_clean = df.drop_duplicates() # 删除完全重复的行print(f"清洗前 {len(df)} 行 → 清洗后 {len(df_clean)} 行")为什么用中位数而非均值填充? 均值 对异常值(outlier,即偏离大多数数据的极端值)非常敏感——一个薪资 500000 的人会把整个均值拉高。中位数(排序后位于中间的值)则不受极端值影响,是更”稳健”(robust,即对异常值不敏感)的集中趋势度量。
例 3:分组聚合——数据分析的核心操作
Section titled “例 3:分组聚合——数据分析的核心操作”import pandas as pd
df = pd.DataFrame({ '部门': ['技术', '市场', '技术', '市场', '技术', '财务'], '姓名': ['张三', '李四', '王五', '赵六', '钱七', '孙八'], '薪资': [15000, 25000, 30000, 18000, 22000, 20000], '工龄': [2, 5, 8, 3, 4, 6]})
# 按部门分组,计算平均薪资和总工龄summary = df.groupby('部门').agg({ '薪资': ['mean', 'max', 'min'], '工龄': 'sum'})print(summary)
# 更灵活的分组分析top_dept = df.groupby('部门')['薪资'].mean().idxmax() # 平均薪资最高的部门print(f"平均薪资最高的部门: {top_dept}")
# 多列分组:部门 + 工龄段df['工龄段'] = pd.cut(df['工龄'], bins=[0, 3, 6, 10], labels=['新手', '中级', '资深'])print(df.groupby(['部门', '工龄段'], observed=True)['薪资'].mean())注意
observed=True:Pandas 2.x 起,对category类型列做 groupby 时默认会输出所有可能的组合(包括计数为 0 的空组)。传入observed=True只输出实际存在的组合,结果更干净、运行更快。
例 4:合并与时间序列基础
Section titled “例 4:合并与时间序列基础”import pandas as pd
# merge:类似 SQL JOIN,按共同列拼接两个表employees = pd.DataFrame({ '员工ID': [1, 2, 3], '姓名': ['张三', '李四', '王五'], '部门ID': [10, 20, 10]})departments = pd.DataFrame({ '部门ID': [10, 20, 30], '部门名': ['技术部', '市场部', '财务部']})merged = pd.merge(employees, departments, on='部门ID', how='left') # 左连接print(merged)
# 时间序列:Pandas 的强项dates = pd.date_range('2024-01-01', periods=6, freq='D') # 6天的日期序列sales = pd.DataFrame({ '日期': dates, '销售额': [100, 150, 120, 200, 180, 250]}).set_index('日期') # 以日期为索引
print(sales.rolling(window=3).mean()) # 3天移动平均print(sales.resample('2D').sum()) # 每2天重采样求和移动平均(moving average)的数学定义为:
其中 是窗口大小。它本质是一个低通滤波器(low-pass filter,即只保留低频趋势、平滑掉高频波动的数学操作),能滤除数据的短期波动、揭示长期趋势,在股票分析和传感器数据处理中极为常用。
例 5:矢量化运算——为什么不要写 for 循环
Section titled “例 5:矢量化运算——为什么不要写 for 循环”import pandas as pdimport numpy as npimport time
# 模拟 100 万行数据n = 1_000_000df = pd.DataFrame({'value': np.random.randn(n)})
# ❌ 错误示范:用 iterrows 逐行遍历(极慢)start = time.time()result_slow = []for _, row in df.iterrows(): result_slow.append(row['value'] ** 2 + 1)print(f"iterrows 耗时: {time.time() - start:.3f}s")
# ✅ 正确做法:矢量化运算(底层是 C/NumPy,快 100-1000 倍)start = time.time()result_fast = df['value'] ** 2 + 1print(f"矢量化耗时: {time.time() - start:.3f}s")这个差距的根源在于:Python 是解释型语言,每次循环都要做类型检查和动态分发;而 NumPy/Pandas 的矢量化操作底层是编译好的 C 代码,配合 CPU 的 SIMD 指令(一条指令同时处理 4-16 个浮点数),速度天差地别。经验法则:在 Pandas 中,能用矢量化就绝不写 for 循环。
常用 API 速查
Section titled “常用 API 速查”| API | 用途 | 示例 |
|---|---|---|
pd.read_csv(path) | 读取 CSV 文件 | pd.read_csv('data.csv') |
pd.read_json(path) | 读取 JSON 文件 | pd.read_json('data.json') |
pd.DataFrame(dict) | 从字典创建 | pd.DataFrame({'a':[1,2]}) |
df.head(n) | 查看前 n 行 | df.head(10) |
df.info() | 概览结构与类型 | df.info() |
df.describe() | 数值列统计摘要 | df.describe() |
df.shape | 行数与列数 | df.shape |
df['列名'] | 选取单列 | df['薪资'] |
df.loc[行, 列] | 按标签选取 | df.loc[0:2, '姓名'] |
df.iloc[行号, 列号] | 按位置选取 | df.iloc[0:3, 1:3] |
df[df['列'] > 值] | 条件筛选 | df[df['薪资'] > 20000] |
df.fillna(值) | 填充缺失值 | df.fillna(0) |
df.dropna() | 删除缺失行 | df.dropna() |
df.drop_duplicates() | 删除重复行 | df.drop_duplicates() |
df.groupby('列') | 分组聚合 | df.groupby('部门').mean() |
df.merge(other) | 表连接(推荐用法) | df.merge(other, on='id') |
df.map(func) | 逐元素映射(2.1+) | df['列'].map(lambda x: x*2) |
df.to_csv(path) | 导出 CSV | df.to_csv('out.csv') |
- 链式调用提升可读性:Pandas 大多数方法返回新 DataFrame,可以串起来写。
df.dropna().groupby('部门')['薪资'].mean().sort_values(ascending=False)一行完成”清洗→分组→排序”。 - loc 与 iloc 的区别:
loc按标签(索引名、列名)选取,切片是闭区间;iloc按位置(整数)选取,切片是左闭右开。混用是新手最常犯的错误。 - Copy-on-Write 时代:在 Pandas 2.2+ 中启用 CoW 后,
SettingWithCopyWarning将彻底消失。在此之前,修改数据请始终用df.loc[条件, '列'] = 值,而非df[条件]['列'] = 值。 - 大数据考虑内存:Pandas 全部数据加载到内存。百万行级别的数据读 CSV 时,指定
dtype参数(如dtype={'id': 'int32'})或使用dtype_backend='pyarrow'可减少 50% 以上内存占用。超大文件考虑chunksize分块读取。 - 分类类型节省内存:重复字符串很多的列(如性别、城市)转为
category类型,可大幅节省内存并加速 groupby。内存占用从 降为 ,其中 是类别数,通常 。 - 用
map替代applymap:Pandas 2.1 起统一了逐元素变换 API——df.map(func)同时适用于 DataFrame 和 Series,旧的applymap已弃用。 - 时间序列优先用日期索引:设为索引后,切片、重采样、移动平均等操作极其方便,这是 Pandas 相比通用数据库的独特优势。详见时间序列分析。
典型应用场景
Section titled “典型应用场景”- 数据清洗与特征工程:机器学习项目 60% 的时间花在这里。缺失值处理、类别编码(One-Hot(独热编码,将 个类别展开为 个 0/1 列)、Target Encoding(目标编码,用每个类别对应的目标变量均值替代原始类别标签))、特征衍生,Pandas 是主力工具。详见特征工程。
- 探索性数据分析(EDA,Exploratory Data Analysis):拿到一份数据,先用
df.describe()看分布、用groupby找规律、用value_counts()看类别平衡——这一套流程是数据科学家的肌肉记忆。 - 数据 ETL 管道(Extract-Transform-Load,即”抽取-转换-加载”的数据流水线):从数据库 / API / 文件读取,经过清洗变换,输出为模型可用的格式。配合 Matplotlib 可快速生成分析报告。
- 模型评估结果分析:把预测结果和真实值放进 DataFrame,按分组分析误差分布,定位模型薄弱环节。详见模型评估。
- 金融与商业数据分析:Pandas 诞生于金融行业,对时间序列、滚动窗口、重采样有原生且强大的支持,是量化分析的标配。
与同类工具对比
Section titled “与同类工具对比”| 特性 | Pandas | Polars | SQL (PostgreSQL) | Dask / Spark |
|---|---|---|---|---|
| 定位 | 内存表格分析 | 高性能表格分析 | 关系数据库查询 | 分布式大数据 |
| 数据规模 | 单机内存(GB 级) | 单机内存(GB 级,更快) | 磁盘级(TB 级) | 分布式(TB 级) |
| 执行方式 | 逐行(部分矢量化) | 全矢量化(Rust 底层) | 声明式查询引擎 | 并行调度 |
| API 风格 | 命令式链式调用 | 类似 Pandas(惰性) | 声明式 SQL 语法 | 模仿 Pandas |
| 延迟模式 | 及早求值 | 惰性求值(查询优化) | 惰性 | 惰性 |
| 学习曲线 | 中等(API 较多) | 低(会 Pandas 即可) | 中等(需学 SQL) | 低(Pandas 超集) |
| 生态成熟度 | 极高(事实标准) | 快速增长 | 极高 | 中等 |
选择建议:日常分析首选 Pandas(生态最成熟);数据量大到 Pandas 吃力时,Polars 是最顺滑的升级路径(API 相似但快数倍);超大规模数据上 Spark / Dask。
2025 年趋势:DataFrame Interchange Protocol。Python 数据科学生态正在推进一个跨库的”数据帧交换协议”(
__dataframe__),让 Pandas、Polars、PyArrow、Dask 之间零拷贝地传递数据。这意味着你可以用 Pandas 做原型开发,再把同一个 DataFrame 无缝交给 Polars 跑高性能计算——工具之间的迁移成本正在趋近于零。
- Pandas 官方文档:pandas.pydata.org/docs — 包含 Cookbook、User Guide 和完整 API 参考,遇到问题第一站。
- Wes McKinney,「Python for Data Analysis」(第 3 版):Pandas 作者亲笔,O’Reilly 出版,是学习 Pandas 最权威的书籍,覆盖从入门到高级技巧。
- Polars 官方文档:pola.rs — Pandas 的高性能替代品,Rust 实现,API 风格相似但速度快数倍,值得关注的下一代工具。
- Matt Harrison,「Effective Pandas」(2022) 与「Effective Pandas」系列更新:聚焦实战技巧的进阶书,教你写出地道的、链式调用的 Pandas 代码。作者在 2024–2025 年持续更新,覆盖 CoW 和 PyArrow 后端的内容。
- Pandas 3.0 迁移指南:pandas dev blog — 官方持续更新的版本日志,追踪 CoW、PyArrow 默认后端等重大变更的最佳参考。
- Apache Arrow 官方文档:arrow.apache.org — Pandas 2.x/3.0 的底层列式格式,理解它有助于掌握 PyArrow 后端为何更快更省内存。