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のセットアップを実行するよう提案します。言語設定と技術スタックには、ユーザーのプロフィールを使用します。
主な操作
座標変換
サッカーデータの座標はプロバイダーによって異なります。データを結合する前に、必ず確認して変換してください。
各プロバイダーの原点、範囲、y軸の向きを調べるには、search_docs(query="coordinate system", provider="[provider]")を使用してください。変換式を記憶に頼って書かないでください。プロバイダーによってy=0となるタッチラインが異なるため、単純な縮尺変更だけを行う式では、ピッチが反転することがあります。
- Python:各プロバイダーの向きを考慮したkloppyの
.transform()またはmplsoccerのStandardizerを優先します。 - 手書きの変換:検索した定義から導き出し、テストしてください。キックオフの位置がセンタースポットに一致し、チームのシュートが攻めるゴール側に集まることを確認します。
イベントのフィルタリング
サッカーのイベントデータでよく使われるフィルタリングのパターン:
イベントタイプ別:
- シュート:シュート/ミス/ゴール/セーブのイベントタイプでフィルタリングします
- ファイナルサードでのパス:プロバイダーのx範囲の最後の3分の1にあるパスを抽出します(0~100のピッチの場合、x > 66.7)
- 守備アクション:定義に応じて、タックル+インターセプトに、ファウル、チャレンジ、ブロック、またはボール奪取を加えます(定義には名前を付けてください)
試合状況別:
- オープンプレーのみ:セットプレー(コーナーキック、フリーキック、スローイン、ペナルティー)を除外します
- 前半と後半:periodIdまたはタイムスタンプを使用します
- スコア状況:試合中の得点を追跡し、「リードしているとき」「負けているとき」のイベントを抽出します
ゾーン別:
- ペナルティーエリア内のアクション:
search_docs(query="pitch zones", provider="[provider]")でプロバイダーのペナルティーエリアの境界を調べます。正規化されたピッチ上では、おおよその値です - ハイプレス:相手の守備サード(プロバイダーのx範囲の最後の3分の1)でのアクションを抽出します
データセットの結合
サッカーデータでよく使われる結合:
| 結合対象 | キー | 備考 |
|---|---|---|
| イベント+メンバー表 | 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、またはReepリリースのDuckDBへのクエリを使って、一括結合用のIDを調べます。 - 解決できなかった行は残し、プロバイダーごとにその件数を報告してください。黙って削除してはいけません。
- Reepが対応していないエンティティに対しては、最後の手段としてのみ手動の名前マップを使い、ユーザーが確認できるよう、名前照合した行に印を付けてください。
データの再構成
よく使われるデータの再構成操作:
- ワイド形式からロング形式へ: シーズンの統計テーブル(統計項目ごとに1列)を、整然とした形式(チームと統計項目の組み合わせごとに1行)に変換します
- イベントからポゼッションの連鎖へ: 同じチームの連続するイベントをまとめ、ポゼッションのシーケンスにします
- 試合単位からシーズン集計へ: チームごとにグループ化し、試合ごとの値を合計/平均します
- 選手・試合単位から選手・シーズン単位へ: 出場時間で重み付けしながら、試合ごとのデータを集計します
大規模データセットの処理
シーズン全体のイベントデータは数百メガバイトに達することがあり、トラッキングデータはさらに大きくなります。以下の方法があります。
Python:
- 大規模なテーブルには pandas ではなく polars を使用します。通常、こちらのほうがはるかに高速です
- すべてをメモリに読み込まず、ループ内で試合ごとに処理します
- メモリに読み込まずに Parquet ファイルを SQL クエリするには DuckDB を使用します
JavaScript/TypeScript:
readlineまたはJSONStreamで JSON ファイルをストリーミングします- ローカルでのクエリには SQLite(better-sqlite3)を使用します
- ワーカースレッドでファイルを並列処理します
R:
- 大規模なデータセットには tidyverse ではなく data.table を使用します
- メモリに収まらないデータの処理には Arrow/Parquet を使用します
データ品質チェック
データ加工後は必ず検証します。
| チェック項目 | 確認する内容 |
|---|---|
| イベント数 | 同じデータセット内の各試合と比較します。一般的な件数はプロバイダーによって異なるため、固定値ではなく外れ値を検出します |
| 座標の範囲 | プロバイダーが想定する範囲内であることを確認します |
| 欠落している選手 ID | 一部のイベントでは選手が特定されません(ボールが場外に出た場合など) |
| イベントの重複 | 同じ event_id が2回出現していないか確認します |
| 時間の空白 | 試合中のイベントのタイムスタンプに大きな空白がないか確認します |
| チームの割り当て | ホーム/アウェイの割り当てが一貫していることを確認します |
形式変換
| 変換元 | 変換先 | ツール/方法 |
|---|---|---|
| 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 に読み込みます |