4、数据清洗基础:处理缺失值、重复值,统一日期格式,重置索引,为分析打好基础
说实话,我见过太多人一拿到数据就急着跑模型。结果呢?跑出来的结果自己都不敢信。数据清洗这事儿,看着不起眼,但绝对是整个分析流程的基石。你想想看,地基没打好,楼盖得再高也是危房。
我个人习惯,拿到任何数据集,第一件事不是看收益率,而是先做清洗。今天咱们就把这块硬骨头啃下来。
4.1 缺失值处理:别让「空」坑了你
纳斯达克的数据里,缺失值太常见了。比如某只股票停牌了,或者数据源没更新,那一行就是空的。我刚开始做量化的时候,直接用了 dropna() 把缺失行全删了,结果回测曲线漂亮得不像话——后来才发现,我把亏损最多的几天全删了。
处理缺失值,我一般分三步走:
- 先查缺:用
df.isnull().sum()看看每列缺了多少 - 再定性:是随机缺失?还是因为某些特定条件导致的缺失?
- 后处理:根据情况选择删除、填充或插值
举个例子,某只股票某天没有交易数据,我会用前一天的收盘价填充:
# 用前向填充处理缺失值
df['close'].fillna(method='ffill', inplace=True)
# 或者用均值填充
df['volume'].fillna(df['volume'].mean(), inplace=True)
我曾经遇到过一个坑:某只小盘股连续三天没交易,用前向填充后,那三天的收益率全是0。这直接导致我的波动率计算偏小。后来我改用线性插值,效果就好多了。
4.2 重复值处理:别让数据「胖」了
重复数据在纳斯达克数据里其实不多见,但一旦出现,后果很严重。你想想看,同样的日期、同样的价格出现了两次,计算收益率时就会多出一行0,累计收益直接偏掉。
我一般这样处理:
# 检查重复行
print(df.duplicated().sum())
# 删除重复行,保留第一个
df.drop_duplicates(inplace=True)
# 按指定列检查重复(比如按日期和股票代码)
df.drop_duplicates(subset=['date', 'symbol'], keep='first', inplace=True)
这里有个细节:keep='first' 保留第一次出现的行,keep='last' 保留最后一次。我个人习惯用 first,因为数据源通常是按时间顺序排列的。
4.3 统一日期格式:时间线不能乱
日期格式不统一,是我见过最多的问题。有的数据源用 2024-01-15,有的用 01/15/2024,还有的用 20240115。如果不统一,排序、合并、计算时间差全都会出错。
我的标准做法:
# 统一转换为 datetime 类型
df['date'] = pd.to_datetime(df['date'], format='%Y-%m-%d')
# 设置日期为索引
df.set_index('date', inplace=True)
# 按日期排序
df.sort_index(inplace=True)
为什么要设置成索引?因为 pandas 对时间索引有大量优化,比如按年/月/日切片、重采样、滚动计算,都特别方便。
pd.to_datetime() 会报错。可以用 errors='coerce' 参数,把无法解析的变成 NaT(缺失时间)。
我记得有一次,某个数据源的日期列里混进了「2024-02-30」这种不存在的日期。用 errors='coerce' 处理后,这些行变成了 NaT,我再手动排查,才发现是数据录入错误。
4.4 重置索引:让数据「归位」
重置索引听起来简单,但用不好会出大问题。尤其是当你做了筛选、排序、分组之后,索引会变得乱七八糟。
我常用的场景:
# 筛选后重置索引
df_filtered = df[df['volume'] > 1000000]
df_filtered.reset_index(drop=True, inplace=True)
# 或者保留原日期索引
df_filtered.reset_index(inplace=True)
drop=True 这个参数很关键。如果设为 True,原来的索引会被丢弃;如果设为 False,原来的索引会变成一列数据。
4.5 知识体系总览
下面这张图,是我自己总结的数据清洗流程。每次拿到新数据,我就按这个步骤走一遍,基本不会漏掉什么。
4.6 实战:完整的数据清洗流水线
最后,我把自己常用的清洗流程封装成一个函数。你直接拿去用就行:
def clean_nasdaq_data(df):
"""
纳斯达克数据清洗流水线
"""
# 1. 处理缺失值:前向填充
df.fillna(method='ffill', inplace=True)
# 如果开头还有缺失,用后向填充
df.fillna(method='bfill', inplace=True)
# 2. 删除重复行
df.drop_duplicates(subset=['date', 'symbol'], keep='first', inplace=True)
# 3. 统一日期格式
df['date'] = pd.to_datetime(df['date'], errors='coerce')
# 删除日期为 NaT 的行
df.dropna(subset=['date'], inplace=True)
# 4. 设置日期索引并排序
df.set_index('date', inplace=True)
df.sort_index(inplace=True)
return df
# 使用示例
df_clean = clean_nasdaq_data(df_raw)
print(f"清洗前:{df_raw.shape},清洗后:{df_clean.shape}")
嗯,这套流程我用了好几年,在几十个数据集上验证过。不能说百分百完美,但应付90%的纳斯达克数据是没问题的。
数据清洗这事儿,说白了就是「磨刀不误砍柴工」。你花10分钟把数据洗干净,后面跑模型、做回测,至少能省下2小时排查错误的时间。这笔账,怎么算都划算。