【第5話 特徴量生成】現役データサイエンティストが機械学習でtotoを当てるまでの物語

スポーツアナリティクス

本記事を読んでいただき、ありがとうございます。本記事は第5話とある通り、連載形式にて記事を掲載しています。ここまでの話について気になる方は、以下のリンクからご覧ください。

さて、第4話までは前処理工程について紹介してきました。この第5話では、特徴量の生成について紹介していきたいと思います。基本的には外部のデータはこの段階では使わず、今あるデータから特徴量を工夫して作っていきます。

得点に関する特徴量

このステップでは、得点に関する特徴量を生成していきます。また、得点の結果から勝ち点の処理も同時に行います。

今回作りたいデータとしては、直近の試合の得点・失点・得失点・勝ち点・勝ち数・負け数・引き分け数、ここら辺から特徴量を作っていきたいと思います。
チームの勢いという点を、これらの結果から表現したいと思います。直近1試合〜10試合の合計を特徴量とします。(平均・分散・最大・最小とか使っても良いかもしれませんね。)

そのままだとなかなかうまく処理できず、この点なかなか苦慮しました。チームごとにモデルを構築して、予測する形で考えていきます。対戦チームがどこか、ホームなのかアウェイなのか、ここら辺が新たに処理を踏まえて生成していかないといけないポイントになります。以下、ざっくり処理手順を書かせていただくと、

  1. チーム名を抽出
  2. チームごとに処理を回していく
  3. 該当するチームの試合を抽出する
  4. 該当するチームがホームの場合とアウェイの場合で処理を分岐させる
  5. 該当チームの結果(得点や失点、試合結果など)を集計して列として生成する
  6. 細々した処理をする
  7. これを全チームで処理する

最終的には、各チームで生成したデータを結合して、抽象化したデータでモデル構築もやろうと考えています。それは追々。

チーム名を抽出する

チーム名を抽出するコードは以下の通りです。

df = pd.read_csv('./steps/step_10.csv')

teams = [team for team in df["ホーム"].unique()]
ignore_teams = settings.ignore_teams


for remove_team_name in ignore_teams:
    try:
        teams.remove(remove_team_name)
    except:
        continue

for team in tqdm(teams, total=len(teams)):
    pass

こんな感じの処理をします。チームごとの処理は以降このような感じで処理していきます。(モデル構築の時とか)

以下紹介する処理は、全てこの最後のfor文内で行われていると考えてください。

該当するチームの試合を抽出する

これは難しいものではありません。

df_team_all = df.query(f"ホーム == '{team}' or アウェイ == '{team}'")
df_team_all = df_team_all.sort_values('day')

こちらもどうぞ

該当するチームがホームの場合とアウェイの場合で処理を分岐させる

for index, row in df_team_all.iterrows():
    if row["ホーム"] == team:
        df_team_all.loc[index, "team_get_score"] = row["home_score"]
        df_team_all.loc[index, "team_lost_score"] = row["away_score"]

        if row["home_score"] > row["away_score"]:
            df_team_all.loc[index, "team_win"] = 1
            df_team_all.loc[index, "team_draw"] = 0
            df_team_all.loc[index, "team_lose"] = 0
            df_team_all.loc[index, "team_point"] = 3
        elif row["home_score"] == row["away_score"]:
            df_team_all.loc[index, "team_win"] = 0
            df_team_all.loc[index, "team_draw"] = 1
            df_team_all.loc[index, "team_lose"] = 0
            df_team_all.loc[index, "team_point"] = 1
        elif row["home_score"] < row["away_score"]:
            df_team_all.loc[index, "team_win"] = 0
            df_team_all.loc[index, "team_draw"] = 0
            df_team_all.loc[index, "team_lose"] = 1
            df_team_all.loc[index, "team_point"] = 0

    elif row["アウェイ"] == team:
        df_team_all.loc[index, "team_get_score"] = row["away_score"]
        df_team_all.loc[index, "team_lost_score"] = row["home_score"]

        if row["home_score"] > row["away_score"]:
            df_team_all.loc[index, "team_win"] = 0
            df_team_all.loc[index, "team_draw"] = 0
            df_team_all.loc[index, "team_lose"] = 1
            df_team_all.loc[index, "team_point"] = 0
        elif row["home_score"] == row["away_score"]:
            df_team_all.loc[index, "team_win"] = 0
            df_team_all.loc[index, "team_draw"] = 1
            df_team_all.loc[index, "team_lose"] = 0
            df_team_all.loc[index, "team_point"] = 1
        elif row["home_score"] < row["away_score"]:
            df_team_all.loc[index, "team_win"] = 1
            df_team_all.loc[index, "team_draw"] = 0
            df_team_all.loc[index, "team_lose"] = 0
            df_team_all.loc[index, "team_point"] = 3

