🎃

サッカー選手の成績を分析するための準備

2022/06/04に公開約9,200字1件のコメント

はじめに

サッカー選手の試合成績を分析したい

本記事で説明する内容

データ分析用に以下項目を実施

  • データ基盤整理
  • UI 設計
  • CSV 出力
  • 分析チュートリアル

API を使ってデータを取得する

今回はマンチェスター・シティの選手を対象とします。

使用する API

今回使用する API はこちらです。

https://www.api-football.com/

この API で取得できるデータは様々ですので、目的に応じて下記ページでエンドポイントを変えてテストしてみることをお勧めします。ちなみに、このページで自分の API_KEY を取得できます。

https://dashboard.api-football.com/soccer/tester

何をデータとして持つのか

まずはデモとして、以下のデータを取得します。

  • Name:名前
  • Age:年齢
  • Game (min):出場試合数(分)
  • Goal (shots):ゴール数(決定機シュート本数)
  • Assist (pass):アシスト数(決定機パス本数)

※今後分析したい目的に応じてデータ基盤はその都度整理します。

レスポンスの型定義

API で取得したパラメータの型定義は types ではなく api フォルダ配下で行います。

もちろんエンドポイントによって型が異なるのでその際は新しく受け皿を作る必要があります。

APIの型定義
src/types/api/player.ts
export type Player = {
  player: {
    id: number;
    name: string;
    firstname: string;
    lastname: string;
    age: number;
    birth: {
      date: string;
      place: string;
      country: string;
    };
    nationality: string;
    height: string;
    weight: string;
    injured: boolean;
    photo: string;
  }
  statistics: {
    team: {
      id: number;
      name: string;
      logo: string;
    },
    league: {
      id: number;
      name: string;
      country: string;
      logo: string;
      flag: string;
      season: number;
    },
    games: {
      appearences: number;
      lineups: number;
      minutes: number;
      number: number;
      position: string;
      rating: number;
      captain: boolean;
    },
    substitutes: {
      in: number;
      out: number;
      bench: number;
    },
    shots: {
      total: number;
      on: number;
    },
    goals: {
      total: number;
      conceded: number;
      assists: number;
      saves: number;
    },
    passes: {
      total: number;
      key: number;
      accuracy: number;
    },
    tackles: {
      total: number;
      blocks: number;
      interceptions: number;
    },
    duels: {
      total: number;
      won: number;
    },
    dribbles: {
      attempts: number;
      success: number;
      past: number;
    },
    fouls: {
      drawn: number;
      committed: number;
    },
    cards: {
      yellow: number;
      yellowred: number;
      red: number;
    },
    penalty: {
      won: number;
      commited: number;
      scored: number;
      missed: number;
      saved: number;
    },

  }
};

カスタムフックを作る

axios インスタンスを使い、API の指定エンドポイントを叩き、選手の情報を取得します。

APIからデータ取得
src/hooks/useGetPlayers.ts
import axios from "axios";
import { useState } from "react";
import { Player } from "../types/api/player";

