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 行
locdf.loc[行标签, 列标签]基于标签的索引按日期或股票代码筛选
ilocdf.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 数据清洗流程总览

量化因子数据清洗流程 1. 数据读取 CSV / Excel / SQL 2. 类型检查 dtypes 确认与转换 3. 索引设置 日期 / 股票代码 4. 缺失值处理 检测 → 填充 / 删除 5. 重复值处理 检测 → 去重(保留策略) 6. 最终验证 唯一性 / 范围 / 分布检查 ⚠ 注意事项 • 先转换类型再处理缺失 • 时间序列用 ffill 填充 • 截面数据用分组均值

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 等参数可减少后续清洗工作量。
  • 缺失值处理需根据数据类型选择策略:时间序列用向前填充,截面数据用分组均值或中位数填充。
  • 重复值处理应基于业务主键(股票代码+日期)进行去重,并明确保留策略。
  • 数据类型转换不仅影响计算精度,更直接决定内存占用,是处理大规模因子数据的关键优化点。