【第4話 前処理】現役データサイエンティストが機械学習でtotoを当てるまでの物語

スポーツアナリティクス

第3話では、データの取得から前処理の工程に入ってきました。本話も機械学習にとって非常に重要になる前処理の工程をご紹介いたします。
本記事では、文字列の表記ゆれをどうにかしたり、簡略化したりといったステップの処理をご紹介いたします。

前話までのお話は、以下の記事をご参照ください。

大会名の修正

取得したデータには大会名が存在しています。「J1」や「J2」だったり、「ナビスコカップ」だったり。大会名、つまり試合のカテゴリーについても試合結果を予測するのに十分な説明変数になり得ると私は思っていますので、これもうまく処理していきます。

中身を見ていくと、非常に多様なカテゴリーが出てきます。この点は、ネーミングライツの関係やレギュレーションの変更などによって非常に多様な表現になっています。私の経験上、リーグ戦かカップ戦かという二つのくくりで良いかなと考えていますので、そのように表現していきたいと思います。処理のコードは以下の通り。

# 大会カテゴリーを絞る
match_category = ['J1 サントリー','J1 NICOS','J1', 'J2', 'J3', 'J1 1st', 'J1 2nd','YNC', 
                'YLC グループステージAグループ', 'YLC グループステージBグループ', 'YLC グループステージCグループ', 'YLC グループステージDグループ', 
                'YLC プレーオフステージ', 'YLC プライムステージ', 'YLC ノックアウトステージ', 
                'YNC グループステージAグループ', 'YNC グループステージBグループ', 'YNC 予選Aグループ', 'YNC 予選Bグループ', 'YNC 決勝トーナメント']

df_pick = base_df_score_mod.copy()
df_mod = df_pick[df_pick["大会"].isin(match_category)]

df_match_category_mod = df_mod.copy()

df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r" 1st"), '', regex=True)
df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r" 2nd"), '', regex=True)
df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r" サントリー"), '', regex=True)
df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r" NICOS"), '', regex=True)

df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r"YLC .+"), 'カップ戦', regex=True)
df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r"YNC .+"), 'カップ戦', regex=True)
df_match_category_mod["大会"] = df_match_category_mod["大会"].replace(re.compile(r"YNC"), 'カップ戦', regex=True)

df_match_category_mod.to_csv('./steps/step_4.csv', index=False)

このような感じの処理になります。リーグ戦はJ1~J3までと、カップ戦の合計4種類になります。

「節」の対処

取得したデータには「節」という列があり、「第○日」というものがあるので、以下のコードで処理します。

# 「節」表記の調整
df_mod = df_match_category_mod.copy()
df_mod["節"] = df_mod["節"].replace(re.compile(r"第.日"), '', regex=True)

df_mod.to_csv('./steps/step_5.csv', index=False)

予想に使わないデータへの対処

取得したデータには、スコアの情報が入っています。第3話にてその処理をしましたが、ここでその列を落とします。加えて、予測には使用しない列も合わせて落とします。使わないのは「入場者数」と「中継」のデータです。入場者数は事後的な数字ですし、中継は結果には影響しないという仮説に基づいています。

また、中止になった試合も過去あったようです。この試合もデータに混ざっていると予測ができなくなってしまうので、こちらも合わせて処理します。以下のコードで処理をします。

# totoの予想に使えない列の削除
df_drop = df_mod.copy()
df_drop = df_drop.drop(["スコア", "入場者数", "インターネット中継・TV放送"], axis=1)
df_drop.to_csv('./steps/step_6.csv', index=False)

# 中止の行を除く
df_cancel = df_drop.query("home_score != '中止'")
df_cancel.to_csv('./steps/step_7.csv', index=False)

時刻のデータをカテゴライズする

キックオフの時刻もデータとして使えそうです。分析こそしていないですが、昼間の試合が苦手だったりするパターンもあり得るかもしれません。
とはいえ、14時のキックオフと15時のキックオフに大きな差があるように思えないし、17時キックオフの試合は、試合終了時には夜になっていたりするので、昼か夜かでも分けにくそうです。なので、昼間か夕方か夜、という3種類に分類して処理していきます。

# 時刻のデータをカテゴライズする
df_mod = df_cancel.copy()
df_mod["match_time"] = df_mod["K/O時刻"].replace(re.compile(r":\d\d"), '', regex=True)
df_mod = df_mod.dropna(subset=["match_time"])
df_mod = df_mod.query("match_time != ''")
df_mod = df_mod.astype({'match_time': int})
df_mod['match_time'] = np.where(df_mod['match_time'] >= 18, "night", np.where(df_mod['match_time'] < 16, "day", "twilight"))

