第四章:REITs基础数据清洗——缺失值处理、异常值检测、数据标准化、时间序列对齐
数据清洗这件事,说白了就是「把脏数据变成能用的数据」。我在REITs量化这条路上摸爬滚打了好几年,最深的体会就是:数据清洗花的时间,往往比建模还多。你想想看,REITs的数据来源五花八门——交易所、托管行、第三方数据商,每个渠道的数据格式、时间戳、字段定义都不一样。不洗干净,后面所有因子都是垃圾。
这一章,我带你过一遍REITs数据清洗的四个核心环节:缺失值处理、异常值检测、数据标准化、时间序列对齐。每个环节我都会结合自己踩过的坑来讲。
4.1 缺失值处理——别急着删,先看看为什么缺失
REITs数据里缺失值太常见了。比如分红除权日那天,价格数据可能为空;或者某个REITs刚上市,历史数据还没积累够。我个人习惯是:先分类,再处理。
缺失值大致分三类:
- 完全随机缺失:比如网络波动导致某天数据没传过来。这种可以直接删,或者用前后值填充。
- 随机缺失:比如某些REITs在特定月份披露数据不全。这种需要谨慎,不能简单删除。
- 非随机缺失:比如业绩差的REITs故意不披露某些指标。这种最危险,处理不好会引入偏差。
我的经验法则:缺失率低于5%的字段,用中位数或前向填充;缺失率在5%-20%之间,考虑用同类REITs的均值填充;超过20%,我建议直接放弃这个字段。
代码实现上,我常用pandas的fillna()和interpolate()。给你看个例子:
import pandas as pd
import numpy as np
# 模拟REITs日度价格数据
dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')
reits_data = pd.DataFrame({
'date': dates,
'price': np.random.randn(len(dates)) * 10 + 100
})
# 人为制造缺失值
reits_data.loc[10:15, 'price'] = np.nan
reits_data.loc[50:55, 'price'] = np.nan
# 前向填充(适合价格数据)
reits_data['price_ffill'] = reits_data['price'].fillna(method='ffill')
# 线性插值(适合收益率数据)
reits_data['price_interp'] = reits_data['price'].interpolate(method='linear')
# 中位数填充(保守做法)
median_price = reits_data['price'].median()
reits_data['price_median'] = reits_data['price'].fillna(median_price)
小技巧:处理REITs分红除权日的缺失值时,我建议用「除权调整后的价格」来填充,而不是简单的前向填充。否则你的收益率计算会出问题。
4.2 异常值检测——别让一个极端值毁掉整个模型
REITs数据里的异常值,我遇到过最离谱的一次:某只REITs的日收益率突然飙到+50%,后来发现是数据商把分红除权日的数据搞错了。嗯,这种错误如果不处理,你的因子相关性分析会完全失真。
常用的异常值检测方法有三种:
| 方法 | 适用场景 | 优缺点 |
|---|---|---|
| Z-score法 | 正态分布的数据 | 简单快速,但对非正态分布效果差 |
| IQR四分位法 | 偏态分布的数据 | 稳健,不受极端值影响 |
| MAD绝对中位差法 | 含有大量异常值的数据 | 最稳健,但计算稍复杂 |
我个人在REITs数据清洗中,最常用的是IQR法。为什么?因为REITs的收益率分布往往有厚尾特征,Z-score会误判很多正常值。给你看代码:
def detect_outliers_iqr(data, column, multiplier=1.5):
"""
使用IQR法检测异常值
multiplier=1.5 是标准阈值,REITs数据我常用2.0
"""
Q1 = data[column].quantile(0.25)
Q3 = data[column].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - multiplier * IQR
upper_bound = Q3 + multiplier * IQR
outliers = data[(data[column] < lower_bound) | (data[column] > upper_bound)]
return outliers, lower_bound, upper_bound
# 检测REITs日收益率的异常值
outliers, lb, ub = detect_outliers_iqr(reits_data, 'daily_return', multiplier=2.0)
print(f"检测到 {len(outliers)} 个异常值")
print(f"正常范围: [{lb:.4f}, {ub:.4f}]")
注意:我曾经犯过一个错误——把所有异常值直接删掉。后来发现,有些异常值其实是「真实的极端事件」,比如REITs宣布破产重组。这种数据应该保留,单独标记即可。
4.3 数据标准化——让不同量纲的因子站在同一起跑线
REITs的因子数据,量纲差异巨大。比如P/FFO倍数可能是5-30,而市值可能是几十亿到几百亿。如果不做标准化,聚类分析或者回归模型会天然偏向数值大的特征。
常用的标准化方法:
- Z-score标准化:减去均值,除以标准差。适合数据近似正态分布的情况。
- Min-Max归一化:缩放到[0,1]区间。适合有明确边界的数据。
- Rank归一化:将数据转换为百分位数。我最推荐的方法,对异常值不敏感。
为什么我偏爱Rank归一化?你想想看,REITs的估值指标经常有极端值,比如某只REITs因为特殊事件P/FFO突然飙到100。如果用Z-score,这个值会被放大;用Min-Max,其他值会被压缩。但Rank归一化只关心排序,不受数值大小影响。
from scipy.stats import rankdata
def rank_normalize(series):
"""
Rank归一化:将数据转换为0-1之间的百分位数
这是我在REITs因子构建中最常用的方法
"""
ranks = rankdata(series)
normalized = (ranks - 1) / (len(series) - 1)
return normalized
# 对REITs的P/FFO因子做Rank归一化
reits_data['p_ffo_rank'] = rank_normalize(reits_data['p_ffo'])
reits_data['dividend_yield_rank'] = rank_normalize(reits_data['dividend_yield'])
避坑指南:做标准化之前,一定要先处理缺失值和异常值。我曾经先标准化再处理异常值,结果标准化后的数据被极端值拉偏,后面怎么调都不对。
4.4 时间序列对齐——不同频率的数据怎么合并
REITs数据最头疼的问题之一:不同数据源的频率不一样。价格数据是日频,财务数据是季频,宏观经济数据是月频。怎么把它们对齐到同一个时间轴上?
我常用的对齐策略:
- 向前填充(Forward Fill):用最近的有效值填充缺失的时间点。适合财务数据,因为季报数据在发布后一直有效,直到下一季报发布。
- 向后填充(Backward Fill):用未来的值填充。适合某些「已知未来事件」的场景,比如已知分红日,可以向后填充分红金额。
- 重采样(Resample):将高频数据降采样为低频,或者低频数据升采样为高频。
给你看一个实际案例:把季度财务数据和日度价格数据对齐。
# 模拟季度财务数据
quarterly_dates = pd.date_range('2023-01-01', '2023-12-31', freq='Q')
financial_data = pd.DataFrame({
'date': quarterly_dates,
'ffo_per_share': [2.5, 2.7, 2.8, 3.0],
'nav_per_share': [120, 125, 130, 128]
})
# 模拟日度价格数据
daily_dates = pd.date_range('2023-01-01', '2023-12-31', freq='D')
price_data = pd.DataFrame({
'date': daily_dates,
'price': np.random.randn(len(daily_dates)) * 5 + 100
})
# 将财务数据对齐到日度数据(向前填充)
financial_data.set_index('date', inplace=True)
price_data.set_index('date', inplace=True)
# 重采样并向前填充
financial_daily = financial_data.resample('D').ffill()
# 合并两个DataFrame
merged_data = price_data.join(financial_daily, how='left')
print(merged_data.head())
注意:时间序列对齐时,一定要确认时区是否一致。我遇到过数据商提供的美国REITs数据用EST时区,而国内数据用CST时区,差13个小时,导致对齐后数据错位。建议统一转换为UTC时间。
4.5 本章知识体系总览
下面这张图,是我自己总结的REITs数据清洗流程。每次做新项目,我都会先过一遍这个框架。
这张图看起来简单,但每一步都有坑。我刚开始做REITs量化的时候,觉得数据清洗就是调几个函数的事。后来发现,真正决定模型质量的,往往不是算法多高级,而是数据洗得多干净。
嗯,这一章的内容就到这里。数据清洗是基本功,但也是最容易被忽视的环节。你把这些方法吃透了,后面构建因子的时候会省很多力气。