前言
Pandas是Python数据分析领域最核心的库之一,构建于NumPy之上,提供了高效便捷的数据结构和数据分析工具。本文将深入解析Pandas的核心概念与功能。
一、Pandas核心数据结构:Series与DataFrame
Pandas的强大源于其两个核心数据结构:Series(一维数据) 和 DataFrame(二维表格)。它们为数据处理提供了高效且直观的容器,支持自动数据对齐、灵活索引和丰富的操作方法。
1.1 Series:带标签的一维数组
- 核心特性
- 带索引的数组:每个元素都有对应的标签。
- 数据类型统一:所有元素共享同一数据类型(int, float, string等)。
- 大小不可变:创建后不能改变大小(但内容可修改)。
- 创建Series
import pandas as pd
import numpy as np
# 从列表创建
s1 = pd.Series([10, 20, 30, 40])
# 从数组创建(带自定义索引)
s2 = pd.Series(np.array([1.1, 2.2, 3.3]),
index=['a', 'b', 'c'],
name="温度数据",
dtype='float32')
# 从字典创建(键自动转为索引)
s3 = pd.Series({'北京': 2154, '上海': 2428, '广州': 1490})
- 关键属性
print(s2.index) # Index(['a', 'b', 'c'], dtype='object')
print(s2.values) # array([1.1, 2.2, 3.3], dtype=float32)
print(s2.dtype) # float32
print(s2.name) # '温度数据'
print(s2.shape) # (3,)
- 索引与切片
# 位置索引
print(s2[0]) # 1.1
# 标签索引
print(s2['b']) # 2.2
# 切片操作
print(s2[1:]) # b:2.2, c:3.3
print(s2['a':'c']) # 包含两端点(与Python切片不同)
# 布尔索引
print(s2[s2 > 2]) # b:2.2, c:3.3
- 重要功能
# 向量化运算
print(s2 * 10) # a:11.0, b:22.0, c:33.0
# 索引对齐运算
s4 = pd.Series([10, 20], index=['a', 'd'])
print(s2 + s4) # a:11.1, b:NaN, c:NaN, d:NaN
# 缺失值处理
print((s2 + s4).fillna(0)) # a:11.1, b:0, c:0, d:0
# 统计方法
print(s2.mean()) # 2.2
print(s2.idxmax()) # 'c' (最大值索引)
1.2 DataFrame:二维表格型数据结构
- 核心特性
- 行索引(index) + 列标签(columns) 的二维结构
- 每列是一个Series(可不同数据类型)
- 大小可变(可动态增删行列)
- 创建DataFrame
# 从字典创建(最常用)
data = {
'城市': ['北京', '上海', '广州', '深圳'],
'人口(万)': [2154, 2428, 1490, 1303],
'GDP(万亿)': [3.92, 4.32, 2.82, 2.76]
}
df = pd.DataFrame(data, index=['a', 'b', 'c', 'd'])
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'City': ['NY', 'LA', 'SF']
}
df = pd.DataFrame(data)
print(df)
# 从列表创建
data = [['北京', 2154], ['上海', 2428], ['广州', 1490]]
df2 = pd.DataFrame(data, columns=['城市', '人口'])
# 从NumPy数组创建
arr = np.random.rand(4, 3)
df3 = pd.DataFrame(arr, columns=['A', 'B', 'C'])
- 关键属性
print(df.index) # Index(['a', 'b', 'c', 'd'], dtype='object')
print(df.columns) # Index(['城市', '人口(万)', 'GDP(万亿)'], dtype='object')
print(df.dtypes) # 城市:object, 人口(万):int64, GDP(万亿):float64
print(df.shape) # (4, 3)
print(df.values) # 底层NumPy数组
- 数据访问
# 选择列(返回Series)
pop_series = df['人口(万)']
# 选择多列
sub_df = df[['城市', 'GDP(万亿)']]
# 行选择(loc基于标签)
print(df.loc['b']) # 第二行数据
print(df.loc[['a', 'c']]) # 多行数据
# 行选择(iloc基于位置)
print(df.iloc[0]) # 第一行
print(df.iloc[1:3]) # 第二、三行
# 行列混合选择
print(df.loc['c', 'GDP(万亿)']) # 2.82
print(df.iloc[2, 1]) # 1490
- 数据操作
# 新增列
df['人均GDP'] = df['GDP(万亿)'] * 10000 / df['人口(万)']
# 删除列
df.drop('人均GDP', axis=1, inplace=True)
# 修改值
df.loc['a', '人口(万)'] = 2200
# 条件筛选
high_gdp = df[df['GDP(万亿)'] > 3] # GDP>3万亿的城市
# 排序
df.sort_values('人口(万)', ascending=False)
- 高级索引
# 多层索引(MultiIndex)
index = pd.MultiIndex.from_tuples([
('华东', '上海'),
('华东', '杭州'),
('华南', '广州'),
('华南', '深圳')
])
df_multi = pd.DataFrame(np.random.randn(4, 2),
index=index,
columns=['指标1', '指标2'])
# 多层索引访问
print(df_multi.loc[('华南', '广州')])
print(df_multi.xs('华东', level=0)) # 华东地区所有城市
1.3 Series与DataFrame的关系
- 结构关系:
- DataFrame可视为由多个Series组成的字典(列名为键)
- DataFrame的每一列都是Series对象
- 相互转换:
# Series转DataFrame
s = pd.Series([1, 2, 3], name='值')
s_df = s.to_frame()
# DataFrame单列转Series
df_col = df['人口(万)'].squeeze() # 转为Series
- 运算兼容性:
# DataFrame与Series运算(自动按列广播)
df['人口(万)'] = df['人口(万)'] * 1.05 # 所有城市人口增长5%
1.4 数据结构选择指南
场景 | 推荐结构 |
---|---|
单列数据(温度序列 | Series |
多列关联数据(城市统计表) | DataFrame |
时间序列数据 | Series(带DatetimeIndex) |
面板数据(三维数据) | 使用MultiIndex的DataFrame |
掌握Series和DataFrame是高效使用Pandas的基础。它们的设计充分考虑了数据操作的便捷性和性能,通过索引对齐、向量化运算等特性,让复杂的数据处理变得简单直观。
二、核心语法
2.1 数据输入/输出:支持多种格式
# 读写文件示例
df.to_csv('data.csv', index=False) # 写入CSV
df_excel = pd.read_excel('data.xlsx') # 读取Excel
df_sql = pd.read_sql('SELECT * FROM table', con) # 从SQL读取
支持格式:
- CSV、Excel
- JSON、HTML
- SQL数据库
- Parquet、HDF5(大数据存储)
2.2 数据选择与索引
- 基于标签选择:loc
print(df.loc[0, 'Name']) # 输出: Alice
print(df.loc[:, ['Name', 'City']]) # 选择多列
- 基于位置选择:iloc
print(df.iloc[1, 2]) # 输出: LA (第二行第三列)
- 布尔索引
print(df[df['Age'] > 28]) # 筛选年龄>28的行
2.3 数据清洗与预处理
- 处理缺失值
df.fillna(0) # 填充为0
df.dropna() # 删除含NaN的行
- 重复值处理
df.drop_duplicates() # 删除重复行
- 数据转换
df['Age'].apply(lambda x: x+1) # 年龄列+1
df.replace('NY', 'New York') # 值替换
2.4 数据分组与聚合
# 按城市分组计算平均年龄
grouped = df.groupby('City')['Age'].mean()
print(grouped)
City
LA 30
NY 25
SF 35
Name: Age, dtype: int64
2.5 表格合并:concat, merge, join
在数据处理中,表格合并是最常见的操作之一。Pandas提供了三种主要的合并方法:concat、merge和join。它们各有特点,适用于不同的合并场景。
pd.concat:沿轴堆叠数据
concat主要用于沿指定轴(行或列)堆叠多个DataFrame或Series,是最简单的数据拼接方法。
基本语法:
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None)
核心参数:
参数 | 说明 |
---|---|
objs | 要连接的DataFrame/Series列表 |
axis | 连接轴:0=行方向(纵向),1=列方向(横向) |
join | 连接方式:‘outer’(并集,默认)或 ‘inner’(交集) |
ignore_index | 是否重置索引(默认False保留原索引) |
keys | 创建多层索引,标识原始数据来源 |
示意图:
DataFrame A (3x2) DataFrame B (2x2)
X Y X Y
0 A0 B0 0 C0 D0
1 A1 B1 1 C1 D1
2 A2 B2
沿行轴堆叠 (axis=0):
X Y
0 A0 B0
1 A1 B1
2 A2 B2
0 C0 D0 ← 保留原始索引
1 C1 D1
沿列轴堆叠 (axis=1):
X Y X Y
0 A0 B0 C0 D0
1 A1 B1 C1 D1
2 A2 B2 NaN NaN ← 缺失值填充
使用示例:
纵向堆叠(增加行):
df1 = pd.DataFrame({'A': ['A0', 'A1'], 'B': ['B0', 'B1']})
df2 = pd.DataFrame({'A': ['A2', 'A3'], 'B': ['B2', 'B3']})
# 纵向堆叠
result = pd.concat([df1, df2])
A B
0 A0 B0
1 A1 B1
0 A2 B2
1 A3 B3
横向拼接(增加列):
df3 = pd.DataFrame({'C': ['C0', 'C1'], 'D': ['D0', 'D1']})
# 横向拼接
result = pd.concat([df1, df3], axis=1)
A B C D
0 A0 B0 C0 D0
1 A1 B1 C1 D1
处理索引和列名:
# 重置索引
result = pd.concat([df1, df2], ignore_index=True)
# 添加来源标识
result = pd.concat([df1, df2], keys=['df1', 'df2'])
A B
df1 0 A0 B0
1 A1 B1
df2 0 A2 B2
1 A3 B3
内连接(只保留共有列):
df4 = pd.DataFrame({'A': ['A2', 'A3'], 'C': ['C2', 'C3']})
# 内连接(只保留A列)
result = pd.concat([df1, df4], join='inner')
pd.merge:基于键值连接(类似SQL JOIN)
merge用于基于一个或多个键(列)将不同DataFrame的行连接起来,功能最强大,类似SQL的JOIN操作。
基本语法:
pd.merge(left, right, how='inner', on=None, left_on=None, right_on=None)
核心参数:
参数 | 说明 |
---|---|
left/right | 要连接的左右DataFrame |
how | 连接类型:‘inner’, ‘outer’, ‘left’, ‘right’ |
on | 连接键(两表共有列名) |
left_on/right_on | 左右表不同的连接键 |
suffixes | 列名冲突时的后缀(默认(‘_x’, ‘_y’)) |
连接类型(how参数):
连接类型 | 说明 | SQL等效 |
---|---|---|
inner | 内连接(默认) | INNER JOIN |
outer | 全外连接 | FULL OUTER JOIN |
left | 左连接 | LEFT JOIN |
right | 右连接 | RIGHT JOIN |
示意图:
Left DataFrame (df1) Right DataFrame (df2)
Key ValL Key ValR
0 K0 A0 0 K0 B0
1 K1 A1 1 K2 B1
2 K2 A2 2 K3 B2
内连接 (how='inner', on='Key'):
Key ValL ValR
0 K0 A0 B0 ← 仅保留两表共有的键 (K0, K2)
1 K2 A2 B1
左连接 (how='left', on='Key'):
Key ValL ValR
0 K0 A0 B0
1 K1 A1 NaN ← 保留左表所有键
2 K2 A2 B1
使用示例:
多键连接:
left_multi = pd.DataFrame({'key1': ['K0', 'K0'], 'key2': ['K0', 'K1'], 'A': ['A0', 'A1']})
right_multi = pd.DataFrame({'key1': ['K0', 'K0'], 'key2': ['K0', 'K0'], 'B': ['B0', 'B1']})
result = pd.merge(left_multi, right_multi, on=['key1', 'key2'])
key1 key2 A B
0 K0 K0 A0 B0
df.join:基于索引的连接
join是merge的简化版,主要用于基于索引的连接,语法更简洁。
基本语法:
df1.join(df2, how='left', lsuffix='', rsuffix='')
核心特点:
- 默认基于索引连接(除非通过on指定列)
- 默认左连接(how=‘left’)
- 可同时连接多个DataFrame
- 处理列名冲突时添加后缀
示意图:
Left DataFrame (df1) Right DataFrame (df2)
ValL ValR
K0 A0 K0 B0
K1 A1 K2 B1
K2 A2
左连接 (how='left'):
ValL ValR
K0 A0 B0
K1 A1 NaN ← 保留左表所有索引
K2 A2 B1
外连接 (how='outer'):
ValL ValR
K0 A0 B0
K1 A1 NaN
K2 A2 B1
K3 NaN B2 ← 保留两表所有索引
使用示例:
基本索引连接:
left = pd.DataFrame({'A': ['A0', 'A1'], 'B': ['B0', 'B1']}, index=['K0', 'K1'])
right = pd.DataFrame({'C': ['C0', 'C1'], 'D': ['D0', 'D1']}, index=['K0', 'K2'])
# 左连接(默认)
result = left.join(right)
A B C D
K0 A0 B0 C0 D0
K1 A1 B1 NaN NaN
连接多个DataFrame:
right2 = pd.DataFrame({'E': ['E0', 'E1']}, index=['K0', 'K1'])
result = left.join([right, right2])
基于列连接(转为索引):
# 将列设为索引后再连接
result = left.set_index('key').join(right.set_index('key'))
三种方法对比与选择指南
特性 | concat | merge | join |
---|---|---|---|
主要用途 | 简单堆叠 | 键值连接 | 索引连接 |
连接维度 | 行或列 | 列 | 索引 |
连接逻辑 | 位置对齐 | 键值匹配 | 索引匹配 |
多表连接 | 支持 | 仅两表 | 支持多表 |
列名处理 | 自动处理 | 后缀处理 | 后缀处理 |
索引保留 | 可选保留 | 新建索引 | 保留左索引 |
选择指南:
- 需要简单堆叠数据 → concat
- 需要基于列值连接(类似SQL JOIN) → merge
- 需要基于索引连接 → join
- 连接多个表 → concat(轴向堆叠)或 join(索引连接)
- 需要复杂连接条件 → merge
注意事项
- 处理大型数据集
# 分块合并大文件
chunk_iter = pd.read_csv('large.csv', chunksize=10000)
results = []
for chunk in chunk_iter:
merged = pd.merge(chunk, lookup_table, on='id')
results.append(merged)
final = pd.concat(results)
- 性能优化
# 设置索引加速连接
left.set_index('key', inplace=True)
right.set_index('key', inplace=True)
result = left.join(right) # 比merge更快
- 避免常见错误
# 错误:列名相同导致冲突
result = pd.merge(left, right, on='key') # 非连接列同名会加后缀
# 正确:显式指定后缀
result = pd.merge(left, right, on='key', suffixes=('_left', '_right'))
- 复杂条件合并
# 非等值合并(merge不支持,需筛选后concat)
cond = (left['date'] >= right['start_date']) & (left['date'] <= right['end_date'])
filtered = left[cond].reset_index(drop=True)
result = pd.merge(filtered, right, left_index=True, right_index=True)
- 多层索引合并
# 多层索引连接
left_index = pd.MultiIndex.from_tuples([('A',1), ('B',2)])
right_index = pd.MultiIndex.from_tuples([('A',1), ('C',3)])
result = left.set_index(left_index).join(right.set_index(right_index))
掌握concat、merge和join的使用场景和技巧,能够高效地解决各类数据合并问题,是Pandas数据处理的核心技能之一。根据数据特性和需求选择合适的方法,可以大幅提升数据整合的效率和质量。
2.6 时间序列处理
Pandas提供了强大的时间序列处理功能,尤其适用于金融、物联网、日志分析等领域。其核心是Timestamp、DatetimeIndex和Period对象,结合重采样、滑动窗口等操作,可以高效处理时间相关数据。
时间序列基础
- 时间戳创建
import pandas as pd
import numpy as np
# 单个时间戳
ts = pd.Timestamp('2023-08-15 14:30:00')
print(ts.year, ts.month, ts.day) # 2023 8 15
# 时间序列
dates = pd.date_range('2023-01-01', periods=5, freq='D')
print(dates)
DatetimeIndex(['2023-01-01', '2023-01-02', '2023-01-03',
'2023-01-04', '2023-01-05'],
dtype='datetime64[ns]', freq='D')
- 时间频率代码
代码 | 说明 | 代码 | 说明 |
---|---|---|---|
D | 日历日 | B | 工作日 |
H | 小时 | T或min | 分钟 |
S | 秒 | L或ms | 毫秒 |
M | 月末 | Q | 季末 |
A | 年末 | W | 周 |
BM | 工作日月末 | QS | 季初 |
时间序列索引操作
- 创建时间索引序列
# 创建带时间索引的Series
ts = pd.Series(np.random.randn(5), index=dates)
print(ts)
2023-01-01 0.469
2023-01-02 -0.282
2023-01-03 -1.509
2023-01-04 -1.136
2023-01-05 1.212
Freq: D, dtype: float64
- 时间切片与选择
# 按年份切片
print(ts['2023'])
# 按月份切片
print(ts['2023-01'])
# 按日期范围
print(ts['2023-01-02':'2023-01-04'])
# 部分字符串匹配
print(ts.truncate(before='2023-01-03'))
- 时间属性访问
# 提取日期组件
print(ts.index.year)
print(ts.index.day_name())
# 添加为数据列
ts['Year'] = ts.index.year
ts['Quarter'] = ts.index.quarter
时间重采样(Resampling)
重采样是时间序列处理的核心操作,用于改变时间频率。
- 降采样(低频聚合)
# 日数据 -> 月均值
monthly = ts.resample('M').mean()
print(monthly)
2023-01-31 -0.449
Freq: M, dtype: float64
- 升采样(高频插值)
# 日数据 -> 小时数据(前向填充)
hourly = ts.resample('6H').ffill()
print(hourly.head())
2023-01-01 00:00:00 0.469
2023-01-01 06:00:00 0.469
2023-01-01 12:00:00 0.469
2023-01-01 18:00:00 0.469
2023-01-02 00:00:00 -0.282
Freq: 6H, dtype: float64
- 灵活的重采样方法
# 自定义聚合
result = ts.resample('W').agg(['min', 'max', 'mean'])
# OHLC金融数据(开盘、最高、最低、收盘)
ohlc = ts.resample('D').ohlc()
# 分组重采样
grouped = df.groupby('Category').resample('W').sum()
滑动窗口操作
- 滚动窗口(Rolling)
# 3天滚动平均
rolling_mean = ts.rolling(window=3).mean()
print(rolling_mean)
2023-01-01 NaN
2023-01-02 NaN
2023-01-03 -0.440667
2023-01-04 -0.975667
2023-01-05 -0.144333
dtype: float64
- 扩展窗口(Expanding)
# 累计平均
expanding_mean = ts.expanding().mean()
- 指数加权窗口(EWM)
# 指数加权移动平均
ewma = ts.ewm(span=7).mean()
- 自定义窗口计算
# 滚动窗口应用自定义函数
def custom_agg(window):
return window.max() - window.min()
ts.rolling(5).apply(custom_agg)
时间偏移与日期范围
- 时间偏移(DateOffset)
# 基础偏移
print(ts.index + pd.DateOffset(days=3))
# 工作日偏移
from pandas.tseries.offsets import BDay
print(ts.index + 2*BDay())
# 复杂偏移
offset = pd.DateOffset(months=2, days=10)
print(ts.index + offset)
- 日期范围生成
# 工作日范围
bdate_range = pd.bdate_range('2023-01-01', periods=5)
# 自定义频率
custom_range = pd.date_range('2023-01-01', periods=12, freq='2W-TUE')
- 节假日处理
from pandas.tseries.holiday import USFederalHolidayCalendar
# 创建美国节假日日历
cal = USFederalHolidayCalendar()
holidays = cal.holidays('2023-01-01', '2023-12-31')
# 工作日计算(排除节假日)
bday_us = pd.offsets.CustomBusinessDay(calendar=cal)
时区处理
# 设置时区
ts_utc = ts.tz_localize('UTC')
print(ts_utc.index)
DatetimeIndex(['2023-01-01 00:00:00+00:00', ...],
dtype='datetime64[ns, UTC]', freq='D')
通过灵活组合时间索引、重采样和窗口操作,可以提取出丰富的时间特征,为后续分析和建模打下坚实基础。
总结与学习资源
Pandas的核心价值在于:
- 提供表格化数据结构,直观易用
- 实现高效数据清洗与复杂分析
学习资源:
- 官方文档
- 《Python for Data Analysis》(作者:Pandas创始人Wes McKinney)
- Kaggle Pandas教程
掌握Pandas将极大提升你的数据处理能力,为数据科学和机器学习打下坚实基础!