qinghuan
← 三个动手实验

QINGHUAN / 动手查证 / 2026.10.30

新用户与未来日期:把两种数据边界分别验清楚

用八条订单记录检查用户交集、时间先后、标签与特征何时可用,并验证 gap 行数和预处理均值的边界。

你准备在第 6 天训练一个模型,预测之后订单的退款风险。测试目标是:未来日期里,模型在训练时没见过的用户。 如果只按用户分组,训练数据可能来自测试日期之后;如果只按日期切开,老用户仍可能出现在两侧。两条规则解决的是不同问题。

已有数据划分与泄漏入门讲过随机、分组与时间切分。本实验把它们放到同一份数据里,逐项检查哪些条件已经满足,并补上容易遗漏的“当时是否拿得到特征和标签”。

先写评估目标,再挑切分方法

将来要回答的问题本轮至少应检查什么
老用户未来的记录时间先后、信息可用时间;用户出现在两侧可以符合任务目标
训练中没见过的用户用户组隔离;还要确认采样与真实使用场景相符
未来日期里训练中没见过的用户同时检查用户隔离、时间先后和信息可用时间

GroupKFold 的契约是各折训练与测试的组不重叠;文档说明组顺序可以任意。因此它不会替你保证训练记录早于测试记录。GroupKFold API

本实验选择第三个目标。“新用户”在这里指模型训练中未见过,不自动等于业务上刚注册的人。若你的目标是给老用户预测明天的订单,不要为了得到零用户交集而把评估目标换掉。

八条记录,能暴露四种不同的问题

以下全部是自行设计的教学数据。日期用同一个月里的整数日表示;第 6 天固定模型,第 15 天离线评分。x 是一项数值特征。标签要等退款观察结束后才确定,不能把最终拿到的结果误当成第 6 天已经知道的结果。

记录用户预测日标签可用日x特征可用日
a1A13101
b1B24202
a2A48304
c1C681006
b2B79407
d1D8101208
c2C91114010
a3A10125010

先试两种不完整的方案:

  1. 把 A、B 全给训练,把 C、D 全给测试。 用户确实没有交集,但训练集包含第 10 天的 a3,测试却从第 6 天开始。它没有模拟“第 6 天固定模型”的条件。
  2. 第 6 天之前给训练,之后给测试。 时间顺序正确,但 A、B 在两侧重现,不能直接当作“未见用户”的测试。此外,a2 的标签第 8 天才确定;c2 的特征第 10 天才出现,却声称用于第 9 天预测。

后两项属于本实验依据业务时间定义的检查。只存一列“记录日期”无法判断它们;需要分别保留预测时间、特征可用时间和标签可用时间。晚到数据、事后回填的字段也需要按其当时可用状态重建。

固定一个可检查的划分

在这个教学协议中,训练记录必须同时满足:预测日早于 6、标签在第 6 天已确定、特征在各自预测时已可用。因此只留下 a1、b1。测试再选第 6 天起、训练中未见过的用户,并检查特征当时可用、标签在第 15 天评分时已确定,留下 c1、d1。

文中示例 · python
train = [r for r in rows
         if r.day < 6 and r.label_known_day <= 6
         and r.feature_known_day <= r.day]
seen = {r.user for r in train}
test = [r for r in rows
        if r.day >= 6 and r.user not in seen
        and r.feature_known_day <= r.day
        and r.label_known_day <= 15]

这是一种明确的人工保留集规则,不是 GroupKFold 与 TimeSeriesSplit 的组合调用,也不声称适合所有数据。本例没有 c2 在第 9 天的历史特征值,故将其排除;实际项目应优先重建当时可得的特征,再决定能否评估。过滤后只有两条测试记录,不能据此评价模型泛化。还要记录排除原因、人数、类别比例和时间覆盖;不能悄悄删掉难样本,只汇报剩下的漂亮分数。若评估对象被排光,应报告无法完成该评估,而不是输出零错误。

gap 为 2,不等于隔开两天

TimeSeriesSplit 面向已按时间排序的数据,gap 排除的是训练末尾的样本数;它不读取业务日历,也忽略传入的 groups。官方提到等时间间隔,是为了让各折覆盖相同时间长度、指标容易比较,并不表示不规则时间戳会被 API 自动拒绝。TimeSeriesSplit API

假设一折的行号是:训练 [0,1,2],跳过 [3,4],测试 [5]。同样跳过两行:

六行的日期最后一条训练 → 第一条测试相差
1、2、3、4、5、63 → 63 天
1、2、20、21、22、3020 → 3010 天

如果订单量每天不同,用固定行数不能表达固定天数。若标签要观察 30 天才能确定,应按模型固定时刻检查标签是否已形成;不要只设置 gap=30 就声称问题解决。这个表仅演示行数与日期的区别,不是在运行 scikit-learn。

预处理也不能偷看测试值

训练特征是 [10,20],测试特征是 [100,120]。只从训练集学习“减去均值”的规则,均值是 15,测试变成 [85,105]。如果先把两侧合在一起算,均值变为 62.5,测试得到 [37.5,57.5]。即使没有读取测试标签,测试输入已经改变了训练流程。

scikit-learn 的建议是先切分,再只对训练数据 fit,对测试数据使用已学到的转换;交叉验证时也要在每折训练部分内拟合预处理。Pipeline 有助于守住这一步,但不能替你选择正确的分组或时间规则。常见陷阱:数据泄漏

这个均值反例只证明变换受到了污染,并没有证明某个模型的准确率一定升高。不要给未训练的模型编造分数。

运行后检查什么

下载完整 Python 实验,保存后运行:

文中示例 · text
python group-time-split.py

脚本已在 Python 3.14.0 运行,关键结果如下:

文中示例 · text
group_only: group_overlap=[], train_before_test=false
time_only: group_overlap=["A", "B"], train_before_test=true
training_labels_unavailable=["a2"]
features_unavailable_at_prediction=["c2"]
joint: train_ids=["a1", "b1"], test_ids=["c1", "d1"]
centering: train_mean=15, leaky_mean=62.5
calendar_distance_days=[3, 10]
PASS: split, availability, centering and row-gap invariants

它会断言错误方案被发现、最终规则通过、修改测试值不会影响训练均值,并拒绝空划分。运行范围是 Python 标准库的集合、时间条件与算术检查;没有调用 scikit-learn,也没有训练模型。

留给你的三项练习

  • 将目标改成“老用户未来的记录”。明确哪些组交集应被允许,哪些时间检查仍须保留;写出理由和新测试。
  • 给某用户添加第二个账号 ID。仅检查字符串交集还能发现同一实体跨集合吗?设计一个来源于业务事实的实体映射,并记录无法确定的记录。
  • 把第 6 天以前的一条记录改成“标签第 20 天才能确定”,再把评分日改成第 9 天。分别记录训练与评分阶段应排除哪些记录,以及测试样本还剩多少。

来源与复核

2026 年 10 月 30 日复核以上三份 scikit-learn 官方文档,页面显示版本 1.9.1;独立发布日期未标明。本文的八条数据、日期协议、均值对照和断言为原创教学设计。继续学习时,可把检查接到概率校准的数据隔离之前,先确认基础预测来自合适的数据划分,再比较概率质量。

改变条件,观察名单

这八条记录,哪些能留下

第 6 天固定模型,第 15 天评分。勾选条件,查看训练、测试与排除原因。这是网页内的数据规则推演,使用正文同一份教学数据。

训练 2 条:a1、b1;测试 2 条:c1、d1

用户交集:无。训练标签晚到:无。预测特征晚到:无。

当前选择满足本例的信息可用时间检查;这不代表已经训练或评估了模型。

记录 / 用户预测 / 标签 / 特征可用日去向原因
a1 / A1 / 3 / 1训练第 6 天之前的记录
b1 / B2 / 4 / 2训练第 6 天之前的记录
a2 / A4 / 8 / 4排除训练时标签尚未形成
c1 / C6 / 8 / 6测试符合当前选择的测试范围
b2 / B7 / 9 / 7排除用户已在训练中出现
d1 / D8 / 10 / 8测试符合当前选择的测试范围
c2 / C9 / 11 / 10排除预测时特征尚未可用
a3 / A10 / 12 / 10排除用户已在训练中出现

先判断,再解释

三个问题,检查是否真的理解

这些是概念判断题。下载实验后还要运行代码,核对实际输出。

已答对 0 / 3

1. 目标是评估“未来从未见过的用户”。只按日期切开数据,就一定满足目标吗?
2. 模型在 6 月 1 日上线,标签是“下单后 30 天是否退款”。5 月 31 日的订单能直接当作已有完整标签的训练样本吗?
3. 数据行间隔不规则,TimeSeriesSplit 的 gap=7 等于隔离 7 天吗?