【Python】真剣にM-1必勝法を考えてみる。

# 1. 内容
* 毎年盛り上がっているM-1グランプリだが、毎年楽しく見させていただいている。芸人さんにとってその後のキャリアを大きく左右する重要なグランプリであることもよく分かる。
* でも毎年見ていてちょっと気になることがある。ブッチ切りな候補が優勝する年もあるものの、僅差でとあるコンビが制する年もあり、やはりこの場合**審査員の好みが色濃く出ている**と感じられることがある。
* もともと、お笑い芸人の技量について絶対的なモノサシなんか存在しないだろうし、だからこそ**審査員との相性が重要**となってくるのではないかと思うことがある。
* そうなればこそ、どうしても**審査員の好みに合わせてウケにいって効率よく点数を稼ぐ方法**があるのではないかと考えてしまう。
* なお僕はお笑いには特別詳しくなく、バラエティでよく見る芸人さんについては知っている程度である。詳細なお笑い論で間違っていることを主張していたらごめんなさい。

# 2. 今年のM-1のデータ
* 安定のWikipediaさんを参照しました。（https://ja.wikipedia.org/wiki/M-1%E3%82%B0%E3%83%A9%E3%83%B3%E3%83%97%E3%83%AA）
* ||kaminuma|matsumoto|reiji|tomizawa|shiraku|hanawa|kyojin|
|:--|--:|--:|--:|--:|--:|--:|--:|
|milkboy|98|97|96|97|97|99|97|
|kamaitachi|95|95|94|93|95|95|93|
|pekopa|96|94|92|94|91|94|93|
|wagyu|92|92|93|91|96|96|92|
|mitorizu|94|91|93|91|94|92|94|
|karashi|94|90|93|90|89|90|93|
|ozuwarudo|94|90|94|91|89|89|91|
|suwe|92|89|91|90|92|91|92|
|indians|94|88|92|90|87|89|92|
|newyork|90|82|88|88|90|91|87|



* コピペして使いたい方は以下をどうぞ。
<details><summary>2019年M-1データ</summary><div>

```python
data = pd.DataFrame([[98,97,96,97,97,99,97],
                     [95,95,94,93,95,95,93],
                     [96,94,92,94,91,94,93],
                     [92,92,93,91,96,96,92],
                     [94,91,93,91,94,92,94],
                     [94,90,93,90,89,90,93],
                     [94,90,94,91,89,89,91],
                     [92,89,91,90,92,91,92],
                     [94,88,92,90,87,89,92],
                     [90,82,88,88,90,91,87]],
                   columns=['kaminuma','matsumoto','reiji','tomizawa','shiraku','hanawa','kyojin'],
                   index=['milkboy','kamaitachi','pekopa','wagyu','mitorizu','karashi','ozuwarudo','suwe','indians','newyork'])
```
</div></details>

# 3. データ概観
## 平均と標準偏差

```python
data.describe()
```

|  | kaminuma | matsumoto | reiji | tomizawa | shiraku | hanawa | kyojin |
|--:|:--|:--|:--|:--|:--|:--|:--|
| count | 10.000000 | 10.000000 | 10.0000 | 10.00000 | 10.000000 | 10.000000 | 10.000000 |
| mean | 93.900000 | 90.800000 | 92.6000 | 91.50000 | 92.000000 | 92.600000 | 92.400000 |
| std | 2.233582 | 4.184628 | 2.1187 | 2.54951 | 3.366502 | 3.306559 | 2.503331 |
| min | 90.000000 | 82.000000 | 88.0000 | 88.00000 | 87.000000 | 89.000000 | 87.000000 |
| 25% | 92.500000 | 89.250000 | 92.0000 | 90.00000 | 89.250000 | 90.250000 | 92.000000 |
| 50% | 94.000000 | 90.500000 | 93.0000 | 91.00000 | 91.500000 | 91.500000 | 92.500000 |
| 75% | 94.750000 | 93.500000 | 93.7500 | 92.50000 | 94.750000 | 94.750000 | 93.000000 |
| max | 98.000000 | 97.000000 | 96.0000 | 97.00000 | 97.000000 | 99.000000 | 97.000000 |

* 一応平均を見てみると、上沼の93.9が最高、松本の90.8が最低となっていますが、これ自体はあまり意味をなす数字ではありません。みんなに高い点数をつける人から高い点数をもらったところでライバルとの差がつかないからです。
* 標準偏差（表中の'std'）は、松本の4.18が最高、礼二の2.12が最低となっています。これは松本にハマるとライバルに差をつけやすい一方で、礼二にハマっても差がつきにくいというように解することができます。

