2. 数据清洗

任务目标

本案例基于案例1获取的原始股票数据(含日期、开盘价、最高价、最低价、收盘价、成交量),完成数据清洗:处理缺失值与异常值,输出可直接用于后续特征工程和模型训练的干净数据集。

环境与依赖

Python 3.8+,依赖库:pandas、numpy。若尚未安装,执行 pip install pandas numpy。

核心实现

清洗逻辑包含三个步骤:缺失值处理(前向填充+剩余丢弃)、异常值检测(基于IQR法则)、异常值替换(用上下边界截断而非删除,保留时间序列连续性)。

import pandas as pd
import numpy as np

def clean_stock_data(df):
    """
    清洗股票日线数据:处理缺失值与异常值。
    参数:
        df: DataFrame,必须包含列 ['date','open','high','low','close','volume']
    返回:
        DataFrame,清洗后的数据,按日期升序排列
    """
    # 深拷贝避免修改原数据
    df = df.copy()
    
    # 1. 基础检查:确保必需列存在
    required_cols = ['date', 'open', 'high', 'low', 'close', 'volume']
    missing_cols = [c for c in required_cols if c not in df.columns]
    if missing_cols:
        raise ValueError(f"缺少必需列: {missing_cols}")
    
    # 2. 日期列标准化并排序
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values('date').reset_index(drop=True)
    
    # 3. 缺失值处理
    # 3.1 数值列前向填充(交易日之间缺失用上一交易日填充)
    numeric_cols = ['open', 'high', 'low', 'close', 'volume']
    df[numeric_cols] = df[numeric_cols].ffill()
    
    # 3.2 若仍存在缺失(如首行缺失),直接丢弃
    df = df.dropna(subset=numeric_cols).reset_index(drop=True)
    
    # 4. 异常值检测与处理(IQR法则)
    def cap_outliers(series):
        Q1 = series.quantile(0.25)
        Q3 = series.quantile(0.75)
        IQR = Q3 - Q1
        lower_bound = Q1 - 1.5 * IQR
        upper_bound = Q3 + 1.5 * IQR
        # 用边界值截断异常值,保留数据点
        return series.clip(lower_bound, upper_bound)
    
    # 对价格列和成交量列分别处理
    for col in ['open', 'high', 'low', 'close']:
        df[col] = cap_outliers(df[col])
    
    # 成交量下限设为0(成交量不应为负)
    df['volume'] = cap_outliers(df['volume'])
    df['volume'] = df['volume'].clip(lower=0)
    
    # 5. 逻辑校验:确保 high >= max(open, close),low <= min(open, close)
    # 若因异常值截断导致违反逻辑,则修正
    df['high'] = df[['high', 'open', 'close']].max(axis=1)
    df['low'] = df[['low', 'open', 'close']].min(axis=1)
    
    return df

# 最小调用示例(使用模拟数据验证清洗逻辑)
if __name__ == "__main__":
    # 构造含缺失值和异常值的模拟数据
    raw_data = pd.DataFrame({
        'date': ['2024-01-01', '2024-01-02', '2024-01-03', '2024-01-04', '2024-01-05'],
        'open': [100.0, 101.5, np.nan, 103.0, 500.0],   # 含缺失和异常高价
        'high': [102.0, 103.0, 104.0, 104.5, 510.0],
        'low':  [99.0, 100.5, 101.0, 102.0, 490.0],
        'close':[101.0, 102.0, 102.5, 103.5, 505.0],
        'volume':[1000000, 1200000, np.nan, 1100000, 99999999]  # 含缺失和异常量
    })
    
    cleaned = clean_stock_data(raw_data)
    print(cleaned)
    print(f"\n清洗后剩余记录数: {len(cleaned)}")

运行与验证

运行方式:将上述代码保存为 data_cleaning.py,在命令行执行 python data_cleaning.py。

验证方法:

  • 观察输出:2024-01-03 的缺失值已由前一日数据填充(open=101.5, volume=1200000);
  • 2024-01-05 的异常价格(500/510/490/505)和异常成交量(99999999)已被截断至正常边界;
  • 输出中不存在任何 NaN 值;
  • 每条记录满足 low ≤ min(open, close) ≤ max(open, close) ≤ high。

通过条件:程序无报错运行完毕,输出5条记录且无缺失值,价格逻辑校验全部通过。

预期输出

        date    open    high     low   close       volume
0 2024-01-01  100.00  102.00   99.00  101.00  1.000000e+06
1 2024-01-02  101.50  103.00  100.50  102.00  1.200000e+06
2 2024-01-03  101.50  104.00  101.00  102.50  1.200000e+06
3 2024-01-04  103.00  104.50  102.00  103.50  1.100000e+06
4 2024-01-05  103.00  104.50  102.00  103.50  1.100000e+06

清洗后剩余记录数: 5

常见问题

  • 真实数据中首行即缺失:前向填充无法处理首行缺失,代码会直接丢弃该行,属预期行为。
  • 停牌日缺失:若某交易日完全无数据,前向填充会用最近交易日数据填充,适合作为特征输入;若需严格剔除停牌日,可在清洗前先过滤。
  • IQR边界过宽/过窄:可根据股票波动特性调整系数(默认1.5),但需保持全流程一致。