【競馬AI②】ほぼコピペだけ!netkeibaから取得したデータを加工する

競馬AI

前回からの続きの記事になります。

今回は、前回取得したデータの加工を行っていきます。AIでは日本語をパラメーターに入れることはできないので数値化したり、馬名、騎手などをエンコードします。

さらに学習するデータを増やし、予測精度を上げるために馬ごとの近5走のデータも付加していきます

データの加工

コピペするからコードを早く見せろ!という方はコードまで飛ばしてくださって結構です!

数値化

以下のカラムを数値化させていきます。

  • クラス
    “クラスに以下の文字が含まれていたら”という条件で数値化しています。
    ‘G1’⇒10, ‘G2’⇒9, ‘G3’⇒8, ‘(L)’⇒7, ‘オープン’⇒7, ‘3勝’⇒6, ‘1600’⇒6,
    ‘2勝’⇒5, ‘1000’⇒5, ‘1勝’⇒4, ‘500’⇒4, ‘新馬’⇒3, ‘未勝利’⇒1, ‘障害’⇒0
  • 走破時間
    走破時間は「1:31.5」のようなフォーマットになっているので、「:」を取り除いて秒数に変換しています。「1:31.5」であれば「91.5」秒になります。
  • 通過順
    通過順は「10-8-5」のようになっているので、ハイフンを取り除いて割って出しています。「10-8-5」であれば、「7.666…」となります。
  • 性別
    性別は牡馬:0、牝馬:1、セン馬:2としています。
  • 芝・ダート
    芝・ダートは、芝:0、ダート:1、障害:2としています。
  • 回り
    回りは右:0、左:1、芝:2、直:2としています。障害レースの場合は「芝」と表現されるようですね。
  • 馬場状態
    馬場状態は良:0、稍:1、重:2、不:3としています。
  • 天気
    天気は晴:0、曇:1、小:2、雨:3、雪:4としています。

エンコーディング

学習に使わないデータはラベルエンコーディングしてしまいます。

エンコーディングするのは「’馬’, ‘騎手’, ‘レース名’, ‘開催’, ‘場名’」です。取得しなくてもよかったまでありますが、人が目でデータを確認する時には必要なので、しょうがないですね。

ちなみにラベルエンコーディングとは、カテゴリごとに一意の整数を割り当てます。例えば、「赤」を0、「青」を1、「緑」を2のようにします。この方法はシンプルですが、カテゴリ間に順序関係(「赤」<「青」<「緑」)がないにも関わらず、無意識のうちに順序関係を持たせてしまう欠点があります。
–ChatGPTより–

競馬は騎手による影響が大きいので、今回はできませんでしたが次回は騎手の実力も数値化して学習させたいと思っています。

近5走のデータの追加

予測の精度を上げるためには、予測するためのデータ量を増やす必要があります。
そこで直近5レース分のデータを追加します。

追加するデータは以下です。
オッズ, 体重, 体重変化, 上がり, 通過順, 着順, 距離, クラス, 走破時間, 芝・ダート, 天気,馬場

5レース分なので
オッズ1, 体重1, 体重変化1, 上がり1, 通過順1, 着順1, 距離1, クラス1, 走破時間1, 芝・ダート1, 天気1,馬場1・・・オッズ5, 体重5, 体重変化5, 上がり5, 通過順5, 着順5, 距離5, クラス5, 走破時間5, 芝・ダート5, 天気5,馬場5
とデータの横に追加されていくことになります。

上記にプラスして、「日付差」(レース間隔)と「距離差」(前走からの距離変化)も追加しています。

もっとデータを増やす場合は、ここの追加するデータのプログラムを修正します。

変換コード

コード

変換を実行するコードは以下となります。コピペでファイルを作成してください。

import pandas as pd
from sklearn.preprocessing import LabelEncoder, StandardScaler
import numpy as np
import scipy.stats
import sys

def class_mapping(row):
    mappings = {'障害':0, 'G1': 10, 'G2': 9, 'G3': 8, '(L)': 7, 'オープン': 7,'OP': 7, '3勝': 6, '1600': 6, '2勝': 5, '1000': 5, '1勝': 4, '500': 4, '新馬': 3, '未勝利': 1}
    for key, value in mappings.items():
        if key in row:
            return value
    return 0  # If no mapping is found, return 0
# データの読み込み
yearStart = 2020
yearEnd = 2023
yearList = np.arange(yearStart, yearEnd + 1, 1, int)
df = []
print("ファイル取得:開始")
for for_year in yearList:
    var_path = "data/" + str(for_year) + "_new.csv"
    var_data = pd.read_csv(
        var_path,
        encoding="SHIFT-JIS",
        header=0,
        parse_dates=['日付'], 
        date_parser=lambda x: pd.to_datetime(x, format='%Y年%m月%d日')
    )
    # '着順'カラムの値を数値に変換しようとして、エラーが発生する場合はNaNにする
    var_data['着順'] = pd.to_numeric(var_data['着順'], errors='coerce')
    # NaNの行を削除する
    var_data = var_data.dropna(subset=['着順'])
    # 必要であれば、'着順'カラムのデータ型を整数に変換する
    var_data['着順'] = var_data['着順'].astype(int)

    df.append(var_data)

