“球员H的每90分钟射门数排第几?”这个问题只要一句话就能问出来,麻烦在“排第几”是在谁当中排。全联赛所有登记球员?那么门将会拉低平均,坐板凳只踢了十几分钟的球员会带来极端值,防守型中场自然排在后面。全队?样本只有十几人,排名的意义有限。真正有意义的比较,需要一个大小适中、性质相近的参照群体,也就是比较组。半岛体育AI在回答“某某和别人比怎么样”这类问题时,首先要做的不是排序,而是决定拿谁来比。

一次不合理的比较,是怎么发生的

先看一个错误案例(全部为示例数据)。用户问:“前锋林某本赛季场均传球成功次数怎么样?”一个偷懒的系统会把联赛里所有球员的场均传球成功次数拉出来,林某排在中间偏后,于是回答“低于平均水平”。

问题出在哪里?联赛平均里包含了中场组织者、后卫和门将,他们承担的任务就是大量传球;前锋的职责是接应和终结,传球次数少是角色使然。拿前锋去和整个群体比,得出的“偏低”并不是能力判断,只是位置不同的副产品。同样,如果问的是“射门次数”,让后卫和门将也参与排名,前锋会显得“极其出色”,同样没有信息量。

所以,一个比较结果能不能说明问题,先取决于被比较的人是否具有可比性,而不是指标算得是否精确。

比较组的四道筛选条件

合理的比较组通常从四个维度来筛选。每一个维度都有对应的字段,也都有各自的取舍。

位置

最基础的一条。前锋对前锋,边后卫对边后卫,中场则要细分到防守型、组织型、进攻型。数据里的“位置”字段常常粗糙:同一名球员在不同比赛里被登记为不同位置,甚至一场比赛里前后变化。稳妥的做法是根据球员最近一段时间的实际出场记录,取出场时间占比最高的位置,或者按热区数据归类,而不是直接读球员档案里的一个静态标签。

出场时间

一个只出场了120分钟的球员,每90分钟射门数可能非常漂亮,但只是小样本的偶然。所以比较组需要设置最低出场门槛,例如总出场时间不低于某个数值。门槛具体定多少并没有唯一答案,要看联赛的赛程长度和比较的统计量:进球、射门这类稀疏事件需要更多分钟数,传球次数这类高频事件较少的分钟数也能稳定。系统应在结果里公开所用的门槛,而不是暗自决定。

联赛级别与赛事类型

同一位球员在不同级别联赛的数据不能直接并列。级别较低的联赛里,对手强度、比赛节奏、场地条件都不同。如果比较组里混入不同级别的球员,比较结果既可能高估,也可能低估。数据里需要有联赛级别字段,并且在生成比较组时默认只取同级别;如果用户明确要求跨级别比较,则要给出显著提示。

比赛情境与球队风格

这一条最容易被忽略。控球型球队的前锋和防守反击型球队的前锋,触球次数、射门机会差别很大。比较组可以选择性地加入“球队风格相近”作为可选条件,但要谨慎:风格本身也没有统一的定义,引入之后容易让比较组过小。半岛体育的取舍是把它作为可选的补充筛选,默认不开启,只有用户明确追问时才提供。

把规则落成查询:一个示例

假设要为前锋球员H生成比较组,并算出每90分钟射门数的排位。按上面的规则,查询大致如下(表与字段为示例):

WITH base AS (
  SELECT s.player_id,
         SUM(s.shots)   AS shots,
         SUM(s.minutes) AS minutes
  FROM player_game_stats s
  JOIN players p ON p.player_id = s.player_id
  JOIN games g   ON g.game_id   = s.game_id
  WHERE g.season = '示例赛季'
    AND g.league_level = 1
    AND p.main_position = 'FW'
  GROUP BY s.player_id
  HAVING SUM(s.minutes) >= 900
),
rate AS (
  SELECT player_id,
         shots * 90.0 / minutes AS shots_per90
  FROM base
)
SELECT r.player_id, r.shots_per90,
       RANK() OVER (ORDER BY r.shots_per90 DESC) AS rk,
       COUNT(*) OVER ()                          AS group_size
FROM rate r;

值得留意的地方:分母是把所有比赛的分钟数相加,再统一折算成每90分钟,而不是对每场的“每90分钟射门数”取平均,后者会被短暂出场的比赛严重扭曲;HAVING 里的出场门槛决定了谁进入比较组;最后一并返回 group_size,让答案能够说“在同位置的多少名球员中排第几”,而不是孤立地报一个名次。

