300億円欲しい

メジャーリーグのデータ解析します

メジャーリーグのデータ解析をしたい ( 21世紀限定HRランキング)

序論

ある程度まとまったデータの解析をしたいです.
でも, 興味のないデータを漫然と弄っても楽しくないです.
今回は, みんなが大好きなメジャーリーグのデータで遊びます.
野球に詳しくなって, さらにRの関数の使い方も練習できます.

さっそく, 21世紀のデータからホームラン数のランキングを作ります.
内容としては,
1. dataframeの扱い
2. apply関数の使用
3, applyを並列化して高速処理
あたりの勉強ができると思います.

最後にコピペ用ソースコードが載せてあるので, そこだけ見てもいいです.

メジャーリーグのデータで遊ぶ

データ取得と内容確認

メジャーリーグの試合データはいろいろ公開されていて, とても扱いやすいです.
まずは次のサイトからデータをダウンロードします.
Lahmanのdatabaseです. 2012年までのデータを使います.

http://www.seanlahman.com/baseball-archive/statistics/

camma区切りのデータが扱いやすいと重います.
ダウンロードしたら, 早速遊びます.
ワーキングディレクトリを適当に指定して, Rを起動.
データを読み込みます. 最初はBatting.csvです

Batting <- read.csv("Batting.csv")

中身はこんな感じ.

>head(Batting)
   playerID yearID stint teamID lgID  G G_batting AB R H X2B X3B HR RBI SB CS BB SO IBB HBP SH SF GIDP G_old
1 aardsda01   2004     1    SFN   NL 11        11  0 0 0   0   0  0   0  0  0  0  0   0   0  0  0    0    11
2 aardsda01   2006     1    CHN   NL 45        43  2 0 0   0   0  0   0  0  0  0  0   0   0  1  0    0    45
3 aardsda01   2007     1    CHA   AL 25         2  0 0 0   0   0  0   0  0  0  0  0   0   0  0  0    0     2
4 aardsda01   2008     1    BOS   AL 47         5  1 0 0   0   0  0   0  0  0  0  1   0   0  0  0    0     5
5 aardsda01   2009     1    SEA   AL 73         3  0 0 0   0   0  0   0  0  0  0  0   0   0  0  0    0    NA
6 aardsda01   2010     1    SEA   AL 53         4  0 0 0   0   0  0   0  0  0  0  0   0   0  0  0    0    NA

全選手分, 全年度の記録が詰まっているみたいです. これはすごい.

せっかくだからイチローの成績を確認したいです.
playerIDは, 5文字+2文字+"01" という構造みたいです. よく分かりませんけど.
suzuk + ic + 01ですかね. subset関数を利用して, playerID=="suzukic01"と条件指定して抽出.

> subset(Batting, playerID=="suzukic01")
       playerID yearID stint teamID lgID   G G_batting  AB   R   H X2B X3B HR RBI SB CS BB SO IBB HBP SH SF GIDP G_old
84890 suzukic01   2002     1    SEA   AL 157       157 647 111 208  27   8  8  51 31 15 68 62  27   5  3  5    8   157
84891 suzukic01   2003     1    SEA   AL 159       159 679 111 212  29   8 13  62 34  8 36 69   7   6  3  1    3   159
84892 suzukic01   2004     1    SEA   AL 161       161 704 101 262  24   5  8  60 36 11 49 63  19   4  2  3    6   161
84893 suzukic01   2005     1    SEA   AL 162       162 679 111 206  21  12 15  68 33  8 48 66  23   4  2  6    5   162
84894 suzukic01   2006     1    SEA   AL 161       161 695 110 224  20   9  9  49 45  2 49 71  16   5  1  2    2   161
84895 suzukic01   2007     1    SEA   AL 161       161 678 111 238  22   7  6  68 37  8 49 77  13   3  4  2    7   161
84896 suzukic01   2008     1    SEA   AL 162       162 686 103 213  20   7  6  42 43  4 51 65  12   5  3  4    8   162
84897 suzukic01   2009     1    SEA   AL 146       146 639  88 225  31   4 11  46 26  9 32 71  15   4  2  1    1    NA
84898 suzukic01   2010     1    SEA   AL 162       162 680  74 214  30   3  6  43 42  9 45 86  13   3  3  1    3    NA
84899 suzukic01   2011     1    SEA   AL 161       161 677  80 184  22   3  5  47 40  7 39 69  13   0  1  4   11   161
84900 suzukic01   2012     1    SEA   AL  95        NA 402  49 105  15   5  4  28 15  2 17 40   4   0  0  4   10    NA
84901 suzukic01   2012     2    NYA   AL  67        NA 227  28  73  13   1  5  27 14  5  5 21   1   2  5  1    2    NA

去年までの結果です. ヒット数はHですね. 半端じゃ無いH数です.

