より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

ラグビーの点差をPythonで分析しながらヒストグラムについて学ぶ

これまでPythonラグビーについて分析してきましたが、個人成績についてばかりでした。

試合の点差についてPythonで分析することにします。

いきなり結論

書いてみたのですが、ややダラダラした内容になってしまいました。

ですので、いきなり結論から入ってしまいます。

  • 1トライで追いつけたかもしれない7点差以内の試合は、40%ある。
  • 3トライ以上差がついた試合も40%もある。比較対象はないが、力の差は出やすいスポーツの気がしている。
  • 40点がボーダー。これ以上取られると勝ち目は薄い。
  • 逆を言えば40点取れば、ほぼ勝利確定。
  • 国同士のテストマッチでも100点差以上の試合となったことがある。

これらに少しでも興味を持たれた方は読み進めてください。

データについて

ラグビー現日本代表監督であるジェイミー・ジョセフが監督になったのテストマッチについて分析したいので、

点差

ラグビーについて興味深いのは点差です。

一度に7点入るスポーツということもあるのですが、100点差ゲームがあります。

高校野球で100点以上の差というのがありましたが、他のスポーツでは100点差というのは、なかなか聞かれないことだと思います。

今回はこの点差について注目したいので、データをゲットし、点差をヒストグラムに出してみます。

ですが。

なんか長いな。

色々動かしすぎたのかな。

ん?

f:id:rimever:20181208213509p:plain

変だ。ヒストグラムってこんな感じだっけ。

数値がおかしいのかな。

f:id:rimever:20181208213707p:plain

2G? 点差なのに、2ゴールド?

f:id:rimever:20181208213250p:plain

変なデータが混ざってobject型の配列になっていることが原因でした。

convert_objectsは非推奨となっているので、to_numericでcoerceを指定し、dropnaで除外してしまいます。

temp = pd.to_numeric(data['Diff'], errors='coerce').dropna()

f:id:rimever:20181209174321p:plain

ヒストグラム

ヒストグラムが出せました。

なのですが、これでは、大体のことしかわからず、何も見えてきません。

  • ヒストグラムの幅が決まってしまうが、5点差と10点差だと観る側からすると全然違う(1トライ=7点で逆転できたかもなゲームかそうでないか)
  • 300とか出てきても全体のうちの何%かを出してくれなきゃ、自分ですらよくわからない。

下記を参考にしました。

matplotlib でヒストグラムを描く – Python でデータサイエンス

pylab_examples example code: histogram_percent_demo.py — Matplotlib 2.0.2 documentation

調べて気づきましたが、実は大変です。

f:id:rimever:20181209133146p:plain

こういうヒストグラムを出してみたものの。コレジャナイ感が。

君は何が知りたいの

少し整理してみます。

自分が知りたいのは、

  • 3点差以内(ペナルティゴールで同点または逆転)
  • 7点差以内(トライ+コンバージョンで同点または逆転)

あとは、適当に「10点差以内, 20点差以内 , 50点差以内 , 100点差以内 , 100点差以上」

みたいなことです。

なので、1トライ=7点と換算するために、関数を定義します。

def categorize(diff):
    if diff == 0:
        return 0
    if diff <= 3:
        return 0.5
    return round(diff / 7 , 0)

あとは、この関数をmap関数内で処理すればOK。

temp = pd.to_numeric(data['Diff'], errors='coerce').dropna()
temp = list(map((lambda x: categorize(x)), temp))

array = np.asarray(temp)

自分としての注意点としては、listという変数を宣言してはならないということ。

よくC#でListの変数を気軽にlistにする癖が私にはあります。

すると、Pythonでlist関数が使えなくなり泣きます。これはlist関数が己で定義した変数で上書きされてしまい、list関数が使えなくなります。

'list' object is not callable

Pythonで変数名をつけるときには予約語だけでなく組み込み関数との衝突も気を付けましょう - Qiita

これは緩すぎるような。

from matplotlib.ticker import FuncFormatter

def to_percent(y, position):
    value = round(100 * y / len(array), 2)
    s = str(value)

    # The percent symbol needs escaping in latex
    if matplotlib.rcParams['text.usetex'] is True:
        return s + r'$\%$'
    else:
        return s + '%'