print("ファイル取得:完了")
print("データ変換:開始")
# DataFrameの結合
df_combined = pd.concat(df, ignore_index=True)

# 既存のコード:走破時間を秒に変換
time_parts = df_combined['走破時間'].str.split(':', expand=True)
seconds = time_parts[0].astype(float) * 60 + time_parts[1].str.split('.', expand=True)[0].astype(float) + time_parts[1].str.split('.', expand=True)[1].astype(float) / 10
# 前方補完
seconds = seconds.fillna(method='ffill')

# 平均と標準偏差を計算
mean_seconds = seconds.mean()
std_seconds = seconds.std()

# 標準化を行う
df_combined['走破時間'] = -((seconds - mean_seconds) / std_seconds)

# 外れ値の処理:-3より小さい値は-3に、2.5より大きい値は2に変換
df_combined['走破時間'] = df_combined['走破時間'].apply(lambda x: -3 if x < -3 else (2 if x > 2.5 else x))

# 2回目の標準化の前に再度平均と標準偏差を計算
mean_seconds_2 = df_combined['走破時間'].mean()
std_seconds_2 = df_combined['走破時間'].std()

# 2回目の標準化
df_combined['走破時間'] = (df_combined['走破時間'] - mean_seconds_2) / std_seconds_2
print('1回目平均' + str(mean_seconds))
print('2回目平均' + str(mean_seconds_2))
print('1回目標準偏差' + str(std_seconds))
print('2回目標準偏差' + str(std_seconds_2))

# データを格納するDataFrameを作成
time_df = pd.DataFrame({
    'Mean': [mean_seconds, mean_seconds_2],
    'Standard Deviation': [std_seconds, std_seconds_2]
})
# indexに名前を付ける
time_df.index = ['First Time', 'Second Time']
# DataFrameをCSVファイルとして出力
time_df.to_csv('config/standard_deviation.csv')

#通過順の平均を出す
pas = df_combined['通過順'].str.split('-', expand=True)
df_combined['通過順'] = pas.astype(float).mean(axis=1)

# mapを使ったラベルの変換
df_combined['クラス'] = df_combined['クラス'].apply(class_mapping)          
sex_mapping = {'牡':0, '牝': 1, 'セ': 2}
df_combined['性'] = df_combined['性'].map(sex_mapping)
shiba_mapping = {'芝': 0, 'ダ': 1, '障': 2}
df_combined['芝・ダート'] = df_combined['芝・ダート'].map(shiba_mapping)
mawari_mapping = {'右': 0, '左': 1, '芝': 2, '直': 2}
df_combined['回り'] = df_combined['回り'].map(mawari_mapping)
baba_mapping = {'良': 0, '稍': 1, '重': 2, '不': 3}
df_combined['馬場'] = df_combined['馬場'].map(baba_mapping)
tenki_mapping = {'晴': 0, '曇': 1, '小': 2, '雨': 3, '雪': 4}
df_combined['天気'] = df_combined['天気'].map(tenki_mapping)
print("データ変換:完了")
print("近5走取得:開始")
# '馬'と'日付'に基づいて降順にソート
df_combined.sort_values(by=['馬', '日付'], ascending=[True, False], inplace=True)

features = ['馬番', '騎手', '斤量', 'オッズ', '体重', '体重変化', '上がり', '通過順', '着順', '距離', 'クラス', '走破時間', '芝・ダート', '天気','馬場']
#斤量、周り
# 同じ馬の過去5レースの情報を新しいレース結果にマージ
for i in range(1, 6):
    df_combined[f'日付{i}'] = df_combined.groupby('馬')['日付'].shift(-i)
    for feature in features:
        df_combined[f'{feature}{i}'] = df_combined.groupby('馬')[feature].shift(-i)

# 同じ馬のデータで欠損値を補完
for feature in features:
    for i in range(1, 6):
        df_combined[f'{feature}{i}'] = df_combined.groupby('馬')[f'{feature}{i}'].fillna(method='ffill')

# race_id と 馬 でグルーピングし、各特徴量の最新の値を取得
df_combined = df_combined.groupby(['race_id', '馬'], as_index=False).last()

# race_idでソート
df_combined.sort_values(by='race_id', ascending=False, inplace=True)

