关于 nutmeg-wrangle
nutmeg-wrangle 是处理足球数据的技能,涵盖筛选、合并、重塑、格式转换和数据校验,并提供坐标变换及大数据处理方法。指导会根据用户使用的语言和工具调整。
主要功能
- 01指导按事件和区域筛选
- 02说明数据集合并与重塑
- 03涵盖不同提供方的坐标变换
- 04建议大数据处理方法
- 05列出数据质量检查项
适用场景
- 01按类型或比赛状态筛选事件
- 02将事件与阵容或 xG 合并
- 03合并不同来源前转换坐标
- 04校验并重塑赛季数据
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ withqwerty/ nutmeg --skill nutmeg-wrangle安装后在 Claude Code 中输入/nutmeg-wrangle确认已加载。
详细介绍
数据整理
帮助用户高效处理足球数据。此技能介绍处理数据的方法,并根据用户的语言和工具进行调整。
准确性
在回答任何关于特定数据提供方的事实(ID、端点、架构、坐标、速率限制)的问题前,先阅读并遵循 ${CLAUDE_PLUGIN_ROOT}/docs/accuracy-guardrail.md。始终使用 search_docs,切勿根据训练数据猜测。
首先:检查配置
读取 .nutmeg.user.md。如果文件不存在,则使用合理的默认设置(Python 和 pandas,具有中级水平),并在最后建议运行 /nutmeg setup。根据用户的配置选择语言和技术栈。
核心操作
坐标转换
足球数据坐标因提供方而异。合并数据前,务必先核实并转换坐标。
使用 search_docs(query="coordinate system", provider="[provider]") 查询各提供方的原点、范围和 y 轴方向。不要凭记忆编写转换公式:各提供方的 y=0 所在边线不同,因此仅重新缩放的公式可能会镜像球场。
- Python:优先使用 kloppy 的
.transform()或 mplsoccer 的Standardizer,它们能处理各提供方的方向定义。 - 手动编写转换:根据查到的定义推导公式,然后进行测试。开球位置应落在中圈中心,球队的射门应集中在其进攻方向的球门附近。
筛选事件
足球事件数据的常见筛选模式:
按事件类型:
- 射门:筛选射门、射偏、进球或被扑救等事件类型
- 进攻三区传球:筛选位于提供方 x 轴范围最后三分之一内的传球(对于 0-100 的球场,x > 66.7)
- 防守动作:抢断 + 拦截,并根据定义加入犯规、争抢、封堵或球权恢复(请明确说明采用的定义)
按比赛状态:
- 仅开放式进攻:排除定位球(角球、任意球、界外球、点球)
- 上半场与下半场:使用 periodId 或时间戳
- 比分状态:跟踪实时比分,以筛选“领先时”或“落后时”的数据
按区域:
- 禁区内动作:使用
search_docs(query="pitch zones", provider="[provider]")查询提供方的禁区边界;这些边界在标准化球场上为近似值 - 高位压迫:筛选发生在对手防守三区内的动作(提供方 x 轴范围的最后三分之一)
合并数据集
足球数据中的常见合并方式:
| 合并数据 | 键 | 说明 |
|---|---|---|
| 事件 + 阵容 | player_id + match_id | 获取每个事件对应的球员姓名和位置 |
| 事件 + xG | match_id + 事件序列 | 将 xG 匹配到具体射门 |
| 多个提供方 | Reep ID | 将各提供方的 (provider, namespace, id) 映射到 Reep ID,然后按 Reep ID 合并 |
| 赛季数据 + Elo | date | 合并比赛当时的 Elo 评级 |
**合并不同提供方的数据:**通过 Reep Register ID 合并,而不是通过名称(参见 ${CLAUDE_PLUGIN_ROOT}/docs/entity-resolution-routing.md)。不同提供方使用的名称可能不同(“Man City” / “Manchester City”),不同球员也可能同名,而且模糊名称匹配可能静默失败。
- 使用 Reep 工具(
resolve、translate)或resolve_entity查询 ID,或者查询 Reep 发布版 DuckDB 进行批量合并。 - 保留无法解析的行,并报告各提供方未解析的行数。绝不要静默丢弃这些行。
- 只有在 Reep 未涵盖相关实体时,才将手动创建的名称映射作为最后手段;并将这些行标记为按名称匹配,以便用户检查。
重塑数据
常见的数据重塑操作:
- **宽表转长表:**将赛季统计表(每项统计各占一列)转换为整洁格式(每支球队的每项统计各占一行)
- **事件转控球链:**将同一球队连续发生的事件分组为控球序列
- **比赛级数据转赛季汇总:**按球队分组,计算每场比赛数值的总和或平均值
- **球员-比赛数据转球员-赛季数据:**跨比赛汇总,并按上场分钟数加权
处理大型数据集
一个完整赛季的赛事数据可能达到数百 MB,追踪数据则可能大得多。可采用以下策略:
Python:
- 处理大型表格时使用 polars 替代 pandas;它通常快得多
- 在循环中逐场处理,不要将所有数据都加载到内存中
- 使用 DuckDB 对 Parquet 文件执行 SQL 查询,无需将数据加载到内存中
JavaScript/TypeScript:
- 使用
readline或JSONStream流式读取 JSON 文件 - 使用 SQLite(better-sqlite3)进行本地查询
- 使用工作线程并行处理文件
R:
- 处理大型数据集时使用 data.table 替代 tidyverse
- 使用 Arrow/Parquet 进行内存外处理
数据质量检查
完成数据整理后,务必进行验证:
| 检查项 | 检查内容 |
|---|---|
| 赛事计数 | 将每场比赛与同一数据集中的其他比赛进行比较。典型计数取决于数据提供方,因此应标记异常值,而不是使用固定数值 |
| 坐标范围 | 应处于数据提供方预期的范围内 |
| 缺失的球员 ID | 部分事件缺少球员归属信息(如球出界等) |
| 重复事件 | 同一个 event_id 出现两次 |
| 时间间隔 | 一场比赛的事件时间戳之间存在较大间隔 |
| 球队归属 | 确认主队/客队归属一致 |
格式转换
| 来源 | 目标 | 工具/方法 |
|---|---|---|
| JSON 事件 | DataFrame | 使用 pandas/polars 的 read_json 或手动解析 |
| CSV | Parquet | df.write_parquet()(polars)或 df.to_parquet()(pandas) |
| 数据提供方格式 | kloppy 模型 | 在 Python 中使用 kloppy.load_{provider}() |
| kloppy 模型 | DataFrame | dataset.to_df() |
| 任意格式 | SQLite | 加载到 SQLite 中以便临时查询 |