Skip to content

7.6 缺失值与异常值处理

课前导读

本节目标

你将学会:识别 DataFrame 里的缺失值和异常值,理解为什么不能随便填补或删除,并建立清洗记录习惯。

适合基础:已经学习 DataFrame、读取保存数据和时间序列处理,知道 A 股数据会受交易日、停牌和披露时间影响。

预计用时:约 38 分钟。

本节边界:只讲数据清洗的基础判断,不展开完整回测系统、因子检验或自动化数据工程。


知识正文

1. 把缺失值和异常值想成“表格里的空格与离谱数字”

你拿到一张体检表。

有些格子是空的,比如血压没有填。

有些格子看起来离谱,比如身高写成 18 米。

你会马上把空格填成平均值吗?

你会直接把 18 米这一行删掉吗?

当然不会。

你会先问:为什么空了?为什么离谱?是没测、录错、单位错,还是确实发生了特殊情况?

A 股数据也是一样。

缺失值是表格里的空格。

异常值是看起来不合理的数字。

它们不是简单的“脏东西”。

它们可能代表停牌、新股未上市、公告未披露、数据源导出失败、复权口径错误或单位混淆。

清洗数据不是把表格擦得越平滑越好。

清洗数据的目标,是让研究尽量接近当时真实可见的投资事实。

2. 什么是缺失值

在 pandas 里,缺失值常见表现是 NaN

你可以把它理解为“这里没有有效数据”。

下面是一张手写的 A 股样例表。

python
import pandas as pd

data = {
    "日期": ["2026-01-05", "2026-01-06", "2026-01-07", "2026-01-08"],
    "股票代码": ["600000", "600000", "600000", "600000"],
    "收盘价": [10.20, None, 10.45, 10.30],
    "成交量": [1200000, None, 1500000, 1100000],
    "备注": ["正常交易", "疑似停牌或缺数据", "正常交易", "正常交易"],
}

df = pd.DataFrame(data)

print(df)

这里的 None 会在 pandas 中表现为缺失。

2026-01-06 的收盘价和成交量都没有值。

但我们不能立刻说“用前一天价格补上”。

也不能立刻说“把这一行删掉”。

要先判断缺失原因。

3. A 股中缺失值可能来自哪里

缺失场景通俗解释处理前要问的问题
停牌股票当天没有正常交易数据源是否保留停牌日期
新股未上市股票在某日期还不存在交易记录是否不应纳入当时样本
财报未披露财务字段当时还不可见是否会提前使用未来信息
数据导出失败文件下载或保存过程出错能否回到来源重新核对
字段口径变化数据源改了字段含义或单位是否需要分版本处理

缺失值不是一个单一问题。

同样是空格,原因不同,处理方式就不同。

停牌造成的缺失,和数据下载失败造成的缺失,不应该被同样对待。

财务数据未披露造成的缺失,更不能用未来财报倒填。

风险提醒

用未来才知道的数据填补过去的空格,会制造“偷看未来”的研究结果。

4. 先统计缺失,再查看具体行

拿到数据后,先做两步检查。

第一,统计每列缺失数量。

第二,把含缺失的行单独看出来。

python
print(df.isna().sum())

missing_rows = df[df["收盘价"].isna() | df["成交量"].isna()]

print(missing_rows)

isna() 会判断每个格子是否缺失。

sum() 会把 True 当成 1 来统计。

| 表示“或者”。

这段代码会找出收盘价缺失或成交量缺失的行。

注意,这只是发现问题。

发现问题不等于立刻处理问题。

专业的数据清洗一定要先观察,再决策。

5. 不要随便向前填充

有一种常见做法叫向前填充。

意思是用前一个有效值填补当前空格。

python
df["前值填充收盘价"] = df["收盘价"].ffill()

print(df[["日期", "收盘价", "前值填充收盘价"]])

ffill() 有时有用。

例如某些静态信息在下一次更新前保持不变。

但对收盘价要非常谨慎。

如果某天停牌,没有真实成交,把前一天收盘价填进去,可能会让程序误以为当天发生了一次“价格不变的交易日”。

这会影响收益率、成交量、持有天数和重采样结果。

所以对行情价格,填补前要写清楚原因。

新手阶段宁可先保留缺失并标记,也不要为了表格整齐而盲目填平。

6. 计算收益率时如何看待缺失

缺失值会影响收益率。

如果中间某天价格缺失,直接计算可能产生你没有意识到的跳跃。

可以先让 pandas 不自动填补。

python
df["日期"] = pd.to_datetime(df["日期"])
df = df.set_index("日期").sort_index()

df["日收益率"] = df["收盘价"].pct_change(fill_method=None)

print(df[["收盘价", "日收益率", "备注"]])

fill_method=None 表示不要在计算前自动填补缺失。

这样结果可能会多出空值。

这些空值是在提醒你:这里的数据链条断了。

对于投资研究,这种提醒比假装连续更有价值。

7. 什么是异常值

异常值不是“亏损”或“上涨”。

异常值是与常识、规则或数据口径明显不一致的数字。

例如收盘价为负数。

例如成交量为负数。

例如某只主板股票在普通交易日单日涨幅显示为 80%。

例如成交量单位本应是股,却被误当成手。

这些都需要核对。

但要注意,A 股也会出现真实的大幅变化。

新股上市初期、复牌、重大公告后跳空,都可能让价格变化很大。

所以异常值的第一处理动作是“标记和核查”,不是马上删除。

8. 用简单规则标记疑似异常

下面扩展一个手写样例。

