“新冠期间饿了么骑士行为预估”这个赛题,我从拿到数据包到真正开始建模,中间隔了整整一周。这一周我几乎没碰模型,全在啃数据和业务逻辑。事后回头看,这一周恰恰是整个比赛周期里价值最大的一段时间——因为所有特征工程的灵感、标签设计的依据、甚至验证集切分的坑,都是从“数据理解”这一步里长出来的。这篇复盘我就把这部分工作摊开讲清楚:怎么读数据字典、怎么从字段分布反推业务机制、怎么识别疫情带来的结构突变,以及怎么避免被一份看似“干净”的数据骗过去。如果你也要打这类带强业务背景的预测赛,或者只是想把一份陌生的行为数据真正“看明白”,这篇内容应该能帮你少走不少弯路。
1. 赛题背景与业务逻辑拆解
1.1 “骑士行为预估”预测的到底是骑手还是订单
拿到赛题的第一件事,不是急着打开 CSV,而是反复读题,搞清楚“行为预估”这四个字的边界。我当时的判断是:这类赛题表面在预测“骑手行为”,实际预测粒度通常落在订单或骑士的时间窗口上,区别非常大。
- 如果是订单级预测,目标是每笔订单是否准时送达、配送时长多少,特征可以围绕订单属性、商家、天气、路况来构建。
- 如果是骑士级预测,目标是某个骑手未来一段时间是否活跃、是否接单、是否可能取消,特征则要围绕骑手历史行为序列来构建。
- 还有一种常见设定,是把“行为预估”做成排序问题,比如预测骑手对订单的接受概率,本质上是一个点击率预估式的任务。
这三种玩法对数据理解的要求完全不同。我在数据里先找的字段是订单状态、事件时间戳、骑手编号和订单编号,然后看它们之间的关系。如果同一订单号下有多条状态记录,且状态枚举包含“已接单”“已取餐”“已送达”“已取消”,那大概率可以做订单级的履约预测;如果数据带骑士ID且跨多天重复出现,那还可以延伸到骑手级行为建模。
防疫期间的数据还有个特殊点:平台很可能增加了无接触配送、异常订单标记、健康状态上报等额外字段。这些字段有没有、怎么分布,都能反过来帮你判断赛题想让你关注哪一段“行为”。我在解析字段时会把所有字段按“实体维度”归类:订单实体、骑手实体、商家实体、地理实体、时间实体。归类之后,预测目标的轮廓会清晰很多,后面做特征也不会东一榔头西一棒子。
1.2 疫情这个外部变量改变了哪些行为逻辑
疫情对骑手行为的影响不是“平均变慢了10%”这么简单,而是结构性、分阶段的变化。我在数据理解阶段把它拆成供给、需求、履约三层来看。
- 供给端:可用骑手数量波动剧烈。某些区域因为封控、健康监测、隔离等原因,骑手运力锐减,而另一些区域的骑手可能因为订单集中反而出现短时过载。
- 需求端:线上订单结构发生变化。囤货型订单变多,防疫物资订单激增,小区门口集中取货成为常态,单笔订单的商品件数分布可能被拉高。
- 履约端:无接触配送普及,骑手在写字楼和小区门口的等待时间变长,配送距离未必增加,但“门到门”的时间却被明显拉长。
如果建模时不把这些机制考虑进去,很容易把疫情当作一个简单的时间戳特征,然后发现模型在高峰期表现很差。我做数据理解时会强制自己追问每个字段:这个字段的分布,在疫情前、疫情早期、疫情高峰期、恢复期之间,是不是存在肉眼可见的迁移?这种迁移是均值平移,还是方差变大?
实际操作中,我会按周为单位聚合关键指标,比如日均订单量、平均配送时长、取消率,然后画趋势线。通常在疫情政策调整、防控措施升级的节点上,这些曲线会出现明显的断崖或跳变。识别出断点之后,后续做特征工程时就能有针对性地引入“阶段标识”或“分段归一化”,而不是让模型自己在一片混乱里摸索。
2. 数据理解的关键动作:从字段到业务信号
2.1 数据字典与字段枚举值的交叉验证
数据理解的第一步永远是读数据字典,但读字典不能只看字段名注释,还要看枚举值的业务含义。我在这个赛题里遇到过一个典型情况:某个字段叫‘区域类型’,注释只写了‘1/2/3’,但1、2、3分别代表什么,字典里没有。唯一的办法就是拿它与天气、订单密度、配送时长做交叉分析,反推数字背后的真实含义。
通常我会先做一遍“字段体检”:每个字段缺失率、唯一值数量、数据类型、示例值。这一遍跑完,很多问题就暴露了。比如经纬度字段存在大量0值,说明可能是默认填充;时间戳字段有未来时间,说明存在脏数据;还有订单金额字段出现负数,大概率是退款订单混了进来。
做完体检后,我习惯把字段分成几类:
- 直接可用字段:骑手ID、订单ID、下单时间、送达时间等,清洗后才能用。
- 需要加工字段:经纬度需要算距离,时间戳需要提取小时、星期、是否为节假日。
- 潜在标签字段:订单状态、取消原因、超时时长,这类字段决定了标签怎么构造。
- 噪音字段:大量缺失且无业务含义的字段,该删就删,不必恋战。
这里特别说一下枚举值。骑手行为预估里,最容易出错的就是把“枚举值编号”当成数值来用。比如‘配送方式’的1、2、3,如果直接喂给树模型,模型能学,但可解释性很差,而且在某些采样不均匀的时候会误导分裂。我的习惯是先把枚举值转成带语义的字符串,再根据后续需要决定是做 one-hot 还是 target encoding。这种处理方式在数据理解阶段就要想清楚,否则后面特征代码越写越乱。
2.2 时间维度:从下单到送达的完整链路
行为数据的核心是时间。外卖配送本身就是一条时间链路:用户下单,骑手接单,骑手到店,骑手取餐,骑手送达。每一步都有时间戳的,比赛数据里通常不会全部给齐,但至少会有接单时间和送达时间。
我在数据理解阶段会把时间字段拆成几组“时间差”:接单耗时、到店耗时、取餐耗时、配送耗时。如果字段不全,就用自己的业务理解估算。例如同时有下单时间和送达时间,但没有接单时间,那我至少能算出总时长;剩下每段能拆多少算多少,实在拆不出来的就放弃,不要硬凑。
接下来按时间粒度做分布。先看小时级分布:一天之中订单量什么时候冲高,配送时长什么时候变长。疫情阶段尤其要看“午餐峰”和“晚餐峰”是否出现了时间偏移——有些地区的居家办公导致早餐时段订单上升,写字楼午餐峰变得不明显。这种偏移是很有价值的特征信号,也直接影响后续按小时做归一化的方式。
再看天级和周级。周级分布可以暴露星期效应:周末和节假日的骑手行为与工作日完全不同。疫情让这种周期变得更加复杂,比如封控期间可能天天都是“周末效应”,解封后又会恢复明显的周中/周末差异。我会把这种周期性做成可视化图表,然后在脑子里形成对数据节奏的感觉,这会指导我后面决定隐藏特征怎么滑窗、滑窗窗口取多大。
2.3 空间维度:距离与栅格化
骑手行为离不开空间。数据里可能有精确经纬度,也可能只有匿名化的街区网格编号,不管哪种,都要理解空间分辨率。
如果只有经纬度,最基础也最有效的加工是计算配送距离。我在计算距离时用的是高德/百度风格的球面距离公式,也就是 haversine 公式。算完之后我会重点关注距离分布:是不是大量订单集中在短距离区间?是不是有少量订单距离异常大,比如从城市的一端跑到另一端?
如果数据给的是网格ID,那就更直接。网格ID天然适合做栅格特征,可以直接统计每个网格的订单密度、配送时长均值、取消率等。我在理解数据时会画一张“网格-订单量”的透视表,快速找出哪些网格是高频区域。这些高频栅格往往对应商业区、医院、大型小区,是疫情期间骑手行为变化最敏感的地方。
空间维度还有一个容易忽略的点:同一路段在不同时间的路况完全不同,所以“空间特征”和“时间特征”必须交互使用。我的做法是构造“时段-区域”交叉特征,比如“早高峰时段的高频区域”“晚高峰时段的低频区域”,这种交叉特征往往比单独的时间或空间特征更有解释力。数据理解阶段把这些交叉规律摸清楚,后面特征工程就顺理成章。
2.4 数据质量的系统性体检
数据质量体检我放在最后讲,不是因为不重要,而是因为它是查漏补缺的工具。前面几节已经把字段属性和业务逻辑梳理清楚了,再做体检会更有针对性。
常见的数据质量问题包括:
- 缺失值:占比超过50%的字段要慎重使用。缺失模式比缺失本身更重要,比如某个字段只在特定区域缺失,说明采集过程中针对某些区域有特殊逻辑。
- 异常值:配送时长出现负数或超过12小时,基本是脏数据。我的处理方式是先识别再决定是剔除还是修正,而不是一律删除。
- 重复记录:同一个订单ID出现多次,可能是状态更新日志,也可能是重复采集。如果是状态更新,那一定要按时间排序后取最新状态。
- 时间倒挂:下单选时间晚于送达时间,或者接单时间晚于取餐时间,这类记录要么删除,要么修正为NaN,因为它会严重污染时间差特征。
我还会做一致性检查:比如订单状态是“已送达”的样本,必须有送达时间;状态是“已取消”的样本,不应有送达时间。如果发现大量样本违反这种业务约束,那说明数据生成过程有bug,后面的特征工程必须小心。
3. 实操手记:分层解读骑士行为数据
3.1 骑手维度:活跃时间、配送效率与取消率
骑手是“多订单并发”的调度主体,同一时间可能同时持有多个订单,所以只看单笔订单维度会丢失很多信息。数据理解阶段我习惯先把数据按骑手ID聚合,看每个骑手一天里跑了多少单、分布在哪些小时、平均每单耗时多少。
我当时做了三个关键透视:
- 骑手活跃时长:骑手首次接单时间到末次完成时间的跨度,这能反映骑手是全职还是兼职。疫情阶段兼职骑手比例可能会上升,这会直接影响运力稳定性。
- 骑手配送效率:每小时完成订单数。效率过低的骑手可能是新手或者路况不熟,效率过高的骑手则可能是同时接多单的“老手”。
- 骑手取消率:骑手主动取消的订单占比。取消率过高说明骑手挑单严重,对平台调度是很大的挑战。
这三个指标做出来之后,我惊讶地发现配送效率和时段关系很大:有些骑手午高峰效率极高,但下午基本消失;有些骑手全天效率平稳,但高峰时段反而表现一般。这种“骑手-时段”的交互模式,在后面的特征工程里是非常好的素材。
3.2 订单维度:品类、时段与配送方式的组合效应
订单维度的数据理解,核心在于找出“配送行为”和“订单属性”之间的关系。
我最先关注的是商品品类。疫情囤货期间,粮油、日用品、生鲜这类订单的配送时长明显高于餐饮订单,因为商品体积大、重量大,且可能需要额外的取货时间。我统计了不同品类的平均配送时长和超时率,发现生鲜类目的超时率几乎是奶茶类的三倍。这种差异如果不加处理,模型很容易把“品类”当作简单枚举值,而忽略它背后的物理约束。
时段效应同样重要。同样是奶茶订单,午高峰的配送时长和下午茶的配送时长就完全不同。我按“小时×品类”做了交叉统计,发现午高峰的订单不仅密度大,而且商家出餐时间普遍更长。这提醒我在后面构造特征时,不能简单把“小时”或“品类”单独使用,而是要做交叉。
配送方式也是一个关键字段。疫情期间无接触配送普及,我看到不同配送方式的订单在配送时长分布上有明显差异:无接触配送平均耗时更高,但超时率反而更低。这可能是因为无接触配送有更宽松的时间预期,也可能是平台在分配这类订单时预留了更多缓冲。这种信息对预测任务非常重要。
3.3 疫情阶段变化在数据上的呈现方式
疫情不是一根“铁板一块”的时间线,而是分阶段的:爆发期、高峰期、平台期、恢复期。我尝试把订单数据按周聚合,画出一条条趋势线,然后人工标出断点。
在趋势线上,我重点看了四个指标:
- 日均订单量:订单量出现明显跳升或跳水的周,往往是疫情相关政策变化的节点。
- 平均配送时长:如果配送时长突然拉长,可能对应无接触配送的全面推广,或者某个区域的运力短缺。
- 取消率:取消率突然升高,可能是用户因为配送慢而主动取消,也可能是骑手因为封控无法履约。
- 超时率:平台允许的最大配送时长在疫情期间可能被动态调整过,所以我只看“相对超时”,不只看绝对时长。
这样标出来的断点,对后续建立“阶段特征”非常有帮助。我给每个样本加了一个“疫情阶段标识”,然后在后续建模时看看这个特征的重要性排序。结果不出所料,阶段特征排在了模型重要性前列。这说明把外部环境信息编码成离散阶段,比让模型自己去拟合时间连续性更高效。
4. 常见问题与排查技巧实录
4.1 标签不平衡与阈值选择
骑手行为预估里,如果目标是“是否准时送达”,大概率会面临标签不平衡问题。准时配送的订单可能占80%以上,超时订单只有20%。这种情况下,模型即使全预测“准时”,准确率也有80%,但AUC可能很难看。
我的处理方式分几步:
- 先看标签分布,确定不做任何处理时模型的 baseline。
- 如果严重不平衡,考虑做负样本下采样,或者调整分类阈值。
- 观察模型的 PR 曲线而不是只看 AUC,因为在正样本极少的情况下,PR 曲线更能反映真实表现。
实际比赛中,我往往不会一上来就做正负样本平衡,而是先用原始分布跑一个 baseline 模型,记录下它在验证集上的表现。然后针对性地做重采样或阈值调整,对比结果后再决定要不要保留。这种做法能帮你判断不平衡问题是不是当前最主要的瓶颈。
4.2 缺失值里藏着的业务信息
缺失值不一定是垃圾,很多时候它自己就是特征。以配送距离为例,如果距离字段缺失,可能意味着该订单走的是“到店自取”或“特殊配送”模式,而不是骑手从商家送到用户家。直接删除或均值填充都会丢失这部分业务信号。
我的建议是:在数据理解阶段,把所有关键字段的缺失情况整理成一张矩阵,然后看缺失模式之间是否存在关联。如果发现“配送地址缺失”和“订单取消”几乎同时出现,那缺失值本身就可以作为预测标签的强特征。实际操作上,我会先为每个字段生成一个“是否缺失”的布尔特征,再在模型重要性排序里观察它们。多次经验证明,这些缺失指示特征往往能排进前20。
4.3 时间穿越与预测泄露
行为预测最忌讳的就是时间穿越。如果我预测的是T时刻的配送时长,那模型只能使用T时刻之前产生的信息。如果我在特征里加入了“送达后的用户评价”或者“订单完成后的金额”,模型在训练时就会“偷看未来”,导致在线推理时完全崩掉。
我在数据理解阶段就会特意标记每个字段的产生时间。比如订单金额在用户下单时就有了,可以用;用户是否给出高评分,在配送完成后才有,不能用;取消原因,如果发生在配送过程中,则需要根据业务逻辑判断它是前置还是后置信息。
一个典型的泄露场景是:用全样本的均值填充缺失值。如果我按整个数据集计算某个区域的配送时长均值,然后填充到缺失样本里,这个均值实际上包含了未来信息。正确做法是用训练集的均值,或者用时间上只包含过去的滑动均值。
4.4 分布漂移与验证集切分
疫情数据天然存在严重的时间分布漂移。前两周的订单特征分布和后两周的可能完全不同。如果用随机切分的方式把数据分成训练集和验证集,验证集会包含大量时间相近的样本,导致模型评估结果偏乐观。
我采用的切分方式是严格按时间切分:前70%的时间段做训练集,后30%做验证集。切分之后再确认验证集的时间范围里没有出现极端的政策变化。如果必须针对不同疫情阶段分别评估,我会额外划分“早/中/晚”三个验证子集,分别看模型的稳定性。这比只报一个总体分数更有说服力,也更容易暴露模型在哪类时段上泛化差。
5. 从数据理解到建模方案的关键衔接
5.1 行为标签选择的三个候选方案
数据理解完成之后,接下来最重要的一步是定义标签。我当时列了三个候选:
- 方案A:是否超时(二分类)。优点是业务含义清晰,只管“准不准时”;缺点是忽略了超时程度的差异。
- 方案B:实际配送时长(回归)。优点是信息量更细,可以精确比较不同骑手、不同订单;缺点是受极端值影响大。
- 方案C:超时时长的分桶(多分类)。优点是对异常值更鲁棒,且可以通过阈值控制业务风险;缺点是标签定义相对主观,需要尝试不同分桶方式。
我用数据理解阶段产出的洞察来比较这三个方案。方案A虽然简洁,但容易受阈值影响,疫情期平台对配送时长的容忍度可能在变化;方案B更直接,但需要做好数据截断处理;方案C最灵活,但可解释性稍差。我最终选择以“是否超时”为第一版标签,因为它的评估指标和业务表达最匹配。后续如果时间充裕,再对比另外两个方案的模型效果。
5.2 快速基线的搭建与学习曲线观察
标签确定后,我搭建了第一版轻量基线:用原始字段里最直接的几十个特征,包括配送距离、下单小时、区域ID编码、天气状况、商品品类,喂给LightGBM。这个基线的AUC在0.72左右,不算高,但是已经能验证整个训练-验证代码链路是否顺畅。
基线跑通后,我做了一次特征重要性和学习曲线分析,结果很说明问题:
- 配送距离和下单小时排在前几位,符合业务直觉。
- 骑行时段交叉特征比纯小时特征更稳定。
- 训练集和验证集的AUC差异不大,说明没有严重过拟合,但也没有明显的欠拟合。
这正是数据理解的回报——因为没有盲目堆特征,所以能更清晰地看到哪些变量在驱动预测结果。后续我在此基础上按骑手维度、订单维度、时空交互维度逐步加特征,每加一批就重新评估一次,保持模型的迭代方向始终有依据。
5.3 一份数据理解报告的沉淀方法
比赛过程中养成的一个习惯,是在每个阶段都沉淀一份简短的数据理解报告,包含数据字典补充说明、字段分布快照、关键趋势图、假设清单。这份报告不需要写得非常正式,甚至可以只用 Markdown 或 Jupyter Notebook 来记录,但对后续建模帮助极大。
我自己的报告模板一般是这样的:
- 数据概览:多少行,多少列,时间范围。
- 字段分类:哪些字段已经清洗,哪些还在排查。
- 关键发现:3-5条数据洞察,每条都要注明对应的业务逻辑。
- 风险清单:哪些字段可能泄漏,哪些缺失模式需要关注。
- 下一步计划:标签候选、特征候选、验证集策略。
这样做的好处是,即使比赛周期拉长,或者中途需要更换队友重新交接,也能在半小时内恢复对全部数据的理解。我在不少项目里都吃过“记不清某个字段当初为什么这么处理”的亏,后来养成了这个习惯才彻底解决了问题。
数据理解这件事,做到最后其实是在训练一种“翻译能力”——把数字翻译回业务场景,把业务场景翻译成特征方案。我在这场比赛里的一个很深体会是,越是在数据混乱、外部环境剧烈变化的场景里,冷静下来做数据理解的时间就越不会白费。它不会直接帮你把AUC从0.7拉升到0.9,但能让你在后续每一次特征迭代和模型调优时,都清楚地知道自己为什么这么做、下一步该往哪里走。翻车最多的比赛,往往不是模型不够强,而是从一开始就没有把数据看明白。