騎手の各種勝利データを使った因子分析をしてみる

# はじめに

今回は騎手のデータを因子分析することでどのようなクラスタリングができるかを試してみます。
希望としては平場に強い騎手や、重賞で強い騎手などのクラスタリングができればとても面白いと思うところです。

# 使うもの
- Python
    - Factor Analyzer
    - Seaborn
    - Matplotlib
    - Pandas

# 使用データ

今回も[netkeiba](https://www.netkeiba.com/)さんからデータをスクレイピングさせていただきます。
データベースの騎手データから「現役」「美浦・栗東所属」とフィルタリングをかけたときのテーブルデータをそのままお借りしました。

```python
import pandas as pd
import time

df = pd.read_html("https://db.netkeiba.com/?pid=jockey_list&word=&bel%5B%5D=1&bel%5B%5D=2&sort=name&list=100&act%5B%5D=0")[0]
time.sleep(1)
df = pd.concat([df, pd.read_html("https://db.netkeiba.com//?pid=jockey_list&word=&bel%5B0%5D=1&bel%5B1%5D=2&sort=name&list=100&act%5B0%5D=0&page=2")[0]])
```

最低限の前処理をして、生データとして書き出しておきます。
```python
df = df.set_axis([i[0] for i in df.columns], axis=1).reset_index(drop=True)
df.set_axis(['騎手名', '所属', '生年月日', '1着', '2着', '3着', '着外', '重賞_出走', '重賞_勝利', '特別_出走', '特別_勝利',
       '平場_出走', '平場_勝利', '芝_出走', '芝_勝利', 'ダート_出走', 'ダート_勝利', '勝率', '連対率', '複勝率',
       '収得賞金 (万円)', '代表馬'], axis=1).to_csv("./row_data/jockey.csv", index=False)
```

# データの前処理

まず、数字ではないデータも含まれるので数字データのみにしておきます（一応それ以外も別のデータフレームで保存しておきます）。出走数と勝利数は勝率という一つの変数に統合できるのでしておきます。データを見てみると欠損値は出走数0で割ったときのものなので、0にしておきます。

```python
df = pd.read_csv("row_data/jockey.csv").dropna()

# 数字データとそうでないので分ける
df_non_num = df[['騎手名', '所属', '生年月日', '代表馬']]
df_num = df[['1着', '2着', '3着', '着外', '重賞_出走', '重賞_勝利', '特別_出走',
       '特別_勝利', '平場_出走', '平場_勝利', '芝_出走', '芝_勝利', 'ダート_出走', 'ダート_勝利', 
       '勝率', '連対率', '複勝率', '収得賞金 (万円)']]

# 勝率へ変換、それ以外は除外
df_num["重賞_勝率"] = df_num["重賞_勝利"] / df_num["重賞_出走"]
df_num["特別_勝率"] = df_num["特別_勝利"] / df_num["特別_出走"]
df_num["平場_勝率"] = df_num["平場_勝利"] / df_num["平場_出走"]
df_num["芝_勝率"] = df_num["芝_勝利"] / df_num["芝_出走"]
df_num["ダート_勝率"] = df_num["ダート_勝利"] / df_num["ダート_出走"]
df_num = df_num[['勝率', '連対率', '複勝率', '収得賞金 (万円)', '重賞_勝率', 
                 '特別_勝率', '平場_勝率', '芝_勝率', 'ダート_勝率']]

# 欠損値は0で埋める
df_num = df_num.fillna(0)

# 標準化
from sklearn.preprocessing import StandardScaler

sc = StandardScaler()
std_data = sc.fit_transform(df_num)
```

# 因子分析

Factor Analyzerで因子分析します。sklearnでもやれますが、寄与率を出せるこっちを使っています。

```python
from factor_analyzer import FactorAnalyzer

df_std = pd.DataFrame(std_data, columns=['勝率', '連対率', '複勝率', '収得賞金 (万円)', '重賞_勝率', 
                                         '特別_勝率', '平場_勝率', '芝_勝率', 'ダート_勝率'])

n_factors=3
fa = FactorAnalyzer(n_factors=n_factors)
fitted = fa.fit_transform(df_std)
```

## 因子負荷行列

因子負荷行列を作ってみましょう。

```python
from factor_analyzer import FactorAnalyzer
import japanize_matplotlib
import seaborn as sns


df_loadings = pd.DataFrame(fa.loadings_, 
             columns=[f"第{i+1}因子" for i in range(n_factors)], 
             index=[df_num.columns])
sns.heatmap(df_loadings,annot=True, fmt='g', cmap='Blues')
```

結果です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/3dde1ab5-bfa7-5884-c96c-123ab0a5014d.png)
ざっと見た感じだと、

