独学でプログラミングを学んでみた

python初心者がテックブログを書いてみた

Pythonで特徴量を作る際に気を付けておくこと:rollingについて

rollingを使って特徴量を作る際に気を付けておくことを書いておきます。

正直に申しますとかなりアホな間違えをしてしまったので、戒めのために備忘録として残しておきます。

ここでの記事はpython機械学習での説明になります。

・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データです。
参考にしていただければ幸いです。

csvdatafile
拡大してみていただけると幸いです。

カラム名の簡単な詳細

カラム名 概要
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ハロンなどを追加。

特徴量

・3角から4角に移動した際の順位変換
・父親の平均3ハロンタイムと前走の3ハロンタイム
・父親のタイプ別平均3ハロンタイムと前走の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)で作成する。