Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
13
Help us understand the problem. What is going on with this article?

More than 5 years have passed since last update.

@massa142

Python × Bradley-Terry's Modelでセ・パ両リーグの戦力値遷移を可視化

Bradley-Terry's Model

これは、「強さ」を図るモデルです。
簡単にいうと、試合の結果から各々の「強さ」を数値化できるよっていう概念です。
以下の論文が参考になりましたので、詳しくはこちらをご覧ください。
「強さ」に対する定量的評価法とその応用

n個の要素 (チームや個人) があり、何らかの対戦を行うものとする。
対戦は 1要素 対 1要素 のマッチで行われ、その結果は片方の要素の対して勝利、または敗北しか生じないとする。
何回か対戦した結果から各要素の「強さ」をはかるとする。
ここで要素 i が要素 j に勝利する確率を Pij としたとき、すべての組み合わせに対して、
Pij = πi / πi + πj (1)
となるπi を導入する。
式 (1) の関係式を Bradley-Terry(BT)モデルという。
BT モデルにおいて πi は,要素 i の強さを表すと考えることができる。

参考にしたもの

先日参加した、統計処理及び機械学習に基づくデータマイニング勉強会 #03での講義内容を参考にしています。
ここでBradley-Terry's ModelをPythonで実装したものが紹介されています。
(wsに代入されている数字は、交流戦の成績を除いた2014年度のセ・リーグ6球団の総勝数のリストです。便宜上、引き分けを0.5でカウントしています。)
ここで得られたthetaが各チームの「強さ」の数値です。

import numpy as np
theta = np.ones(6)/6.0 # \theta 初期値
t = np.zeros(6)
r = 24.0 # 対戦数
ws = np.array([66.5, 66.5, 66, 56.5, 55, 50.5])  # 総勝数
for iloop in range(0, 100):
    for i in range(0,6):
        acc = 0 # 総和用のアキュムレータ
        for j in range(0,6):
            if (j == i):
                pass
            else:
                acc += r / (theta[i]+theta[j])
        t[i] = ws[i]/acc
    s = sum(t)
    for i in range(0, 6):
        theta[i] = t[i] / s

過去5年間の戦力値遷移

上記のBradley-Terry's Modelを参考に、セ・リーグの5年間の移り変わりをmatplotlibでグラフ化してみました。

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt
import os

theta = np.ones(6) / 6.0
t = np.zeros(6)
r = 24.0

teams = [u"巨人", u"阪神", u"広島", u"中日", u"横浜", u"ヤクルト"]
colors = ["#f27b00", "#ffff00", "#ff0000", "#002468", "#044a90", "#111c3c"]
scores = [[67.5, 68, 48, 68.5, 42.5, 65.5], [66, 61, 57, 66, 43.5, 66.5], [76.5, 51.5, 55.5, 69, 43, 64.5], [74, 62.5, 58.5, 57.5, 55, 51.5], [66.5, 66.5, 66, 56.5, 55, 50.5]]
years = ["2010", "2011", "2012", "2013", "2014"]
points = [[], [], [], [], [], []]


def reset():
    global theta
    theta = np.ones(6) / 6.0

def bradley_terry(ws):
    for iloop in range(0, 100):
        for i in range(0, 6):
            acc = 0
            for j in range(0, 6):
                if i == j:
                    pass
                else:
                    acc += r / (theta[i] + theta[j])
            t[i] = ws[i] / acc
        s = sum(t)
        for i in range(0, 6):
            theta[i] = t[i] / s

if __name__ == '__main__':
    for score in scores:
        reset()
        bradley_terry(score)
        for i in range(0, 6):
            points[i].append(theta[i])

    for i in range(0, 6):
        data = np.loadtxt(points[i])
        plt.plot(
            data,
            linestyle="-",
            color=colors[i],
            label=teams[i],
            linewidth=2
        )

    plt.ylim(0, 0.5)
    X = np.arange(len(years))
    plt.xticks(X, years)
    plt.tick_params(labelleft="off")
    plt.xlabel("Year")
    plt.ylabel("BradleyTerry Rate")
    plt.title("Central League")
    plt.grid(True)
    plt.legend(loc="best")
    plt.rcParams.update({"font.size": 20})
    os.chdir("graph/")
    plt.savefig("central.eps")

    plt.show()

central.png

こんな感じになりました。
・巨人の安定的な強さ
・中日、ヤクルトの右肩下がり
・広島、横浜の右肩上がり
などが読み取れるのではないでしょうか…

パ・リーグでも同様にやってみると、以下のようなグラフとなります。

pacific.png

セ・リーグのグラフと比較すると6本の折れ線がごちゃごちゃしてるのが特徴的です。
2013年と2014年でABクラスがまるっと入れ替わるということが起きちゃう「混パ」をよく表現できてますね!

13
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
13
Help us understand the problem. What is going on with this article?