## 相関係数
```python
sns.heatmap(data.corr(), annot=True)
plt.show()
```

![save.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191326/193bab6e-7833-28b1-7e73-8b7b457462f9.png)

* どうも上沼と志らくは好みがだいぶ違う方向性を向いているようで、相関係数はかなり低いということが見て取れます。

```python
data.corr().sum(axis=0)
```
|審査員|相関係数合計|
|:--|--:|
|kaminuma|     5.251470|
|matsumoto|    5.876156|
|reiji|        5.272178|
|tomizawa|     5.791959|
|shiraku|      4.487326|
|hanawa|       5.002257|
|kyojin|       5.535278|

* 松本と富澤は他の審査員と同じような好き嫌いをしているようです。言い換えれば、他の審査員の評価と整合性が取れるような評価ばかりで、独自の視点を打ち出してくるタイプではないようです。
* 一方で志らくや塙は自分独自のモノサシに従って点数をつけてくるせいか、他の審査員との相関はそこまで高くありません。

## ペアプロット
```python
sns.pairplot(data)
plt.show()
```
![save.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191326/d101f9da-0681-8c34-601b-09c21c5de109.png)

* 相関係数の数字と概ね整合性が取れる内容となっています。
* 例えば最上行の上沼の評価を見ると、他の審査員が高評価をつければだいたい上沼も高い点数を付与しているものの、志らくと塙との評価ではだいぶバラツキがあって、好みがだいぶ異なっているであろうことが見て取れます。

# 4. モデル
## 戦略
* ここまでで、独自の視点を持っているであろう審査員がいること、多くの審査員は他の審査員と似たような評価をつけがちであることが分かった。
* となれば次に気になるのは**どの審査員に気に入られにいくのが最も効率的に点数を稼ぐことができるか**、という点である。
* まず考えたのが、通常の線形回帰。例えば、上沼と松本の評価について、<br>$\qquad Score_{matsumoto}=\beta Score_{kaminuma}+\epsilon_{kaminuma}, \quad \epsilon_{kaminuma} \sim N(0,\sigma_{kaminuma}^2)$<br>という形に回帰してやれば、<br> $\qquad \Delta Score_{matsumoto}=\beta \Delta Score_{kaminuma}$<br>となって、上沼のスコアを１単位上げにいくことで、それに伴って松本のスコアがどれくらい上がると期待できるかをモデル化できるのではないかと考えた。
* ただ、ここで気になったのがこれを複数の審査員に拡大した時に、$\epsilon_{person}$が互いに独立と仮定していいかという点である。すなわち、上沼へのハマり具合から松本と礼二のスコアを推定する上で、上沼は気にしないが松本と礼二が重視する何かしらの尺度が存在して、$\epsilon_{matsumoto}$と$\epsilon_{reiji}$が同方向の数値となりがちな傾向が存在しないかどうかということである。
* これを捕捉するために、線形回帰をした際の残差の分散についても分析することとした。<br>$\qquad Var(X+Y)=Var(X)+Var(Y)+2Cov(X,Y)$<br>$\qquad Cov(X+Y,Z)=Cov(X,Z)+Cov(Y,Z)$<br>であることを踏まえれば、<br>$\qquad \sum_{person}Score_{person}=\sum_{person}\beta_{person}Score_{kaminuma}+\epsilon_{all}, \quad \epsilon_{all} \sim N(0,\sigma_{all}^2)$<br>$\qquad where \quad \sigma_{all}^2 = \sum_{person}Var(residual_{person}) + \sum_{person_1}\sum_{person_2}Cov(residual_{person_1},residual_{person_2})$<br>となる。

## 実装
* 実際に分析に用いたコードは以下の通りである。
<details><summary>回帰分析のコード</summary><div>

```python
from sklearn.linear_model import LinearRegression
X = data.values
coefs = np.zeros((7,7))
V = np.zeros(7)
model = LinearRegression()
for i in range(X.shape[1]):
    residuals = np.zeros((6,10))
    coef = np.zeros(6)
    x = X[:,i].reshape(-1,1)
    y = np.delete(X, i, 1)
    for j in range(y.shape[1]):
        y_ = y[:,j].reshape(-1,1)
        model.fit(x, y_)
        coef[j] = model.coef_
        residuals[j,:] = (model.predict(x) - y_).flatten()
    coef = np.insert(coef, i, 1)
    coefs[i,:] = coef
    cov_mat = np.cov(residuals, bias=True)
    V[i] = cov_mat.sum()
```
簡単に説明を加えておくと、