特徴量の元となるデータを生成します。データフレームのその行のデータから該当チームが何点得点したかもしくは失点したか、勝ち点はいくつか、試合結果はどうか、ここら辺を作っています。

該当チームの結果(得点や失点、試合結果など)を一旦列として生成する

ここからは主にデータフレームのメソッドであるshiftを使って、データフレームをずらして、集計し、直近の試合の合計データなどを生成しています。

以下のような処理をします。

# 直近の試合の得点結果
df_team_all['situation_recently_10_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 11)])
df_team_all['situation_recently_9_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 10)])
df_team_all['situation_recently_8_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 9)])
df_team_all['situation_recently_7_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 8)])
df_team_all['situation_recently_6_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 7)])
df_team_all['situation_recently_5_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 6)])
df_team_all['situation_recently_4_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 5)])
df_team_all['situation_recently_3_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 4)])
df_team_all['situation_recently_2_matches_get_score'] = sum([df_team_all['team_get_score'].shift(i) for i in range(1, 3)])
df_team_all['situation_recently_1_matches_get_score'] = sum([df_team_all['team_get_score'].shift(1)])

これを得点、失点、勝ち数・・・という感じで増やしていきます。私は一旦10試合分を特徴量として考えています。また、シーズンも跨いでいるという点についても注意してください。このデータではシーズンでデータの切れ間はなく、ずっと連続しているものと考えています。理由としては経験的に、前シーズン強かったチームは今シーズンもある程度強いため、連続していると考えているためです。

細々した処理をする

残り細々とした処理をしていきます。おこなっていく細々した処理は、不要列の削除だったり、フラグだてだったります。

ここまでチーム別で処理してきて、その処理しているチームを主語としたときに、「勝つ」か「勝たない」かを予測します。そのためのフラグを立てます。ここら辺は第1話第2話で言及している点になります。

また、不要列の削除に加えて、生成した特徴量の正規化とダミー変数化もしています。

ここら辺を踏まえて、モデルに流し込んでいくデータとします。

まとめ

以上が現状、主にトライしている特徴量の生成方法になります。

かなり冗長的な処理をしていたりしますので、スクリプトが長くなってしまうのでスクリプトをご紹介できませんでした。企業秘密として考えてもらえれば嬉しいです。どうしても知りたい方がいましたらコメントなどいただけると嬉しいです。

基本的には、得点からわかる直近の特徴量を主に生成してきました。現段階で加えていきたいと考えている特徴量としては、「イロレーティング」のスコアも入れていきたいと思っています。勉強がてらここら辺も独自のロジックで加えていきたいと思います!

試合開催前までに分かっているデータから勝敗を予測するのは非常に難しいですね。使えるデータも限られていますが、逆になんでも使えるというのも難しそうですが。。

次回はここまでの部分を振り返る回にしたいと思います。次回もお楽しみに。

データサイエンティストの書評ブログ
趣味が読書くらいしかない駆け出しデータサイエンティストの書評ブログです。日々の勉強のアウトプットや趣味の読書のおすすめをしていきます。
総合トップページ|スポーツくじオフィシャルサイト
スポーツくじ「WINNER・toto・BIG」オフィシャルサイト。 購入方法・当せん確認・販売スケジュールはこちら!

コメント

タイトルとURLをコピーしました