Pythonで特徴量を作る際に気を付けておくこと:rollingについて
rollingを使って特徴量を作る際に気を付けておくことを書いておきます。
正直に申しますとかなりアホな間違えをしてしまったので、戒めのために備忘録として残しておきます。
・rollingの扱い
基本的なrollingの扱いは以下の通り
import pandas as pd s = pd.Series(range(10)) ######################### 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 #########################
df = pd.DataFrame({ 'num' : s, 'rolling': s.rolling(3).sum() })
index | num | rolling |
0 | 0 | NaN |
1 | 1 | NaN |
2 | 2 | 3.0 |
3 | 3 | 6.0 |
4 | 4 | 9.0 |
5 | 5 | 12.0 |
6 | 6 | 15.0 |
7 | 7 | 18.0 |
8 | 8 | 21.0 |
9 | 9 | 24.0 |
rolling(window=3)の場合は
index[0]+[1]+[2] = 3
index[1]+[2]+[3] = 6
index[2]+[3]+[4] = 9
index[3]+[4]+[5] = 12
index[4]+[5]+[6] = 15
index[5]+[6]+[7] = 18
index[6]+[7]+[8] = 21
index[7]+[8]+[9] = 24
3の場合は2つズレたうえで3つずつを足し算する動きを取る。
但し、min_periodsという引数を入れるとその値の個数のデータが含まれていれば結果が算出される。
さて本題ですが
私はテストデータと訓練データをくっつける際に使用上出てこない数字(python初学者が競馬予測をしてみたをもとにすると[result, 3ftime, rank3, rank4などの指数])
これに関しては欠損値が出てしまうので999の値を入れていたのですが、実際に入れてみるとこんな感じ
s['10'] = 999 ######################### 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 999 #########################
これをrollingすると(結果はわかるかもしれませんが。。。)
index | num | rolling |
0 | 0 | NaN |
1 | 1 | NaN |
2 | 2 | 3.0 |
3 | 3 | 6.0 |
4 | 4 | 9.0 |
5 | 5 | 12.0 |
6 | 6 | 15.0 |
7 | 7 | 18.0 |
8 | 8 | 21.0 |
9 | 9 | 24.0 |
10 | 999 | 1016.0 |
そうです。index10の所が異常値になって返ってきます。
解決策として
df = pd.DataFrame({ 'num' : s, 'rolling': s.rolling(3).sum().shift(1) })
index | num | rolling |
0 | 0 | NaN |
1 | 1 | NaN |
2 | 2 | NaN |
3 | 3 | 3.0 |
4 | 4 | 6.0 |
5 | 5 | 9.0 |
6 | 6 | 12.0 |
7 | 7 | 15.0 |
8 | 8 | 18.0 |
9 | 9 | 21.0 |
10 | 999 | 24.0 |
案の一つかもしれませんがこんな感じでかけたりします。
shiftを使う際はmin_periodsという引数を入れた方がいいのかもしれません。
df = pd.DataFrame({ 'num' : s, 'rolling': s.rolling(3, min_periods=2).sum().shift(1) })
index | num | rolling |
0 | 0 | NaN |
1 | 1 | NaN |
2 | 2 | 1.0 |
3 | 3 | 3.0 |
4 | 4 | 6.0 |
5 | 5 | 9.0 |
6 | 6 | 12.0 |
7 | 7 | 15.0 |
8 | 8 | 18.0 |
9 | 9 | 21.0 |
10 | 999 | 24.0 |
いい感じに加工できたと思います。
明らかに間違いをした場合は機械学習した際に予測値がおかしくなり正常に予想ができなくなります。
そうなった場合は前処理の部分を中心に探してみるのもいいかもしれません。
ではまた。
python初学者が競馬予測をしてみたpart6(特徴量追加)
こんにちはKHnodeです。
なんやかんやでpart6になってしまいましたが今回も調べていたらいい感じの特徴量を見つけてきたのでそれを作成しようと思います。
今回はスピード指数の作成ということでこちらを参考にしました。
西田式スピード指数
西田式スピード指数 Official Website
では早速コードを書いていこうと思います。
df01['conditionindex'] = 0 df01['conditionindex'].mask((df01['condition'] == '良') & (df01['turf'] == '芝'), -25, inplace=True) df01['conditionindex'].mask((df01['condition'] == '稍') & (df01['turf'] == '芝'), -15, inplace=True) df01['conditionindex'].mask((df01['condition'] == '重') & (df01['turf'] == '芝'), -5, inplace=True) df01['conditionindex'].mask((df01['condition'] == '良') & (df01['turf'] == 'ダ'), -20, inplace=True) df01['conditionindex'].mask((df01['condition'] == '稍') & (df01['turf'] == 'ダ'), -10, inplace=True) df['basetime'].fillna(df['basetime'].median()) df['weight'].fillna(df['weight'].median()) time = (df01['basetime'] * 10) - (df01['time'] * 10) disindex = 1 / (df01['basetime'] * 10) * 1000 weight = (df01['weight'] - 55) * 2 df01['speedindex'] = time * disindex + df01['conditionindex'] + weight + 80 df01 = df01.drop(['basetime', 'weight', 'conditionindex'], axis=1)
数式の解説は先ほど紹介したい西田式スピード指数に書いてあるのでこちらを参考にしていただけると幸いです。
今回は新たにCSVデータに斤量と基準タイムを追加しました。
馬場状態の指数に関してですが私独自で作成しています。
また基準タイム(Tragetに記載されている基準)と斤量は最頻値で欠損値を埋めています。
あとは合わせるだけという感じです。
最初の上6行はもっといい感じでかけるような気もしますが、
現状は変にforループを使うよりかは早く処理が終わりそうな気がしたので地道に書いています。
これを前走の成績としてくっつけます。
df['days'] = pd.to_datetime(df['days']) name_days_df = df[["horsename", "days", "pop", "odds", "rank3", "rank4", "3ftime", "result", 'speedindex']].sort_values(['horsename', 'days']) name_list = name_days_df['horsename'].unique() df_list = [] df = df.drop('speedindex', axis=1) for name in name_list: name_df = name_days_df[name_days_df['horsename'] == name] shift_name_df = name_df[["pop", "odds", "rank3", "rank4", "3ftime", "result", 'speedindex']].shift(1) shift_name_df['horsename'] = name df_list.append(shift_name_df) df_before = pd.concat(df_list) df_before['days'] = name_days_df['days'] df_before = df_before.rename(columns={'pop': 'pre_pop', 'odds': 'pre_odds', 'rank3': 'pre_rank3', 'rank4': 'pre_rank4', '3ftime': 'pre_3ftime', 'result': 'pre_result'}) df = pd.merge(df, df_before, on=['horsename', 'days'], how='inner')
くっつける方法はユニークな馬の名前を1頭ずつforループで回して集め終わったらshiftで1こずらしてリストに格納する感じです。
1頭ずづ処理をしているので処理はかなり時間がかかってしまうので何とかはやくしたいなぁと思っているのですが中々妙案が浮かばないです。
いい案などあればコメントなどで教えていただけると幸いです。
これを追加したデータで予想してみました。
予想記事はこちらから
keibatech.hatenablog.jp
結果は◎→〇→無印で決着でした。
ダノンザキットを当てれなかったのはくやしい(ToT)
もっとデータを愛さないといけない(戒め)
本ブログの競馬予測で使用する際のCSVデータ
2021年10月29日現在の「python初学者が競馬予測をしてみた」で使用しているCSVデータです。
参考にしていただければ幸いです。
拡大してみていただけると幸いです。
各カラム名の簡単な詳細
カラム名 | 概要 |
---|---|
raceid | レースID |
days | 日付 |
place | 場所 |
racenum | レース番号 |
class | レースクラス |
turf | 芝orダート |
distance | 距離 |
weather | 天気 |
horsecount | 出場頭数 |
condition | 馬場状態 |
horsenum | 馬番 |
horsename | 馬名 |
sex | 性別 |
age | 年齢 |
father | 父馬 |
mother | 母馬 |
fathermon | 母父馬 |
fathertype | 父タイプ |
legtype | 脚質 |
jockey | 騎手 |
trainer | 調教師 |
pop | 人気 |
odds | オッズ |
result | 結果 |
time | 走破タイム |
rank3 | 3コーナー通過時の順位 |
rank4 | 4コーナー通過時の順位 |
3ftime | 3ハロンタイム |
※追記(2022/2/18)
一部カラム名の変更を行いました。
python初学者が競馬予測をしてみたpart5(特徴量、前処理追加)
皆さんお久しぶりです。KHnodeです。
全く更新できていなくて申し訳ございません。
色々なことが起こりすぎてブログに手が付かない状態でした。
不定期更新にはなりますが、コツコツと更新していきたいです。
######################################################
今回は前処理と特徴量を変更・追加してみました。
※追記(2021.10.19)前処理のコードを一部変更しています。結果自体は変わりません。
概要
前処理
・父親、父親のタイプごとのコース、距離、芝ダート、馬場状態別の連対率を追加。
・騎手のコース別連対率を追加。
・各コースごとの連対した際の平均人気、オッズ、上がり3ハロンなどを追加。
コード
前処理
df = read_csv_data # 父親、父親タイプごとの連対率を作成する。 # 2着以上は1のフラグを追加する。 df.loc[df['result'] >= 3, 'result'] = 0 df.loc[df['result'] == 2, 'result'] = 1 # 父親、父親タイプごとの合計をピポットテーブルでまとめる。(indexとなっているところは父親と父親タイプで切り替え可能性) table_father_place = pd.pivot_table(df, index=index, columns='place', values='result', aggfunc='mean', dropna=False) table_father_distance = pd.pivot_table(df, index=index, columns='distance', values='result', aggfunc='mean', dropna=False) table_father_turf = pd.pivot_table(df, index=index, columns='turf', values='result', aggfunc='mean', dropna=False) table_father_condition = pd.pivot_table(df, index=index, columns='condition', values='result', aggfunc='mean', dropna=False) # 作成したピポットテーブルをマージしていく table_father = pd.merge(table_father_place, table_father_distance, on=index, how='left') table_father = pd.merge(table_father, table_father_turf, on=index, how='left') table_father = pd.merge(table_father, table_father_condition, on=index, how='left') table_father1 = table_father.fillna(0) # 脚質の最頻値を求める df['legtype'] = df['legtype'].map({'逃げ': 0, '先行': 1, '差し': 2, '追込': 3, '自在': 4}) legtypes = df.groupby(index).legtype.apply(lambda x: x.mode()).reset_index() legtype = pd.DataFrame(legtypes) legtype['legtype'] = legtype['legtype'].map({0: '逃げ', 1: '先行', 2: '差し', 3: '追込', 4: '自在'}) legtype = legtype.drop('level_1', axis=1) # 3ハロンタイムを出す time_3f = df.groupby(index).mean()['3ftime'] time3f = pd.DataFrame(time_3f) # 脚質の最頻値、3ハロンタイムをメインのデータにマージする。 father = pd.merge(table_father1, legtype, on=index, how='left') father = pd.merge(father, time3f, on=index, how='left') father = father.round(3) father = father.add_prefix('{}_'.format(index))
少し解説
全体の流れとしては
父親の出走回数と2着の回数をピポットテーブルを用いて作成する。
↓
脚質、平均3ハロンタイムを追加する
といった流れで作成しています。
脚質の最頻値を求めたかった理由としては現役で走っている馬は少なくとも父親の脚質にある程度は引き継がれるのではないかと思い作成しました。
根拠はなく、なんとなくつけているので検証していく中で削除するかもしれません。
父親のタイプも同様のコードで作成することができます。
#騎手ごとの連対率(父親と同様に作成した) df = read_csv_data # 2着以上は1のフラグを追加する。 df.loc[df['result'] >= 3, 'result'] = 0 df.loc[df['result'] == 2, 'result'] = 1 table_jockey = pd.pivot_table(df, index='jocky', columns='place', values='result', aggfunc='mean', dropna=False) table_jockey = table_jockey.fillna(0) table_jockey1 = table_jockey.fillna(0) table_jockey1 = pd.DataFrame(table_jockey1) table_jockey1 = table_jockey1.round(4) table_jockey1 = table_jockey1.add_prefix('jockey_')
こちらも同様にピポットテーブルでまとめて、forループで連対率を出してまとめています。
# 各コースごとの連対 df = read_csv_data df.loc[df['result'] >= 3, 'result'] = 0 df.loc[df['result'] == 2, 'result'] = 1 df = df.query('result == 1') if index == 'distance': table_place_time = pd.pivot_table(df, index=index, values='time', aggfunc='mean') table_place_rank3 = pd.pivot_table(df, index=index, values='rank3', aggfunc='mean') table_place_rank4 = pd.pivot_table(df, index=index, values='rank4', aggfunc='mean') table_place_3ftime = pd.pivot_table(df, index=index, values='3ftime', aggfunc='mean') table_place_pop = pd.pivot_table(df, index=index, values='pop', aggfunc='mean') table_place_odds = pd.pivot_table(df, index=index, values='odds', aggfunc='mean') table_place = pd.merge(table_place_odds, table_place_pop, on=index, how='left') if index == 'distance': table_place = pd.merge(table_place, table_place_time, on=index, how='left') table_place = pd.merge(table_place, table_place_rank3, on=index, how='left') table_place = pd.merge(table_place, table_place_rank4, on=index, how='left') table_place = pd.merge(table_place, table_place_3ftime, on=index, how='left') else: table_place = pd.merge(table_place, table_place_rank3, on=index, how='left') table_place = pd.merge(table_place, table_place_rank4, on=index, how='left') table_place = pd.merge(table_place, table_place_3ftime, on=index, how='left') place = pd.DataFrame(table_place) place = place.round(3) place = place.add_prefix('{}_'.format(index))
これも同様に作成します。
※ifとかある理由は関数で作成しているので条件分岐のためにつけています。
特徴量
# 特徴量の生成 df['re_3_to_4time'] = (df['pre_rank3'] - df['pre_rank4']) df['father_3f_to_my'] = (df['father_3ftime'] - df['pre_3ftime']) df['fathertype_3f_to_my'] = (df['fathertype_3ftime'] - df['pre_3ftime']) df['re_pop_now_pop'] = (df['pre_pop'] - df['pop']) df['re_odds_now_odds'] = (df['pre_odds'] - df['odds'])
こんな感じで特徴量を生成しました。
part1~5までの課題点
・予想の範囲が1~3番人気にと偏っている。(馬券的な旨みが少ない)
・根拠のない(or薄い)前処理特徴量の生成
・新たなモデルの作成。
この3点が挙げられます。
複勝で馬券のうまみのある馬を見つけるために前処理を中心に対策を取っていけたらなと思います。
次の記事では、そういったのに対応するための前処理について書いていこうと思います。
作成した競馬の予想モデルはgithubに挙げていますのでもしよろしければこちらも確認していただくと幸いです。
github.com
flaskで見よう見まねでtodoリストを作ってみた感想。
こんにちは、KHnodeです。
今回から新企画ということで、「flask初学者がwebアプリケーション開発してみた」をやります。
最終的なwebアプリはesports系を作っていきたいと考えています。内容は今後の変わる可能性はあるかもしれませんが今のところはデーター系にしてみようと思います。
今回はその第一回なので、まずはFLASkに慣れてみようということで「ToDoリスト」を作ってみたいと思います。
今回は、todoリストを作るにあたってこちらの動画を参考にしました。
参考した動画はこちら
https://www.youtube.com/watch?v=yKHJsLUENl0
作ってみた感想としては、todoリストは簡単に作成できることが分かった。
目標のwebアプリを作るために頑張っていきたい。
覚えておきたいやつ
query.all() クエリの習得
class Name(SQLAlchemy(app).Model): データベースの作成。db.modelを継承させること
SECRET_KEYはos.urandom(12)で作成する。