2. 数据清洗与预处理

任务目标

对已获取的股票日线数据执行标准化预处理:统一日期列为datetime类型、去除含缺失值的记录、识别并处理收盘价中的异常离群值,最终输出一份可直接用于后续策略回测的干净数据集。

环境与依赖

需要 pandas 与 numpy。若尚未安装,执行:

pip install pandas numpy

关键完整代码

import pandas as pd
import numpy as np

def load_raw_data():
    """构造含缺失值与异常值的原始股票数据(模拟真实获取结果)"""
    return pd.DataFrame({
        'date': ['2024-01-03', '2024-01-04', None, '2024-01-08',
                 '2024-01-09', '2024-01-10', '2024-01-11'],
        'open': [10.5, 10.8, 11.0, 10.9, 11.2, 11.5, 11.3],
        'close': [10.7, 10.9, np.nan, 11.0, 11.4, 11.6, 11.2],
        'volume': [10000, 12000, 11000, 13000, 12500, 14000, 99999999]
    })

def preprocess_stock_data(df):
    """清洗:统一日期格式 → 去缺失 → 处理异常值"""
    # 1. 统一日期格式:转为 datetime,非法值转为 NaT
    df['date'] = pd.to_datetime(df['date'], errors='coerce')

    # 2. 去除缺失值(含 date 为 NaT 的行)
    df = df.dropna().copy()

    # 3. 处理异常值:用 IQR 法识别收盘价离群点,以中位数填充
    q1 = df['close'].quantile(0.25)
    q3 = df['close'].quantile(0.75)
    iqr = q3 - q1
    lower = q1 - 1.5 * iqr
    upper = q3 + 1.5 * iqr
    median_close = df['close'].median()
    df['close'] = df['close'].where(
        (df['close'] >= lower) & (df['close'] <= upper), median_close
    )

    # 4. 按日期排序,重置索引
    df = df.sort_values('date').reset_index(drop=True)
    return df

if __name__ == '__main__':
    raw = load_raw_data()
    print('原始数据:')
    print(raw)
    print('\n数据形状:', raw.shape)

    cleaned = preprocess_stock_data(raw)
    print('\n清洗后数据:')
    print(cleaned)
    print('\n数据形状:', cleaned.shape)
    print('日期类型:', cleaned['date'].dtype)
    print('缺失值数量:', cleaned.isna().sum().sum())

运行与验证

直接运行脚本即可看到清洗前后对比。判定通过条件:

  • 日期统一:清洗后 date 列类型为 datetime64[ns],且无 NaT;
  • 缺失值去除:清洗后 isna().sum().sum() 输出为 0;
  • 异常值处理:原始数据中 volume 列的 99999999 与 close 列的缺失值所在行被移除;close 列中若存在超出 1.5 倍 IQR 的离群值,会被替换为该列中位数(本例原始 close 无离群点,因此数值不变,但逻辑已生效);
  • 行数变化:原始 7 行 → 清洗后 5 行(缺失日期 1 行 + 缺失收盘价 1 行被删除)。

预期输出(由代码逻辑唯一确定)

原始数据:
         date   open   close    volume
0  2024-01-03   10.5    10.7     10000
1  2024-01-04   10.8    10.9     12000
2        None   11.0     NaN     11000
3  2024-01-08   10.9    11.0     13000
4  2024-01-09   11.2    11.4     12500
5  2024-01-10   11.5    11.6     14000
6  2024-01-11   11.3    11.2  99999999

数据形状: (7, 4)

清洗后数据:
        date   open   close    volume
0 2024-01-03   10.5    10.7     10000
1 2024-01-04   10.8    10.9     12000
2 2024-01-08   10.9    11.0     13000
3 2024-01-09   11.2    11.4     12500
4 2024-01-10   11.5    11.6     14000

数据形状: (5, 4)
日期类型: datetime64[ns]
缺失值数量: 0

常见问题

  • 真实数据源日期格式混杂:pd.to_datetime 的 errors='coerce' 会将无法解析的日期转为 NaT,随后被 dropna() 统一移除,避免格式不一致影响后续时间序列操作。
  • 异常值判定口径:本例采用 IQR 法则,适用于无先验阈值场景。若业务上对价格有硬性涨跌幅限制(如 ±10%),可改用自定义上下界替换 IQR 逻辑。
  • 填充 vs 删除:缺失值采用直接删除策略,适合缺失比例低的情况;若缺失集中在某只股票的时间段,可考虑改用前向填充或插值,但需在代码中显式替换 dropna() 逻辑。