量化分析第一步:手把手教你用Pandas清洗网易金融下载的股票CSV数据
刚拿到网易金融导出的股票CSV数据时,很多人会直接扔进分析工具——直到发现中文列名报错、日期格式混乱、停牌日数据缺失等问题才手忙脚乱。作为量化分析的真正起点,数据清洗的质量直接决定后续策略回测的可靠性。本文将带你用Pandas完成从原始CSV到分析就绪数据集的完整蜕变,解决那些没人告诉你的实战细节。
1. 数据加载与初步观察
拿到CSV文件后,别急着动手清洗。先用pd.read_csv()的这几个参数避开初期陷阱:
import pandas as pd raw_data = pd.read_csv('300001.csv', encoding='gbk', # 处理网易中文编码 parse_dates=['日期'], # 自动解析日期列 na_values=['None', '--']) # 自定义缺失值标记首次检查必做三件事:
- 执行
raw_data.head(3)确认中文列名是否正常显示 - 用
raw_data.info()查看各列数据类型——特别注意数字列是否被误判为object - 运行
raw_data.isnull().sum()统计缺失值分布
常见问题示例表:
| 问题类型 | 典型表现 | 解决方案 |
|---|---|---|
| 编码问题 | 列名显示为乱码 | 指定encoding='gbk' |
| 日期格式 | 日期列仍是字符串 | 设置parse_dates参数 |
| 数字异常 | 成交量显示为object | 使用pd.to_numeric转换 |
提示:网易数据中"涨跌幅"列可能含有"%",需先去除符号再转换数据类型
2. 列标准化处理
金融数据分析最忌讳不规范的列名。建议按国际惯例重命名:
column_mapping = { '日期': 'date', '股票代码': 'symbol', '名称': 'name', '收盘价': 'close', '最高价': 'high', '最低价': 'low', '开盘价': 'open', '成交量': 'volume', '成交金额': 'amount' } clean_data = raw_data.rename(columns=column_mapping)关键操作细节:
- 使用英文列名避免编码问题
- 统一命名风格(全部小写,避免空格)
- 保留原始数据副本以备核查
特殊列处理技巧:
# 处理复权因子列 if '复权因子' in clean_data.columns: clean_data['adj_factor'] = pd.to_numeric(clean_data['复权因子']) # 转换涨跌幅百分比 clean_data['pct_change'] = ( clean_data['涨跌幅'].str.replace('%', '').astype(float) / 100 )3. 深度清洗实战
3.1 处理缺失值与停牌日
网易数据中停牌日通常表现为:
- 成交量/成交金额为0或NaN
- OHLC价格相同(但需注意涨跌停情况)
# 标记停牌日 clean_data['is_trading'] = ~( (clean_data['volume'].isna()) | (clean_data['volume'] == 0) ) # 前向填充关键价格数据 fill_cols = ['open', 'high', 'low', 'close'] clean_data[fill_cols] = clean_data[fill_cols].fillna(method='ffill')3.2 复权价格计算
若数据集包含复权因子,建议同时保存原始价格和复权后价格:
if 'adj_factor' in clean_data.columns: for price_col in ['open', 'high', 'low', 'close']: clean_data[f'adj_{price_col}'] = ( clean_data[price_col] * clean_data['adj_factor'] )3.3 日期维度增强
为后续时间序列分析添加实用时间特征:
clean_data['day_of_week'] = clean_data['date'].dt.dayofweek clean_data['month'] = clean_data['date'].dt.month clean_data['quarter'] = clean_data['date'].dt.quarter4. 数据存储优化
CSV已无法满足量化分析需求,推荐两种高性能存储格式:
Parquet格式(适合长期归档):
clean_data.to_parquet('300001.parquet', engine='pyarrow', compression='snappy')Feather格式(适合快速读写):
clean_data.to_feather('300001.feather')格式对比表:
| 特性 | CSV | Parquet | Feather |
|---|---|---|---|
| 读取速度 | 慢 | 快 | 最快 |
| 磁盘占用 | 大 | 小 | 中等 |
| 跨语言支持 | 好 | 优秀 | 有限 |
| 适合场景 | 原始数据交换 | 长期存储 | 临时分析 |
5. 数据质量验证
清洗完成后必须进行完整性检查:
# 检查时间连续性 date_diff = clean_data['date'].diff().dt.days missing_dates = date_diff[date_diff > 1] print(f"发现{len(missing_dates)}个时间间隔异常") # 验证价格逻辑 price_check = ( (clean_data['high'] >= clean_data['low']) & (clean_data['high'] >= clean_data['close']) & (clean_data['high'] >= clean_data['open']) & (clean_data['low'] <= clean_data['close']) & (clean_data['low'] <= clean_data['open']) ) assert price_check.all(), "存在价格逻辑错误"6. 进阶处理技巧
6.1 多股票数据合并
当处理多个股票CSV时,使用concat前注意:
all_stocks = [] for file in csv_files: stock_data = pd.read_csv(file) # 添加股票代码标识 stock_data['symbol'] = file.stem all_stocks.append(stock_data) combined = pd.concat(all_stocks).sort_values(['symbol', 'date'])6.2 内存优化技巧
处理大规模数据时,可大幅减少内存占用:
dtype_optimized = { 'open': 'float32', 'high': 'float32', 'low': 'float32', 'close': 'float32', 'volume': 'int32' } clean_data = clean_data.astype(dtype_optimized)6.3 快速查看清洗效果
制作关键指标对比图:
import matplotlib.pyplot as plt fig, ax = plt.subplots(2, 1, figsize=(12, 8)) raw_data['收盘价'].plot(ax=ax[0], title='原始数据') clean_data['close'].plot(ax=ax[1], title='清洗后数据') plt.tight_layout()清洗过程中最常遇到的坑是复权因子处理不当——有次我直接使用后复权价格计算收益率,结果导致整个月的夏普比率计算失真。后来发现网易的复权因子需要与最新交易日同步更新,现在我会定期检查调整系数的基准日期。另一个经验是:永远保留原始数据的副本,在清洗过程的每个阶段都添加版本标记,这样当发现逻辑错误时可以快速回溯。