4. Pandas 数据处理基础:Series 与 DataFrame 的创建与索引;数据读取与清洗
在量化因子研究中,原始数据通常以 CSV、Excel 或数据库表的形式存在。Pandas 是 Python 生态中最核心的数据分析库,它提供了两种基础数据结构:Series(一维带标签数组)和 DataFrame(二维表格)。本章将系统讲解如何创建、索引、读取数据,并完成缺失值、重复值处理及数据类型转换。
4.1 Series 与 DataFrame 的创建
4.1.1 Series 创建
Series 由索引(index)和数据(values)两部分组成。创建方式包括从列表、字典或标量生成。
import pandas as pd
import numpy as np
# 从列表创建,默认整数索引
s1 = pd.Series([1.5, 2.3, 3.8, 4.1])
print(s1)
# 从字典创建,键自动成为索引
s2 = pd.Series({'AAPL': 150.25, 'GOOGL': 2800.50, 'MSFT': 310.75})
print(s2)
# 指定索引
s3 = pd.Series([0.05, 0.03, 0.07], index=['因子A', '因子B', '因子C'])
print(s3)
4.1.2 DataFrame 创建
DataFrame 是最常用的表格结构,可通过字典、列表或 NumPy 数组创建。
# 从字典创建(键为列名,值为列数据)
data = {
'股票代码': ['000001', '600519', '300750'],
'收盘价': [12.35, 2050.00, 180.60],
'成交量(万手)': [1500, 320, 890],
'市盈率': [8.5, 45.2, 60.8]
}
df = pd.DataFrame(data)
print(df)
# 从 NumPy 数组创建,指定列名和索引
arr = np.random.randn(4, 3)
df2 = pd.DataFrame(arr, columns=['因子1', '因子2', '因子3'],
index=['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04'])
print(df2)
量化提示:在因子研究中,建议始终将日期列设为 DataFrame 的索引(index),便于后续时间序列对齐和滚动计算。
4.2 索引与选择
4.2.1 基础索引方法对比
| 方法 | 语法 | 说明 | 适用场景 |
|---|---|---|---|
| 列索引 | df['列名'] | 返回 Series | 提取单列因子 |
| 行切片 | df[start:end] | 按行号切片 | 快速查看前 N 行 |
| loc | df.loc[行标签, 列标签] | 基于标签的索引 | 按日期或股票代码筛选 |
| iloc | df.iloc[行位置, 列位置] | 基于整数位置的索引 | 按行列序号定位 |
| 布尔索引 | df[df['列'] > 阈值] | 条件筛选 | 过滤异常值或极值 |
# loc 示例:按日期标签和列名提取
df_date = df2.loc['2024-01-01':'2024-01-03', ['因子1', '因子2']]
print(df_date)
# iloc 示例:提取前两行、后两列
df_sub = df2.iloc[:2, 1:3]
print(df_sub)
# 布尔索引:筛选市盈率大于 40 的股票
high_pe = df[df['市盈率'] > 40]
print(high_pe)
4.3 数据读取:CSV、Excel 与 SQL
4.3.1 读取 CSV
CSV 是量化数据最通用的存储格式。常用参数包括文件路径、分隔符、编码、日期解析等。
# 基础读取
df_csv = pd.read_csv('daily_factor_data.csv')
# 常用参数组合
df_csv = pd.read_csv(
'daily_factor_data.csv',
sep=',', # 分隔符
encoding='utf-8', # 编码
parse_dates=['date'], # 自动解析日期列
index_col='date', # 将日期列设为索引
na_values=['NA', 'NULL', ''], # 指定缺失值标记
dtype={'股票代码': str} # 指定列数据类型
)
4.3.2 读取 Excel
Excel 文件通常包含多个工作表,可通过 sheet_name 参数指定。
# 读取第一个工作表
df_excel = pd.read_excel('因子数据.xlsx', sheet_name=0)
# 读取指定工作表
df_excel = pd.read_excel(
'因子数据.xlsx',
sheet_name='因子表',
header=0, # 第 0 行为列名
usecols='A:F', # 只读取 A 到 F 列
dtype={'因子代码': str}
)
4.3.3 读取 SQL 数据库
使用 read_sql 函数可直接从数据库查询结果创建 DataFrame。
from sqlalchemy import create_engine
# 创建数据库连接
engine = create_engine('mysql+pymysql://user:password@localhost:3306/quant_db')
# 执行 SQL 查询
query = """
SELECT trade_date, stock_code, close_price, volume, pe_ratio
FROM daily_factor
WHERE trade_date >= '2024-01-01'
"""
df_sql = pd.read_sql(query, con=engine, parse_dates=['trade_date'])
性能注意:读取大文件时(超过 100 万行),建议使用
chunksize 参数分块读取,或使用 dtype 指定列类型以节省内存。
4.4 缺失值处理
4.4.1 检测缺失值
Pandas 使用 NaN(Not a Number)表示缺失值。常用检测方法如下:
# 检测缺失值
print(df.isnull().sum()) # 每列缺失数量
print(df.isnull().sum().sum()) # 总缺失数量
print(df.isnull().mean() * 100) # 每列缺失比例(%)
4.4.2 缺失值处理策略
| 方法 | 函数 | 说明 | 量化场景建议 |
|---|---|---|---|
| 删除含缺失的行 | df.dropna() | 删除任何包含 NaN 的行 | 缺失比例 < 5% 时使用 |
| 删除全为缺失的列 | df.dropna(axis=1, how='all') | 删除整列全为 NaN 的列 | 因子完全缺失时使用 |
| 向前填充 | df.fillna(method='ffill') | 用上一个有效值填充 | 时间序列数据常用 |
| 向后填充 | df.fillna(method='bfill') | 用下一个有效值填充 | 避免未来信息泄露时慎用 |
| 固定值填充 | df.fillna(value) | 用指定值(如 0、均值)填充 | 截面因子常用行业均值 |
| 插值法 | df.interpolate() | 线性插值填充 | 连续时间序列 |
# 示例:用行业均值填充缺失的市盈率
industry_mean = df.groupby('行业')['市盈率'].transform('mean')
df['市盈率'].fillna(industry_mean, inplace=True)
# 时间序列向前填充
df_time['收盘价'].fillna(method='ffill', inplace=True)
4.5 重复值处理
4.5.1 检测与删除重复值
重复值会导致因子计算偏差,尤其在合并多个数据源时常见。
# 检测重复行
print(df.duplicated().sum()) # 完全重复的行数
print(df.duplicated(subset=['股票代码', '交易日期']).sum()) # 基于关键列检测
# 删除重复行
df_clean = df.drop_duplicates() # 删除完全重复
df_clean = df.drop_duplicates(subset=['股票代码', '交易日期'], keep='last') # 保留最后一条
关键原则:在因子数据中,
(股票代码, 交易日期) 应构成唯一主键。建议在数据清洗后使用 df.set_index(['股票代码', '交易日期']).index.is_unique 验证唯一性。
4.6 数据类型转换
4.6.1 常见类型转换
正确的数据类型能提升计算效率并避免隐式错误。
| 目标类型 | 转换方法 | 典型场景 |
|---|---|---|
| 数值型 | pd.to_numeric() | 将字符串数字转为 float/int |
| 日期型 | pd.to_datetime() | 将字符串日期转为 datetime |
| 类别型 | df['列'].astype('category') | 行业、板块等有限分类 |
| 字符串 | df['列'].astype(str) | 股票代码等标识符 |
# 数值转换(处理混合类型)
df['成交量'] = pd.to_numeric(df['成交量'], errors='coerce') # 无法转换的变为 NaN
# 日期转换
df['交易日期'] = pd.to_datetime(df['交易日期'], format='%Y%m%d')
# 类别转换(节省内存)
df['行业'] = df['行业'].astype('category')
# 查看转换后的数据类型
print(df.dtypes)
4.6.2 内存优化技巧
量化数据通常包含数百万行,合理的数据类型可大幅降低内存占用。
# 将 float64 降为 float32(精度足够因子计算)
for col in df.select_dtypes(include=['float64']).columns:
df[col] = df[col].astype('float32')
# 将 int64 降为 int32
for col in df.select_dtypes(include=['int64']).columns:
df[col] = df[col].astype('int32')
print(f"内存占用: {df.memory_usage(deep=True).sum() / 1024**2:.2f} MB")
4.7 数据清洗流程总览
4.8 实战案例:因子数据清洗流水线
以下代码整合了本章所有知识点,构建一个完整的因子数据清洗函数:
def clean_factor_data(file_path, date_col='trade_date', id_col='stock_code'):
"""
因子数据清洗流水线
参数:
file_path: CSV 文件路径
date_col: 日期列名
id_col: 股票代码列名
返回:
清洗后的 DataFrame
"""
# 1. 读取数据
df = pd.read_csv(file_path, parse_dates=[date_col], dtype={id_col: str})
# 2. 设置索引
df.set_index([date_col, id_col], inplace=True)
# 3. 数据类型优化
for col in df.select_dtypes(include=['float64']).columns:
df[col] = df[col].astype('float32')
# 4. 缺失值处理(时间序列向前填充)
df = df.groupby(level=id_col).apply(lambda x: x.fillna(method='ffill'))
# 5. 重复值处理(保留最后一条)
df = df[~df.index.duplicated(keep='last')]
# 6. 验证
assert df.index.is_unique, "索引不唯一,请检查数据"
print(f"清洗完成,数据维度: {df.shape}")
print(f"缺失值比例: {df.isnull().sum().sum() / df.size:.2%}")
return df
# 调用示例
clean_df = clean_factor_data('factor_data_2024.csv')
进阶提示:在实际量化生产中,建议将清洗逻辑封装为类(class),并加入日志记录和异常处理,便于回测系统集成。
4.9 本章小结
- Series 与 DataFrame 是 Pandas 的两大核心数据结构,掌握创建与索引是数据处理的基础。
- 数据读取支持 CSV、Excel、SQL 等多种来源,合理设置
parse_dates、dtype等参数可减少后续清洗工作量。 - 缺失值处理需根据数据类型选择策略:时间序列用向前填充,截面数据用分组均值或中位数填充。
- 重复值处理应基于业务主键(股票代码+日期)进行去重,并明确保留策略。
- 数据类型转换不仅影响计算精度,更直接决定内存占用,是处理大规模因子数据的关键优化点。