第4章:数据清洗实战:缺失值处理、异常值检测、数据类型转换、去重与标准化
数据清洗,说白了就是给数据「洗澡」。
我做了这么多年金融数据,见过太多「脏数据」了。有的字段空着,有的数值离谱到天上,有的明明该是数字却存成了字符串。嗯,今天咱们就把这些坑一个一个填上。
4.1 缺失值处理:别让空值毁了你的模型
金融数据里,缺失值太常见了。比如某只股票某天停牌,交易量就是空的。再比如财报里某个指标没披露,也是空值。
我个人习惯,先搞清楚缺失的原因。是随机缺失?还是系统缺失?这决定了后续的处理策略。
4.1.1 删除法
如果缺失比例很小(比如低于5%),直接删掉那几行就行。我在项目中遇到过,某次处理日频交易数据,缺失率只有0.3%,删掉完全不影响统计结果。
# 删除含有缺失值的行
df_clean = df.dropna()
# 删除缺失比例超过50%的列
df_clean = df.dropna(thresh=len(df) * 0.5, axis=1)
4.1.2 填充法
缺失比例高的时候,删除就不划算了。这时候得填充。
- 均值/中位数填充:适合数值型字段。比如某只股票的历史收益率缺失,用过去30天的均值填上。
- 前向填充:时间序列数据常用。比如某天的收盘价缺失,用上一个交易日的价格填。
- 插值法:更平滑的处理方式。我记得有一次处理债券收益率曲线,用线性插值效果特别好。
# 均值填充
df['column'] = df['column'].fillna(df['column'].mean())
# 前向填充
df['price'] = df['price'].fillna(method='ffill')
# 线性插值
df['yield'] = df['yield'].interpolate(method='linear')
4.2 异常值检测:揪出那些「离谱」的数据
异常值,说白了就是数据里的「刺头」。比如某只股票日收益率突然涨了1000%,这明显是数据录入错误。
你想想看,如果带着这些异常值去做回测,结果能信吗?
4.2.1 基于统计的方法
| 方法 | 适用场景 | 阈值 |
|---|---|---|
| Z-Score | 正态分布数据 | |Z| > 3 |
| IQR(四分位距) | 偏态分布数据 | Q1 - 1.5*IQR 或 Q3 + 1.5*IQR |
| MAD(中位数绝对偏差) | 含极端值的数据 | MAD > 3 |
# Z-Score 检测
from scipy import stats
z_scores = stats.zscore(df['return'])
outliers = df[abs(z_scores) > 3]
# IQR 检测
Q1 = df['volume'].quantile(0.25)
Q3 = df['volume'].quantile(0.75)
IQR = Q3 - Q1
outliers = df[(df['volume'] < Q1 - 1.5*IQR) | (df['volume'] > Q3 + 1.5*IQR)]
4.2.2 基于业务规则的方法
有些异常,统计方法检测不出来,但业务规则一眼就能看出来。
我曾经处理过一份债券数据,某只债券的票面利率竟然填了50%。这明显是录入时多打了个0。用业务规则一过滤,这种错误就无处遁形了。
# 业务规则:股票日收益率不能超过±20%
df_valid = df[(df['return'] >= -0.2) & (df['return'] <= 0.2)]
# 业务规则:交易量不能为负数
df_valid = df[df['volume'] > 0]
4.3 数据类型转换:让数据「对号入座」
数据类型不对,是金融数据清洗里最常见的坑。明明是个日期,存成了字符串;明明是个数字,存成了对象。
为什么会这样? 很多时候是因为数据源来自不同的系统,格式不统一。
4.3.1 日期时间转换
# 字符串转日期
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
# 时间戳转日期
df['date'] = pd.to_datetime(df['timestamp'], unit='s')
4.3.2 数值类型转换
# 字符串转浮点数
df['price'] = pd.to_numeric(df['price'], errors='coerce')
# 整数类型转换
df['volume'] = df['volume'].astype('int64')
errors='coerce' 参数,遇到无法转换的值会自动变成NaN。这样就不会因为一个脏数据导致整个流程崩溃。
4.4 去重:别让重复数据「浑水摸鱼」
重复数据在金融数据里也很常见。比如同一个交易日的同一只股票,被录入了两次。
我记得有一次做因子分析,发现某个因子的IC值特别高,高兴了半天。后来一查,原来是数据重复了,导致相关性被高估。
# 完全去重
df_unique = df.drop_duplicates()
# 按指定列去重
df_unique = df.drop_duplicates(subset=['stock_code', 'date'])
4.5 标准化:让不同量纲的数据「同台竞技」
标准化,说白了就是把不同尺度的数据拉到同一个量级。比如股价是几百块,交易量是几百万股,不标准化的话,模型会天然偏向数值大的特征。
4.5.1 Z-Score 标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
df_scaled = scaler.fit_transform(df[['price', 'volume', 'return']])
4.5.2 Min-Max 标准化
from sklearn.preprocessing import MinMaxScaler
scaler = MinMaxScaler(feature_range=(0, 1))
df_scaled = scaler.fit_transform(df[['price', 'volume', 'return']])
知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作数据清洗的「操作手册」。
数据清洗这件事,说白了就是「慢工出细活」。我见过太多人急着跑模型,结果被脏数据坑得怀疑人生。嗯,把上面这五个步骤走一遍,你的数据基本就「干净」了。
公众号:蓝海资料掘金营,微信deep3321