1. 这节究竟解决什么实际问题?¶
2.2 数据清洗 的具体任务是:处理缺失、重复、复权和交易日不一致,让数据表先可信再可用。 本节将明确输入数据、计算过程、输出及其在后续研究中的用途。
2. 前置知识¶
掌握 DataFrame 的行、列、索引与缺失值;建议先完成收益率一节。 对公式的理解应包括变量定义、单位、时间索引和适用条件。
3. 数据说明¶
默认使用固定 CSV;在线接口只用于扩展。必须记录来源、下载日、字段定义、时区和复权方式。 运行前先检查 shape、日期范围、列名与缺失值。数据看起来能画图,不代表它适合回答研究问题。
4. 学习目标与完成标准¶
完成本节后,你应能用自己的话解释 2.2 数据清洗 的输入、计算步骤、输出和局限;尤其能说明:处理缺失、重复、复权和交易日不一致,让数据表先可信再可用。 你还应能独立改动一个参数并解释结果为什么变化,指出至少一个会让结论失真的假设。
5. 核心概念与公式¶
原始数据需要先完成字段核验、缺失值处理、异常检查和来源记录,随后才能用于特征计算。
特征只由时点 t 及以前的信息计算;目标值可以在未来,但绝不能混入特征列。 看到公式时依次确认:每个符号的单位是什么?它属于哪个时点?分母会不会为零?它是描述性统计量、预测量,还是交易规则?
6. 从零实现¶
原始代码按照“准备输入 → 计算中间量 → 得到结果 → 解释输出”的顺序组织。每个单元应检查变量类型、索引、维度和代表性数值。中间变量用于核对公式实现和定位计算错误。
7. 成熟库实现¶
Pandas 负责加载、对齐和清洗;数据源库只提供原料,不保证研究可用性。 使用库函数前先阅读参数含义、默认值和返回对象;库输出一个数字,并不说明这个数字适合你的交易假设。
8. 两种实现的差异¶
从零实现便于核对公式、调试和识别隐含假设;成熟库适合在计算逻辑已验证后复用。两种实现依赖相同的数据口径与时点假设,使用库函数不会改变输入数据的质量。
9. 预期结果与检查范围¶
你应看到维度正确的 Series/DataFrame、日期单调递增的索引,以及能支持“处理缺失、重复、复权和交易日不一致,让数据表先可信再可用。”这一任务的表格或图形。金融日收益率均值通常接近 0;若出现极大的连续收益、空表、全 NaN 或索引错位,应先排查数据和对齐逻辑,而不是立即相信结果。
10. 常见错误与诊断¶
把日期当字符串排序、重复索引、错误前向填充、混用 Close 与 Adj Close、把未来标签泄漏进特征。
诊断顺序:先打印类型与 shape,再看 head()/tail(),接着检查日期和 NaN,最后才比较数值。这样能避免在错误的对象上反复改参数。
11. 研究偏差检查¶
数据修订、退市股票缺失和接口字段变化都会制造看不见的幸存者偏差或未来函数。 结果至少应与一个简单基线比较,并明确哪些部分只是教学演示。
12. 三层练习与答案¶
- 基础练习:验证一个中间变量、补全一段小计算,并用一句话解释输出。
- 研究练习:改变窗口、样本区间、约束或标的,记录哪些结论仍成立。
- 开放项目:把本节方法嵌入模块项目,报告基线、失败结果和限制。
题目在 ../exercises/02_data/02_data_cleaning_exercises.ipynb;完整推导、可运行参考实现与结果检查在 ../solutions/02_data/02_data_cleaning_solutions.ipynb。请先独立作答,再打开答案。
13. 延伸阅读¶
延伸材料:pandas 用户指南。阅读记录应包括研究假设、数据频率、样本范围和结论适用条件。
14. 下一节¶
下一节是 03_feature_engineering。本节的概念会成为理解它的输入,而不是孤立的名词。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
plt.rcParams['figure.figsize'] = (12, 4)
# 下载数据
raw = yf.download(['AAPL', 'MSFT', 'JPM'], start='2020-01-01',
end='2024-01-01', progress=False)['Close']
print(f'原始数据形状: {raw.shape}')
raw.head()
原始数据形状: (1006, 3)
| Ticker | AAPL | JPM | MSFT |
|---|---|---|---|
| Date | |||
| 2020-01-02 | 72.400520 | 119.036415 | 152.158401 |
| 2020-01-03 | 71.696648 | 117.465553 | 150.263702 |
| 2020-01-06 | 72.267937 | 117.372162 | 150.652206 |
| 2020-01-07 | 71.928062 | 115.376770 | 149.278549 |
| 2020-01-08 | 73.085121 | 116.276817 | 151.656311 |
# 默认使用固定样本,保证课程与 CI 不依赖实时接口。
USE_OFFLINE_DATA = True
if USE_OFFLINE_DATA:
from pathlib import Path
import sys
docs_root = next(parent for parent in [Path.cwd(), *Path.cwd().parents]
if (parent / 'utils' / 'offline_data.py').exists())
sys.path.insert(0, str(docs_root))
from utils.offline_data import configure_yfinance_offline
configure_yfinance_offline(yf)
else:
print('在线下载为扩展步骤;请核对数据字段、时区和下载日期。')
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
# 人为注入缺失值
data = raw.copy()
np.random.seed(42)
mask_rows = np.random.choice(len(data), 20, replace=False)
mask_cols = np.random.choice(len(data.columns), 20, replace=True)
for r, c in zip(mask_rows, mask_cols):
data.iloc[r, c] = np.nan
# 推荐:日线数据常用前向填充
clean_data = data.ffill().bfill()
print(f'缺失值已处理,剩余 NaN: {clean_data.isnull().sum().sum()}')
缺失值已处理,剩余 NaN: 0
2. 异常值处理:对数变换与噪声压制¶
QuantEcon 严谨性提醒:
金融数据的波动往往是“等比”的(比如涨 5%)。如果在原始价格上算标准差,高股价时的波动会显著大于低股价时,这叫“异方差性”。
处理说明:对于严格为正的价格,可以先计算对数收益率再检测异常值。对数变换不能直接用于零值或负值;是否剔除观测还需要结合成交、复权和数据源记录判断。
# 基于对数收益率的异常值检测
log_returns = np.log(clean_data / clean_data.shift(1)).dropna()
# 3-sigma 法则:识别出偏离平均值 3 个标准差以外的点
ticker = 'AAPL'
ret_series = log_returns[ticker]
mean, std = ret_series.mean(), ret_series.std()
outliers = ret_series[np.abs(ret_series - mean) > 3 * std]
plt.figure(figsize=(10, 5))
plt.plot(ret_series, label='对数收益率', alpha=0.6)
plt.scatter(outliers.index, outliers, color='red', label='3-sigma 异常值')
plt.title(f"{ticker} 的收益率异常点检测")
plt.legend()
plt.show()
print(f"检测到 {len(outliers)} 个极端波动点。")
检测到 16 个极端波动点。
3. 股票复权¶
复权是为了保证价格序列的连续性。分红或拆股后的价格跳变是“非市场行为”,如果不处理,回测会产生巨大的虚假亏损。
# 对比复权与不复权价格
aapl_adj = yf.download('AAPL', start='2010-01-01', end='2024-01-01', auto_adjust=True, progress=False)['Close']
aapl_raw = yf.download('AAPL', start='2010-01-01', end='2024-01-01', auto_adjust=False, progress=False)['Close']
plt.plot(aapl_adj, label='复权价格 (Adj Close)')
plt.plot(aapl_raw, label='原始价格 (Close)', alpha=0.5, linestyle='--')
plt.yscale('log') # 价格跨度大时,用对数轴观察更清晰
plt.title("复权对比:AAPL (对数坐标系)")
plt.legend()
plt.show()
4. 资产对齐:处理“非共同工作日”¶
美股和港股、A 股的节假日不同。在进行多市场策略回测时,必须对齐时间轴。常用做法是求交集(Inner Join)。
assets = yf.download(['AAPL', '0005.HK'], start='2023-01-01', end='2023-06-01', progress=False)['Close']
print(f"美股+港股 原始数据天数: {len(assets)}")
aligned = assets.dropna()
print(f"对齐(删除非共同交易日)后天数: {len(aligned)}")
美股+港股 原始数据天数: 106 对齐(删除非共同交易日)后天数: 96
练习¶
- 尝试使用
data.interpolate()处理缺失值,并对比它和ffill()在收益率计算上的区别。 - 使用
np.log(price).diff()来计算一次 SPY 的日收益率,并与price.pct_change()对比。 - 检测 2020 年 3 月熔断期间,哪些交易日被视为统计学意义上的异常值(Z-score > 3)。
下一节 → 03_feature_engineering.ipynb