* for i in ...のところでは、上沼のスコアを元に他の審査員のスコアを回帰分析し、次に松本のスコアを元に・・・というループを回している。
* for j in ...のところで、例えば上沼のスコアを元に、松本のスコアを回帰、礼二のスコアを回帰というループを回している。また回帰の係数（上の数式で$\beta_{person}$にあたる）をcoefという配列に保存している。また、回帰の残差をresidualsという配列に保存している。
* for j in ...のループが終わったところで、np.insertによって、例えば上沼から上沼への回帰の係数は1であるというところを処理している。また、coefをcoefsという配列に保存している。
* 残差の分散の和をVという配列に保存している。residuals共分散行列の和がそれに当たる。
</div></details>

## 結果
* 各審査員のスコアに対する他の審査員の感応度である$\beta_{person}$については以下のようになった。データは行ごとに入っていて、上沼のスコアに対する他の審査員の感応度が１行目となっている。

```python
coefs_df = pd.DataFrame(coefs, 
                        columns=['kaminuma','matsumoto','reiji','tomizawa','shiraku','hanawa','kyojin'],
                        index=['kaminuma','matsumoto','reiji','tomizawa','shiraku','hanawa','kyojin'])
sns.heatmap(coefs_df, annot=True)
plt.title('regression coefficient')
plt.show()
```
![save.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/191326/f71d9641-1b23-f8c3-38a6-48220b95b4bc.png)

* また、残差の標準偏差は以下のようになった。

```python
std_df = pd.DataFrame(pow(V,0.5),
                      columns=['residual std'],
                      index=['kaminuma','matsumoto','reiji','tomizawa','shiraku','hanawa','kyojin'])
```

|  | residual std |
|:--|--:|
| kaminuma | 9.332082 |
| matsumoto | 4.644780 |
| reiji | 9.057326 |
| tomizawa | 5.586553 |
| shiraku | 10.673552 |
| hanawa | 8.872448 |
| kyojin | 7.665711 |

# 5. 考察
* どの審査員の好感度を狙いに行くのが最も効率的を考えるにあたって、ある審査員から追加で１点もらったときに他の審査員からの点数がどうなるかを考えるよりも、審査員ごとにスコアの標準偏差が異なっていたことを考えると、**追加的に１標準偏差（$1\sigma$）分の点数をもらったときに他の審査員からの点数がどうなるか**を考えたほうがよいと思われる。

```python
sensitivity_df = pd.DataFrame(data.std(axis=0)*coefs.sum(axis=1), columns=['sensitivity'])
sensitivity_df = pd.concat([sensitivity_df, std_df], axis=1)
```
|  | sensitivity | residual std |
|:--|--:|--:|
| kaminuma | 14.714779 | 9.332082 |
| matsumoto | 17.009344 | 4.644780 |
| reiji | 14.904319 | 9.057326 |
| tomizawa | 16.691662 | 5.586553 |
| shiraku | 13.664036 | 10.673552 |
| hanawa | 15.027370 | 8.872448 |
| kyojin | 15.747906 | 7.665711 |

* これを見る限りでは、**松本**のウケを狙うと他の審査員からのスコアアップも期待できやすく、最も効率的に点数を稼ぐことができそうであることがわかる。
* 一方で残差の標準偏差は松本が最も小さく、志らくが最も大きくなっている。志らくにハマっても他の審査員は別の視点から評価をしており全体的なスコアアップに直結しにくいが、他の審査員の別の尺度にハマるかどうかでスコアがブレやすく、残差の標準偏差が大きくなっていると解することができる。
* 以上より**まずは松本好みの漫才に寄せて評点全体の底上げをした上で、余裕があるなら志らくに引っかかる要素を織り交ぜてライバルとの差別化を図る**のがM-1を制する一助となるのではないかと考えられる。

# 6. まとめ
* 2019年の１回戦のみに限っていえば、松本の好みに寄せた漫才をするのが１回戦を制する鍵になるのではないかという示唆が得られた。
* 松本の好む漫才がどのような漫才かという点については具体的な指摘はできません。お笑い素人の限界です。
* 他の年や決勝についても似たような分析をすることで面白いインプリケーションが得られそうである。
* まあこのような小手先の戦略がワークする局面があったとしても、余程の僅差である場合に限られるでしょう。難しいこと考えずに気楽に見て笑うのが一番の楽しみ方だと思います。