print("近5走取得:終了")
# '---' をNaNに置き換える
df_combined.replace('---', np.nan, inplace=True)
print("日付変換:開始")
#距離差と日付差を計算
df_combined = df_combined.assign(
    距離差 = df_combined['距離'] - df_combined['距離1'],
    日付差 = (df_combined['日付'] - df_combined['日付1']).dt.days,
    距離差1 = df_combined['距離1'] - df_combined['距離2'],
    日付差1 = (df_combined['日付1'] - df_combined['日付2']).dt.days,
    距離差2 = df_combined['距離2'] - df_combined['距離3'],
    日付差2 = (df_combined['日付2'] - df_combined['日付3']).dt.days,
    距離差3 = df_combined['距離3'] - df_combined['距離4'],
    日付差3 = (df_combined['日付3'] - df_combined['日付4']).dt.days,
    距離差4 = df_combined['距離4'] - df_combined['距離5'],
    日付差4 = (df_combined['日付4'] - df_combined['日付5']).dt.days
)

# 斤量に関連する列を数値に変換し、変換できないデータはNaNにします。
kinryo_columns = ['斤量', '斤量1', '斤量2', '斤量3', '斤量4','斤量5']
for col in kinryo_columns:
    df_combined[col] = pd.to_numeric(df_combined[col], errors='coerce')

# 平均斤量を計算します。
df_combined['平均斤量'] = df_combined[kinryo_columns].mean(axis=1)

# 騎手の勝率
jockey_win_rate = df_combined.groupby('騎手')['着順'].apply(lambda x: (x==1).sum() / x.count()).reset_index()
jockey_win_rate.columns = ['騎手', '騎手の勝率']
jockey_win_rate.to_csv('calc_rate/jockey_win_rate.csv', index=False)
# '騎手'をキーにしてdf_combinedとjockey_win_rateをマージする
df_combined = pd.merge(df_combined, jockey_win_rate, on='騎手', how='left')