这个查询里的数字900只是示例门槛,没有普遍意义。更重要的是,它出现在查询里,而不是藏在系统的某个默认配置中。

答案应该怎么呈现比较组

比较是否可信,最终要靠用户看得见比较组的定义。一份合格的展示至少包含下面几项:

项目 示例内容
比较组定义示例赛季一级联赛,主要位置为前锋
出场门槛 累计出场不少于900分钟(可调整)
组内人数 示例值:46人
被比较指标 每90分钟射门数(累计射门数除以累计分钟数)
球员H的位置组内第12位,位于上游但非最前
提示该指标反映射门参与度,不等于终结效率

最后一行的提示不是装饰。“每90分钟射门数”高,只能说明他射门多,未必说明他射得准;要评价终结,需要另一组指标,比如射门转化率或者进球与预期进球的差距。将指标本身的含义说清楚,可以避免用户把排位当成“能力排名”。关于“谁更重要”“谁更好”这类词如何拆成明确指标,半岛体育在体育语义搜索一篇里做过更细的讨论。

自动选比较组的四个风险

让系统自动决定比较组,比让用户自己选省事,但也带来风险。这里把它们明确写出来。

  • 比较组过小。筛选条件叠加之后,只剩十几个人,名次几乎完全由偶然因素决定。系统应设置最小组规模,低于阈值时放宽条件并说明放宽了什么,或者直接提示“可比样本不足”。
  • 标签偏差。位置字段本身有噪声。球员H被登记为前锋,但实际经常回撤到中场组织,那么用前锋组去比,他的传球数据就会显得异常。遇到这类多位置球员,应同时给出两个比较组的结果,而不是硬选一个。
  • 幸存者偏差。出场门槛会把主要替补球员排除在外,而这些人恰恰可能是“每90分钟数据极高”的那一批。使用门槛是必要的,但用户需要知道这个过滤器存在。
  • 比较目的被替换。用户可能是想看“他相比上赛季有没有进步”,系统却给出“他在联赛里的排位”。这两种比较对象完全不同,前者是自己与自己,后者是与同类比较。拆解用户意图时要分清,具体拆法可参考复杂体育搜索的拆解。

这些风险无法靠某个参数一劳永逸地消除。半岛体育更看重的是:即便系统选错了比较组,用户也能从展示的定义中看出问题,并能够自己改,比如把出场门槛调低、加入更多位置、只看某一个赛季。

三种常见追问,比较组各自怎么变

用户在看到第一个答案之后,往往会顺势追问,每种追问对应的比较组变化也不一样,系统需要识别出来,而不是每次重新猜一遍。

  1. “只和同队的比呢?”比较组从联赛收缩为球队内部,位置条件可以保留,人数会明显减少。此时排名的意义变成“队内角色对照”,系统应主动提醒样本量偏小,并把展示方式从名次改为并排的数值对照。
  2. “和他上赛季比呢?”比较对象变成同一名球员的另一段时间,位置和联赛级别需要检查是否发生变化。如果他这个赛季转了会或者换了位置,两段数据的可比性本身就要先说明,这里正好用到球员归属随时间变化的信息。
  3. “和最好的几个比呢?”比较组缩成组内靠前的一小部分,目的从“定位”变成“找差距”。系统不宜简单截取名次前列,而要同时给出这几名球员的出场时间,避免把只踢了很短时间的高值球员当成标杆。

这三种追问背后都是同一个原则:比较组是问题的一部分,而不是系统的内部细节。用户每次改变问法,比较组的定义也应该随之更新,并在答案里显性地写出来。把“比较组”做成一个可查看、可调整的对象,正是半岛体育AI在分析类问题上希望坚持的产品设计方向。

比较对象,比比较结果更值得先说清楚

把开头那个问题重新回答一遍:“球员H的每90分钟射门数排第几”,好的答案不是一个名次,而是一句完整的话:“在示例赛季一级联赛累计出场不少于900分钟的46名前锋中,他的每90分钟射门数位列第12。”这句话里,比较组、门槛、指标、样本量都在,读者才知道这个“第12”意味着什么。

体育数据里的“排名”“好不好”,本质上都是相对的,脱离了比较对象就没有意义。一个只报名次不报参照的答案,哪怕计算完全正确,也可能在无意中误导用户。所以,与其花力气把指标算到小数点后三位,不如把比较组说清楚。图表如何呈现这样的相对位置,也是一个容易出错的地方,感兴趣的读者可以看看图表选择的相关讨论。