4. 数据清洗与预处理
任务目标
对获取的股票日线行情数据执行标准化清洗:处理缺失值、去除重复记录、统一日期与数值格式,并输出数据质量报告,确保后续策略计算使用的数据干净一致。
实现方案
使用 pandas 构建一个可复用的清洗流水线,覆盖以下核心环节:
- 缺失值处理:对价格类字段(开高低收)采用前向填充,成交量缺失则补 0(停牌日无成交);若开盘价仍缺失则丢弃该行。
- 去重:按
股票代码 + 日期去重,保留最后一条记录(应对盘中多次抓取)。 - 格式转换:日期统一为
YYYY-MM-DD,数值列转为float64,股票代码转为字符串并补零到 6 位。 - 质量检查:输出每列的缺失计数、重复行数、日期范围及价格非正数检查结果。
完整代码
import pandas as pd
import numpy as np
def clean_stock_data(df: pd.DataFrame) -> pd.DataFrame:
"""
对股票日线数据执行清洗。
要求输入列:ts_code, trade_date, open, high, low, close, vol
"""
# 复制避免修改原数据
df = df.copy()
# ---------- 1. 格式转换 ----------
# 日期统一为 datetime 类型
df['trade_date'] = pd.to_datetime(df['trade_date'], format='%Y%m%d')
# 股票代码转为 6 位字符串
df['ts_code'] = df['ts_code'].astype(str).str.zfill(6)
# 数值列转为 float
price_cols = ['open', 'high', 'low', 'close']
for col in price_cols:
df[col] = pd.to_numeric(df[col], errors='coerce')
df['vol'] = pd.to_numeric(df['vol'], errors='coerce')
# ---------- 2. 去重 ----------
# 按代码+日期去重,保留最后一条
df = df.drop_duplicates(subset=['ts_code', 'trade_date'], keep='last')
# ---------- 3. 缺失值处理 ----------
# 先按股票代码分组,组内按日期排序后前向填充价格
df = df.sort_values(['ts_code', 'trade_date']).reset_index(drop=True)
df[price_cols] = df.groupby('ts_code')[price_cols].ffill()
# 成交量缺失补 0(停牌无成交)
df['vol'] = df['vol'].fillna(0)
# 若开盘价仍缺失(该股票首个交易日即缺失),删除该行
df = df.dropna(subset=['open'])
# ---------- 4. 质量检查 ----------
print("=== 数据质量报告 ===")
print(f"总行数: {len(df)}")
print(f"股票数量: {df['ts_code'].nunique()}")
print(f"日期范围: {df['trade_date'].min().date()} ~ {df['trade_date'].max().date()}")
print("\n缺失值统计:")
print(df.isnull().sum())
print("\n重复行数:", df.duplicated(subset=['ts_code', 'trade_date']).sum())
# 价格非正数检查
invalid_price = df[(df[price_cols] <= 0).any(axis=1)]
print(f"价格非正数行数: {len(invalid_price)}")
# 检查 high >= low 且 close 在 [low, high] 区间内
invalid_hl = df[(df['high'] < df['low']) | (df['close'] > df['high']) | (df['close'] < df['low'])]
print(f"高低价逻辑异常行数: {len(invalid_hl)}")
return df
# ---------- 最小可复现示例 ----------
if __name__ == "__main__":
# 构造含脏数据的示例输入
raw_data = pd.DataFrame({
'ts_code': ['600000', '600000', '600000', '000001', '000001', '000001'],
'trade_date': ['20240102', '20240102', '20240103', '20240102', '20240103', '20240104'],
'open': [10.5, 10.8, None, 5.2, 5.4, 5.6],
'high': [10.9, 11.0, 10.7, 5.5, 5.7, 5.9],
'low': [10.2, 10.5, 10.1, 5.0, 5.2, 5.4],
'close': [10.7, 10.6, 10.5, 5.3, 5.5, 5.8],
'vol': [10000, None, 12000, 8000, 9000, None]
})
cleaned = clean_stock_data(raw_data)
print("\n=== 清洗后数据 ===")
print(cleaned.to_string(index=False))
运行与验证
环境依赖:pandas 与 numpy(pip install pandas numpy)。
运行方式:直接执行脚本,示例数据会经过完整清洗流程并打印质量报告。
通过条件:
- 质量报告中“重复行数”为 0;
- “缺失值统计”中所有列均为 0;
- “价格非正数行数”为 0;
- “高低价逻辑异常行数”为 0;
- 清洗后数据中
600000在 20240102 的重复记录仅保留 1 条,且open缺失值被前向填充为 10.8(来自前一行),vol缺失值被填充为 0。
预期输出关键行(由给定输入唯一确定):清洗后共 5 行数据,其中 600000/20240102 仅保留最后一条(open=10.8),600000/20240103 的 open 被填充为 10.8,000001/20240104 的 vol 被填充为 0。