WinningPost10の馬券王データを効率よく集めるためのツールを作成

# はじめに
今回はWinningPost10の馬券王で遊ぶときに使うツールを作成しました。馬券王の結果を簡単に入力し、記録した結果の勝率、連帯率、複勝率を表示させます。

馬券王で遊んでいると、「この馬はよく来るな」「この馬は来ないな」と何となく傾向がつかめてきます。それをしっかりデータとして記録し、馬券購入をしていきたいと考えたので作ってみました。

# 事前知識
一応、以下で紹介したOCRの技術を使っています。

https://qiita.com/sugimochi_1019/items/0b91ce59765d2e38de51

# データ入力部分

まず、データを入力する場所についてです
以下のような表をDataFrameとして入力します。

|1着|2着|3着|horse_nam|
|-|-|-|-|
|False|True|False|リバティアイランド|
|False|False|False|ヒカリデュール|
|True|False|False|イクイノックス|
|False|False|False|ゼニヤッタ|
|False|False|True|エエヤン|
|False|False|False|カンパニー|

実際のUIだとこんな感じです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/ed80d8fd-766b-6d4c-a3cb-deff8646638e.png)

これを入力として、競馬でよく見る[0-0-0-0]の形式に変換していきます。
変換した後、既存データに加算を行い、勝率などの割合を計算して、データを保存。

```python
def update_data(df, race_name):
    # 既存のデータを読み込む
    df_race_data = pd.read_csv(f"race_data/{race_name}.csv")
    
    # 既存の成績データに基づいて追加データを更新
    for _, row in df.iterrows():
        horse_name = row['horse_name']
        # 馬名が既に存在する場合、成績を加算
        if horse_name in df_race_data['horse_name'].values:
            idx = df_race_data[df_race_data['horse_name'] == horse_name].index[0]
            df_race_data.loc[idx, '1'] += int(row['1着'])
            df_race_data.loc[idx, '2'] += int(row['2着'])
            df_race_data.loc[idx, '3'] += int(row['3着'])
            df_race_data.loc[idx, '4-'] += int(not row['1着'] and not row['2着'] and not row['3着'])
        else:
            # 馬名が存在しない場合、新たに行を追加
            new_row = pd.DataFrame([{'horse_name': horse_name, '1': int(row['1着']), '2': int(row['2着']), '3': int(row['3着']), '4-': int(not row['1着'] and not row['2着'] and not row['3着'])}])
            df_race_data = pd.concat([df_race_data, new_row], ignore_index=True)
            
    # 勝率、連帯率、複勝率を計算して成績データに追加
    df_race_data['勝率'] = df_race_data['1'] / (df_race_data[['1', '2', '3', '4-']].sum(axis=1))
    df_race_data['連帯率'] = (df_race_data['1'] + df_race_data['2']) / (df_race_data[['1', '2', '3', '4-']].sum(axis=1))
    df_race_data['複勝率'] = (df_race_data['1'] + df_race_data['2'] + df_race_data['3']) / (df_race_data[['1', '2', '3', '4-']].sum(axis=1))

    # データを保存
    df_race_data.to_csv(f"race_data/{race_name}.csv", index=False)
    
    return df_race_data
```

ここまで終わればほぼ完成したようなものです。

# データの表示
ここでは入力された馬の名前から、該当するもののみを表示させます。

```python
def view_data(horse_list, race_name):
    # レースデータを読み込む
    df_race_data = pd.read_csv(f"race_data/{race_name}.csv")
    
    # 指定された馬名の成績のみをフィルタリングして表示
    filtered_results = df_race_data[df_race_data['horse_name'].isin(horse_list)]
    
    return filtered_results
```

# セッションで保存しておく用のクラスを定義
最近は`streamlit`でデータを保存しておくためにクラスを定義し、そのオブジェクトを`st.sessions_state`で保存しています。

```python
class RaceData:
    def __init__(self):
        # 初期化
        self.race_list = None
        self.race_name = None
        self.img = None
        self.ocr_texts = None
        self.df_ocr = None
        self.df_view = None
        self.df_log = None
        self.edit_df = None
        
    def select_race(self):
        self.race_list = pd.read_csv("C:/Users/tashi/Programming/wipo10/bakeno/race_names.csv")["race_name"].to_list()
        self.race_name = st.selectbox("登録するレースを選択", self.race_list)
    
    
    def upload_img(self):
        self.img = st.file_uploader("画像アップロード", accept_multiple_files=True)
    
    def view_ocr(self):
        # 画像がアップロードされていない場合
        if self.img == [] or self.img is None:
            st.write("画像をアップロードしてください")
            return
        
        # OCRの実行
        if st.button("OCR実行"):
            self.ocr_texts = detect_text(self.img[0].read())
        
        if self.ocr_texts is None:
            return
        
        # OCR結果をデータフレームに変換して表示
        self.df_ocr = pd.DataFrame(self.ocr_texts.split("\n"))
        st.dataframe(self.df_ocr, hide_index=True)
        
    def record_result(self):
        # レース結果を記録する場合
        if self.ocr_texts is None:
            return
            
        self.df_log = self.df_ocr.copy()

        self.df_log.insert(0, "1着", False)
        self.df_log.insert(1, "2着", False)
        self.df_log.insert(2, "3着", False)
        self.df_log.columns = ["1着", "2着", "3着", "horse_name"]
        
        self.edit_df = st.data_editor(self.df_log, hide_index=True)
        
        if st.button("レース結果を記録する"):
            update_data(self.edit_df, self.race_name)
```

## メイン部分
`Streamlit`をつかって表示させた部分。
今回はとりあえずクラスメソッドの中で表示の条件を指定しているが、ここで指定するのもよいと思う。

```python
if "client" not in st.session_state:
    st.session_state.client = RaceData()
    client = st.session_state.client
else:
    client = st.session_state.client
client.select_race()
client.upload_img()
client.view_ocr()
client.record_result()
```

# 完成

起動するとこんな感じ。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/471b65ed-ba34-dfc4-3fa8-bb83f378c6a4.png)

レースを選択し、馬の一覧部分のみになっているスクショを撮影→アップロード

そしてレース後に着順を設定して、レース結果を記録したらOK

Undoなどは作ってないので、使う際はGitとかでバージョン管理した方がデータ壊れないかもです....