//カスタムフックでマンチェスターシティの選手スタッツを取得
export const useGetPlayers = () => {
  const [player, setPlayer] = useState<Array<Player>>([]);
  const [playerNext, setPlayerNext] = useState<Array<Player>>([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(false);

  const getPlayers = () => {
    setLoading(true);
    setError(false);
    axios
      .get("https://v3.football.api-sports.io/players?league=39&season=2021&team=50", {
        "method": "GET",
        "headers": {
            "x-rapidapi-host": "v3.football.api-sports.io",
            "x-rapidapi-key": "" //API_KEYをこちらにセットします
        }
      })
      .then((res) => {
        const playersLists = res.data?.response
        setPlayer(playersLists);
      })
      .catch(() => {
        setError(true); //エラー発生
      })
      .finally(() => {
        setLoading(false); //正常終了
      });
  };

  return { getPlayers, getPlayersNext, player, playerNext, loading, error };
};

UI に反映する

使用したいデータを表示するテーブルを作成します。

テーブルを作成
src/components/templates/players/index.tsx
import React from 'react';
import { Player } from '../../../types/api/player';
import Table from '@mui/material/Table';
import TableBody from '@mui/material/TableBody';
import TableCell from '@mui/material/TableCell';
import TableContainer from '@mui/material/TableContainer';
import TableHead from '@mui/material/TableHead';
import TableRow from '@mui/material/TableRow';
import Paper from '@mui/material/Paper';

export interface playerProps {
  players: Player[]
}

//実際に画面に表示するデータ型の定義
export const PlayerCard: React.FC<playerProps> = ({players}) => {
  return (
    <TableContainer component={Paper}>
      <Table sx={{ minWidth: 600 }} aria-label="simple table">
        <TableHead>
          <TableRow>
            <TableCell align="center">Name</TableCell>
            <TableCell align="center">Age</TableCell>
            <TableCell align="center">Game&nbsp;(min)</TableCell>
            <TableCell align="center">Goal&nbsp;(shots)</TableCell>
            <TableCell align="center">Assist&nbsp;(pass)</TableCell>
          </TableRow>
        </TableHead>
        <TableBody>
          {players.map((player) => (
            <TableRow
              key={player.player.id}
              sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
            >
              <TableCell component="th" scope="row" align="left">{player.player.firstname}</TableCell>
              <TableCell align="left">{player.player.age}</TableCell>
              <TableCell align="left">{player.statistics[0]?.games?.appearences}&nbsp;({player.statistics[0]?.games?.minutes})</TableCell>
              <TableCell align="left">{player.statistics[0]?.goals?.total}&nbsp;({player.statistics[0]?.shots?.total})</TableCell>
              <TableCell align="left">{player.statistics[0]?.goals?.assists}&nbsp;({player.statistics[0]?.passes?.key})</TableCell>
            </TableRow>
          ))}
        </TableBody>
      </Table>
    </TableContainer>
  );
};
export default React.memo(PlayerCard);

フロントページに反映します

useEffectでカスタムフックを呼び出す
src/pages/player/index.tsx
import React, { useEffect } from 'react';
import Head from 'next/head'
import Layout, { siteTitle } from '../../components/templates/layout/layout'
import Link from 'next/link';
import { Button } from '@mui/material';
import { useGetPlayers } from '../../hooks/useGetPlayers';
import { PlayerCard } from '../../components/templates/players';

const PlayerPage: React.FC = () => {
  const { getPlayers, getPlayersNext, player, playerNext, loading, error} = useGetPlayers();

  //useEffectでカスタムフックを呼び出す
  useEffect(()=>{
    getPlayers();
  },[])
    return (
    <Layout home>
      <Head>
        <title>{siteTitle}</title>
      </Head>
      <Link href='/'><Button>Home</Button></Link>
      <section>
      <br />
      {error ? (
        <p>データ取得に失敗しています</p>
      ) : loading ? (
        <p>Loading...</p>
      ) : (
        <>
        <PlayerCard players={playerLists}/>
        </>
      )}
      </section>
    </Layout>
  )
};
export default React.memo(PlayerPage);

CSV 出力

画面表示されているデータを CSV 出力します。

裏側で持っているデータをUIに出すこともできますが、表示せずとも出力することはできますし、データが増えていくとむしろ見栄えが悪くなるのでそこはお好みで実装します。

CSV出力関数
src/pages/player/index.tsx
  //CSV出力関数を定義
  const handleClickCsvDownloadBtn = async (
    format: "xlsx" | "csv",
    charcode?: "UTF8" | "SJIS"
  ) => {

    const workbook = new ExcelJS.Workbook();
    workbook.addWorksheet("sheet1");
    const worksheet = workbook.getWorksheet("sheet1");

    worksheet.columns = [
      { header: "Name", key: "id" },
      { header: "Age", key: "age" },
      { header: "Game", key: "game" },
      { header: "Goal", key: "goal" },
      { header: "Assist", key: "assist" },
    ];

    // 取得した選手情報を全て出力するループ分
    for (let i = 0; i < playerLists?.length; i++) {
      worksheet.addRows([
        {
          id: playerLists[i].player.name,
          age: playerLists[i].player.age,
          game: playerLists[i].statistics.games?.appearences,
          goal: playerLists[i].statistics.goals?.total,
          assist: playerLists[i].statistics.goals?.assists
        },
      ]);
    }

    //文字化けしないようにSJIS指定する、まあutf8でも良い
    const uint8Array =
      format === "xlsx"
        ? await workbook.xlsx.writeBuffer()
        : charcode === "UTF8"
        ? await workbook.csv.writeBuffer()
        : new Uint8Array(
            encoding.convert(await workbook.csv.writeBuffer(), {
              from: "UTF8",
              to: "SJIS"
            })
          );
    const blob = new Blob([uint8Array], {
      type: "application/octet-binary"
    });
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "sampleData." + format;
    a.click();
    a.remove();
  };

分析チュートリアルを回す

下記リソースを使い選手スタッツの分析チュートリアルを行います。

https://github.com/mckayjohns/Viz-Templates

個人選手のスキル比較

こちらのソースとデータを使います。

https://github.com/mckayjohns/Viz-Templates/blob/master/code/Radars Tutorial.ipynb
https://github.com/mckayjohns/Viz-Templates/blob/master/data/radars.csv

フローとしては以下のようになります。

  1. CSVで読み込んだデータ内の名前を整理する(名前をキーにデータを読み込みたいため)
  2. 抽出したいデータ以外のカラムを排除
  3. 分析したい対象の数値をRaderチャートのプロパティに割り当てる

試しに、ジョアンカンセロとトレントアレクサンダーアーノルドのスタッツ比較を行った結果です。

こちらも参考になりそうです。

https://github.com/Slothfulwave612/soccerplots/blob/master/docs/radar_chart.md

おわりに

分析対象のデータを整理し、チュートリアルに従いサッカー選手の試合成績スタッツを分析することができました。

今後は移籍マーケット市場が盛り上がるタイミングや大きな大会(ワールドカップや欧州チャンピオンズリーグ)前に面白そうなデータ分析結果をサッカーコミュニティ内で発信して反応を楽しみたいと思います。

最後までお読みいただきありがとうございました。

GitHubで編集を提案

Discussion

ログインするとコメントできます