4. 数据清洗基础:缺失值处理、异常值检测、重复值处理与数据类型转换
各位同学,欢迎来到数据清洗的实战环节。说实话,在量化投资领域,我见过太多人把精力花在策略模型上,结果数据一塌糊涂,跑出来的回测曲线漂亮得不像话,一上实盘就崩。为什么?因为数据没洗干净。今天这一章,咱们就把数据清洗的四个基本功彻底讲透。
核心观点:数据清洗不是体力活,而是决定模型上限的关键步骤。80%的量化问题,根源都在数据质量上。
4.1 缺失值处理:别让空值毁了你的因子
先说说缺失值。我在做宏观因子库的时候,最头疼的就是这个。你想想看,一个GDP数据,某个月突然没了,或者CPI数据少了一行,整个时间序列就断了。怎么办?三种思路:删除、填充、插值。
4.1.1 删除缺失值
最简单粗暴的方法。如果缺失比例很小,比如不到5%,直接删掉那一行或那一列,影响不大。但要注意——千万别在关键数据上乱删。我曾经有个项目,把缺失的PMI数据行全删了,结果时间序列不连续,后面建模全乱套了。
import pandas as pd
import numpy as np
# 示例数据
df = pd.DataFrame({
'date': ['2024-01', '2024-02', '2024-03', '2024-04'],
'gdp_growth': [2.1, np.nan, 2.3, 2.2],
'cpi': [3.0, 3.1, np.nan, 3.2]
})
# 删除含有缺失值的行
df_dropna = df.dropna()
print(df_dropna)
# 输出:只剩两行完整数据
警告:删除操作会丢失信息。如果缺失值集中在某个变量上,建议先检查数据采集流程,而不是盲目删除。
4.1.2 填充缺失值
我个人习惯用填充法。常用的有均值填充、中位数填充、前向填充(ffill)和后向填充(bfill)。对于宏观时间序列,前向填充特别实用——比如某个月的数据没出来,用上个月的顶一下,逻辑上说得通。
# 均值填充
df['gdp_growth_filled'] = df['gdp_growth'].fillna(df['gdp_growth'].mean())
# 前向填充(用上一个值填充)
df['cpi_ffill'] = df['cpi'].fillna(method='ffill')
print(df[['date', 'gdp_growth_filled', 'cpi_ffill']])
小技巧:对于利率、汇率这类平稳序列,前向填充效果很好。但对于波动大的数据(比如股票收益率),用均值填充更稳妥。
4.1.3 插值法
插值比填充更精细。它利用已知数据点之间的趋势来估算缺失值。线性插值是最常用的,对于宏观数据中的季度GDP、月度工业增加值,效果不错。
# 线性插值
df['gdp_interp'] = df['gdp_growth'].interpolate(method='linear')
print(df[['date', 'gdp_growth', 'gdp_interp']])
嗯,这里要注意:插值假设数据是平滑变化的。如果数据有突变(比如金融危机期间),插值会失真。我建议先用可视化看看数据走势,再决定用哪种方法。
4.2 异常值检测与处理:揪出那些离谱的数据点
异常值,说白了就是数据里的「害群之马」。一个极端值就能把均值拉偏,把模型带歪。怎么揪出来?三种经典方法:3σ、IQR、分位数。
4.2.1 3σ法则(拉依达准则)
假设数据服从正态分布,那么99.7%的数据会落在均值±3个标准差范围内。超出这个范围的,就是异常值。这个方法简单粗暴,但有个前提——数据得近似正态。我在处理国债收益率数据时常用这个,因为收益率数据相对稳定。
def detect_outliers_3sigma(data):
mean = data.mean()
std = data.std()
outliers = data[(data < mean - 3*std) | (data > mean + 3*std)]
return outliers
# 示例
data = pd.Series([1, 2, 2, 3, 3, 3, 4, 4, 5, 100]) # 100是异常值
outliers = detect_outliers_3sigma(data)
print("异常值:", outliers.values)
实战经验:3σ法对极端值敏感。如果数据本身有多个异常值,均值和标准差会被污染,导致漏检。这时候可以考虑用中位数和MAD(中位数绝对偏差)替代。
4.2.2 IQR方法(四分位距法)
IQR方法不依赖正态分布假设,更稳健。它用Q1(25%分位数)和Q3(75%分位数)计算IQR = Q3 - Q1。通常认为小于Q1 - 1.5*IQR或大于Q3 + 1.5*IQR的点是异常值。
def detect_outliers_iqr(data):
Q1 = data.quantile(0.25)
Q3 = data.quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
outliers = data[(data < lower_bound) | (data > upper_bound)]
return outliers
outliers_iqr = detect_outliers_iqr(data)
print("IQR异常值:", outliers_iqr.values)
为什么我喜欢用IQR?因为它在宏观因子中特别实用。比如处理不同国家的通胀数据,各国分布差异大,IQR能自适应地找到异常点,不会因为某个国家数据波动大就误判。
4.2.3 分位数法
分位数法更灵活。你可以自定义上下限,比如取1%和99%分位数作为阈值。这在处理金融数据时很常见——极端行情下,3σ可能太宽松,IQR可能太严格,分位数法让你自己控制。
def detect_outliers_quantile(data, lower=0.01, upper=0.99):
lower_bound = data.quantile(lower)
upper_bound = data.quantile(upper)
outliers = data[(data < lower_bound) | (data > upper_bound)]
return outliers
outliers_quant = detect_outliers_quantile(data, lower=0.05, upper=0.95)
print("分位数异常值:", outliers_quant.values)
建议:对于宏观因子库,我通常先用IQR做初步筛查,再用分位数法做精细化调整。两种方法结合,基本能覆盖大部分异常情况。
4.3 重复值处理:别让数据「复制粘贴」
重复值这个问题,说大不大,说小不小。我在做跨境宏观数据整合时,经常遇到同一个指标从不同数据源重复获取的情况。比如美国GDP,彭博和万得各有一份,合并时没去重,结果模型里同一个因子被用了两次,权重翻倍,你说离谱不离谱?
4.3.1 完全重复行
最简单的情况——整行数据一模一样。直接用drop_duplicates()搞定。
df_dup = pd.DataFrame({
'date': ['2024-01', '2024-01', '2024-02'],
'value': [100, 100, 200]
})
df_clean = df_dup.drop_duplicates()
print(df_clean)
4.3.2 部分重复(基于关键列)
更常见的是部分重复——比如日期相同但其他列不同。这时候需要指定去重依据。我一般用subset参数指定关键列,比如日期+指标名称。
# 基于'date'列去重,保留第一个
df_clean = df_dup.drop_duplicates(subset=['date'], keep='first')
print(df_clean)
注意:去重前一定要确认哪些列是唯一标识。如果去重依据选错了,可能会误删有效数据。我曾经因为没检查,把不同来源的同一指标当成了重复数据删掉,结果因子库少了一半数据,排查了半天才发现。
4.4 数据类型转换:让数据「说同一种语言」
最后一步,也是容易被忽视的一步。数据类型不对,后续计算全错。比如日期列是字符串,你没法做时间序列分析;数值列是object类型,你没法做数学运算。
4.4.1 数值类型转换
用pd.to_numeric(),配合errors='coerce'参数,把非数值强制转为NaN,再处理缺失值。
df['value'] = pd.to_numeric(df['value'], errors='coerce')
4.4.2 日期类型转换
用pd.to_datetime(),指定格式。我习惯统一转为YYYY-MM-DD格式,方便后续排序和合并。
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
4.4.3 类别类型转换
对于国家、行业等分类变量,用astype('category')可以节省内存,还能加速分组操作。
df['country'] = df['country'].astype('category')
个人习惯:每次加载数据后,第一件事就是df.dtypes检查类型。发现问题立刻转换,别等到建模时再报错,那时候排查成本就高了。
总结一下
数据清洗这四个步骤,说白了就是给数据「体检+治病」。缺失值处理是补漏洞,异常值检测是排雷,重复值处理是去冗余,类型转换是统一语言。每一步都马虎不得。我在构建宏观因子库时,清洗环节通常占整个项目时间的40%以上——别嫌多,这是值得的。
好了,这一章的内容就到这里。记住:干净的数据是量化投资的基石。下一章我们会讲因子标准化和中性化处理,到时候你会感谢今天认真清洗数据的自己。