以前、このブログでは共分散や相関係数について扱ったことがある。 共分散や相関係数というのは、二つの変数間に線形な関係があるかを調べる方法だった。
しかし、実はただの相関係数では「第三の変数」からの影響を受けてしまう場合がある。 それというのは、第三の変数の存在によって、あたかも相関しているように見える (疑似相関) あるいは相関していないように見える (疑似無相関) というもの。
これは実際の例がないと、なかなか分かりづらいものだと思うんだけど良い例があったので紹介してみる。 今回はプロ野球の打撃成績に潜む疑似無相関を偏相関係数であぶり出してみることにする。
データをスクレイピングする
ひとまずデータがないと話にならないので、まずはスクレイピングしてくるところから始める。
使った環境は次の通り。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.12.5 BuildVersion: 16F73 $ python --version Python 3.6.1
今回は全て Python を使って処理する。
使うライブラリは pandas
と numpy
だけ。
ただし pandas
をスクレイピングに使うときは別途依存ライブラリが必要になる。
$ pip install pandas beautifulsoup4 requests lxml html5lib
各年度の選手成績はプロ野球の公式サイトから得られる。
試しに規定打席に達した選手の打撃成績を取得してみよう。
pandas#read_html()
にスクレイピングしたい URL を指定する。
>>> import pandas as pd >>> url = 'http://npb.jp/bis/2016/stats/bat_c.html' >>> tables = pd.read_html(url)
これだけでページに含まれるテーブルを上手いことパースして DataFrame
に落とし込んでくれる。
>>> df_with_header = tables[0] # 成績のテーブルを取り出す >>> df_with_header 0 1 2 3 4 5 6 7 8 \ 0 規定打席 :チーム試合数×3.1 (端数は四捨五入) NaN NaN NaN NaN NaN NaN NaN NaN 1 順 位 選 手 打 率 試 合 打 席 打 数 得 点 安 打 二塁打 2 1 角中 勝也 (ロ) .339 143 607 525 74 178 3 2 西川 遥輝 (日) .314 138 593 493 76 155 4 3 浅村 栄斗 (西) .309 143 611 557 73 172 5 4 糸井 嘉男 (オ) .306 143 616 532 79 163 6 5 柳田 悠岐 (ソ) .306 120 536 428 82 131 7 6 内川 聖一 (ソ) .304 141 605 556 62 169 8 7 秋山 翔吾 (西) .296 143 671 578 98 171 9 8 陽 岱鋼 (日) .293 130 555 495 66 145 10 9 中村 晃 (ソ) .287 143 612 488 69 140 ...
十年分のデータを取得する
先ほど実行した内容にもとづいて十年分のデータを取得してみよう。 これは、単年ではデータ点数が少なくてあまり信頼のおけるものではなくなるため。
今回扱うのは「安打数」と「三振数」と「打席数」なので、それを入れる変数を用意しておく。
>>> import numpy as np >>> h = pd.Series(dtype=np.int64) >>> bb = pd.Series(dtype=np.int64) >>> ab = pd.Series(dtype=np.int64)
あとは上記の変数に十年分のセ・パのデータを追加していく。
>>> import itertools >>> import time >>> for year, league in itertools.product(range(2006, 2017), ['p', 'c']): ... url = 'http://npb.jp/bis/{year}/stats/bat_{league}.html'.format(year=year, league=league) # スクレイピング対象 の URL ... tables = pandas.read_html(url) # テーブルを抽出する ... df_with_header = tables[0] # 成績のテーブルを取り出す ... features = df_with_header[2:] # ヘッダを捨てる ... h = h.append(features[8]) # 安打数のデータを取り出す ... bb = bb.append(features[21]) # 三振数のデータを取り出す ... ab = ab.append(features[5]) # 打席数のデータを取り出す ... time.sleep(1) # リクエスト間で負荷をかけないように少し待つ ...
これで 636 人分の選手データが集まった。
>>> len(h) 636 >>> len(bb) 636 >>> len(ab) 636
安打と三振の相関係数を計算する
データが揃ったので、次は相関係数を計算してみる。
仮説として、安打をたくさん打つ選手ほど三振の数は少ないのではないか?と考えて両者の相関係数を調べてみよう。 この仮説が正しければ安打と三振の間には負の相関があるはずだ。
それでは numpy#corrcoef()
を使って相関行列を計算させてみよう。
>>> np.corrcoef(h, bb) # 安打数と三振数の相関行列を計算する array([[ 1. , 0.06108444], [ 0.06108444, 1. ]])
安打と三振の相関係数は 0.06
なので、ほとんど相関がないといえる。
これだけ見ると、仮説は間違っていたのだろうか?と思える。
偏相関係数を計算する
しかし、実際には第三の変数によって疑似無相関になっている可能性がある。
そんなときは第三の変数の影響を無くした相関係数として「偏相関係数」を計算してみよう。 次の数式が偏相関係数を求めるためのもの。
ここで は変数 a
と b
の相関係数を表している。
上記は第三の変数 x
の影響を取り除いた y
と z
の偏相関係数を求める式になっている。
調べたところ、どうやら Python の既知のライブラリには偏相関係数を計算する実装がないようだ。 なので、ひとまず自前で関数を用意した。
>>> import math >>> def partial_corrcoef(x, y, z): ... """第三の変数 x の影響を除いた y と z の相関係数 (偏相関係数) を求める関数""" ... correlation_matrix = np.corrcoef((x, y, z)) ... r_xy = correlation_matrix[0, 1] ... r_xz = correlation_matrix[0, 2] ... r_yz = correlation_matrix[1, 2] ... r_yz_x = (r_yz - r_xy * r_xz) / (math.sqrt(1 - r_xy ** 2) * math.sqrt(1 - r_xz ** 2)) # noqa ... return r_yz_x ...
上記の関数を使って「安打数」と「三振数」の相関係数から「打席数」の影響を取り除いてみよう。
>>> partial_corrcoef(ab, h, bb) # 打席数の影響を取り除いた安打数と三振数の相関係数を計算する -0.31105000209505901
結果は -0.311
と「弱い負の相関」があることが分かった。
どうやら打席数という第三の変数が影響することで擬似無相関になっていたらしい。
まあ、実際には第三の変数を見つけることが大変だから一通り偏相関係数を求めた上で相関係数との変動の大小を比べるのが良いのかな。
まとめ
- 単純な相関係数では第三の変数の影響により疑似相関・疑似無相関になっている恐れがある
- 第三の変数の影響を取り除くには偏相関係数を計算すれば良い
スマートPythonプログラミング: Pythonのより良い書き方を学ぶ
- 作者: もみじあめ
- 発売日: 2016/03/12
- メディア: Kindle版
- この商品を含むブログ (1件) を見る