fig = plt.figure(figsize=(12, 8))
ax = fig.add_subplot(1,1,1)
edges = x = np.arange(0, 18, 0.5)
n, bins, patches = ax.hist(array, bins=edges)
ax.set_xlabel('トライ数差 ※0.5 = 3点')
ax.set_ylabel('freq')

y_ticks = []
for i in range(10):
    y_ticks.append(len(array) * i * 25 / 1000)

plt.yticks(y_ticks)
plt.xticks([0,0.5,1,2,3,4,5,10,15])
plt.grid()

formatter = FuncFormatter(to_percent)
plt.gca().yaxis.set_major_formatter(formatter)

plt.show()

長いコードの末に

f:id:rimever:20181209170033p:plain

ポイントは、

  • histのbinsに直接配列を渡すことで、分布をカスタマイズ。
  • ちなみに、分布は0.5ずつ。0.5が欲しいからといって0.5,1.0,2.0ということはできない。0,5,1.0,1.5,2.0とする。
  • %で表示したいので関数を定義する
  • そのまま表示すると変な値(18.4%)とかなるので、キリのいい値になるようにy_ticksにする。(全体が130だったら10%ごとにがなるように13,26,39)

データを見た所、1トライ差で追いつけたかもしれない(引き分けと3点差以内は除く)試合が多いようです。

ですが、全体の20%強なので、5試合に1試合です。

引き分けは2.5%、3点差以内の僅差のゲームは12.5%。

累積グラフでまとめ

このヒストグラムを累積グラフにしてみます。

累積グラフになると、1トライ差の試合に引き分けも3点差以内のゲームも含まれるようになります。

n, bins, patches = ax.hist(array, bins=edges, cumulative=True)

cumulative=Trueを指定すれば出来ます。

f:id:rimever:20181209170637p:plain

7点差以内のゲームは40%弱。

14点差以内のゲームは50%強と半分を超えます。

11/24に日本とロシアの試合で、日本が前半10-22から逆転したというゲームがありましたね。14点差でも何が起きるかはわからないです。

個人的には見ていて3トライ差以上つけられると辛いかなという気もします。

残りの50%以下は終了前に消化ゲームと化しているともいえます。

サッカーでいう3−0のようなものです。

いや、それでも、日本を応援しましょう。

ラグビーはそれだけ力の差が出やすいスポーツかなという気はします。

35点差以内のゲームは80%を超え、49点差以内のゲームは90%を超えます。

日本が50点差つけて勝っても緊張が緩んじゃいますね。

とはいえ、残りの10%近くは、それ以上、50点以上の点差がついてしまうということです。

点差と得点の相関

これまでのトライ数に変換した値ではなく得点のデータをそのまま使って、点差との相関を見ていきたいと思います。

複数グラフを表示する場合には、plt.xlabelが使えなくなってしまいますね。

fig, ax = plt.subplots(1, 2, figsize=(12, 6))

lost_team_points = data['Aga']
win_team_points = data['For']
diff_points = data['Diff']

ax[0].set_xlabel('勝利チームの得点')
ax[0].set_ylabel('敗北チームの得点')
ax[0].scatter(win_team_points,lost_team_points)
ax[0].grid()

ax[1].set_xlabel('勝利チームの得点')
ax[1].set_ylabel('点差')
ax[1].scatter(win_team_points,diff_points)
ax[1].grid()

f:id:rimever:20181209173713p:plain

勝利チームと敗北チームの相関

左は、勝利チームと敗北チームの得点を散布図にしたものです。

60-30のような試合にもなりますし、試合としてはいろんなスコアのゲームになることがわかります。

40-40のようなノーガードの殴り合いのような試合もあります。

勝利チームと点差の相関

右は、点差と勝利チームの相関です。

当然、勝利チームの点につれ、点差も増えます。

やはり40点がピークですかね。

考え方によっては、40点より多く取られてしまったら、勝ち目はほぼないと考えた方が良さそうです。

1番点差のついたゲームは?

あくまでも、2016年9月からのデータです。

f:id:rimever:20181209172035p:plain

デンマーク 127 - 5 エストニア。122点差。