Skip to content

Pandas 数据处理入门

Pandas 是 Python 数据分析的瑞士军刀——加载、清洗、变换、分析结构化数据,一个库全搞定。它是数据科学家和机器学习工程师每天使用频率最高的工具之一。前置阅读:数据预处理、NumPy 科学计算入门。

Pandas 的名字来自 “Panel Data”(面板数据,即沿多个维度观测的数据集),由 Wes McKinney 于 2008 年在金融数据分析工作中创建。它提供了两个核心数据结构——Series(一维带标签数组)和 DataFrame(二维表格),让你能用类似 SQL 或 Excel 的方式在 Python 中操作数据,但灵活性和自动化程度远超两者。

如果说 NumPy 是”纯数学的数组引擎”,那 Pandas 就是”带标签的表格引擎”——每一列有名字、每一行有索引,你可以按列名或条件直接筛选,而非用脆弱的数字下标。它是连接原始数据和机器学习模型之间的桥梁:真实世界的数据又脏又乱,Pandas 负责把它们洗刷整理成模型能吃的干净格式。

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 平滑过渡。

Terminal window
# 安装 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 是一维带标签的数组——你可以把它想象成一列 Excel 数据,每个单元格都有一个行标签(索引)。DataFrame 是二维表格,由多个 Series 组成,每一列可以是不同的数据类型(整数列、字符串列、日期列共存)。

类比理解:Series 是一叠名片,每张名片有名字和内容;DataFrame 是一个名片盒,按类别分了好多格,每格里放一类名片。

从底层看,DataFrame 的每一列本质上是一个 NumPy ndarray(或 PyArrow ChunkedArray)。这意味着:

DataFrame={列名j→ndarrayj}j=1m\text{DataFrame} = \{ \text{列名}_j \to \text{ndarray}_j \}_{j=1}^{m}

对列的操作(如 df['薪资'] * 1.1)底层调用的是 NumPy 的矢量化运算(SIMD(Single Instruction Multiple Data,单指令多数据流,CPU 一条指令同时处理多个数据)),因此远比 Python 原生循环快。

Pandas 的每个 Series 和 DataFrame 都有一个行索引。索引不只是一排数字——它可以是日期、字符串、甚至多级层次结构(MultiIndex)。善用索引能让数据对齐变得极其自然:两个 DataFrame 相加时,Pandas 会自动按索引对齐,而非按位置。

形式化地说,若 DataFrame AA 有索引 {a1,a2,a3}\{a_1, a_2, a_3\},DataFrame BB 有索引 {a2,a3,a4}\{a_2, a_3, a_4\},则 A+BA + B 的结果是索引为 {a1,a2,a3,a4}\{a_1, a_2, a_3, a_4\} 的表,其中 a1a_1 和 a4a_4 行因缺少对应数据而被填为 NaN(Not a Number,缺失值标记)。这种”自动对齐 + 缺失填充”的语义是 Pandas 区别于 NumPy 的核心设计。

SQL 里的 GROUP BY ... SUM() 在 Pandas 中就是 df.groupby('列名').sum()。思路一致:先按某列的值将数据分组,再对每组施加聚合函数(求和、均值、计数等)。这是数据分析中最常用的操作模式,被称为 split-apply-combine(分组-应用-合并)范式。

其数学本质可以表述为:给定分组映射 g:行→组别g: \text{行} \to \text{组别} 和聚合函数 ff,输出为:

result[组]=f({rowi∣g(rowi)=组})\text{result}[\text{组}] = f(\{ \text{row}_i \mid g(\text{row}_i) = \text{组} \})

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() 输出的统计量背后都是经典公式。例如 标准差(衡量数据分散程度)的计算为:

σ=1N∑i=1N(xi−μ)2\sigma = \sqrt{\frac{1}{N} \sum_{i=1}^{N} (x_i - \mu)^2}

其中 μ\mu 是均值,NN 是样本数。Pandas 默认计算的是样本标准差(分母用 N−1N-1,又称 Bessel 校正,即用 N−1N-1 代替 NN 来补偿样本对总体方差的低估)。

例 2:数据清洗——缺失值与重复值

Section titled “例 2:数据清洗——缺失值与重复值”
import pandas as pd
import 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)} 行")

为什么用中位数而非均值填充? 均值 xˉ=1n∑xi\bar{x} = \frac{1}{n}\sum x_i 对异常值(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 只输出实际存在的组合,结果更干净、运行更快。

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)的数学定义为:

MAt=1k∑i=0k−1xt−i\text{MA}_t = \frac{1}{k} \sum_{i=0}^{k-1} x_{t-i}

其中 kk 是窗口大小。它本质是一个低通滤波器(low-pass filter,即只保留低频趋势、平滑掉高频波动的数学操作),能滤除数据的短期波动、揭示长期趋势,在股票分析和传感器数据处理中极为常用。

例 5:矢量化运算——为什么不要写 for 循环

Section titled “例 5:矢量化运算——为什么不要写 for 循环”
import pandas as pd
import numpy as np
import time
# 模拟 100 万行数据
n = 1_000_000
df = 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 + 1
print(f"矢量化耗时: {time.time() - start:.3f}s")

这个差距的根源在于:Python 是解释型语言,每次循环都要做类型检查和动态分发;而 NumPy/Pandas 的矢量化操作底层是编译好的 C 代码,配合 CPU 的 SIMD 指令(一条指令同时处理 4-16 个浮点数),速度天差地别。经验法则:在 Pandas 中,能用矢量化就绝不写 for 循环。

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)导出 CSVdf.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。内存占用从 O(n×字符串长度)O(n \times \text{字符串长度}) 降为 O(n×4字节+k×字符串长度)O(n \times 4\text{字节} + k \times \text{字符串长度}),其中 kk 是类别数,通常 k≪nk \ll n。
  • 用 map 替代 applymap:Pandas 2.1 起统一了逐元素变换 API——df.map(func) 同时适用于 DataFrame 和 Series,旧的 applymap 已弃用。
  • 时间序列优先用日期索引:设为索引后,切片、重采样、移动平均等操作极其方便,这是 Pandas 相比通用数据库的独特优势。详见时间序列分析。
  • 数据清洗与特征工程:机器学习项目 60% 的时间花在这里。缺失值处理、类别编码(One-Hot(独热编码,将 kk 个类别展开为 kk 个 0/1 列)、Target Encoding(目标编码,用每个类别对应的目标变量均值替代原始类别标签))、特征衍生,Pandas 是主力工具。详见特征工程。
  • 探索性数据分析(EDA,Exploratory Data Analysis):拿到一份数据,先用 df.describe() 看分布、用 groupby 找规律、用 value_counts() 看类别平衡——这一套流程是数据科学家的肌肉记忆。
  • 数据 ETL 管道(Extract-Transform-Load,即”抽取-转换-加载”的数据流水线):从数据库 / API / 文件读取,经过清洗变换,输出为模型可用的格式。配合 Matplotlib 可快速生成分析报告。
  • 模型评估结果分析:把预测结果和真实值放进 DataFrame,按分组分析误差分布,定位模型薄弱环节。详见模型评估。
  • 金融与商业数据分析:Pandas 诞生于金融行业,对时间序列、滚动窗口、重采样有原生且强大的支持,是量化分析的标配。
特性PandasPolarsSQL (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 后端为何更快更省内存。