|因子|特徴|解釈|
|-|-|-|
|第1因子|(+)勝率、連帯率、複勝率、平場勝率|平場で多く勝ってる|
|第2因子|(+)収得賞金、芝、ダート勝率|収得賞金の量（レースでの勝利数）|
|第3因子|(+)重賞、特別勝率 (-)平場勝率|平場以外のレースで活躍しがち|

ですかね？

## 累積寄与率

寄与率を見てみましょう。結果は以下の通りです。
最終的に累積寄与率が0.6以上でよいとされています。第3因子で0.7超えていたので問題ないですね。

```python
var = fa.get_factor_variance()
df_var = pd.DataFrame(list(zip(var[1], var[2])), 
                      index=[f"第{i+1}因子" for i in range(n_factors)], 
                      columns=["寄与率", "累積寄与率"])

sns.heatmap(df_var.T,annot=True, fmt='g', cmap='Blues')
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/b495b1ad-1030-f1ab-8a84-811a90ef22ef.png)

## 散布図へプロット

因子それぞれを軸にして、因子得点をプロットするとサンプルごとの関係性を見ることができます。

```python
fact_name = {1: "平場で多く勝ってる",
             2: "収得賞金の量（レースでの勝利数）",
             3: "平場以外のレースで活躍しがち"}
df_non_num = df_non_num.reset_index(drop=True)
```

```python
import matplotlib.pyplot as plt 
import matplotlib

# 軸にする因子を決める
x = 1
y = 2

font = {'family': 'IPAexGothic'}
matplotlib.rc('font', **font)
plt.figure(figsize=(12,10))
plt.scatter(fitted[:,x-1],fitted[:,y-1])
plt.ylabel(fact_name[y], fontsize=20)
plt.xlabel(fact_name[x], fontsize=20)
count = 0
for xy in zip(fitted[:,x-1], fitted[:,y-1]):
    plt.annotate(df_non_num["騎手名"][count], xy=xy, textcoords='data', fontsize=8)
    count = 1+ count
```

まず、収得料金の量とレースでの勝利数が多い因子と、平場で多く勝ってるという因子でプロットしてみました。単純に右上に行くほど成績の良い騎手になってますね。

少し面白いのが下の部分に障害騎手がまとまっているところ。理由としては障害レースは「芝」「ダート」の両方を走るため「障害」という枠組みになるのですが、今回は芝ダートの勝率しか入ってないからですね。なのでレースは多く勝ってるけど、第1因子の得点は低いのが障害騎手になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/87aa805d-1da8-0acd-ba70-6671e199ffaa.png)

次に収得賞金の量と平場以外のレース活躍を軸にしてみます。
すると重賞や○○特別といたレースの勝ち鞍が多い騎手と少ない騎手で右左に分かれます。

右上のエリアにいる騎手は確かに今年、重賞などを勝ってる騎手ですね！
左上エリアは最近あまり大舞台での活躍がないうまい騎手がそろっていたりしますね！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/456646/e53d5ba1-f615-745c-98b6-ebbf10e99a85.png)

# まとめ

実際に勝率などのデータからここまでしっかり特徴が分かれるのは面白いですね。
今後のデータ分析にも生かせそうな気がしています。


# 参考文献
- netkeiba: https://www.netkeiba.com/
- factor_analyzer documentation: https://factor-analyzer.readthedocs.io/en/latest/factor_analyzer.html