1. 数据清洗在赛事预测项目中的真实定位
1.1 为什么数据清洗是预测项目最容易被低估的环节
做赛事预测项目,很多人一上来就想着用什么模型、调什么参数、上什么网络结构。我刚开始接触这类项目的时候也这样,觉得数据清洗就是走个过场,把空值填一填、格式统一一下不就完了。结果第一个版本跑出来,模型在训练集上表现好得离谱,一到验证集就崩了,排查了整整两天才发现问题出在数据清洗阶段——把比赛结束后的统计数据泄露到了赛前特征里。
这个教训让我重新理解了数据清洗在赛事预测项目中的位置。它不是一个独立的、可以快速跳过的预处理步骤,而是直接决定模型上限的核心环节。你喂给模型什么数据,模型就学什么规律。如果数据里混入了未来信息、重复样本、口径不一致的统计项,再精巧的模型架构也救不回来。
具体到英雄联盟赛事预测这个场景,数据清洗要解决的问题比通用机器学习任务更棘手。原因在于:
- 数据来源分散:比赛数据可能来自官方接口、第三方数据平台、手工录入等多个渠道,每个渠道的字段命名、统计口径、更新频率都不一样。
- 时间维度敏感:预测比赛结果时,只能使用比赛开始之前就能获取的信息。但很多数据表里混着赛后统计,比如总击杀数、比赛时长、经济曲线,这些在赛前根本拿不到。
- 版本迭代频繁:游戏版本更新会直接影响英雄强度、战术体系、比赛节奏,不同版本的数据不能简单混在一起训练。
- 样本量有限:顶级联赛一个赛季的比赛场次就那么多,清洗过程中每丢掉一条有效样本都是损失。
所以这一篇的重点不是讲某个具体的清洗工具怎么用,而是把我在这个项目里踩过的坑、总结出来的清洗框架和实操细节完整地分享出来。如果你正在做类似的赛事预测项目,或者任何涉及时间序列、多源异构数据的预测任务,这些经验应该都能直接参考。
1.2 清洗目标:从原始数据到可用特征矩阵
在动手写代码之前,我先明确了这个项目的数据清洗目标。不是笼统的“把数据弄干净”,而是具体到每一步要产出什么。
整个清洗流程的最终产出是一张赛前特征矩阵,每一行代表一场比赛,每一列代表一个在赛前可获取的特征,标签是比赛结果(胜/负)。这个矩阵要满足几个硬性条件:
- 无未来信息泄露:任何在比赛开始后才能确定的统计量都不能出现在特征列中。
- 时间一致性:所有特征的计算时间戳必须早于比赛开始时间。
- 样本独立性:同一场比赛不能以任何形式重复出现,也不能因为数据关联产生隐式的样本泄漏。
- 版本可追溯:每条样本要能追溯到对应的游戏版本,方便后续做版本分组分析。
- 缺失可解释:缺失值不能简单填充,要区分是“真的没有这个数据”还是“数据采集失败”。
明确了这些目标之后,清洗流程的每一步都有了判断标准。比如遇到一个字段,先问自己:这个字段在比赛开始前能拿到吗?拿不到就直接排除,不管它跟结果的相关性有多高。
2. 原始数据摸底与字段分类
2.1 先搞清楚手里有什么数据
拿到原始数据之后,我做的第一件事不是写清洗脚本,而是花了大半天时间做数据摸底。这一步很多人会跳过,觉得浪费时间,但我的经验是:摸底越充分,后面写清洗逻辑的时候返工越少。
摸底主要看几个维度:
- 数据表清单:一共有多少张表,每张表大概多少行、多少列,表与表之间怎么关联。
- 字段清单:每个字段的名称、类型、含义、取值范围、缺失比例。
- 时间字段:哪些字段包含时间信息,时间粒度是什么,时区是否统一。
- 主键与外键:每张表的主键是什么,关联其他表的键是什么,关联关系是一对一还是一对多。
我当时的原始数据大致分为几类:
| 数据类别 | 典型字段 | 更新时机 | 是否可用于赛前预测 |
|---|---|---|---|
| 赛程信息 | 比赛ID、战队ID、比赛时间、版本号 | 赛前确定 | 是 |
| 战队历史战绩 | 近N场胜负、胜率、场均时长 | 赛前可算 | 是 |
| 选手历史数据 | 近N场KDA、参团率、伤害占比 | 赛前可算 | 是 |
| 本场BP数据 | 双方选人、禁用 | 赛前确定 | 是 |
| 本场赛后统计 | 总击杀、总经济、比赛时长 | 赛后产生 | 否 |
| 实时事件流 | 每分钟经济差、击杀事件 | 赛中产生 | 否 |
这张表看起来简单,但实际摸底的时候发现了很多坑。比如“战队历史战绩”这个类别里,有些字段的计算口径是“最近10场”,有些是“最近5场”,还有些是“本赛季至今”。如果不统一口径,模型学到的就是混乱的信号。
2.2 字段分类的核心原则:时间边界
字段分类最核心的原则就是时间边界。我当时的做法是给每个字段打一个标签,标记它的“最早可获取时间”。
具体操作上,我把所有字段分成三类:
- 赛前确定型:比赛开始前就已经确定的信息,比如赛程、版本号、双方首发名单、BP结果。这类字段可以直接用。
- 赛前可计算型:需要基于历史数据计算,但计算所需的数据在赛前都已经产生。比如“战队近10场胜率”,只要这10场比赛都打完了,赛前就能算出来。这类字段可以用,但要注意计算窗口不能包含当前这场比赛。
- 赛后产生型:比赛结束后才能确定的统计量,比如本场总击杀、本场经济差、比赛时长。这类字段绝对不能作为特征,但可以作为标签或者用于构造其他样本的滞后特征。
这里有一个很容易犯的错误:把“赛后产生型”字段用来构造“赛前可计算型”特征时,不小心把当前场次的数据也算进去了。比如计算“战队近10场场均击杀”时,如果窗口包含了当前这场比赛,那就造成了标签泄露。这个坑我在第一版的时候就踩了,模型在训练集上AUC高得离谱,后来逐字段排查才发现问题。
注意:任何涉及滑动窗口计算的字段,窗口的结束时间必须严格早于当前比赛开始时间。建议在代码里显式传入比赛开始时间作为窗口边界,而不是依赖数据行的顺序。
2.3 数据质量问题的初步扫描
摸底阶段还要做一轮数据质量扫描,把明显有问题的数据先标记出来。我主要看这几个指标:
- 缺失率:每个字段的缺失比例。缺失率超过一定阈值的字段要考虑是否保留。
- 唯一值比例:如果某个字段几乎全是唯一值,可能是ID类字段,不适合直接作为特征。
- 异常值:数值型字段的分布情况,有没有明显超出合理范围的取值。
- 格式一致性:同一字段在不同数据源里的格式是否统一,比如日期格式、战队名称写法。
扫描的时候发现了一个典型问题:战队名称在不同表里的写法不一致。有的表用全称,有的表用缩写,还有的表用了不同的翻译。如果不做统一,关联的时候就会丢数据。这个问题后面在实体对齐那一步专门处理了。
3. 缺失值与异常值的处理策略
3.1 缺失值的分类与处理
缺失值处理是数据清洗里最考验判断力的环节。我的原则是:先分类,再处理,能保留信息就绝不简单填充。
在这个项目里,我把缺失值分成了几类:
- 结构性缺失:某些字段天然就不适用于所有样本。比如“选手A的某项数据”在选手A没有上场的比赛里就是缺失的,这是合理的,不应该填充。
- 采集性缺失:数据源本身的问题导致的数据丢失。比如某场比赛的数据接口挂了,导致这场比赛的所有统计都是空的。
- 计算性缺失:因为历史数据不足导致无法计算。比如赛季初的前几场比赛,“近10场胜率”就算不出来,因为历史场次不够。
针对不同类型的缺失,处理方式完全不同:
| 缺失类型 | 判断方法 | 处理策略 |
|---|---|---|
| 结构性缺失 | 结合业务逻辑判断 | 保留缺失,用特殊标记表示,或改用其他等价特征 |
| 采集性缺失 | 检查数据源日志 | 如果整场比赛数据都缺失,考虑删除该样本;如果只是个别字段缺失,用同版本同位置的统计量填充 |
| 计算性缺失 | 检查历史窗口长度 | 用较短窗口的计算结果替代,或标记为缺失让模型自己学习 |
我当时的做法是给每一列都加一个“缺失标记”列,记录原始值是否缺失。这样模型可以自己学习“缺失”这个信号是否有预测价值。实测下来,某些字段的缺失标记确实有信息量,比如“某选手首次登场”这种信息就藏在缺失模式里。
3.2 异常值检测与处理
异常值处理比缺失值更棘手,因为异常值不一定是错误数据。在赛事数据里,有些看起来异常的值其实是真实发生的极端情况,比如一场比赛打了70分钟、某选手单场击杀数特别高。这些极端值如果直接删掉,模型就学不到这些边界情况;如果保留,又可能影响某些对尺度敏感的模型。
我的处理策略是分两步:
第一步,用统计方法标记可疑值。对每个数值型字段,计算四分位距(IQR),把超出1.5倍IQR范围的值标记为可疑。同时结合业务知识设定硬边界,比如比赛时长不可能小于15分钟,也不可能超过120分钟。
第二步,逐个判断可疑值的性质。对于标记出来的可疑值,我写了一个简单的分类逻辑:
- 如果值超出了业务硬边界,直接判定为错误数据,按缺失处理。
- 如果值在业务合理范围内但统计上异常,保留原值,同时添加一个“异常标记”列。
- 如果某个字段的异常值比例超过5%,说明这个字段的统计口径可能有问题,需要回到数据源排查。
这里有一个实操心得:不要用均值填充异常值。赛事数据里的极端值往往包含重要信息,用均值填充等于人为抹掉了这些信息。我试过用均值填充比赛时长,结果模型对“膀胱局”的预测能力明显下降。
3.3 重复样本的识别与去重
重复样本是另一个容易被忽视的问题。在这个项目里,重复样本可能来自几个方面:
- 同一场比赛被多个数据源重复采集。
- 数据关联时产生笛卡尔积,导致一行变多行。
- 数据更新时旧版本没有删除,新版本又插入了一份。
识别重复样本的关键是定义“什么算重复”。对于比赛数据,我用的主键是“比赛ID+战队ID”,如果这个组合出现多次,就是重复。但要注意,有些重复是合理的,比如一场比赛有多局,每局都应该有独立的记录。
去重的时候不能简单用drop_duplicates,要先确认重复行的内容是否完全一致。如果内容不一致,说明数据源之间有冲突,需要决定以哪个为准。我的做法是优先保留数据源可靠性更高的版本,同时记录冲突情况供后续分析。
提示:去重之后一定要重新检查样本量。我有一次去重后发现样本量少了将近三成,排查发现是关联逻辑写错了,把本该保留的多局比赛也去掉了。
4. 特征工程中的数据清洗实操
4.1 时间窗口特征的构造与清洗
时间窗口特征是赛事预测里最核心的特征类型。比如“战队近10场胜率”“选手近5场场均伤害”这类特征,都需要基于历史数据做滑动窗口计算。
构造这类特征的时候,清洗的重点是窗口边界的正确性。我当时的实现方式是:
- 把所有比赛按时间排序,每场比赛有一个唯一的时间戳。
- 对于每场比赛,确定它的“特征计算截止时间”,即比赛开始时间。
- 从历史比赛中筛选出时间戳早于截止时间的记录,按时间倒序取最近N场。
- 基于这N场比赛计算统计量。
这个逻辑看起来简单,但实操中有几个细节要注意:
- 同一时间有多场比赛怎么办:如果两场比赛同时开始,计算其中一场的特征时,另一场的数据不应该被包含进去,因为那场比赛的结果在赛前是未知的。我的处理方式是给每场比赛加一个微秒级的时间偏移,确保排序唯一。
- 窗口内样本不足怎么办:如果历史比赛不足N场,可以选择用实际场次计算,同时记录实际窗口长度作为一个额外特征。这样模型可以区分“基于10场计算的胜率”和“基于3场计算的胜率”。
- 版本变更怎么处理:如果窗口跨越了版本更新,历史数据的参考价值会下降。我的做法是在特征里加入“窗口内比赛的平均版本距离”,让模型自己学习版本变更的影响。
4.2 类别型特征的编码与清洗
赛事数据里有大量类别型特征,比如战队ID、选手ID、英雄ID、版本号。这些特征的清洗重点是实体对齐和编码一致性。
实体对齐方面,前面提到的战队名称不一致问题就是典型。我的处理方式是建一张映射表,把所有变体映射到统一的实体ID。映射表的构建不能全靠手工,我先用字符串相似度做了一轮自动匹配,然后人工校验了匹配结果。
编码一致性方面,要注意训练集和验证集的编码方式必须一致。我见过有人用LabelEncoder分别对训练集和验证集编码,结果同一个战队在训练集里编码是3,在验证集里编码是7,模型完全懵了。正确的做法是在全量数据上拟合编码器,然后应用到各个子集。
对于高基数类别特征(比如选手ID可能有几百个),直接做独热编码会导致维度爆炸。我的做法是:
- 对于出现次数少于一定阈值的类别,统一归为“其他”类。
- 对于战队和选手,除了ID本身,还构造一些聚合特征,比如“该战队历史胜率”“该选手历史KDA”,用这些连续特征来补充类别特征的信息。
4.3 特征缩放与分布清洗
特征缩放是很多教程里会讲的内容,但在赛事预测项目里,缩放本身不是难点,难点在于分布清洗。
什么叫分布清洗?就是检查特征的分布是否在训练集和验证集之间保持一致。如果某个特征在训练集里的分布和验证集里差异很大,说明这个特征可能不稳定,或者数据清洗过程中引入了偏差。
我当时的做法是对每个数值型特征做分布对比,主要看几个指标:
- 均值和中位数的差异
- 标准差的比例
- 分位数分布的偏移
如果发现某个特征的分布差异明显,就回到清洗流程排查原因。常见的原因包括:
- 训练集和验证集的时间范围不同,导致版本分布不同。
- 某个数据源在验证集时间段内出现了采集问题。
- 特征计算逻辑在某个时间点之后发生了变更。
分布清洗之后再做缩放,效果会好很多。缩放方法我一般用RobustScaler而不是StandardScaler,因为赛事数据里极端值比较多,用均值和标准差缩放容易受异常值影响。
5. 数据泄露的排查与防范
5.1 数据泄露的常见形式
数据泄露是赛事预测项目里最致命的问题,没有之一。它会让模型在离线评估时表现优异,但上线后一塌糊涂。我在这个项目里遇到过几种典型的数据泄露:
第一种,标签泄露。最直接的形式是把比赛结果相关的字段作为特征。比如把“本场总击杀”作为特征来预测胜负,而总击杀数高的队伍往往就是赢家。这种泄露比较容易被发现,因为字段名称通常能看出来。
第二种,时间泄露。特征的计算时间晚于比赛开始时间。比如用“赛后更新的战队排名”来预测比赛结果,而排名更新是在比赛结束后才发生的。这种泄露比较隐蔽,需要仔细检查每个字段的更新时间。
第三种,样本泄露。训练集和验证集之间有重叠样本,或者同一场比赛的不同局被分到了不同的集合。这种泄露会导致模型在验证集上“见过”类似的数据,评估结果偏乐观。
第四种,预处理泄露。在划分训练集和验证集之前做了全局的预处理,比如用全量数据计算均值来填充缺失值。这样验证集的信息就通过填充值泄露到了训练过程中。
5.2 泄露排查的实操方法
排查数据泄露,我总结了一套流程:
第一步,逐字段审查。对每个特征字段,问三个问题:这个字段在赛前能拿到吗?这个字段的计算是否用到了赛后信息?这个字段的更新频率是什么?任何一个问题答不上来,就标记为可疑。
第二步,时间穿越测试。写一个测试脚本,模拟“只使用比赛开始前的数据”来重新计算所有特征,然后和原始特征对比。如果发现某个特征在模拟计算中无法复现,说明它可能用到了赛后信息。
第三步,打乱标签测试。把标签随机打乱,重新训练模型。如果模型在打乱标签后仍然能取得不错的评估指标,说明数据里存在泄露。正常情况下,打乱标签后模型应该完全学不到东西,评估指标接近随机水平。
第四步,时间分割验证。不要用随机分割,而是用时间分割。用较早时间段的比赛做训练,用较晚时间段的比赛做验证。这样更接近真实预测场景,也更容易暴露时间泄露问题。
注意:打乱标签测试是一个非常好用的泄露检测手段。我每次做完数据清洗都会跑一遍这个测试,只要模型在打乱标签后表现明显好于随机,就说明清洗流程有问题。
5.3 防范泄露的工程实践
防范泄露最好的办法是在工程层面做好隔离。我的做法是:
- 数据分层:把原始数据分成“赛前数据层”和“赛后数据层”,物理隔离。特征工程只能从赛前数据层取数。
- 时间戳强制校验:所有特征计算函数都必须接收一个“截止时间”参数,函数内部强制校验所有用到的数据的时间戳都早于截止时间。
- 流水线固化:把清洗和特征工程写成固定的流水线,每次新增特征都走同样的流程,避免手工操作引入泄露。
- 自动化测试:写单元测试检查关键特征的计算逻辑,比如“近10场胜率”的计算结果是否与手工计算一致。
这些工程实践看起来增加了前期工作量,但相比上线后才发现泄露然后返工,成本要低得多。
6. 清洗流程的自动化与可复现
6.1 为什么清洗流程必须可复现
数据清洗最怕的就是“这次跑出来的结果和上次不一样”。如果清洗流程不可复现,后面模型效果波动的时候根本没法排查是数据问题还是模型问题。
可复现的核心要求是:给定相同的原始数据,清洗流程每次运行都应该产出完全相同的结果。这听起来是基本要求,但实操中很容易被破坏。比如:
- 用了随机数但没有固定种子。
- 依赖了字典的遍历顺序,而字典顺序在不同运行环境下可能不同。
- 用了当前时间作为特征,导致每次运行结果不同。
- 数据源在两次运行之间发生了更新。
我的做法是把清洗流程写成一个独立的脚本,所有随机操作固定种子,所有时间相关的操作都显式传入时间参数,不依赖系统时间。每次运行前先对原始数据做快照,确保输入一致。
6.2 清洗流水线的结构设计
我把清洗流水线分成了几个独立的模块,每个模块负责一个明确的清洗任务:
- 数据加载模块:负责从各个数据源读取原始数据,统一格式,输出标准化的数据表。
- 实体对齐模块:负责战队、选手、英雄等实体的名称统一和ID映射。
- 时间边界模块:负责给每条数据打上时间标签,区分赛前和赛后数据。
- 缺失异常处理模块:负责缺失值分类填充和异常值标记。
- 特征计算模块:负责滑动窗口特征、聚合特征的计算。
- 泄露检查模块:负责运行泄露检测测试,输出检查报告。
- 输出模块:负责把清洗后的特征矩阵保存为标准格式,供模型训练使用。
每个模块之间通过标准化的数据接口通信,模块内部可以独立测试和替换。这样当某个环节出问题的时候,可以快速定位和修复,而不用重跑整个流水线。
6.3 清洗日志与数据版本管理
清洗过程中会产生大量中间数据,如果不做版本管理,很快就会搞不清楚哪个文件对应哪个版本。我的做法是:
- 每次运行清洗流水线,生成一个唯一的运行ID,包含时间戳和配置哈希。
- 所有中间文件和最终输出都带上运行ID作为后缀。
- 维护一个运行记录表,记录每次运行的配置、输入数据版本、输出数据路径、关键统计指标。
- 对于重要的版本,打上标签,比如“v1.0-基础清洗”“v1.1-修复泄露问题”。
清洗日志方面,我重点记录几类信息:
- 每个字段的缺失率变化
- 异常值的数量和比例
- 被删除的样本数量和原因
- 实体对齐的匹配率和冲突情况
- 泄露检查的结果
这些日志在排查问题的时候非常有用。比如模型效果突然下降,可以先看清洗日志里有没有异常变化,快速缩小排查范围。
7. 常见问题与排查技巧实录
7.1 清洗过程中的典型问题速查
在实际操作中,我遇到并记录了不少问题,整理成一张速查表,方便快速定位:
| 问题现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
| 模型训练集表现远好于验证集 | 数据泄露 | 打乱标签测试、时间穿越测试 | 逐字段审查,隔离赛后数据 |
| 样本量突然大幅减少 | 关联逻辑错误、去重过度 | 检查关联键和去重条件 | 修正关联逻辑,保留合理重复 |
| 某特征在验证集上分布异常 | 数据源更新、版本变更 | 对比训练验证集分布 | 按版本分组,或调整特征计算窗口 |
| 缺失值填充后模型效果下降 | 填充方式不合理 | 对比填充前后的特征分布 | 改用缺失标记,让模型自己学习 |
| 类别特征编码不一致 | 分别编码训练验证集 | 检查编码映射表 | 全量拟合编码器 |
| 滑动窗口特征计算缓慢 | 逐行循环计算 | 检查是否用了向量化 | 改用向量化操作或窗口函数 |
7.2 几个让我印象深刻的踩坑经历
坑一:用赛后数据构造赛前特征。这个前面提过,但值得再强调一次。我当时构造了一个“战队近期状态”特征,用的是最近5场比赛的场均经济差。逻辑上没问题,但实现的时候窗口边界写错了,把当前这场比赛也算进去了。结果这个特征和标签的相关性高得离谱,模型直接学到了“经济差大的队伍赢”这个规律。修复之后模型效果下降了不少,但泛化能力明显提升。
坑二:战队名称不一致导致样本丢失。不同数据源里同一个战队有不同写法,关联的时候匹配不上,导致大量样本被丢弃。后来建了映射表才解决。这个问题的教训是:在关联之前一定要先做实体对齐,不要假设不同数据源的命名是一致的。
坑三:版本更新导致特征失效。游戏版本更新后,英雄强度和战术体系都会变化,旧版本的数据参考价值下降。我一开始没有按版本分组,模型在版本更新后的比赛上表现明显变差。后来在特征里加入了版本距离,并且按版本分组做了验证,才解决了这个问题。
坑四:缺失值填充引入偏差。我一开始用均值填充所有缺失值,结果发现模型对某些样本的预测系统性偏高。排查发现是缺失值填充导致的:某些字段的缺失不是随机的,而是和标签相关。比如弱队的数据采集往往不完整,用均值填充等于给弱队“美化”了数据。后来改用缺失标记加分组填充,问题才解决。
7.3 独家避坑技巧
除了上面这些具体问题,我还总结了几条通用的避坑技巧:
技巧一:先做小样本验证。不要一上来就在全量数据上跑清洗流程。先用几百条样本跑通整个流程,确认逻辑正确后再扩展到全量。这样出问题的时候排查范围小,修复成本低。
技巧二:保留原始数据快照。每次清洗之前,先对原始数据做快照。这样即使清洗逻辑写错了,也能快速回滚,不用重新采集数据。
技巧三:特征计算函数要幂等。同一个特征计算函数,输入相同的数据,无论调用多少次,输出都应该一致。这要求函数内部不依赖外部状态,不修改输入数据。
技巧四:定期做泄露回归测试。数据清洗流程不是一次性的,每次新增特征或者修改逻辑,都要重新跑一遍泄露检测。我一般会在流水线里内置泄露检测,每次运行自动执行。
技巧五:清洗结果要可视化。数值型特征的分布图、缺失值的模式图、样本量的时间趋势图,这些可视化能帮你快速发现异常。我习惯在清洗完成后生成一份可视化报告,花几分钟扫一眼,往往能发现一些统计指标看不出来的问题。
8. 清洗完成后的验证与交付
8.1 清洗结果的验收标准
清洗流程跑完之后,不能直接拿去训练模型,要先做一轮验收。我的验收标准包括:
- 样本量合理:清洗后的样本量应该在预期范围内,不能出现大幅减少。
- 特征完整性:核心特征的缺失率在可接受范围内,缺失模式符合业务逻辑。
- 时间一致性:所有特征的计算时间都早于对应的比赛开始时间。
- 分布稳定性:训练集和验证集的特征分布没有显著差异。
- 泄露检测通过:打乱标签测试和时间穿越测试都通过。
- 可复现性:重新运行清洗流程,输出结果完全一致。
这些标准看起来多,但实际检查起来很快。我一般写一个验收脚本,自动跑完所有检查,输出一份验收报告。
8.2 特征矩阵的交付格式
清洗完成后的特征矩阵,我一般保存为Parquet格式,而不是CSV。原因有几个:
- Parquet是列式存储,读取特定列的时候速度快很多。
- Parquet保留数据类型,不会像CSV那样把数值读成字符串。
- Parquet压缩率高,节省存储空间。
- Parquet支持嵌套结构,方便保存一些复杂的特征。
除了特征矩阵本身,我还会交付几份额外的文件:
- 特征字典:每个特征的名称、含义、计算方式、时间边界、缺失处理方式。
- 清洗日志:清洗过程中的关键统计信息和异常记录。
- 验收报告:各项验收标准的检查结果。
- 版本信息:数据版本、代码版本、运行环境信息。
这些文件在后续模型训练和问题排查的时候非常有用。特别是特征字典,当团队其他人接手项目的时候,能快速理解每个特征的含义和限制。
8.3 从清洗到建模的衔接
数据清洗的最终目的是为建模服务,所以清洗完成后的特征矩阵要能无缝衔接到建模流程。我在设计清洗输出的时候,会考虑几个衔接问题:
- 特征命名规范:特征名称要能反映其含义和时间窗口,比如
team_a_win_rate_last10表示战队A近10场胜率。 - 标签列明确:标签列单独命名,比如
match_result,避免和特征列混淆。 - 分组列保留:比赛时间、版本号、战队ID这些列要保留,方便后续做分组验证和分析。
- 缺失标记列:每个有缺失的特征,对应的缺失标记列也要保留,让模型可以学习缺失模式。
这样建模的时候,只需要指定特征列和标签列,就可以直接开始训练,不需要再做额外的数据准备。
我个人在实际操作中的体会是,数据清洗这个环节投入的时间越多,后面建模和调优的时候就越省心。我见过太多项目在清洗上偷懒,结果模型效果怎么调都上不去,最后不得不回头重做清洗。与其这样,不如一开始就把清洗做扎实。另外一个小建议是,清洗流程一定要写成脚本,不要手工操作。手工操作不仅效率低,而且不可复现,出了问题根本没法排查。把清洗流程自动化之后,你可以把精力集中在特征设计和模型优化上,而不是反复处理数据问题。