#日付
# 日付カラムから年、月、日を抽出
df_combined['year'] = df_combined['日付'].dt.year
df_combined['month'] = df_combined['日付'].dt.month
df_combined['day'] = df_combined['日付'].dt.day
# (年-yearStart)*365 + 月*30 + 日 を計算し新たな '日付'カラムを作成
df_combined['日付'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

df_combined['year'] = df_combined['日付1'].dt.year
df_combined['month'] = df_combined['日付1'].dt.month
df_combined['day'] = df_combined['日付1'].dt.day
df_combined['日付1'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

df_combined['year'] = df_combined['日付2'].dt.year
df_combined['month'] = df_combined['日付2'].dt.month
df_combined['day'] = df_combined['日付2'].dt.day
df_combined['日付2'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

df_combined['year'] = df_combined['日付3'].dt.year
df_combined['month'] = df_combined['日付3'].dt.month
df_combined['day'] = df_combined['日付3'].dt.day
df_combined['日付3'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

df_combined['year'] = df_combined['日付4'].dt.year
df_combined['month'] = df_combined['日付4'].dt.month
df_combined['day'] = df_combined['日付4'].dt.day
df_combined['日付4'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

df_combined['year'] = df_combined['日付5'].dt.year
df_combined['month'] = df_combined['日付5'].dt.month
df_combined['day'] = df_combined['日付5'].dt.day
df_combined['日付5'] = (df_combined['year'] - yearStart) * 365 + df_combined['month'] * 30 + df_combined['day']

# 不要となった 'year', 'month', 'day' カラムを削除
df_combined.drop(['year', 'month', 'day'], axis=1, inplace=True)
print("日付変換:終了")

categorical_features = ['馬', '騎手', 'レース名', '開催', '場名', '騎手1', '騎手2', '騎手3', '騎手4', '騎手5']  # カテゴリカル変数の列名を指定してください

# ラベルエンコーディング
for i, feature in enumerate(categorical_features):
    print(f"\rProcessing feature {i+1}/{len(categorical_features)}", end="")
    le = LabelEncoder()
    df_combined[feature] = le.fit_transform(df_combined[feature])

# エンコーディングとスケーリング後のデータを確認
print("ファイル出力:開始")
df_combined.to_csv('encoded/encoded_data.csv', index=False)
print("ファイル出力:終了")

※2023/8/26更新
①走破時間の標準化に使用した平均と標準偏差をファイルに出力
→レースデータの取得の際に使用(競馬AI⑥の記事)
※2023/7/11更新
①走破時間を標準化
②過去5走データに馬番、騎手、斤量を追加
エラーが出た場合はコメントください。
※2023/12/13更新
①着順が「中」「除」などの対応として、数値に変換できないレコードの削除

実行

※実行前に「encoded」と「config」、「calc_rate」などのフォルダの作成をお忘れなく!
※yearStartとyearEndの変数はスクレイピングで取得した年数の範囲に書き換えてください

実行するには以下のコマンドをターミナルで打ち込んでください。

python encode.py

実行すると「encoded」フォルダにファイルが作成されているはずです。

ファイルの確認

そのままファイルを開くとデータが多すぎて開けないことがあるので、以下のコードを実行して100行だけ抽出し内容を確認してみてください。

import pandas as pd

# CSVファイルの読み込み
df = pd.read_csv('encoded/encoded_data.csv')

# 上位100行の表示
print(df.head(100))

# 上位100行を新しいCSVファイルとして出力
df.head(100).to_csv('encoded/encoded_data_100.csv', index=False)

必要に応じて「pandas」のライブラリをインストールしてください。

まとめ

今回もほぼコピペだけでデータの変換が出来たと思います。

これでAIに学習させるデータの準備が完了です。

次回はLightGBMという機械学習における分析アルゴリズムを使ってモデルを作成していきます。

ブックマークしてお待ちください!

予想した結果はこちらで公開中!

コメント

  1. sho より:

    最近このブログを読み始めた者です。
    機械学習に興味があり触ってみたかったので、読みやすいコードを載せてくださっているこのブログは大変勉強になり助かっております。
    さて、encoding.pyをコピペして実行したのですが、次のようなエラーが出ました。
    2019年から2023年のデータをエンコードしようとしています。
    ラベルエンコーディングのところでエラーが出ているようなのですが、どのような修正が必要か教えていただけないしょうか。よろしくおねがいします。

    Traceback (most recent call last):
    File “/home/sho/anaconda3/lib/python3.8/site-packages/sklearn/preprocessing/_label.py”, line 113, in _encode
    res = _encode_python(values, uniques, encode)
    File “/home/sho/anaconda3/lib/python3.8/site-packages/sklearn/preprocessing/_label.py”, line 61, in _encode_python
    uniques = sorted(set(values))
    TypeError: ‘<' not supported between instances of 'str' and 'float'

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File "encode.py", line 176, in
    df_combined[feature] = le.fit_transform(df_combined[feature])
    File “/home/sho/anaconda3/lib/python3.8/site-packages/sklearn/preprocessing/_label.py”, line 256, in fit_transform
    self.classes_, y = _encode(y, encode=True)
    File “/home/sho/anaconda3/lib/python3.8/site-packages/sklearn/preprocessing/_label.py”, line 117, in _encode
    raise TypeError(“Encoders require their input to be uniformly ”
    TypeError: Encoders require their input to be uniformly strings or numbers. Got [‘float’, ‘str’]

    • agus agus より:

      コメントありがとうございます。
      ラベルエンコーディングしようとしている列にfloat型と文字列型が混じっているために発生しているようです。
      “df_combined[feature] = le.fit_transform(df_combined[feature])”
      以下のようにこの部分をすべて文字列型に変換してしまえば解決すると思うのですが、、、
      “df_combined[feature] = le.fit_transform(df_combined[feature].astype(str))”
      ただ、私も同じコードを使っていますが、エラーが出ていないのでエンコーディング対象の列が正しく取得できているかを確認された方が良いかもしれません。
      ***********
      [‘馬’, ‘騎手’, ‘レース名’, ‘開催’, ‘場名’, ‘騎手1’, ‘騎手2’, ‘騎手3’, ‘騎手4’, ‘騎手5’]
      2019.csv~2023.csvの上記列に文字列と数値が混じっているものがないか
      スクレイピング時点で何か間違っていないか、文字化けしていないか等も確認してみてください。
      ***********

  2. rei より:

    このブログで機械学習を勉強しているものです。
    スクレイピングは問題なくできたのですが、データ加工を実行し、encodedファイル内のCSVファイルをExcelで確認したところ日本語の部分が変な文字になっていました。文字化けですかね。何が問題か分かりますでしょうか。

    • agus agus より:

      コメントありがとうございます。
      文字コードの違いによるものです。
      プログラム内ではutf-8を使っており、Excelではshift-jisで開こうとするために文字化けしてしまいます。
      文字化け自体は問題ありません。
      テキストファイルや、Visual Studio Codeを使っているのであればそこで開けば文字化けしていないはずです。
      私はEdit csvというプラグインを使いVisual Studio Code上で参照しています。
      Googleスプレッドシートで確認するという方法もあります。

      • rei より:

        返信ありがとうございます!
        文字化け自体は問題ないとのことですが、このまま次の工程に進んでも大丈夫と言うことでしょうか?

        • agus agus より:

          大丈夫です。
          ただ一度Excelで開いて保存してしまうと文字コードが変わってしまうので注意してください。

  3. mr より:

    raise OSError(rf”Cannot save file into a non-existent directory: ‘{parent}'”)
    OSError: Cannot save file into a non-existent directory: ‘config’

    python初心者です。上記のようなエラーが表示されて上手くいきません。どのように対処すればよいでしょうか?

    • agus agus より:

      コメントありがとうございます。
      実行プログラムと同じ階層に「config」フォルダがないというエラーになります。
      configフォルダを作成すれば解決します。

  4. ranran より:

    Python初心者です。以下のエラーが出てしまいました。

    ファイル取得:開始
    —————————————————————————
    TypeError Traceback (most recent call last)
    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in _to_datetime_with_format(arg, orig_arg, name, tz, fmt, exact, errors, infer_datetime_format)
    508 try:
    –> 509 values, tz = conversion.datetime_to_datetime64(arg)
    510 dta = DatetimeArray(values, dtype=tz_to_dtype(tz))

    ~\anaconda3\lib\site-packages\pandas\_libs\tslibs\conversion.pyx in pandas._libs.tslibs.conversion.datetime_to_datetime64()

    TypeError: Unrecognized value type:

    During handling of the above exception, another exception occurred:

    ValueError Traceback (most recent call last)
    ~\AppData\Local\Temp/ipykernel_10632/3509144094.py in
    19 for for_year in yearList:
    20 var_path = “data/” + str(for_year) + “.csv”
    —> 21 var_data = pd.read_csv(
    22 var_path,
    23 encoding=”SHIFT-JIS”,

    ~\anaconda3\lib\site-packages\pandas\util\_decorators.py in wrapper(*args, **kwargs)
    309 stacklevel=stacklevel,
    310 )
    –> 311 return func(*args, **kwargs)
    312
    313 return wrapper

    ~\anaconda3\lib\site-packages\pandas\io\parsers\readers.py in read_csv(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, squeeze, prefix, mangle_dupe_cols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, verbose, skip_blank_lines, parse_dates, infer_datetime_format, keep_date_col, date_parser, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, error_bad_lines, warn_bad_lines, on_bad_lines, delim_whitespace, low_memory, memory_map, float_precision, storage_options)
    584 kwds.update(kwds_defaults)
    585
    –> 586 return _read(filepath_or_buffer, kwds)
    587
    588

    ~\anaconda3\lib\site-packages\pandas\io\parsers\readers.py in _read(filepath_or_buffer, kwds)
    486
    487 with parser:
    –> 488 return parser.read(nrows)
    489
    490

    ~\anaconda3\lib\site-packages\pandas\io\parsers\readers.py in read(self, nrows)
    1045 def read(self, nrows=None):
    1046 nrows = validate_integer(“nrows”, nrows)
    -> 1047 index, columns, col_dict = self._engine.read(nrows)
    1048
    1049 if index is None:

    ~\anaconda3\lib\site-packages\pandas\io\parsers\c_parser_wrapper.py in read(self, nrows)
    306 data = {k: v for k, (i, v) in zip(names, data_tups)}
    307
    –> 308 names, data = self._do_date_conversions(names, data)
    309 index, names = self._make_index(data, alldata, names)
    310

    ~\anaconda3\lib\site-packages\pandas\io\parsers\base_parser.py in _do_date_conversions(self, names, data)
    793
    794 if self.parse_dates is not None:
    –> 795 data, names = _process_date_conversion(
    796 data,
    797 self._date_conv,

    ~\anaconda3\lib\site-packages\pandas\io\parsers\base_parser.py in _process_date_conversion(data_dict, converter, parse_spec, index_col, index_names, columns, keep_date_col)
    1091 if _isindex(colspec):
    1092 continue
    -> 1093 data_dict[colspec] = converter(data_dict[colspec])
    1094 else:
    1095 new_name, col, old_names = _try_convert_dates(

    ~\anaconda3\lib\site-packages\pandas\io\parsers\base_parser.py in converter(*date_cols)
    1053 )
    1054 except Exception:
    -> 1055 return generic_parser(date_parser, *date_cols)
    1056
    1057 return converter

    ~\anaconda3\lib\site-packages\pandas\io\date_converters.py in generic_parser(parse_func, *cols)
    98 for i in range(N):
    99 args = [c[i] for c in cols]
    –> 100 results[i] = parse_func(*args)
    101
    102 return results

    ~\AppData\Local\Temp/ipykernel_10632/3509144094.py in (x)
    25 dtype={0: str, 3: int, 7: int, 8: int, 9: int, 11: int, 12: float, 13: float, 14: str, 20: int, 24: int},
    26 parse_dates=[‘日付’],
    —> 27 date_parser=lambda x: pd.to_datetime(x, format=’%Y年%m月%d日’)
    28 )
    29 # “芝・ダート”が”芝”だけの行を選択

    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in to_datetime(arg, errors, dayfirst, yearfirst, utc, format, exact, unit, infer_datetime_format, origin, cache)
    912 result = convert_listlike(arg, format)
    913 else:
    –> 914 result = convert_listlike(np.array([arg]), format)[0]
    915
    916 # error: Incompatible return value type (got “Union[Timestamp, NaTType,

    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in _convert_listlike_datetimes(arg, format, name, tz, unit, errors, infer_datetime_format, dayfirst, yearfirst, exact)
    391
    392 if format is not None:
    –> 393 res = _to_datetime_with_format(
    394 arg, orig_arg, name, tz, format, exact, errors, infer_datetime_format
    395 )

    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in _to_datetime_with_format(arg, orig_arg, name, tz, fmt, exact, errors, infer_datetime_format)
    511 return DatetimeIndex._simple_new(dta, name=name)
    512 except (ValueError, TypeError):
    –> 513 raise err
    514
    515

    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in _to_datetime_with_format(arg, orig_arg, name, tz, fmt, exact, errors, infer_datetime_format)
    498
    499 # fallback
    –> 500 res = _array_strptime_with_fallback(
    501 arg, name, tz, fmt, exact, errors, infer_datetime_format
    502 )

    ~\anaconda3\lib\site-packages\pandas\core\tools\datetimes.py in _array_strptime_with_fallback(arg, name, tz, fmt, exact, errors, infer_datetime_format)
    434
    435 try:
    –> 436 result, timezones = array_strptime(arg, fmt, exact=exact, errors=errors)
    437 if “%Z” in fmt or “%z” in fmt:
    438 return _return_parsed_timezone_results(result, timezones, tz, name)

    ~\anaconda3\lib\site-packages\pandas\_libs\tslibs\strptime.pyx in pandas._libs.tslibs.strptime.array_strptime()

    ValueError: time data ‘2019/7/27’ does not match format ‘%Y年%m月%d日’ (match)

    そのままコピペさせていただいたら、このようなエラーが出てしまいました。どのように直せばいいですか?

    • agus agus より:

      エラーメッセージからは具体的なエラーは分かりません。
      ブログに記載のコードがメインのエラーではなさそうなので、
      Pandas、NumPy、Pythonあたりを更新してみてはいかがでしょうか?
      ちなみに私の環境は以下の通りです。
      Python 3.10.4
      pandas 2.0.3
      numpy 1.24.3

  5. coristo より:

    こんにちは。機械学習を最近勉強し始めました。このコードを実行したところ以下のエラーを出力しました。コピペ後、yearStartとyearEndを変更しただけで、「encoded」ファイルも作ってあります。原因がわかりません。アドバイスよろしくお願いいたします。もしかしてファイルの位置が問題なのでしょうか?「data」と「encoded」ファイルは同位置にあります。

    ファイル取得:開始
    Traceback (most recent call last):
    File “c:(パス表記)\encode.py”, line 21, in
    var_data = pd.read_csv(
    ^^^^^^^^^^^^
    File “C:(パス表記)\Python\Python311\Lib\site-packages\pandas\io\parsers\readers.py”, line 948, in read_csv
    return _read(filepath_or_buffer, kwds)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:(パス表記)\Python\Python311\Lib\site-packages\pandas\io\parsers\readers.py”, line 611, in _read
    parser = TextFileReader(filepath_or_buffer, **kwds)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:(パス表記)\Python\Python311\Lib\site-packages\pandas\io\parsers\readers.py”, line 1448, in __init__
    self._engine = self._make_engine(f, self.engine)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:(パス表記)\Python\Python311\Lib\site-packages\pandas\io\parsers\readers.py”, line 1705, in _make_engine
    self.handles = get_handle(
    ^^^^^^^^^^^
    File “C:(パス表記)\Python\Python311\Lib\site-packages\pandas\io\common.py”, line 863, in get_handle
    handle = open(
    ^^^^^
    FileNotFoundError: [Errno 2] No such file or directory: ‘data/2021_new.csv’

    • agus agus より:

      スクレイピングで取得したファイルが見つからないというエラーなので、20行目の
      var_path = “data/” + str(for_year) + “_new.csv”
      という部分を見直してください。
      dataフォルダの中にスクレイピングで取得したファイルがあるのかどうか、
      ファイル名に「_new」を付けているが、自分のファイルと命名規則が一致しているか。

      dataフォルダにファイルを格納していなければ、格納してください。
      ファイル名の規則が違うのであれば、20行目のコードを修正するか、ファイル名を修正してください。

      • coristo より:

        返信有難うございます。①でスクレイピングしたデータの名前に合わせてコードを修正しましたがやはり同じようなエラーを出してしまいます。ファイルの位置がおかしいのでしょうか?
        「ドキュメント」
        →「aikeiba」
        →「data」「encoded」「encode.py」
        このようなファイル配置になっていますが、間違っていますでしょうか?

        • agus agus より:

          以下のようなフォルダ構成のイメージです。
          -dataフォルダ
          |-2023.csv
          -encodedフォルダ
          |-encoded_data.csv
          encode.py
          predict.py
          race_table_scraping.py

        • coristo より:

          主様のOS環境はLinuxでしょうか?私の環境はWindowsなのでコード中の「/」が原因なのではないかと思いまして…

          • coristo より:

            度々すみません!無事にCSVファイル出力できました!ですが今回出力したCSVファイルなんですが、raceIDが降順になっており度々raceIDの欠損が見られます。2022.csvにはraceIDが202205010101のものがあったはずなのですが、encoded_dataの方には202205010101にあたるものがありませんでした。これは正常に動作しているのでしょうか?

          • agus agus より:

            出力できて良かったです。
            すみません。30行目ですが、芝だけに限定したコードになっていました。
            30行目をコメントアウトしていただければ正常に動作するはずです。
            よろしくお願いします。

  6. より:

    データの前処理で直近5レースを追加する意味は何ですか?

    • agus agus より:

      過去レースの成績も学習データに使って精度を上げるためです。
      過去レースの成績は予想する上で非常に重要な情報となります。

  7. いにぽん より:

    データ変換:開始
    の後がエラー文でわからず進めないです、、

  8. りんご より:

    いつも参考にさせていただいております。

    以下のエラーが出ましたが、解決法をご教授いただけると幸いです。

    ファイル取得:開始
    Traceback (most recent call last):
    File “parsers.pyx”, line 1160, in pandas._libs.parsers.TextReader._convert_tokens
    TypeError: Cannot cast array data from dtype(‘O’) to dtype(‘int32’) according to the rule ‘safe’

    During handling of the above exception, another exception occurred:

    Traceback (most recent call last):
    File “c:\Python\競馬\encode.py”, line 21, in
    var_data = pd.read_csv(
    ^^^^^^^^^^^^
    File “C:\.venv\Lib\site-packages\pandas\io\parsers\readers.py”, line 948, in read_csv
    return _read(filepath_or_buffer, kwds)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:\.venv\Lib\site-packages\pandas\io\parsers\readers.py”, line 617, in _read
    return parser.read(nrows)
    ^^^^^^^^^^^^^^^^^^
    File “C:\.venv\Lib\site-packages\pandas\io\parsers\readers.py”, line 1748, in read
    ) = self._engine.read( # type: ignore[attr-defined]
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:\.venv\Lib\site-packages\pandas\io\parsers\c_parser_wrapper.py”, line 234, in read
    chunks = self._reader.read_low_memory(nrows)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “parsers.pyx”, line 843, in pandas._libs.parsers.TextReader.read_low_memory
    File “parsers.pyx”, line 920, in pandas._libs.parsers.TextReader._read_rows
    File “parsers.pyx”, line 1065, in pandas._libs.parsers.TextReader._convert_column_data
    File “parsers.pyx”, line 1166, in pandas._libs.parsers.TextReader._convert_tokens
    ValueError: invalid literal for int() with base 10: ‘中’

    • agus agus より:

      ソースコードの29行目~34行目あたりに以下のようなコードは入っていますか?
      差分を確認してみてください。

      # ‘着順’カラムの値を数値に変換しようとして、エラーが発生する場合はNaNにする
      var_data[‘着順’] = pd.to_numeric(var_data[‘着順’], errors=’coerce’)
      # NaNの行を削除する
      var_data = var_data.dropna(subset=[‘着順’])
      # 必要であれば、’着順’カラムのデータ型を整数に変換する
      var_data[‘着順’] = var_data[‘着順’].astype(int)

      • りんご より:

        返信ありがとうございます。そのコード入れております。。

        • agus agus より:

          25行目の以下のコードをコメントアウトまたは削除してみてください。
          dtype={0: str, 3: int, 7: int, 8: int, 9: int, 11: int, 12: float, 13: float, 14: str, 20: int, 24: int},

          私の方のコードのデグレかもしれません。
          着順に”中”とか”除”が含まれるようになったのに、型をintに指定しているので。

          • りんご より:

            返信遅くなり申し訳ありません。
            25行目をコメントアウトしたところ問題なく動作いたしました。
            ありがとうございました!

  9. なか より:

    今回のコードを実行したところ
    ….
    近5走取得:開始
    C:\Users……\encode.py:106: PerformanceWarning: DataFrame is highly fragmented. This is usually the result of calling `frame.insert` many times, which has poor performance. Consider joining all columns at once using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`
    df_combined[f'{feature}{i}’] = df_combined.groupby(‘馬’)[feature].shift(-i)
    ce using pd.concat(axis=1) instead. To get a de-fragmented frame, use `newframe = frame.copy()`
    df_combined = df_combined.groupby([‘race_id’, ‘馬’], as_index=False).last()
    (同じメッセージがもう一回繰り返し)
    近5走取得:終了
    となります。メッセージが出ている間は止まっているような感じですが最終的にプログラムは動いているようです。
    (負荷がかかっている?)

    • agus agus より:

      近5走を取得するコードは負荷が高いです。
      プログラムの書き方が効率的ではないというメッセージかもしれません。

  10. r より:

    すみません、142行目の

    # 平均オッズを計算します。
    df_combined[‘平均斤量’] = df_combined[kinryo_columns].mean(axis=1)

    これは平均オッズを計算しようとしているのにコードは平均斤量を計算していますよね。
    正しいのはどちらですか?

  11. cocomo より:

    最近プログラミングを始めたものです。
    今回プログラムを実行したところ以下のようなエラーが出ました。
    原因を教えていただきたいです。
    Traceback (most recent call last):
    File “C:\Users\owner\Desktop\競馬Ai\encode.py”, line 21, in
    var_data = pd.read_csv(
    ^^^^^^^^^^^^
    File “C:\Users\owner\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py”, line 948, in read_csv
    return _read(filepath_or_buffer, kwds)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:\Users\owner\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py”, line 611, in _read
    parser = TextFileReader(filepath_or_buffer, **kwds)
    File “C:\Users\owner\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py”, line 1448, in __init__
    self._engine = self._make_engine(f, self.engine)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File “C:\Users\owner\anaconda3\Lib\site-packages\pandas\io\parsers\readers.py”, line 1705, in _make_engine
    self.handles = get_handle(
    ^^^^^^^^^^^
    File “C:\Users\owner\anaconda3\Lib\site-packages\pandas\io\common.py”, line 863, in get_handle
    handle = open(
    ^^^^^
    FileNotFoundError: [Errno 2] No such file or directory: ‘data/2020_new.csv’

    • agus agus より:

      スクレイピングしたデータが格納されているファイルが見つからないようです。
      ‘data/2020_new.csv’
      ファイル名が正しいか、フォルダ名が正しいか、パスが正しいかを確認してみてください。

  12. HIRO より:

    初めまして、初心者ですが

    コードをコピペして貼り付けましたが、下記の問題(エラー)が発生しました。

    インポート “sklearn.preprocessing” をソースから解決できませんでした

    インポート “scipy.stats” を解決できませんでした

    コードの最初の方だと思いますが

    解決方法を教えて下さい。

    宜しくお願い致します。

    • agus agus より:

      sklearnとscipyがインストールされていないからだと思います。
      以下のコマンドを実行し、インストールしてください。
      pip install scikit-learn
      pip install scipy

  13. なか より:

    encoded_data.CSVを確認すると走破時間がマイナスの値になっているものがあります。
    今回行った走破時間の標準化について解説いただけないでしょうか?

    • agus agus より:

      通常、評価値が高いほど良い成績を示すようにするのが一般的です。
      走破時間に関しては、短い時間が良い成績を示すため、そのままでは評価値が低いほど良い成績になります。
      したがって、マイナス記号を付けることにより、評価値の方向を逆転させています。

      • なか より:

        ご教示ありがとうございます。
        追加の質問で恐縮ですが、走破時間は距離が違うと変わると思いますが、そのあたりはどういう扱いとしていますか?

        • agus agus より:

          あまり深く考えていなかったですが、特徴量に距離も含まれているので合わせてAIの方で解析してくれるのではないでしょうか?
          私は過去5レースの走破時間と距離を使って平均スピードという特徴量を作成して使用しています。

          • なか より:

            ありがとうございます。自分なりに特徴量考えて増やしてみたいと思います!

  14. TT より:

    # race_idでソート

    df_combined.sort_values(by=’race_id’, ascending=False, inplace=True)

    この部分で、エラーが発生します。

    not supported between instances of ‘int’ and ‘str’
    File “D:\——\——\——\——.py”, line 117, in
    df_combined.sort_values(by=’race_id’, ascending=False, inplace=True)
    TypeError: ‘<' not supported between instances of 'int' and 'str'

    このようなエラーです。
    intとstrの型の間では関数が対応していないといわれてると思うのですが、
    解決方法を教えていただたけないでしょうか?

    • agus agus より:

      race_idがstr型になっているようなので変換が必要だと思います。
      # ‘race_id’ 列を整数型に変換
      df_combined[‘race_id’] = df_combined[‘race_id’].astype(int)

  15. TT より:

    ご返信ありがとうございます。
    早速コードを組み込みましたが、

    ValueError: invalid literal for int() with base 10: ‘スプレンディダ’

    このエラーが出ます。
    race_idではなく、一つ隣の馬名を変換してしまっているということでしょうか?

  16. Python初心者 より:

    騎手情報の勝率などを処理する前に、
    # 騎手名の変更
    data_df[‘騎手’] = data_df[‘騎手’].replace({
    ‘佐々木大’: ‘佐々木大輔’,
    ‘シュタル’: ‘シュタルケ’,
    ‘M.デム’:’デムーロ’
    })
    print(data_df[data_df[‘騎手’].isin([‘佐々木大輔’, ‘シュタルケ’,’デムーロ’])])
    のように騎手名を変更してた方がいいかも。
    新しい出馬表を読み込んで予測する際に「佐々木大」「シュタル」のままだと、勝率がNaNになるかと思います。

    • agus agus より:

      ご指摘ありがとうございます。
      騎手の表示名ってたまに変更されたりもするので、整合性を合わせるのに苦労しますよね。

  17. 勉強中 より:

    いつもお世話になっております。エンコードされたデータを見返していたところ、クラスマッピングでG1もG2もG3もオープンもすべて同じ値になっていました。きちんとマッピングする場合どのようなコードにすればよいか教えていただけたら幸いです。よろしくお願いいたします。