python
sample = pd.DataFrame(
    {
        "日期": ["2026-01-05", "2026-01-06", "2026-01-07", "2026-01-08"],
        "股票代码": ["600000", "600000", "600000", "600000"],
        "收盘价": [10.20, 10.10, 30.30, 10.30],
        "成交量": [1200000, 980000, -500, 1100000],
    }
)

sample["日期"] = pd.to_datetime(sample["日期"])
sample = sample.set_index("日期").sort_index()

sample["日收益率"] = sample["收盘价"].pct_change(fill_method=None)
sample["疑似价格异常"] = sample["日收益率"].abs() > 0.22
sample["疑似成交量异常"] = sample["成交量"] < 0

print(sample)

这里用 22% 作为一个非常粗的提示线。

为什么不是 10%?

因为 A 股不同板块、不同阶段涨跌幅限制不同,新股早期也有特殊情况。

本节不展开所有制度细节。

我们只用这个阈值提醒自己:超过常见范围时要核查。

负成交量通常不符合常识,更需要检查来源。

9. 清洗决策表

问题类型先做什么可能处理不建议做法
收盘价缺失查原因:停牌、未上市还是数据失败标记原因,必要时排除该日收益计算无说明地用前值填充
成交量缺失查是否停牌或字段导出失败保留缺失并记录,或回源重取随便填 0
成交量为负核对数据源和单位标记为错误,回源修正或设为缺失当成真实交易
单日涨跌幅极端查板块、日期、复牌和公告标记疑似异常,确认后保留或修正直接删除所有大涨大跌
财务字段缺失查披露时间未披露前保持不可用用未来财报倒填

这张表不是死规则。

它的作用是提醒你:先问原因,再选处理方式。

量化开发面对数据问题时,要像医生看病历。

不能只看到一个数字异常,就立刻“开药”。

10. 记录清洗过程

清洗数据必须留痕。

否则你三天后可能忘记为什么删掉某些行。

一个最小清洗记录可以这样写。

日期股票代码问题判断处理备注
2026-01-06600000收盘价缺失疑似停牌或样例缺数据保留缺失,不计算当日收益待核对来源
2026-01-07600000收盘价跳变过大疑似数据录入错误标记异常,不直接删除检查原始文件
2026-01-07600000成交量为负不符合交易常识设为缺失并回源核对不能当真实成交

记录不是形式主义。

它能让你复盘研究过程。

当策略结果突然变化时,你可以回头检查是不是清洗规则变了。

当别人质疑你的结论时,你也能说明每一步如何处理。

关键心得

好清洗不是把数据变好看,而是把每个处理决定变得可解释、可复查。

11. 删除、填补、保留,哪个更好

没有永远正确的统一答案。

删除会减少样本。

填补会引入假设。

保留缺失会让后续计算更麻烦。

所以决策要服务于研究问题。

如果你只是学习 DataFrame,保留缺失并标记原因最安全。

如果你计算单日收益率,缺失日不应被悄悄填平。

如果你处理静态行业分类,前值填充可能比价格字段更合理。

如果你遇到负成交量,通常应视为数据错误并回源核对。

专业研究不是追求“表里没有 NaN”。

专业研究是知道哪些 NaN 代表不可用,哪些异常代表真实风险,哪些异常代表数据错误。

12. A 股语境下最容易忽视的三件事

第一,停牌不是普通休息日。

停牌期间没有正常成交,不能简单当成价格横盘。

第二,涨跌幅限制不是所有股票所有时期都一样。

主板、创业板、科创板、新股早期、风险警示股票,规则可能不同。

新手阶段不必背完所有细则,但看到极端涨跌要先核查背景。

第三,复权口径会改变历史价格。

如果一段价格看起来突然断裂,可能是分红送转后的口径问题。

这些问题都说明:数据清洗离不开 A 股制度理解。


交互实验室

练习 1:统计缺失值

运行本节第一段 DataFrame 示例。

使用 df.isna().sum() 统计每列缺失数量。

请写下哪几列存在缺失。

再把含缺失的行打印出来。

不要直接填补,先在笔记里写下你猜测的原因。

练习 2:比较填补前后

对“收盘价”执行 ffill()

观察填补前后 2026-01-06 的变化。

请回答:如果这一天其实是停牌,前值填充会让研究产生什么误解?

练习 3:标记异常而不是删除异常

运行“疑似价格异常”和“疑似成交量异常”的示例。

请找出被标记的日期。

然后写一句清洗记录。

示例:“2026-01-07 收盘价跳变过大,先标记为疑似异常,等待核对原始来源。”

练习 4:写自己的清洗原则

请把下面三句话补完整。

当价格缺失时,我先检查________。

当成交量为负时,我先________。

当单日涨跌幅极端时,我不会马上删除,而是先________。

如果你能写出这三句,说明你已经开始从“写代码”转向“做可靠研究”。


核心复盘

知识清单

  • [ ] 缺失值是表格里的空格,可能来自停牌、未上市、未披露或数据错误。
  • [ ] 异常值是看起来不合理的数字,但可能是真实事件,也可能是数据问题。
  • [ ] isna().sum() 可以快速统计缺失,筛选缺失行可以帮助定位问题。
  • [ ] 对行情价格不能随便前值填充,尤其要注意停牌和不可交易日期。
  • [ ] 异常值应先标记和核查,不应机械删除所有极端变化。
  • [ ] 每次清洗都要记录原因、处理方式和保留假设。

金句总结

数据清洗不是把表格擦干净,而是把每个空格和离谱数字问清楚。

风险提醒

随意填补缺失、删除极端涨跌或用未来数据倒填过去,会让量化研究产生虚假的稳定性。任何清洗结论都不构成投资建议。

本文档仅供学习交流用途,不构成任何投资建议。股市有风险,入市需谨慎。