Pandas 库使用教程:面向 Python 数据科学
引言:为什么选择 Pandas?
在 Python 数据科学领域,Pandas 库是无可争议的基石。它提供了一系列高性能、易于使用的数据结构和数据分析工具,使得处理、清洗、转换、分析和可视化结构化数据(如表格数据、时间序列数据)变得极其高效和直观。无论您是数据分析师、数据科学家、机器学习工程师,还是任何需要处理数据的研究人员或开发人员,掌握 Pandas 都将极大地提升您的工作效率。
Pandas 这个名字来源于“Panel Data”(面板数据),这是一个计量经济学中用于描述多维结构化数据集的术语。该库最初由 Wes McKinney 在 2008 年开发,旨在为 Python 提供一个强大且灵活的数据分析框架,弥补当时 Python 在数据处理方面相较于 R 等语言的不足。如今,Pandas 已成为 Python 生态系统中最受欢迎和最重要的库之一。
Pandas 的核心优势:
- 强大的数据结构: 核心是
Series
(一维带标签数组)和DataFrame
(二维带标签表格),它们能够高效地处理不同类型的数据(整数、浮点数、字符串、布尔值、Python 对象等),并带有灵活的索引系统。 - 丰富的数据操作功能: 提供大量用于数据清洗、转换、合并、重塑、切片、索引、分组、聚合等操作的内置函数。
- 高效的数据读写: 支持从多种文件格式(如 CSV、Excel、JSON、SQL 数据库、HDF5 等)读取数据,并将数据写回这些格式。
- 灵活的数据处理: 轻松处理缺失数据(NaN),支持时间序列数据的专门处理,能够进行向量化操作以提高性能。
- 与其他库的集成: 与 NumPy(底层依赖)、Matplotlib/Seaborn(可视化)、Scikit-learn(机器学习)等 Python 数据科学生态系统中的其他核心库无缝集成。
本教程将带您从 Pandas 的基础知识开始,逐步深入到更高级的数据操作和分析技巧,旨在为您打下坚实的 Pandas 应用基础。
一、 安装与基础概念
1.1 安装 Pandas
安装 Pandas 非常简单。如果您已经安装了 Anaconda 发行版,那么 Pandas 通常已经预装好了。如果没有,或者您使用的是标准的 Python 环境,可以通过 pip 进行安装:
bash
pip install pandas
为了充分利用 Pandas 的功能,通常还会一起安装 NumPy(Pandas 的底层依赖)和 Matplotlib(用于可视化):
bash
pip install numpy matplotlib
在 Python 脚本或 Jupyter Notebook 中,我们通常按以下约定导入 Pandas 和 NumPy:
python
import pandas as pd
import numpy as np
1.2 核心数据结构:Series 与 DataFrame
Pandas 的两大核心数据结构是 Series
和 DataFrame
。
1.2.1 Series
Series
是一种一维带标签的数组,类似于 NumPy 的 ndarray
,但它有一个额外的索引(Index)。索引可以是数字、字符串或其他 Python 对象,为每个数据点提供了标签。
- 创建 Series:
“`python
从列表创建,默认数字索引
s1 = pd.Series([1, 3, 5, np.nan, 6, 8])
print(s1)
输出:
0 1.0
1 3.0
2 5.0
3 NaN
4 6.0
5 8.0
dtype: float64
从列表创建,并指定索引
s2 = pd.Series([10, 20, 30], index=[‘a’, ‘b’, ‘c’])
print(s2)
输出:
a 10
b 20
c 30
dtype: int64
从字典创建,字典的键成为索引
data_dict = {‘Ohio’: 35000, ‘Texas’: 71000, ‘Oregon’: 16000, ‘Utah’: 5000}
s3 = pd.Series(data_dict)
print(s3)
输出:
Ohio 35000
Texas 71000
Oregon 16000
Utah 5000
dtype: int64
“`
- Series 的属性与操作:
s.values
: 返回包含 Series 数据的 NumPy 数组。s.index
: 返回 Series 的索引。s.dtype
: 返回数据的类型。s.shape
: 返回 Series 的形状(元组)。s.size
: 返回元素的数量。s.name
: 给 Series 本身命名。s.index.name
: 给索引命名。
Series 支持类似 NumPy 数组的向量化操作和索引方式,也支持基于标签的索引。
python
print(s2['b']) # 输出: 20 (标签索引)
print(s2[1]) # 输出: 20 (位置索引,有时不推荐与标签混用)
print(s2[['a', 'c']]) # 选择多个标签
print(s2 * 2) # 向量化乘法
print(s2 > 15) # 向量化比较
1.2.2 DataFrame
DataFrame
是一个二维带标签的数据结构,可以看作是一个共享相同索引的 Series 的集合,或者是一个带有行索引和列索引的表格。它是 Pandas 中最常用的数据结构。
- 创建 DataFrame:
“`python
从字典创建(常用方式),字典的键成为列名,值可以是列表、数组或 Series
data = {‘state’: [‘Ohio’, ‘Ohio’, ‘Ohio’, ‘Nevada’, ‘Nevada’, ‘Nevada’],
‘year’: [2000, 2001, 2002, 2001, 2002, 2003],
‘pop’: [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
df = pd.DataFrame(data)
print(df)
输出:
state year pop
0 Ohio 2000 1.5
1 Ohio 2001 1.7
2 Ohio 2002 3.6
3 Nevada 2001 2.4
4 Nevada 2002 2.9
5 Nevada 2003 3.2
指定列顺序和索引
df2 = pd.DataFrame(data, columns=[‘year’, ‘state’, ‘pop’],
index=[‘one’, ‘two’, ‘three’, ‘four’, ‘five’, ‘six’])
print(df2)
输出:
year state pop
one 2000 Ohio 1.5
two 2001 Ohio 1.7
three 2002 Ohio 3.6
four 2001 Nevada 2.4
five 2002 Nevada 2.9
six 2003 Nevada 3.2
从 NumPy 数组创建,指定列名和索引
dates = pd.date_range(‘20230101’, periods=6)
df_np = pd.DataFrame(np.random.randn(6, 4), index=dates, columns=list(‘ABCD’))
print(df_np)
输出: (随机数每次不同)
A B C D
2023-01-01 -0.876543 1.234567 -0.123456 0.987654
2023-01-02 0.111111 -0.222222 0.333333 -0.444444
… (共6行)
“`
- DataFrame 的基本属性与方法:
df.index
: 返回行索引。df.columns
: 返回列索引(列名)。df.dtypes
: 返回每列的数据类型。df.shape
: 返回 DataFrame 的形状(行数, 列数)。df.size
: 返回总元素数量。df.values
: 返回 DataFrame 的 NumPy 表示形式(可能包含多种数据类型)。df.head(n)
: 显示前 n 行数据(默认 5 行)。df.tail(n)
: 显示后 n 行数据(默认 5 行)。df.info()
: 打印 DataFrame 的简明摘要,包括索引类型、列信息(非空值数量、数据类型)和内存使用情况。df.describe()
: 生成描述性统计数据(计数、均值、标准差、最小值、分位数、最大值),主要针对数值列。
二、 数据加载与存储
Pandas 提供了方便的函数来读写各种格式的数据文件,最常用的是 CSV 和 Excel。
2.1 读取 CSV 文件
使用 pd.read_csv()
函数。它有许多参数可以控制读取过程。
“`python
假设有一个名为 ‘data.csv’ 的文件
df = pd.read_csv(‘data.csv’)
常用参数示例:
df = pd.read_csv(‘data.csv’,
sep=’,’, # 指定分隔符,默认为逗号
header=0, # 指定哪一行作为列名,默认第一行 (0)
index_col=0, # 指定哪一列作为行索引
usecols=[‘col1’, ‘col2’], # 只读取指定的列
nrows=100, # 只读取前 100 行
na_values=[‘NA’, ‘?’], # 指定哪些值应被视为空值 (NaN)
encoding=’utf-8′ # 指定文件编码,处理中文时常用 ‘gbk’ 或 ‘utf-8’
)
“`
2.2 读取 Excel 文件
使用 pd.read_excel()
函数。需要安装额外的库 openpyxl
(用于 .xlsx)或 xlrd
(用于旧版 .xls)。
bash
pip install openpyxl xlrd
“`python
读取 Excel 文件的第一个工作表
df_excel = pd.read_excel(‘data.xlsx’)
读取指定的工作表
df_sheet2 = pd.read_excel(‘data.xlsx’, sheet_name=’Sheet2′)
df_sheet_by_index = pd.read_excel(‘data.xlsx’, sheet_name=1) # 按索引读取
读取所有工作表到一个字典,键是工作表名,值是 DataFrame
all_sheets = pd.read_excel(‘data.xlsx’, sheet_name=None)
“`
2.3 存储数据
使用 df.to_csv()
和 df.to_excel()
方法。
“`python
保存为 CSV 文件
df.to_csv(‘output.csv’,
index=False, # 通常不保存 DataFrame 的索引到文件中
sep=’;’, # 指定分隔符
encoding=’utf-8-sig’ # 使用 ‘utf-8-sig’ 可以在 Excel 中正确显示中文
)
保存为 Excel 文件
df.to_excel(‘output.xlsx’,
sheet_name=’MyData’,
index=False # 不保存索引
)
将多个 DataFrame 保存到同一个 Excel 文件的不同工作表
with pd.ExcelWriter(‘output_multi.xlsx’) as writer:
df1.to_excel(writer, sheet_name=’Sheet1′, index=False)
df2.to_excel(writer, sheet_name=’Sheet2′, index=False)
“`
Pandas 还支持 JSON (pd.read_json
, df.to_json
)、SQL (pd.read_sql
, df.to_sql
)、HTML (pd.read_html
) 等多种格式。
三、 数据选择与索引 (Indexing & Selection)
这是 Pandas 操作的核心部分,有多种方式可以选择和访问数据。
3.1 选择列
“`python
选择单列,返回一个 Series
state_col = df[‘state’]
或者使用属性访问(仅当列名是有效 Python 标识符且不与 DataFrame 方法冲突时)
state_col_attr = df.state
选择多列,返回一个新的 DataFrame
subset = df[[‘year’, ‘pop’]]
“`
3.2 选择行
主要使用 .loc
和 .iloc
。
.loc
:基于标签(索引名和列名)的选择df.loc[label]
: 选择单行(基于行索引标签)。df.loc[[label1, label2]]
: 选择多行。df.loc[start_label:end_label]
: 选择行切片(包含结束标签)。df.loc[row_labels, col_labels]
: 同时选择行和列。
python
print(df2.loc['two']) # 选择索引为 'two' 的行
print(df2.loc[['one', 'three']]) # 选择索引为 'one' 和 'three' 的行
print(df2.loc['two':'four']) # 选择从 'two' 到 'four' 的行(包含 'four')
print(df2.loc['one', 'state']) # 选择 'one' 行 'state' 列的值
print(df2.loc[['one', 'five'], ['year', 'pop']]) # 选择特定行和列
.iloc
:基于整数位置(从 0 开始)的选择df.iloc[pos]
: 选择单行(基于行位置)。df.iloc[[pos1, pos2]]
: 选择多行。df.iloc[start_pos:end_pos]
: 选择行切片(不包含结束位置,类似 Python 列表切片)。df.iloc[row_positions, col_positions]
: 同时选择行和列。
python
print(df.iloc[0]) # 选择第一行
print(df.iloc[[1, 3, 5]]) # 选择第 2, 4, 6 行
print(df.iloc[0:3]) # 选择前三行(位置 0, 1, 2)
print(df.iloc[0, 1]) # 选择第一行第二列的值
print(df.iloc[[0, 2], [0, 2]]) # 选择第 1, 3 行的第 1, 3 列
注意:
* 直接使用 df[]
进行索引比较复杂:
* df['col_name']
选择列。
* df[start:end]
选择行切片(基于位置,但有时会因索引类型而异,不推荐)。
* df[boolean_array]
进行布尔索引(常用)。
* 强烈推荐使用 .loc
和 .iloc
进行显式索引,避免混淆和潜在错误。
3.3 布尔索引 (Boolean Indexing)
这是非常强大和常用的功能,允许根据条件选择数据。
“`python
选择 ‘year’ 大于 2001 的行
print(df[df[‘year’] > 2001])
选择 ‘state’ 为 ‘Ohio’ 的行
print(df[df[‘state’] == ‘Ohio’])
组合多个条件(使用 & 表示 AND,| 表示 OR,~ 表示 NOT)
注意:每个条件必须用括号括起来
print(df[(df[‘year’] > 2001) & (df[‘state’] == ‘Nevada’)])
使用 isin() 选择特定值的行
print(df[df[‘year’].isin([2000, 2003])])
“`
3.4 设置值
可以通过上述选择方法定位到数据,然后进行赋值。
“`python
修改单个值
df.loc[0, ‘pop’] = 1.6
修改整列的值
df[‘pop’] = df[‘pop’] * 1000000
根据条件修改值
df.loc[df[‘year’] == 2000, ‘state’] = ‘OHIO_Updated’
添加新列
df[‘debt’] = np.random.rand(len(df)) * 1000 # 添加基于计算的新列
df[‘eastern’] = df[‘state’].isin([‘Ohio’, ‘OHIO_Updated’]) # 添加基于条件的新列
“`
四、 数据清洗与准备
真实世界的数据往往不规整,包含缺失值、重复值、错误类型等。Pandas 提供了强大的工具来处理这些问题。
4.1 处理缺失数据 (NaN)
Pandas 使用 NaN
(Not a Number) 来表示缺失值。
- 检测缺失值:
pd.isnull(obj)
或obj.isnull()
: 返回一个布尔型的对象,指示哪些值是缺失的。pd.notnull(obj)
或obj.notnull()
: 与isnull
相反。df.isnull().sum()
: 计算每列的缺失值数量(非常常用)。
“`python
创建一个带有缺失值的 DataFrame
data_missing = {‘A’: [1, 2, np.nan, 4],
‘B’: [5, np.nan, np.nan, 8],
‘C’: [9, 10, 11, 12]}
df_miss = pd.DataFrame(data_missing)
print(df_miss.isnull())
输出:
A B C
0 False False False
1 False True False
2 True True False
3 False False False
print(df_miss.isnull().sum())
输出:
A 1
B 2
C 0
dtype: int64
“`
- 处理缺失值:
- 删除:
df.dropna(axis=0, how='any', thresh=None, subset=None)
axis=0
: 删除包含 NaN 的行(默认)。axis=1
: 删除包含 NaN 的列。how='any'
: 只要有一个 NaN 就删除(默认)。how='all'
: 只有当所有值都为 NaN 时才删除。thresh=N
: 保留至少有 N 个非 NaN 值的行/列。subset=['col1', 'col2']
: 只在指定的列中检查 NaN。
- 填充:
df.fillna(value=None, method=None, axis=None, limit=None)
value
: 用于填充 NaN 的标量值或字典/Series(指定不同列填充不同值)。method='ffill'
: 前向填充(用前一个有效值填充)。method='bfill'
: 后向填充(用后一个有效值填充)。
- 删除:
“`python
删除包含任何 NaN 的行
df_dropped_rows = df_miss.dropna()
print(df_dropped_rows)
删除包含任何 NaN 的列
df_dropped_cols = df_miss.dropna(axis=1)
print(df_dropped_cols)
用 0 填充所有 NaN
df_filled_zero = df_miss.fillna(0)
print(df_filled_zero)
用每列的平均值填充 NaN
df_filled_mean = df_miss.fillna(df_miss.mean())
print(df_filled_mean)
使用前向填充
df_filled_ffill = df_miss.fillna(method=’ffill’)
print(df_filled_ffill)
“`
4.2 处理重复数据
- 检测重复行:
df.duplicated(subset=None, keep='first')
- 返回一个布尔 Series,指示哪些行是重复的。
subset
: 指定要考虑判断重复的列,默认所有列。keep='first'
: 标记除第一个出现之外的重复项为 True。keep='last'
: 标记除最后一个出现之外的重复项为 True。keep=False
: 标记所有重复项为 True。
- 删除重复行:
df.drop_duplicates(subset=None, keep='first', inplace=False)
- 参数与
duplicated
类似。 inplace=True
: 直接修改原 DataFrame。
- 参数与
“`python
data_dup = {‘col1’: [‘A’, ‘B’, ‘C’, ‘A’, ‘B’, ‘D’],
‘col2’: [1, 2, 3, 1, 2, 4]}
df_dup = pd.DataFrame(data_dup)
print(df_dup.duplicated())
输出:
0 False
1 False
2 False
3 True
4 True
5 False
dtype: bool
print(df_dup.drop_duplicates())
输出:
col1 col2
0 A 1
1 B 2
2 C 3
5 D 4
print(df_dup.drop_duplicates(subset=[‘col1′], keep=’last’))
输出:
col1 col2
2 C 3
3 A 1
4 B 2
5 D 4
“`
4.3 数据类型转换
使用 astype()
方法。
“`python
假设 df[‘year’] 是 int64,想转为字符串
df[‘year_str’] = df[‘year’].astype(str)
print(df.dtypes)
假设有一列 ‘price’ 是 object 类型(字符串),包含 ‘$’ 符号,需要转为 float
df[‘price’] = df[‘price’].str.replace(‘$’, ”).astype(float) # 示例,需先处理非数字字符
转为 Category 类型(适用于低基数(少量唯一值)的列,可以节省内存和加速计算)
df[‘state_cat’] = df[‘state’].astype(‘category’)
print(df[‘state_cat’].cat.categories) # 查看类别
print(df[‘state_cat’].cat.codes) # 查看类别的整数编码
“`
4.4 重命名索引和列名
使用 rename()
方法。
“`python
print(df.rename(columns={‘year’: ‘YEAR’, ‘pop’: ‘Population’},
index={0: ‘zero’, 1: ‘one’})) # 不会修改原 df
使用 inplace=True 修改原 df
df.rename(columns={‘year’: ‘YEAR’}, inplace=True)
也可以直接赋值给 .columns 或 .index 属性(需要提供完整的列表)
df.columns = [‘State’, ‘Year’, ‘Population’, …]
df.index = [ … new index labels … ]
“`
4.5 应用函数 (apply, map, applymap)
map()
: 用于Series
,对每个元素应用一个函数或映射(字典)。apply()
: 用于DataFrame
的行或列。df.apply(func, axis=0)
: 将函数func
应用于每列(每列作为 Series 传入 func)。df.apply(func, axis=1)
: 将函数func
应用于每行(每行作为 Series 传入 func)。
applymap()
: 用于DataFrame
,对每个元素应用一个函数。
“`python
map 示例 (Series)
s = pd.Series([1, 2, 3])
print(s.map(lambda x: x * 100))
print(s.map({1: ‘one’, 2: ‘two’})) # 使用字典映射
apply 示例 (DataFrame)
df_apply = df[[‘year’, ‘pop’]]
def min_max_diff(x): # x 是一个 Series (一列或一行)
return x.max() – x.min()
print(df_apply.apply(min_max_diff, axis=0)) # 应用于每列
输出:
year 3.0
pop 2100000.0
dtype: float64
print(df_apply.apply(min_max_diff, axis=1)) # 应用于每行
applymap 示例 (DataFrame, 应用于每个元素)
print(df_apply.applymap(lambda x: f'{x:.2f}’)) # 格式化每个元素为字符串
“`
五、 数据分组与聚合 (Group By)
groupby
操作是数据分析的核心,遵循 “split-apply-combine”(拆分-应用-合并)的模式。
- Split: 根据某些标准将数据拆分成组。
- Apply: 对每个组独立应用一个函数(如聚合、转换、过滤)。
- Combine: 将结果合并成一个新的数据结构。
“`python
准备数据
data_group = {‘Company’: [‘GOOG’, ‘GOOG’, ‘MSFT’, ‘MSFT’, ‘FB’, ‘FB’],
‘Person’: [‘Sam’, ‘Charlie’, ‘Amy’, ‘Vanessa’, ‘Carl’, ‘Sarah’],
‘Sales’: [200, 120, 340, 124, 243, 350]}
df_group = pd.DataFrame(data_group)
按 ‘Company’ 分组
by_comp = df_group.groupby(‘Company’)
Apply: 计算每个公司的平均 Sales (聚合)
print(by_comp.mean())
输出:
Sales
Company
FB 296.5
GOOG 160.0
MSFT 232.0
其他常用聚合函数: count(), sum(), std(), min(), max(), describe()
print(by_comp.sum())
print(by_comp.describe()) # 对每个组进行描述性统计
print(by_comp.describe().transpose()) # 转置结果更易读
按多列分组
by_comp_person = df_group.groupby([‘Company’, ‘Person’])
print(by_comp_person.sum())
使用 agg() 进行更复杂的聚合
print(by_comp[‘Sales’].agg([‘sum’, ‘mean’, ‘std’, ‘count’]))
输出:
sum mean std count
Company
FB 593.0 296.5 75.660426 2
GOOG 320.0 160.0 56.568542 2
MSFT 464.0 232.0 152.735065 2
对不同列应用不同聚合函数
print(by_comp.agg({‘Sales’: ‘sum’, ‘Person’: ‘count’})) # 注意这里对 Person count 没太大意义,仅作示例
Sales Person
Company
FB 593 2
GOOG 320 2
MSFT 464 2
迭代分组
for name, group in by_comp:
print(f”Company: {name}”)
print(group)
print(“-” * 20)
“`
六、 数据合并、连接与拼接
Pandas 提供了多种将不同 DataFrame 对象组合在一起的方法。
6.1 拼接 (Concatenating) – pd.concat()
将多个 DataFrame 沿着一个轴(行或列)堆叠起来。
“`python
df1 = pd.DataFrame({‘A’: [‘A0’, ‘A1’], ‘B’: [‘B0’, ‘B1’]}, index=[0, 1])
df2 = pd.DataFrame({‘A’: [‘A2’, ‘A3’], ‘B’: [‘B2’, ‘B3’]}, index=[2, 3])
df3 = pd.DataFrame({‘C’: [‘C0’, ‘C1’], ‘D’: [‘D0’, ‘D1’]}, index=[0, 1])
沿行拼接 (axis=0, 默认)
result_row = pd.concat([df1, df2])
print(result_row)
输出:
A B
0 A0 B0
1 A1 B1
2 A2 B2
3 A3 B3
沿列拼接 (axis=1)
result_col = pd.concat([df1, df3], axis=1)
print(result_col)
输出:
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
拼接时处理索引冲突
df4 = pd.DataFrame({‘A’: [‘A4’, ‘A5’], ‘B’: [‘B4’, ‘B5’]}, index=[0, 1]) # 与 df1 索引重复
result_ignore = pd.concat([df1, df4], ignore_index=True) # 重置索引
print(result_ignore)
输出:
A B
0 A0 B0
1 A1 B1
2 A4 B4
3 A5 B5
“`
6.2 合并 (Merging) – pd.merge()
类似 SQL 中的 JOIN 操作,根据一个或多个键将不同 DataFrame 的行连接起来。
“`python
left = pd.DataFrame({‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K3’],
‘A’: [‘A0’, ‘A1’, ‘A2’, ‘A3’],
‘B’: [‘B0’, ‘B1’, ‘B2’, ‘B3’]})
right = pd.DataFrame({‘key’: [‘K0’, ‘K1’, ‘K2’, ‘K4’],
‘C’: [‘C0’, ‘C1’, ‘C2’, ‘C4’],
‘D’: [‘D0’, ‘D1’, ‘D2’, ‘D4’]})
内连接 (inner join, 默认): 只保留两个 DataFrame 中键都存在的行
inner_join = pd.merge(left, right, on=’key’, how=’inner’)
print(inner_join)
输出:
key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 C2 D2
左连接 (left join): 保留左 DataFrame 的所有行,匹配右 DataFrame 的行
left_join = pd.merge(left, right, on=’key’, how=’left’)
print(left_join)
输出:
key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 C2 D2
3 K3 A3 B3 NaN NaN
右连接 (right join): 保留右 DataFrame 的所有行
right_join = pd.merge(left, right, on=’key’, how=’right’)
print(right_join)
输出:
key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 C2 D2
3 K4 NaN NaN C4 D4
外连接 (outer join): 保留两个 DataFrame 的所有行
outer_join = pd.merge(left, right, on=’key’, how=’outer’)
print(outer_join)
输出:
key A B C D
0 K0 A0 B0 C0 D0
1 K1 A1 B1 C1 D1
2 K2 A2 B2 C2 D2
3 K3 A3 B3 NaN NaN
4 K4 NaN NaN C4 D4
如果键的列名不同
left_diff = pd.DataFrame({‘key1’: [‘K0’, ‘K1’, ‘K2’], ‘A’: [‘A0’, ‘A1’, ‘A2’]})
right_diff = pd.DataFrame({‘key2’: [‘K0’, ‘K1’, ‘K3’], ‘C’: [‘C0’, ‘C1’, ‘C3′]})
merge_diff_keys = pd.merge(left_diff, right_diff, left_on=’key1′, right_on=’key2′, how=’inner’)
print(merge_diff_keys)
输出:
key1 A key2 C
0 K0 A0 K0 C0
1 K1 A1 K1 C1
“`
6.3 连接 (Joining) – df.join()
一个便捷的方法,主要用于基于索引进行合并,可以看作是 merge
的一种特殊情况。
“`python
left_idx = pd.DataFrame({‘A’: [‘A0’, ‘A1’, ‘A2’]}, index=[‘K0’, ‘K1’, ‘K2’])
right_idx = pd.DataFrame({‘B’: [‘B0’, ‘B1’, ‘B2’]}, index=[‘K0’, ‘K1’, ‘K3’]) # 注意索引不同
默认按索引进行左连接
join_result = left_idx.join(right_idx)
print(join_result)
输出:
A B
K0 A0 B0
K1 A1 B1
K2 A2 NaN
外连接
outer_join_result = left_idx.join(right_idx, how=’outer’)
print(outer_join_result)
输出:
A B
K0 A0 B0
K1 A1 B1
K2 A2 NaN
K3 NaN B2
也可以基于列进行 join
left_on_col = pd.DataFrame({‘key’: [‘K0’, ‘K1’, ‘K2’], ‘A’: [‘A0’, ‘A1’, ‘A2’]})
right_on_idx = pd.DataFrame({‘B’: [‘B0’, ‘B1’, ‘B2’]}, index=[‘K0’, ‘K1’, ‘K3′])
join_on_col = left_on_col.join(right_on_idx, on=’key’) # 左 df 的 ‘key’ 列与右 df 的索引匹配
print(join_on_col)
输出:
key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 NaN
“`
七、 时间序列分析基础
Pandas 最初就是为金融时间序列分析而设计的,因此在处理时间序列数据方面非常强大。
- 创建时间戳和时间范围:
pd.to_datetime()
: 将字符串或数字转换为 Timestamp 对象。pd.date_range()
: 生成一个固定频率的 DatetimeIndex。
“`python
dates = pd.to_datetime([‘2023-01-01’, ‘2023-01-05’, ‘2023/01/10’])
print(dates)
输出: DatetimeIndex([‘2023-01-01’, ‘2023-01-05’, ‘2023-01-10′], dtype=’datetime64[ns]’, freq=None)
date_idx = pd.date_range(‘20230101′, periods=5, freq=’D’) # ‘D’ 表示天
print(date_idx)
输出: DatetimeIndex([‘2023-01-01’, ‘2023-01-02’, ‘2023-01-03’, ‘2023-01-04’, ‘2023-01-05′], dtype=’datetime64[ns]’, freq=’D’)
创建一个以时间为索引的 Series
ts = pd.Series(np.random.randn(len(date_idx)), index=date_idx)
print(ts)
“`
- 时间序列索引:
- 可以使用字符串日期、Timestamp 对象或切片进行索引。
python
print(ts['2023-01-02'])
print(ts['2023-01-01':'2023-01-03'])
print(ts['2023']) # 选择 2023 年的所有数据
print(ts['2023-01']) # 选择 2023 年 1 月的所有数据
- 重采样 (Resampling):
ts.resample(rule)
: 对时间序列进行频率转换和数据聚合。类似于groupby
操作。rule
: 重采样的频率(如 ‘M’ 月末, ‘Q’ 季末, ‘A’ 年末, ‘5T’ 5分钟, ‘H’ 小时等)。- 需要跟上聚合函数,如
.sum()
,.mean()
,.ohlc()
(Open-High-Low-Close)。
“`python
创建一个更高频率的时间序列
longer_ts = pd.Series(np.random.randn(100), index=pd.date_range(‘2023-01-01′, periods=100, freq=’H’))
降采样到每日频率,计算均值
daily_mean = longer_ts.resample(‘D’).mean()
print(daily_mean.head())
升采样(频率变高),通常需要填充
upsampled = daily_mean.resample(‘H’).ffill() # 使用前向填充
print(upsampled.head())
“`
- 时间窗口 (Rolling Windows):
ts.rolling(window)
: 提供滚动窗口计算。window
: 窗口大小(整数表示固定数量,时间偏移字符串如 ‘3D’ 表示时间窗口)。- 需要跟上聚合函数,如
.mean()
,.sum()
,.std()
。
“`python
计算 3 个周期的滚动平均值
rolling_mean = ts.rolling(window=3).mean()
print(rolling_mean)
“`
八、 数据可视化
Pandas DataFrame 和 Series 具有内置的 .plot()
方法,可以方便地调用 Matplotlib 进行快速绘图。
“`python
import matplotlib.pyplot as plt
准备数据
ts = pd.Series(np.random.randn(1000), index=pd.date_range(‘2022-01-01’, periods=1000))
ts = ts.cumsum() # 累积和,模拟随机游走
df_plot = pd.DataFrame(np.random.randn(100, 4), columns=[‘A’, ‘B’, ‘C’, ‘D’])
df_plot = df_plot.cumsum()
线图 (默认)
ts.plot(figsize=(10, 6), title=’Random Walk’)
plt.show()
df_plot.plot(figsize=(10, 6))
plt.show()
其他图形类型 (kind 参数)
df = pd.DataFrame(np.random.rand(50, 4), columns=[‘a’, ‘b’, ‘c’, ‘d’])
散点图
df.plot(kind=’scatter’, x=’a’, y=’b’, figsize=(8, 5))
plt.title(‘Scatter Plot a vs b’)
plt.show()
柱状图
df.iloc[5].plot(kind=’bar’) # 绘制第 5 行的数据
plt.title(‘Bar Plot for row 5’)
plt.show()
直方图
df[‘a’].plot(kind=’hist’, bins=20, alpha=0.7)
plt.title(‘Histogram of column a’)
plt.show()
箱线图
df.plot(kind=’box’, figsize=(8, 6))
plt.title(‘Box Plot for all columns’)
plt.show()
面积图
df.plot(kind=’area’, alpha=0.5, figsize=(10, 6))
plt.title(‘Area Plot’)
plt.show()
“`
虽然 Pandas 的内置绘图很方便进行快速探索,但对于更复杂、更具定制性的可视化,通常会直接使用 Matplotlib 或 Seaborn 库。
九、 性能优化与技巧
- 使用向量化操作: 尽量避免使用 Python 的 for 循环遍历 DataFrame 的行。Pandas 和 NumPy 的底层实现(通常是 C 或 Cython)使得向量化操作(如
df['col1'] + df['col2']
)比逐元素操作快得多。 - 使用
apply
谨慎:apply
比向量化操作慢,但比 Python 循环快。如果可能,优先寻找内置的 Pandas/NumPy 函数。 - 使用
isin()
:df[df['col'].isin(values_list)]
通常比用|
连接多个==
条件更快。 - 使用
Category
类型: 对于字符串列,如果唯一值数量有限(低基数),将其转换为category
类型可以显著减少内存占用并加速某些操作(如groupby
)。 - 避免链式索引赋值: 像
df[df['A'] > 0]['B'] = 5
这样的操作可能不会按预期工作,并可能引发SettingWithCopyWarning
。应使用.loc
一次性完成索引和赋值:df.loc[df['A'] > 0, 'B'] = 5
。 - 读取大文件时分块处理: 对于非常大的文件,可以在
read_csv
中使用chunksize
参数,返回一个迭代器,每次处理一个数据块。
总结
Pandas 是 Python 数据科学生态系统中不可或缺的工具。它提供的 Series
和 DataFrame
数据结构以及围绕它们构建的丰富功能集,使得数据的导入、清洗、转换、分析和可视化变得高效而直观。
本教程涵盖了 Pandas 的核心概念和常用操作,包括:
- 安装和基本数据结构(
Series
,DataFrame
)。 - 数据的读写(CSV, Excel)。
- 强大的数据选择和索引方法(
.loc
,.iloc
, 布尔索引)。 - 数据清洗技术(处理缺失值、重复值、类型转换、重命名)。
- 强大的分组聚合能力 (
groupby
)。 - 合并、连接和拼接数据集 (
concat
,merge
,join
)。 - 时间序列处理基础。
- 内置的数据可视化功能。
- 性能优化的一些建议。
掌握 Pandas 的关键在于实践。尝试使用真实或模拟的数据集,应用本教程中学到的各种操作。随着经验的积累,您将能够更加熟练地运用 Pandas 来解决各种复杂的数据处理和分析问题,为后续的统计分析、机器学习建模等工作打下坚实的基础。继续探索 Pandas 官方文档和社区资源,您会发现更多高级功能和技巧,让您的数据科学之旅更加顺畅。