df_drop = df_mod.copy()
df_drop = df_drop.drop(["K/O時刻"], axis=1)

df_drop.to_csv('./steps/step_8.csv', index=False)

上記のような処理をすることで、キックオフの時刻を3種類に分類することができます。どの時間帯に分類されるかは、私のイメージでしかないので、ご自由に編集してください。

日付に関するデータの処理

次に日付に関するデータの処理を行います。ここまでのデータは、「年度」に実施年が入っていて、「試合日」に月日曜日が入っているデータになっています。実に使いづらいので、この部分を編集します。

ここから作りたいのは、年月日のデータ。年月日のデータをいじれば、月や曜日など如何様にでも処理できます。季節性の強さ(弱さ)があるはずなので、「月」レベルの粒度で特徴量を作ります。また曜日も影響しそうですよね。金曜日開催なのか、土曜日開催なのか、もしくは週中の開催なのか。これもデータを生成していきます。以下のコードで処理します。

# 細かい調整
df_mod = df_drop.copy()
df_mod = df_mod.astype({'年度': str})
df_mod["day"] = df_mod['年度'].str.cat(df_mod['試合日'], sep='/')
df_mod = pd.concat([df_mod, df_mod["day"].str.split('\(', expand=True)], axis=1)
df_mod = df_mod.drop(["day"], axis=1)
df_mod = df_mod.rename(columns={0: 'day', 1: "weekday"})
df_mod = df_mod.drop(["年度", "試合日", "weekday"], axis=1)
df_mod["day"] = pd.to_datetime(df_mod['day'])
df_mod["week_day"] = df_mod["day"].dt.day_name()

df_mod.to_csv('./steps/step_9.csv', index=False)

# 日付などに関するデータ整形
df = df_mod.copy()
df["month"] = pd.to_datetime(df["day"]).dt.month

スタジアム情報の処理

どこの試合会場で行われている試合か、という要素も結果を予測する上で非常に重要な要素となります。海外のチームでは、ホームで何十試合負けなしというような記録もあったりするので非常に重要です。

そんなスタジアム情報を処理していきます。初めにお断りなのですが、この部分は全て見せられません。なぜなら、スタジアムの名前が頻繁に変わる上に、Jリーグのチーム数も増えたことで全国各地の競技場で試合が行われるため、スタジアム名が膨大になります。これをPythonの辞書を使って修正しているのですが、それが長すぎるためお見せできません。ゆえに全てお見せできませんが、みなさんで独自の辞書を作ってみてください。

studium_dict = settings.studium_dict

df = df[~df["スタジアム"].str.contains('.*未定.*', regex=True)]

df = df.reset_index(drop=True)
for index_num, row in tqdm(df.iterrows(), total=len(df)):
    df.at[index_num, "studium_mod"] = studium_dict[row["スタジアム"]]

    if row["ホーム"] == "草津":
        df.at[index_num, "ホーム"] = "群馬"
    elif row["アウェイ"] == "草津":
        df.at[index_num, "アウェイ"] = "群馬"

    if row["ホーム"] == "平塚":
        df.at[index_num, "ホーム"] = "湘南"
    elif row["アウェイ"] == "平塚":
        df.at[index_num, "アウェイ"] = "湘南"

    if row["ホーム"] == "横浜M":
        df.at[index_num, "ホーム"] = "横浜FM"
    elif row["アウェイ"] == "横浜M":
        df.at[index_num, "アウェイ"] = "横浜FM"

    if row["ホーム"] == "市原":
        df.at[index_num, "ホーム"] = "千葉"
    elif row["アウェイ"] == "市原":
        df.at[index_num, "アウェイ"] = "千葉"

    if row["ホーム"] == "V川崎":
        df.at[index_num, "ホーム"] = "東京V"
    elif row["アウェイ"] == "V川崎":
        df.at[index_num, "アウェイ"] = "東京V"

df.to_csv('./steps/step_10.csv', index=False)

漏れていたチーム名の変更への対応もこの処理の中で後付けしています。

まとめ

ここまでが私のおこなっている前処理です。この前処理したデータをベースとして、特徴量を生成していっていきます。その特徴量を持って、機械学習にかけていく予定です。
一気に書いたので、かなり簡単に思えるかもしれませんが、結構試行錯誤してこのスクリプトを仕上げました。少しずつ進めておよそ2ヶ月くらい?かかりました。

次回は特徴量生成のあたりを書いていきます。お楽しみに。

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

コメント

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