Pandas 综合使用教程
Pandas 是一个强大的开源数据分析和操作工具库,建立在 NumPy 之上,提供高性能的数组运算能力。它专为处理结构化数据(如表格、时间序列)而设计,是数据挖掘、数据分析和数据清洗的“利器”。其核心数据结构是
Series
和DataFrame
。
核心数据结构概览
特性 | Series |
DataFrame |
---|---|---|
维度 | 一维 | 二维 |
类比 | 带标签的数组 | 电子表格或 SQL 表 |
组成 | 一组数据 + 一组索引 (标签) | 多个 Series 的集合(按列) |
关系 | DataFrame 的每一行或每一列都是一个 Series |
可视为一个由 Series 组成的字典,其中键是列名,值是 Series |
数据类型 | 通常为同质(所有元素类型相同) | 通常为异构(不同列可有不同数据类型) |
1. Series:一维带标签数组
Series
是 Pandas 中最基本的数据结构,表示一个一维的、带标签的数组。
1.1 创建 Series
import pandas as pd
import numpy as np
# 从列表、元组创建 (默认整数索引)
s1 = pd.Series([1, 3, 5, np.nan, 6, 8])
s2 = pd.Series((1, 2, 3, 4, 5)) # 元组
# 从字典创建 (字典的键自动成为索引)
s3 = pd.Series({'a': 1, 'b': 2, 'c': 3})
# 从 NumPy 数组创建,并指定索引
s4 = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
# 从标量创建 (标量值被重复)
s5 = pd.Series(5., index=['a', 'b', 'c', 'd'])
# 创建时指定索引
s6 = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
1.2 常用属性
.values
: 返回底层的 NumPy 数组。.index
: 返回索引对象(行标签)。.name
: 设置或获取Series
的名称。.dtype
: 返回数据类型。.size
: 返回元素总数。.shape
: 返回形状(元组,例如(5,)
)。.is_unique
: 检查索引是否唯一。.keys()
: 等同于.index
,返回索引。
s = pd.Series([10, 20, 30, 40], index=['w', 'x', 'y', 'z'], name='Scores')
print("Values (底层数组):", s.values) # [10 20 30 40]
print("Index (索引):", s.index) # Index(['w', 'x', 'y', 'z'], dtype='object')
print("Name (名称):", s.name) # Scores
print("Dtype (数据类型):", s.dtype) # int64 (或 float64)
print("Size (元素个数):", s.size) # 4
print("Shape (形状):", s.shape) # (4,)
print("Is Unique (索引是否唯一):", s.index.is_unique) # True
print("Keys (等同于 index):", s.keys().tolist()) # ['w', 'x', 'y', 'z']
1.3 常用方法
- 查看数据:
.head(n=5)
: 显示前n
个元素。.tail(n=5)
: 显示后n
个元素。
- 统计计算 (数值型):
.describe()
: 生成描述性统计摘要。.mean()
,.sum()
,.max()
,.min()
,.std()
: 计算均值、总和、最大值、最小值、标准差。
- 数据转换:
.abs()
: 计算绝对值。.round(decimals=0)
: 四舍五入。.to_list()
: 转换为 Python 列表。
- 缺失值处理:
.dropna()
: 删除缺失值。.fillna(value)
: 填充缺失值。
- 排序:
.sort_values(ascending=True)
: 按值排序。.sort_index()
: 按索引排序。
- 去重与计数:
.unique()
: 返回唯一值数组。.nunique()
: 返回唯一值的数量(排除 NaN)。.value_counts()
: 返回每个唯一值的出现次数。
- 函数应用:
.apply(func)
: 将函数func
应用于每个元素。.map(dict_or_func)
: 将字典映射或函数应用于每个元素。
np.random.seed(0) # 保证结果可重现
s = pd.Series(np.random.randn(10))
print("原始 Series:\n", s)
print("\n前3个元素 (head):\n", s.head(3))
print("\n后3个元素 (tail):\n", s.tail(3))
print("\n描述性统计 (describe):\n", s.describe())
print("\n平均值 (mean):", s.mean())
print("\n绝对值 (abs):\n", s.abs())
print("\n四舍五入到2位小数 (round):\n", s.round(2))
print("\n转换为列表 (to_list):\n", s.head(3).to_list())
print("\n按值降序排序 (sort_values):\n", s.sort_values(ascending=False))
print("\n唯一值 (unique):", s.round().unique()) # 先取整再找唯一值
print("\n唯一值数量 (nunique):", s.round().nunique())
print("\n值计数 (value_counts):\n", s.round().value_counts())
1.4 索引与选择
.loc[label]
: 基于标签(索引名)进行选择。.iloc[position]
: 基于位置(整数索引)进行选择。
scientist = pd.Series(['Rosaline Franklin', '1920-07-25', '1958-04-16', 37, 'Chemist'],
index=['Name', 'Born', 'Died', 'Age', 'Occupation'])
print("Series:\n", scientist)
print("\n通过标签获取 'Name':", scientist.loc['Name']) # Rosaline Franklin
print("通过位置获取第4个元素:", scientist.iloc[3]) # 37
print("通过标签获取 'Age' 和 'Name':\n", scientist.loc[['Age', 'Name']])
print("通过位置获取前3个元素:\n", scientist.iloc[:3])
1.5 布尔索引
使用布尔条件选择元素。
s = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
# 基本条件
filtered_s = s[s > 3] # 保留值大于 3 的元素
# 复合条件 (使用 &, |, ~,注意括号)
filtered_s2 = s[(s > 1) & (s < 5)]
# 检查成员资格
mask = s.isin([2, 4])
print(s[mask])
1.6 运算
支持向量化运算,基于索引对齐。
s1 = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s2 = pd.Series([4, 5, 6], index=['a', 'b', 'c'])
# 基本算术 (索引对齐)
result = s1 + s2 # a:5, b:7, c:9
# 索引不匹配 -> 结果为 NaN
s3 = pd.Series([1, 2], index=['a', 'b'])
s4 = pd.Series([3, 4], index=['b', 'c'])
result = s3 + s4 # a:NaN, b:5, c:NaN
# 与标量运算
scaled = s1 * 10 # 所有元素乘以 10
2. DataFrame:二维表格型数据结构
DataFrame
是 Pandas 的核心,是一个大小可变、通常异构的二维带标签数据结构。
2.1 创建 DataFrame
# 从字典创建 (键为列名,值为数据)
data = {
'Name': ['Alice', 'Bob', 'Charlie'],
'Age': [25, 30, 35],
'City': ['NYC', 'LA', 'Chicago']
}
df = pd.DataFrame(data)
# 从列表的字典或字典的列表创建
df2 = pd.DataFrame([
{'Name': 'Alice', 'Age': 25},
{'Name': 'Bob', 'Age': 30}
])
# 从 NumPy 数组创建
df3 = pd.DataFrame(np.random.randn(4, 3), columns=['A', 'B', 'C'], index=['w', 'x', 'y', 'z'])
# 从 Series 字典创建
series_dict = {'col1': pd.Series([1, 2]), 'col2': pd.Series([3, 4])}
df4 = pd.DataFrame(series_dict)
# 创建时指定列顺序和行索引
df5 = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'c': [5, 6]},
columns=['c', 'b', 'a'], index=['x', 'y'])
2.2 常用属性
.values
: 返回底层的 NumPy 二维数组。.index
: 返回行索引。.columns
: 返回列索引(列名)。.dtypes
: 返回每列的数据类型。.shape
: 返回形状(行数, 列数)
。.size
: 返回元素总数。.ndim
: 返回维度数(2)。.T
/.transpose()
: 返回转置后的 DataFrame。.keys()
: 等同于.columns
,返回列名。
print("DataFrame:\n", df)
print("\nValues (底层数组):\n", df.values)
print("Index (行索引):", df.index.tolist())
print("Columns (列名):", df.columns.tolist())
print("Dtypes (各列数据类型):\n", df.dtypes)
print("Shape (形状):", df.shape) # (4, 4)
print("Size (总元素数):", df.size) # 16
print("NDim (维度):", df.ndim) # 2
print("T (转置):\n", df.T)
2.3 常用方法
- 查看与信息:
.head(n=5)
/.tail(n=5)
: 显示前/后n
行。.info()
: 显示综合信息(列名、非空计数、数据类型、内存使用)。
- 统计计算:
.describe()
: 生成数值列的描述性统计。.mean()
,.sum()
,.max()
,.min()
,.std()
: 计算每列(axis=0
)或每行(axis=1
)的统计量。.count()
: 每列非空值的数量。.corr()
: 计算数值列间的相关系数矩阵。
- 数据操作:
.drop(labels, axis=0/1)
: 删除行 (axis=0
) 或列 (axis=1
)。.dropna(axis=0, how='any', subset=None)
: 删除包含缺失值的行/列。.fillna(value)
: 填充缺失值。.rename(columns={'old': 'new'}, inplace=False)
: 重命名列或行索引。.astype(dtype)
: 转换列的数据类型。.duplicated()
/.drop_duplicates()
: 检测和删除重复行。
- 排序与索引:
.sort_values(by, ascending=True)
: 按一列或多列的值排序。.sort_index(axis=0)
: 按行或列索引排序。.reset_index(drop=False, inplace=False)
: 重置索引(原索引可变为一列)。.set_index(keys, inplace=False)
: 将一列或多列设置为新的行索引。
- 函数应用:
.apply(func, axis=0)
: 将函数应用于每列 (axis=0
) 或每行 (axis=1
)。.agg(func)
/.aggregate(func)
: 聚合操作,可对不同列应用不同函数。
- 分组与聚合:
.groupby(by)
: 根据指定列分组,是聚合的基础。.transform(func)
: 分组后应用函数,返回与原 DataFrame 形状相同的对象。
- 成员资格检查:
.isin(values)
: 检查每个元素是否在给定值中,返回布尔 DataFrame。
- 抽样:
.sample(n=None, frac=None)
: 随机抽样行。
print("前2行 (head):\n", df.head(2))
print("\n后2行 (tail):\n", df.tail(2))
print("\n综合信息 (info):")
df.info() # 打印到控制台
print("\n描述性统计 (describe):\n", df.describe())
print("\n每列均值 (mean):\n", df.mean(numeric_only=True))
print("\n每列最大值 (max):\n", df.max(numeric_only=True))
# 添加、删除、重命名列
df['Pass'] = df['Score'] > 80 # 添加新列
print("\n添加 'Pass' 列后:\n", df)
df_dropped = df.drop('Pass', axis=1) # 删除列,返回新对象
print("\n删除 'Pass' 列后 (新对象):\n", df_dropped)
df_renamed = df.rename(columns={'Score': 'Final_Score'}) # 重命名列,返回新对象
print("\n重命名 'Score' 为 'Final_Score' 后:\n", df_renamed)
# 排序
df_sorted = df.sort_values(by='Age', ascending=False)
print("\n按 'Age' 降序排序:\n", df_sorted)
# 分组聚合
grouped = df.groupby('City')['Score'].mean()
print("\n按 'City' 分组计算 'Score' 平均值:\n", grouped)
# 检测重复行
duplicates = df.duplicated()
print("\n重复行检测:\n", duplicates)
2.4 布尔索引
使用布尔条件选择行或元素。
df = pd.DataFrame({
'Name': ['Alice', 'Bob', 'Charlie', 'Diana'],
'Age': [25, 30, 35, 28],
'City': ['NYC', 'LA', 'Chicago', 'NYC'],
'Score': [85.5, 90.0, 78.0, 92.5]
})
# 选择满足条件的行
adults = df[df['Age'] >= 30]
# 复合条件
ny_young = df[(df['City'] == 'NYC') & (df['Age'] < 30)]
# 使用 isin
in_cities = df[df['City'].isin(['NYC', 'Chicago'])]
# 结合访问器 (如 str)
starts_with_a = df[df['Name'].str.startswith('A')]
# 选择特定列的同时进行布尔索引
names_over_30 = df.loc[df['Age'] > 30, 'Name']
2.5 运算
支持向量化运算,基于行和列索引对齐。
df1 = pd.DataFrame({'A': [1, 2], 'B': [3, 4]}, index=['x', 'y'])
df2 = pd.DataFrame({'A': [5, 6], 'B': [7, 8]}, index=['x', 'y'])
# DataFrame 间运算 (索引对齐)
add_df = df1 + df2
# DataFrame 与 Series 运算 (广播,Series 索引匹配 DataFrame 列)
s = pd.Series([10, 20], index=['A', 'B'])
df_plus_s = df1 + s # 每行都加上 Series s
# 与标量运算
scaled_df = df1 * 100
# 索引不匹配 -> 结果为 NaN
df3 = pd.DataFrame({'A': [1], 'C': [2]}, index=['x'])
df4 = pd.DataFrame({'A': [3], 'B': [4]}, index=['y'])
result_df = df3 + df4
3. 数据读取与写入
3.1 常用格式
操作 | 读取 | 写入 |
---|---|---|
CSV | pd.read_csv('file.csv') |
df.to_csv('file.csv', index=False) |
Excel | pd.read_excel('file.xlsx') |
df.to_excel('file.xlsx', sheet_name='Sheet1') |
SQL | pd.read_sql('query', connection) |
df.to_sql('table_name', connection, if_exists='replace') |
Pickle | pd.read_pickle('file.pkl') |
df.to_pickle('file.pkl') |
注意: 读写 Excel 需要额外安装包:
xlrd
,openpyxl
,xlwt
。
# --- 读取 ---
# df_csv = pd.read_csv('data.csv')
# df_excel = pd.read_excel('data.xlsx')
# df_sql = pd.read_sql('SELECT * FROM table', connection)
# df_pickle = pd.read_pickle('data.pkl')
# --- 写入 ---
# df.to_csv('output.csv', index=False)
# df.to_excel('output.xlsx', sheet_name='Sheet1', index=False)
# df.to_sql('table_name', connection, if_exists='replace', index=False)
# df.to_pickle('output.pkl')
# 示例:创建一个 DataFrame 并写入/读取 CSV
example_df = pd.DataFrame({'A': [1, 2], 'B': [3, 4]})
example_df.to_csv('temp_example.csv', index=False)
read_back_df = pd.read_csv('temp_example.csv')
print("从 CSV 读回的数据:\n", read_back_df)
# 示例:Pickle
example_df.to_pickle('temp_example.pkl')
read_back_pickle = pd.read_pickle('temp_example.pkl')
print("从 Pickle 读回的数据:\n", read_back_pickle)
3.2 Pickle 文件
- 用途: 保存 Python 对象(如 DataFrame, Series)的中间结果,仅限 Python 环境复用。
- 扩展名:
.p
,.pkl
,.pickle
。
4. 数据操作进阶
4.1 合并与连接
pd.concat([df1, df2], axis=0)
: 沿行 (axis=0
) 或列 (axis=1
) 连接多个 DataFrame。df1.merge(df2, on='key', how='inner')
: 基于键进行数据库风格的连接 (inner
,outer
,left
,right
)。
left = pd.DataFrame({'key': ['K0', 'K1', 'K2'], 'A': ['A0', 'A1', 'A2'], 'B': ['B0', 'B1', 'B2']})
right = pd.DataFrame({'key': ['K0', 'K1', 'K3'], 'C': ['C0', 'C1', 'C3'], 'D': ['D0', 'D1', 'D3']})
print("Left DataFrame:\n", left)
print("Right DataFrame:\n", right)
# 内连接 (inner join)
merged_inner = pd.merge(left, right, on='key', how='inner')
print("\n内连接 (how='inner'):\n", merged_inner)
# 左连接 (left join)
merged_left = pd.merge(left, right, on='key', how='left')
print("\n左连接 (how='left'):\n", merged_left)
# 连接 (concat)
concatenated = pd.concat([left, right], axis=0, ignore_index=True) # 沿行连接
print("\n沿行连接 (axis=0):\n", concatenated)
4.2 数据重塑
.pivot(index, columns, values)
: 将长格式数据转换为宽格式。.melt(id_vars, value_vars, var_name, value_name)
: 将宽格式数据转换为长格式。
# 原始宽格式数据
wide_df = pd.DataFrame({
'Student': ['Alice', 'Bob'],
'Math': [85, 90],
'Science': [78, 88],
'English': [92, 85]
})
print("宽格式数据:\n", wide_df)
# 转换为长格式 (melt)
long_df = wide_df.melt(id_vars='Student', value_vars=['Math', 'Science', 'English'],
var_name='Subject', value_name='Score')
print("\n长格式数据 (melt):\n", long_df)
# 转换回宽格式 (pivot)
pivoted_df = long_df.pivot(index='Student', columns='Subject', values='Score')
print("\n宽格式数据 (pivot):\n", pivoted_df)
4.3 时间序列
pd.to_datetime()
: 将数据转换为日期时间类型。df['date'] = pd.to_datetime(df['date'])
: 转换列为日期时间。.resample('D').mean()
: 按天重采样并计算均值(需日期索引)。
# 创建时间序列数据
dates = pd.date_range('20230101', periods=6)
ts = pd.Series(np.random.randn(6), index=dates)
print("时间序列 Series:\n", ts)
# 转换为日期时间
df_time = pd.DataFrame({
'Date': ['2023-01-01', '2023-01-02', '2023-01-03'],
'Value': [100, 105, 110]
})
df_time['Date'] = pd.to_datetime(df_time['Date'])
df_time.set_index('Date', inplace=True)
print("\n设置日期索引后的 DataFrame:\n", df_time)
# 重采样
daily_data = pd.DataFrame({
'Value': np.random.randn(365)
}, index=pd.date_range('2023-01-01', periods=365, freq='D'))
monthly_mean = daily_data.resample('M').mean() # 按月重采样取均值
print("\n按月重采样均值 (前几行):\n", monthly_mean.head())
4.4 访问器 (Accessors)
提供特定类型数据的便捷操作方法:
.str
: 字符串操作(如.str.upper()
,.str.contains()
)。.dt
: 日期时间操作(如.dt.year
,.dt.month
)。.cat
: 分类数据操作。
# 字符串访问器
df_str = pd.DataFrame({'Name': ['alice', 'BOB', 'charlie'], 'Email': ['A@G.COM', 'b@G.COM', 'C@G.COM']})
print("原始 DataFrame:\n", df_str)
print("\n名字转大写:\n", df_str['Name'].str.upper())
print("邮箱转小写:\n", df_str['Email'].str.lower())
print("名字长度:\n", df_str['Name'].str.len())
print("名字是否包含 'a':\n", df_str['Name'].str.contains('a', case=False))
# 日期时间访问器
df_time['Year'] = df_time.index.year
df_time['Month'] = df_time.index.month
df_time['DayOfWeek'] = df_time.index.dayofweek
print("\n添加日期时间信息:\n", df_time)
5. 实用技巧与最佳实践
- 创建副本: 使用
.copy(deep=True)
避免意外修改原数据。 - 链式操作:
df.dropna().sort_values('col').head(10)
。 inplace
参数: 大多数修改方法有inplace=True/False
。False
(默认) 返回新对象,True
修改原对象并返回None
。建议谨慎使用inplace=True
,优先返回新对象以保持代码清晰和可调试性。- 性能: Pandas 操作通常是向量化的,效率高。避免使用
iterrows()
进行循环,优先使用.apply()
或向量化操作。
# 创建副本
df_original = df.copy(deep=True) # 创建深拷贝
df_modified = df_original.drop('Pass', axis=1) # 在副本上操作
# 链式操作
result = (df
.dropna() # 删除缺失值
.sort_values('Score', ascending=False) # 按分数降序
.head(3) # 取前3名
.reset_index(drop=True) # 重置索引
)
print("\n链式操作结果 (前三名):\n", result)
# 谨慎使用 inplace
df_clean = df.drop('Pass', axis=1) # 推荐:返回新对象
# df.drop('Pass', axis=1, inplace=True) # 不推荐:修改原对象,返回 None
# 性能提示:避免 iterrows
# 低效
# for index, row in df.iterrows():
# print(row['Name'], row['Age'])
# 高效 (向量化)
print("\n高效方式 (向量化):")
print(df['Name'] + " is " + df['Age'].astype(str) + " years old.")
# 使用 apply (当向量化不可行时)
def age_category(age):
if age < 30:
return 'Young'
elif age < 40:
return 'Adult'
else:
return 'Senior'
df['Age_Category'] = df['Age'].apply(age_category)
print("\n应用函数后的 DataFrame:\n", df)