HRがホームランの数でしょうね. 今日はHRを使います.

21世紀のHRランキングを作る

工夫したランキングを作ります. 今回は21世紀のホームラン数を比べます.
指定する条件だけ変えれば, いくらでも好きなランキングが作れます.

21世紀のホームラン数を得るのに必要な処理を確認すると,

1. 21世紀の分の成績を抽出
2. 各選手でホームラン数の合計を計算
3. ホームラン数で並べ替え

ですね. 順番にやります.

1. 21世紀の成績を抽出

これは簡単

# 21世紀の成績がほしい
Batting <- subset(Batting, yearID>2001)

ですね.

次.

2. 各選手でホームラン数の合計を計算.

これはもう少し細かく考えると,

2.1 選手の名前のリストをつくる
2.2 選手の名前からHR数の合計を返す関数をつくる
2.3 2.1のリストの各要素に, 2.2の関数を適用する

ですかね.

順番にやります.

# 選手の名前のリストを取り出す. 
# playerIDには毎年同じ名前が出てくるので, uniqueで圧縮
players <- unique(Batting$playerID)

# 名前からホームラン数の和を計算する関数
compute.HR <- function(pid){
  d <- subset(Batting, playerID == pid)
  sum(d$HR)
}

# playersの各要素ににcompute.HRを
HR <- sapply(players, compute.HR)

HR数のベクトルが欲しいので, sapplyですね.
apply周りの関数はlapplyとsapplyだけ分かっておけば生きていける気がします.

これで各選手の, 21世紀のHR数がわかりました.

3. HR数で並べ替え

選手と21世紀HR数のdataframeを作って, HR数で並べ替えます

HRlist <- data.frame(Player = players, HR=HR)

# HR数で降順に並べ替えたい
# order関数を使います. 降順なので, decreasing=TRUEにします. 
HRlist <- HRlist[order(R$HR, decreasing=TRUE), ]

結果を見てみます.

>head(HRlist)
        Player  HR
2495 pujolal01 438
2657 rodrial01 406
858   dunnad01 387
2287 ortizda01 363
2951 soriaal01 351
1654 konerpa01 338


できました.

1位は大正義プホルス. TEXで復活して欲しい.
2位はおクスリA-ROD.
続いて, 三振かホームランか四球のアダム・ダン
レッドソックス優勝に貢献したオルティズ
広島カープにいるはずだったソリアーノ
最後は誰ですか分かりません.

(追記)
ホワイトソックスのポール・コネルコでした.
知らなかった.

並列化して高速に処理

applyのところでとても時間がかかります.
選手が多いですから, 仕方ないのですが.

> time_nonParallel <- system.time(HR <- sapply(players, compute.HR))
> time_nonParallel
   ユーザ   システム       経過  
    78.915      5.190     84.220 

遅いです.
それぞれの処理は独立しているので, 並列計算して速く処理できませんかね.

やってみます. parallelパッケージのparSapplyを使います.

# 並列化パッケージを使います
library(parallel)

# 計算クラスタを4つ作る  
cl <- makeCluster(4,type="SOCK")

# データをクラスタに持たせる
clusterExport(cl, c("Batting"))

# parSapplyで並列計算
time_Parallel <- system.time(HR2 <- parSapply(cl, players, compute.HR))

# おわり
stopCluster(cl)


これで速くなりますかね? 比べてみます

> time_nonParallel
   ユーザ   システム       経過  
    78.915      5.190     84.220 
> time_Parallel
   ユーザ   システム       経過  
    25.709      9.515     80.928

速いです. 並列化してよかった.

まとめ

コピペ用にコードをまとめます.

library(parallel)
cl <- makeCluster(4,type="SOCK")
Batting <- read.csv("Batting.csv")

# head(Batting)

# 21世紀の成績がほしい
Batting <- subset(Batting, yearID>2001)
# head(Batting)

# 名前からホームラン数の和を計算する関数
compute.HR <- function(pid){
  d <- subset(Batting, playerID == pid)
  sum(d$HR)
}

# 選手の名前のリストを取り出す. 
# uniqueで圧縮
players <- unique(Batting$playerID)

# 名前のリストに関数を適用 sapplyを使えばOK
# データをクラスタに持たせて並列計算
clusterExport(cl, c("Batting"))
HR <- parSapply(cl, players, compute.HR)
stopCluster(cl)

# 新しくデータフレームを作成
HRlist <- data.frame(Player = players, HR=HR)

# HR数で並べ替え
HRlist <- HRlist[order(HRlist$HR,decreasing=TRUE),]

# head(HRlist)

参考文献

Analyzing Baseball Data with R (Chapman & Hall/CRC The R Series)

Analyzing Baseball Data with R (Chapman & Hall/CRC The R Series)