[LIST.1] リストの生成

c関数で生成されるアトミックベクトルは、全て同じ型であることが前提でした。

#異なる型のものを入れると強制的に同一の型へと変換
a = c(13,TRUE,"hello")
print(a)
## [1] "13"    "TRUE"  "hello"
# aの型は「character」
typeof(a)
## [1] "character"

list関数でつくられるリストであれば、異なる型を1つの変数にグループとしてまとめることができます。

a = list(13,TRUE,"hello")
print(a)
## [[1]]
## [1] 13
## 
## [[2]]
## [1] TRUE
## 
## [[3]]
## [1] "hello"
# aの型とクラスはともに「リスト」
typeof(a); class(a);
## [1] "list"
## [1] "list"

個々の要素には、a[[n]]でアクセスできます。

a[[1]]; a[[2]]; a[[3]]
## [1] 13
## [1] TRUE
## [1] "hello"

これらの要素は、(今回は)全てアトミックベクトルなので、 typeof関数で型にアクセスできます。

typeof(a[[1]]);typeof(a[[2]]);typeof(a[[3]])
## [1] "double"
## [1] "logical"
## [1] "character"

実際には、リストの要素はアトミックベクトルとは限りません。
次で、そのような例を確認していきます。

[LIST.2] リスト[[n]]とリスト[n]

(復習)リストのn番目の要素はリスト[[n]]で表します。

a = list(13,TRUE,"hello")
a[[1]];a[[2]];a[[3]]
## [1] 13
## [1] TRUE
## [1] "hello"

リストのn番目の要素がベクトルの場合、 そのベクトルのm番目の要素は[[n]][m]で表します。

a[[1]][1];a[[2]][1];a[[3]][1]
## [1] 13
## [1] TRUE
## [1] "hello"

リスト[n]は、リスト[[n]]を要素として持つ「サイズ1」のリストを返します。

print(a[1]);print(a[2]);print(a[3])
## [[1]]
## [1] 13
## [[1]]
## [1] TRUE
## [[1]]
## [1] "hello"

ややこしいですが、以下の違いに注意してください。

a = list(13,TRUE,"hello")
typeof(a[[1]]); typeof(a[1]); typeof(a[1][[1]])
## [1] "double"
## [1] "list"
## [1] "double"

もう少し、例題を通して確認していきましょう。
以下は、リストの要素が全てアトミックベクトルの例です。

a = list(11:13,c(T,F,F,F),"good-bye")
print(a)
## [[1]]
## [1] 11 12 13
## 
## [[2]]
## [1]  TRUE FALSE FALSE FALSE
## 
## [[3]]
## [1] "good-bye"

各ベクトルの要素は以下のように参照できます。

a[[1]][2];a[[2]][4]
## [1] 12
## [1] FALSE
# aの三番目の要素はアトミックベクトル
print(a[[3]])
## [1] "good-bye"
# a[3]は、a[[3]]を要素に持つサイズ1のリストとなります。(復習)
a3 = a[3]; print(a3)
## [[1]]
## [1] "good-bye"
typeof(a3);length(a3)
## [1] "list"
## [1] 1
# a[1]は、11:13(a[[1]])を要素に持つサイズ1のリスト
a1 = a[1]; print(a1)
## [[1]]
## [1] 11 12 13
typeof(a1);length(a1)
## [1] "list"
## [1] 1

リストはリストを要素に加えることができます。

a = list(list(T,c("hello","good-bye")),c(T,F,F,F),101:200)
print(a)
## [[1]]
## [[1]][[1]]
## [1] TRUE
## 
## [[1]][[2]]
## [1] "hello"    "good-bye"
## 
## 
## [[2]]
## [1]  TRUE FALSE FALSE FALSE
## 
## [[3]]
##   [1] 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
##  [19] 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
##  [37] 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
##  [55] 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172
##  [73] 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
##  [91] 191 192 193 194 195 196 197 198 199 200

リストのリストは[[p]][[q]]..という形で掘っていきます。

print(a[[1]]) #aの第一要素もリスト構造
## [[1]]
## [1] TRUE
## 
## [[2]]
## [1] "hello"    "good-bye"
# aの第1要素の第2要素はアトミックベクトル
a[[1]][[2]] 
## [1] "hello"    "good-bye"
# a(リスト)の第1要素(リスト)の第2要素(ベクトル)の第2要素は"good-bye"
print(a[[1]][[2]][2])
## [1] "good-bye"

くどいですが、再び、リストとアトミックベクトルの違いを復習します。

a = c("hello","good-bye")
b = list("hello","good-bye")
# "hello"の参照は?
a[1]; b[[1]][1]
## [1] "hello"
## [1] "hello"
# "good-bye"の参照は?
a[2]; b[[2]][1]
## [1] "good-bye"
## [1] "good-bye"

[LIST.3] $記法(名前属性)

リストの各要素には名前属性をつけることができます。

chubu = list(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
             pop = c(716,211,186,379), #人口
             order = c(4,17,22,10), #人口順位
             capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
             shinkansen = c(T,T,F,T)) #新幹線通過の有無

このリストの特定のベクトルにアクセスするのに、名前属性を使うことができます。この記法をドル記法($記法)と呼びます。

chubu$pop #chubu[[2]]と同じ
## [1] 716 211 186 379
chubu$capital #chubu[[4]]と同じ
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"

さらに、個々の属性の要素には以下のように参照できます。

#愛知県の県庁所在地は、
chubu$capital[1]
## [1] "nagoya"
#岐阜県の人口順位は
chubu$order[2]
## [1] 17
#三重県の新幹線通過の有無は、
chubu$shinkansen[3]
## [1] FALSE
#静岡県の人口は
chubu$pop[4]
## [1] 379

[LIST.4] リストを出力する関数の例

先週の授業の例:n個のサイコロを振って、総和を得るプログラムです。

roll.n = function(n){
  dice = 1:6
  dice2 = sample(dice, size=n, replace = TRUE)
  # dice2 = sample(dice,n,T) #こちらでも可
  sum(dice2)  
}
# サイコロを3回振った総和の1000サンプルのベクトルを出力
sample = replicate(1000,roll.n(3))
print(head(sample,100))
##   [1] 13 11 11 12 15 11  7  9 11  9 13 10 10  6  8 13 15  9 14 13  7  4 11  5 15
##  [26]  9  9 10 12 12  7 10 12 12 13 10 12  8 16 15 13  9  6 14 10  8  9 11 12 13
##  [51]  5  8 13 12 13 15 15 11  6 16 14  8 13 11  7 16 10  8  9 18 17 10 14 13 16
##  [76]  9  7 14 14 12 10 13 14  8 12 13  7 10 10  9  8 12 14 11 16 11 16 13  9 13
# ヒストグラムを計算し、Plotsパネルにグラフを出力します。
hi = hist(sample,breaks=1:20) 

hist関数の返り値であるhiは、 実はhistogramクラスのリストです。

typeof(hi);class(hi)
## [1] "list"
## [1] "histogram"

実際、右上のEnvironmentパネルで「hi」を開くと、 6つの名前属性($breaks,$counts,$density,$mids,$xname,$equidist)の存在が確認できます。

# 総和サンプルの一覧
#リストなので二重角括弧を使うこともできます。
hi$breaks; hi[[1]] 
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20
#個々のビンに含まれるサンプル数(例です)
hi$counts; hi[[2]]
##  [1]   0   1   9  27  34  72 106 124 103 130 123 106  68  42  35  15   5   0   0
##  [1]   0   1   9  27  34  72 106 124 103 130 123 106  68  42  35  15   5   0   0

[LIST.5] 論理値による条件付き抽出

簡単なベクトルを例に、論理値による条件に基づく抽出の方法を見ていきます。
まず1から20までの整数列をつくります。

n = 1:20; print(n)
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20

同じサイズの論理値のベクトルを使うと特定の要素だけ取り出すことができます。

# 奇数部分だけを取り出す例
n[c(T,F,T,F,T,F,T,F,T,F,T,F,T,F,T,F,T,F,T,F)]
##  [1]  1  3  5  7  9 11 13 15 17 19
n[c(T,F)] # 同様(リサイクル規則を使う)
##  [1]  1  3  5  7  9 11 13 15 17 19
# 3要素ごとに値を取り出す
n[c(T,F,F)] #リサイクル規則
## [1]  1  4  7 10 13 16 19

c(T,F,..)というベクトルは以下の方法でも作れます。

n = 1:20; n %% 2 == 1
##  [1]  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE
## [13]  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE

これを使えば、よりスマートに、特定の要素群を取り出すことが可能です。

n = 1:20; 
n[n %% 2 == 1]
##  [1]  1  3  5  7  9 11 13 15 17 19
n = 1:50; 
n[n %% 3 == 1]
##  [1]  1  4  7 10 13 16 19 22 25 28 31 34 37 40 43 46 49

1000から100個のサンプルをランダムに抽出し、
そのうち10で割り切れるもののみを取り出してみます。

# 1000から100サンプルを取得
n = sample(1000,100) 
print(head(n,50)) #最初の50だけを取り出す
##  [1] 570 261 187 693  20 118 763 487 301 725 910 306 239 821  64 319 669 715 723
## [20] 481 312 824 739 285 398  25  29 349 188 662 504 737 762 803 450  66 226 454
## [39] 292 598 954 732 184 881 290 348 434 353 277 699
# 10で割り切れるもののみを羅列
n[n %% 10 == 0]
##  [1] 570  20 910 450 290 660 110 990  60  70 560
#一応、こうなっています。
n %% 10 == 0
##   [1]  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
##  [13] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [25] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE
##  [37] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE
##  [49] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE FALSE FALSE FALSE
##  [61] FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE
##  [73] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
##  [85] FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE FALSE FALSE FALSE
##  [97] FALSE FALSE  TRUE FALSE

ここからリストに戻ります。
まずは、3回サイコロを振った総和のヒストグラムを作成します。

roll.n = function(n){
  dice = 1:6
  dice2 = sample(dice,n,T)
  sum(dice2)  
}

sample = replicate(1000,roll.n(3))

#breaksは区切りのライン
#2.5~3.5, 3.5~4.5, 4.5~5.5, ...17.5~18.5をサンプリング
hi = hist(sample,breaks=2.5:18.5) 

hi$XXXはベクトルのため、同じ探索方法が適用できます。

# hi$midsは、各ビンの中央のサンプル値(最小値は3となります)
hi$mids
##  [1]  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
# それぞれのビンの頻度
hi$counts
##  [1]   3  10  22  47  78 108 113 138 116 106 102  67  43  28  14   5
# 総和10のサンプル数は?
# (後述の論理値サーチを参照)
hi$counts[hi$mids==10]
## [1] 138
# 総和10以上の各サンプル数は?
hi$counts[hi$mids>=10]
## [1] 138 116 106 102  67  43  28  14   5
# 総和10以上のサンプル数の総和は、
sum(hi$counts[hi$mids>=10]) #欠損値があるとNAになります
## [1] 619
# 総和10以上の確率密度は?
sum(hi$counts[hi$mids>=10])/sum(hi$counts) 
## [1] 0.619

[DFM.1] データフレームの生成

いよいよデータフレームを扱います。 データフレームはリストの一種ですが、 よりデータ分析に特化したリストと考えることができます。

まずは、(LIST.3)で作った中部地方のリストの生成関数を、 listからdata.frameに変更します。

chubu = data.frame(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
             pop = c(716,211,186,379), #人口
             order = c(4,17,22,10), #人口順位
             capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
             shinkansen = c(T,T,F,T)) #新幹線通過の有無

chubuの中身を見ると、データが綺麗に整列されています。

print(chubu)
##       name pop order  capital shinkansen
## 1    AICHI 716     4   nagoya       TRUE
## 2     GIFU 211    17     gifu       TRUE
## 3      MIE 186    22      mie      FALSE
## 4 SHIZUOKA 379    10 shizuoka       TRUE

型はリストのまま、クラスがdata.frameとなることに注意してください。

typeof(chubu);class(chubu)
## [1] "list"
## [1] "data.frame"

DF[n]は、n番目の要素をデータフレーム形式で返します。

chubu[2]; chubu[4]
##   pop
## 1 716
## 2 211
## 3 186
## 4 379
##    capital
## 1   nagoya
## 2     gifu
## 3      mie
## 4 shizuoka

データフレームは、全ての要素がアトミックベクトルで、
かつ、それらの要素数が同一でないとエラーが返されます。
例えば、以下の場合、shinkansenの要素数のみが3で揃わないためエラーとなります。

chubu = data.frame(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
                   pop = c(716,211,186,379), #人口
                   order = c(4,17,22,10), #人口順位
                   capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
                   shinkansen = c(T,T,F)) #新幹線通過の有無
## Error in data.frame(name = c("AICHI", "GIFU", "MIE", "SHIZUOKA"), pop = c(716, : arguments imply differing number of rows: 4, 3

[DFM.2] 行列添字(ij)記法 (DF[i,j]

データフレームにおいて、個々の要素を直感的に指定する記法として行列添字(ij)記法があります。

chubu = data.frame(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
                   pop = c(716,211,186,379), #人口
                   order = c(4,17,22,10), #人口順位
                   capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
                   shinkansen = c(T,T,F,T)) #新幹線通過の有無
print(chubu)
##       name pop order  capital shinkansen
## 1    AICHI 716     4   nagoya       TRUE
## 2     GIFU 211    17     gifu       TRUE
## 3      MIE 186    22      mie      FALSE
## 4 SHIZUOKA 379    10 shizuoka       TRUE

DF[i,j]で、データフレームを行列と見立てた時の
i行目j列目の要素を取り出すことができます。

# 三重県の人口順位(3行3列目)
chubu[3,3] #対応するベクトル要素を返します。
## [1] 22
# 名前属性を使った$記法も、リスト要素の記法も使えます。
chubu$order[3]; chubu[[3]][3]
## [1] 22
## [1] 22

行列添字記法の拡張として、 列(j)の要素数が複数の場合、サブのデータフレームを返します。

chubu[3,c(1,3)] #三重の県名と人口順位(3行の1列目と3列目)
##   name order
## 3  MIE    22
chubu[1:3,c(1,5)] #静岡以外の名前と新幹線情報
##    name shinkansen
## 1 AICHI       TRUE
## 2  GIFU       TRUE
## 3   MIE      FALSE
chubu[-4,c(1,5)] #上と同じです。(4行目を除外)
##    name shinkansen
## 1 AICHI       TRUE
## 2  GIFU       TRUE
## 3   MIE      FALSE
chubu[3,] #三重県の全て
##   name pop order capital shinkansen
## 3  MIE 186    22     mie      FALSE

他方で、jの要素数が「1つ」の場合は、ベクトルが返ります。

chubu[,1] #全ての県
## [1] "AICHI"    "GIFU"     "MIE"      "SHIZUOKA"
chubu[,4] #全ての県の県庁所在地
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"

!! 1つの行(i=1)を選択すると,データフレームを返す一方で、< !! 1つの列(j=1)を選択すると,アトミックベクトルを返すことに注意

データフレームの中の特定の一行を抽出しても、 それらの型が同一とは限らないので、 そもそもアトミックベクトルにはなりえません。

chubu[1,] #愛知県(1行目)の全てのデータ
##    name pop order capital shinkansen
## 1 AICHI 716     4  nagoya       TRUE
chubu[2,] #岐阜県(2行目)のデータ
##   name pop order capital shinkansen
## 2 GIFU 211    17    gifu       TRUE

順番を入れ替えることもできます。

# 2行目、1行目、3行目、4行目の順に並べ替え
chubu[c(2,1,3,4),]
##       name pop order  capital shinkansen
## 2     GIFU 211    17     gifu       TRUE
## 1    AICHI 716     4   nagoya       TRUE
## 3      MIE 186    22      mie      FALSE
## 4 SHIZUOKA 379    10 shizuoka       TRUE
# 列指定で順番の入れ替え
# 列のサイズが1なので、やはりベクトルが返ります。
chubu[c(2,1,3,4),2] #人口を愛知・岐阜・三重・静岡の順で
## [1] 211 716 186 379
# 行も列も順番の入れ替え
# 列のサイズが2なので、データフレームで返します。
chubu[c(2,1,3,4),c(4,1)]
##    capital     name
## 2     gifu     GIFU
## 1   nagoya    AICHI
## 3      mie      MIE
## 4 shizuoka SHIZUOKA

[DFM.3] 名前による指定、$記法

再び、同じデータフレームを生成するところからはじめましょう。

chubu = data.frame(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
                   pop = c(716,211,186,379), #人口
                   order = c(4,17,22,10), #人口順位
                   capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
                   shinkansen = c(T,T,F,T)) #新幹線通過の有無
print(chubu)
##       name pop order  capital shinkansen
## 1    AICHI 716     4   nagoya       TRUE
## 2     GIFU 211    17     gifu       TRUE
## 3      MIE 186    22      mie      FALSE
## 4 SHIZUOKA 379    10 shizuoka       TRUE

列の名前属性を、行列添字記法のj(列)に直接に指定することができます。
jのサイズが複数の場合、データフレームとして返ります。

chubu[1,c("name","pop")] #愛知県の名前と人口
##    name pop
## 1 AICHI 716
chubu[1,c("pop","name")] #名前を入れ替えると順番も変わる
##   pop  name
## 1 716 AICHI
chubu[,c("name","pop")] #全ての県の名前と人口
##       name pop
## 1    AICHI 716
## 2     GIFU 211
## 3      MIE 186
## 4 SHIZUOKA 379
# jのサイズが1の場合、ベクトルが返ることに注意
chubu[,c("pop")] #全ての県の人口
## [1] 716 211 186 379

データフレームはリストの一種ですので、[LIST.3]で紹介した$記法を適用できます。

chubu$capital #全ての県の県庁所在地、ベクトルを返します。
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"
chubu$shinkansen #全ての県の新幹線状況(ベクトル)
## [1]  TRUE  TRUE FALSE  TRUE
chubu$pop #全ての県の人口(ベクトル)
## [1] 716 211 186 379

以下の3つの文は全て同じ値を返します。

chubu$capital; chubu[,c("capital")]; chubu[,4]
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"
## [1] "nagoya"   "gifu"     "mie"      "shizuoka"
要するに、$記法における$はデータフレームの列(j)に相当すると覚えてください。(iがメンバ、jが変数(属性)、またはiがレコード、jがフィールドみたいな覚えかたでもいい)

$記法の返り値はアトミックベクトルのため、 一般的な統計関数の引数にそのまま入ることができます。

sum(chubu$pop) #人口の総和
## [1] 1492
mean(chubu$pop) #人口の平均
## [1] 373

数学関数の引数は、アトミックベクトルであることが通常です。
以下のケースではエラーとなることに注意してください。

mean(chubu[2]) #エラー(chubu[2]はベクトルではなくリスト!!)
## Warning in mean.default(chubu[2]): argument is not numeric or logical:
## returning NA
## [1] NA
mean(list(c(716,211,186,379))) #同じくエラー
## Warning in mean.default(list(c(716, 211, 186, 379))): argument is not numeric
## or logical: returning NA
## [1] NA

二重角括弧でベクトルに変換すれば、一般的な関数の引数にとれます。

mean(chubu[[2]]) 
## [1] 373

[DFM.4] 論理値による条件付き抽出

再び、同じデータフレームを使って説明していきます。

chubu = data.frame(name = c("AICHI","GIFU","MIE","SHIZUOKA"), #県の名前
                   pop = c(716,211,186,379), #人口
                   order = c(4,17,22,10), #人口順位
                   capital = c("nagoya","gifu","mie","shizuoka"), #県庁所在地
                   shinkansen = c(T,T,F,T)) #新幹線通過の有無
print(chubu)
##       name pop order  capital shinkansen
## 1    AICHI 716     4   nagoya       TRUE
## 2     GIFU 211    17     gifu       TRUE
## 3      MIE 186    22      mie      FALSE
## 4 SHIZUOKA 379    10 shizuoka       TRUE

(LIST.5)で扱った論理値による条件付き抽出は、データフレームで大活躍します。

chubu[1,c(T,T,F,F,F)] #愛知県の名前と人口のみ
##    name pop
## 1 AICHI 716
chubu[c(T,F,F,F),c(1,2)] #上に同じです。
##    name pop
## 1 AICHI 716
# 人口順位のみをベクトルとして取り出す
chubu$order; #chubu[,3]と同じ
## [1]  4 17 22 10
# 人口順位10番以内の県をサーチ(真偽値ベクトルが返ります)
chubu$order<=10
## [1]  TRUE FALSE FALSE  TRUE
# 条件を満たす行をデータフレームとして取り出す
chubu[chubu$order<=10,]
##       name pop order  capital shinkansen
## 1    AICHI 716     4   nagoya       TRUE
## 4 SHIZUOKA 379    10 shizuoka       TRUE

同様に、特定の条件を満たす県をベクトルで取り出す例をみていきます。

# 以下は、全て人口トップ10の県を取り出す例
chubu[chubu$order<=10,1] 
chubu[[1]][chubu$order<=10]
chubu$name[chubu$order<=10]
## [1] "AICHI"    "SHIZUOKA"
## [1] "AICHI"    "SHIZUOKA"
## [1] "AICHI"    "SHIZUOKA"
# 新幹線の通らない県の人口順位
chubu$order[chubu$shinkansen==F]
## [1] 22
# 新幹線の通る県の人口順位
chubu$order[chubu$shinkansen==T]
## [1]  4 17 10

[DFM.5] CSVからデータフレームを読み取る


以下から、メジャーリーグの各年の成績データが入手できます。
- https://baseballsavant.mlb.com/leaderboard

以下は、上記から2021〜2022年の成績をCSVとしてDLしたものです。

CSVファイルは以下のようなフォーマットになっています。
一行目に名前属性、二行目以降に各データが並んでいます。

それぞれの名前属性は、以下のデータと対応します(野球知らない人ごめん)。

last_name(ラストネーム),first_name(ファーストネーム), player_id(id),year(成績年),player_age(年齢), ab(打席数),hit(安打数),single(単打),double(二塁打),triple(三塁打), home_run(本塁打),strikeout(三振),walk(四球), k_percent(三振率),bb_parcent(四球率), batting_avg(打率),on_base_percent(出塁率),on_base_plus_slg(OPS) wOBA(打撃貢献指標), X(?)

CSVファイルのインクルード

(1)ローカルファイルから

作業ディレクトリを指定します。(以下は僕の環境の例です。)

setwd("/Users/kenrikodaka/Dropbox/DocClass/_2023/B3_情報処理応用")

メニューからも指定できます(Session → ChooseDirectory)
setwdをしておくと、データを読み込むときに楽になります。

以下のようにカレントディレクトリからの相対パスでデータを指定できます。
read.csv(dat.csv)を用いると、csvデータをデータフレームとして読み込むことができます。

datb = read.csv("_dat/mlb.b.2021-22.csv") #打撃成績
datp = read.csv("_dat/mlb.p.2021-22.csv") #投手成績

(2)インターネットから直接

read.csv関数の引数で、直接URL(拡張子csv)を指定することで、インターネット上で公開されているデータフレームに直接にアクセスすることができます。

# バッターの成績:2021 - 2022
url_b = "http://lab.kenrikodaka.com/_download/class/2023_AppliedMedia/mlb.b.2021-22.csv"
# ピッチャーの成績:2021 - 2022
url_p = "http://lab.kenrikodaka.com/_download/class/2023_AppliedMedia/mlb.p.2021-22.csv"

datb = read.csv(url_b) #打撃成績
datp = read.csv(url_p) #投手成績

datbdatpの中身については、次章から確認していきます。


[DFM.6] 練習(MLBのデータから)


まず、データフレームの概要を見るための関数をいくつか紹介します。
まずは、最初(head関数)と最後(tail関数)の指定行を抽出する方法から。

# 打撃成績データの最初の6行をちょっと出し
head(datb)
##      last_name first_name player_id year player_age  ab  pa hit single double
## 1      Frazier       Adam    624428 2021         29 577 639 176    130     36
## 2       Altuve       Jose    514888 2021         31 601 678 167    103     32
## 3        Gallo       Joey    608336 2021         27 498 616  99     47     13
## 4         Sano     Miguel    593934 2021         28 470 532 105     51     24
## 5 Kiner-Falefa      Isiah    643396 2021         26 635 677 172    136     25
## 6        Edman      Tommy    669242 2021         26 641 691 168    113     41
##   triple home_run strikeout walk k_percent bb_percent batting_avg slg_percent
## 1      5        5        69   48      10.8        7.5       0.305       0.411
## 2      1       31        91   66      13.4        9.7       0.278       0.489
## 3      1       38       213  111      34.6       18.0       0.199       0.458
## 4      0       30       183   59      34.4       11.1       0.223       0.466
## 5      3        8        90   28      13.3        4.1       0.271       0.357
## 6      3       11        95   38      13.7        5.5       0.262       0.387
##   on_base_percent on_base_plus_slg  woba  X
## 1           0.368            0.779 0.341 NA
## 2           0.350            0.839 0.357 NA
## 3           0.351            0.809 0.348 NA
## 4           0.312            0.778 0.332 NA
## 5           0.312            0.669 0.293 NA
## 6           0.308            0.695 0.301 NA
head(datb,10) #行数を指定
##       last_name first_name player_id year player_age  ab  pa hit single double
## 1       Frazier       Adam    624428 2021         29 577 639 176    130     36
## 2        Altuve       Jose    514888 2021         31 601 678 167    103     32
## 3         Gallo       Joey    608336 2021         27 498 616  99     47     13
## 4          Sano     Miguel    593934 2021         28 470 532 105     51     24
## 5  Kiner-Falefa      Isiah    643396 2021         26 635 677 172    136     25
## 6         Edman      Tommy    669242 2021         26 641 691 168    113     41
## 7          Happ        Ian    664023 2021         26 465 535 105     59     20
## 8    Merrifield       Whit    593160 2021         32 664 720 184    129     42
## 9      Fletcher      David    664058 2021         27 626 665 164    132     27
## 10      O'Neill      Tyler    641933 2021         26 482 537 138     76     26
##    triple home_run strikeout walk k_percent bb_percent batting_avg slg_percent
## 1       5        5        69   48      10.8        7.5       0.305       0.411
## 2       1       31        91   66      13.4        9.7       0.278       0.489
## 3       1       38       213  111      34.6       18.0       0.199       0.458
## 4       0       30       183   59      34.4       11.1       0.223       0.466
## 5       3        8        90   28      13.3        4.1       0.271       0.357
## 6       3       11        95   38      13.7        5.5       0.262       0.387
## 7       1       25       156   62      29.2       11.6       0.226       0.434
## 8       3       10       103   40      14.3        5.6       0.277       0.395
## 9       3        2        60   31       9.0        4.7       0.262       0.324
## 10      2       34       168   38      31.3        7.1       0.286       0.560
##    on_base_percent on_base_plus_slg  woba  X
## 1            0.368            0.779 0.341 NA
## 2            0.350            0.839 0.357 NA
## 3            0.351            0.809 0.348 NA
## 4            0.312            0.778 0.332 NA
## 5            0.312            0.669 0.293 NA
## 6            0.308            0.695 0.301 NA
## 7            0.323            0.757 0.328 NA
## 8            0.317            0.712 0.306 NA
## 9            0.297            0.621 0.273 NA
## 10           0.352            0.912 0.384 NA
tail(datb) #末尾(6行)を確認
##     last_name first_name player_id year player_age  ab  pa hit single double
## 257    Seager      Corey    608369 2022         28 593 663 145     87     24
## 258     Abreu       Jose    547989 2022         35 601 679 183    128     40
## 259  Bichette         Bo    666182 2022         24 652 697 189    121     43
## 260    Lindor  Francisco    596019 2022         28 630 706 170    114     25
## 261    Arraez       Luis    650333 2022         25 547 603 173    133     31
## 262      Kwan     Steven    680757 2022         24 563 638 168    130     25
##     triple home_run strikeout walk k_percent bb_percent batting_avg slg_percent
## 257      1       33       103   58      15.5        8.7       0.245       0.455
## 258      0       15       110   62      16.2        9.1       0.304       0.446
## 259      1       24       155   41      22.2        5.9       0.290       0.469
## 260      5       26       133   59      18.8        8.4       0.270       0.449
## 261      1        8        43   50       7.1        8.3       0.316       0.420
## 262      7        6        60   62       9.4        9.7       0.298       0.400
##     on_base_percent on_base_plus_slg  woba  X
## 257           0.317            0.772 0.331 NA
## 258           0.378            0.824 0.361 NA
## 259           0.333            0.802 0.347 NA
## 260           0.339            0.788 0.342 NA
## 261           0.375            0.795 0.350 NA
## 262           0.373            0.773 0.341 NA
tail(datb,1) #最後の行のみを取り出す
##     last_name first_name player_id year player_age  ab  pa hit single double
## 262      Kwan     Steven    680757 2022         24 563 638 168    130     25
##     triple home_run strikeout walk k_percent bb_percent batting_avg slg_percent
## 262      7        6        60   62       9.4        9.7       0.298         0.4
##     on_base_percent on_base_plus_slg  woba  X
## 262           0.373            0.773 0.341 NA

全体の構造を見るにはstr関数を使います。

str(datb)
## 'data.frame':    262 obs. of  22 variables:
##  $ last_name       : chr  "Frazier" "Altuve" "Gallo" "Sano" ...
##  $ first_name      : chr  " Adam" " Jose" " Joey" " Miguel" ...
##  $ player_id       : int  624428 514888 608336 593934 643396 669242 664023 593160 664058 641933 ...
##  $ year            : int  2021 2021 2021 2021 2021 2021 2021 2021 2021 2021 ...
##  $ player_age      : int  29 31 27 28 26 26 26 32 27 26 ...
##  $ ab              : int  577 601 498 470 635 641 465 664 626 482 ...
##  $ pa              : int  639 678 616 532 677 691 535 720 665 537 ...
##  $ hit             : int  176 167 99 105 172 168 105 184 164 138 ...
##  $ single          : int  130 103 47 51 136 113 59 129 132 76 ...
##  $ double          : int  36 32 13 24 25 41 20 42 27 26 ...
##  $ triple          : int  5 1 1 0 3 3 1 3 3 2 ...
##  $ home_run        : int  5 31 38 30 8 11 25 10 2 34 ...
##  $ strikeout       : int  69 91 213 183 90 95 156 103 60 168 ...
##  $ walk            : int  48 66 111 59 28 38 62 40 31 38 ...
##  $ k_percent       : num  10.8 13.4 34.6 34.4 13.3 13.7 29.2 14.3 9 31.3 ...
##  $ bb_percent      : num  7.5 9.7 18 11.1 4.1 5.5 11.6 5.6 4.7 7.1 ...
##  $ batting_avg     : num  0.305 0.278 0.199 0.223 0.271 0.262 0.226 0.277 0.262 0.286 ...
##  $ slg_percent     : num  0.411 0.489 0.458 0.466 0.357 0.387 0.434 0.395 0.324 0.56 ...
##  $ on_base_percent : num  0.368 0.35 0.351 0.312 0.312 0.308 0.323 0.317 0.297 0.352 ...
##  $ on_base_plus_slg: num  0.779 0.839 0.809 0.778 0.669 0.695 0.757 0.712 0.621 0.912 ...
##  $ woba            : num  0.341 0.357 0.348 0.332 0.293 0.301 0.328 0.306 0.273 0.384 ...
##  $ X               : logi  NA NA NA NA NA NA ...
str(datp)
## 'data.frame':    204 obs. of  24 variables:
##  $ last_name       : chr  "Wainwright" "Greinke" "Hill" "Morton" ...
##  $ first_name      : chr  " Adam" " Zack" " Rich" " Charlie" ...
##  $ player_id       : int  425794 425844 448179 450203 452657 453286 457918 458681 489119 501985 ...
##  $ year            : int  2021 2021 2021 2021 2021 2021 2021 2021 2021 2021 ...
##  $ player_age      : int  39 37 41 37 37 36 38 34 34 33 ...
##  $ p_game          : int  32 30 32 33 28 30 30 28 28 28 ...
##  $ p_formatted_ip  : num  206 171 158 185 141 ...
##  $ pa              : int  828 697 661 756 627 693 671 641 690 669 ...
##  $ ab              : int  765 652 582 669 558 644 615 588 627 619 ...
##  $ hit             : int  168 164 137 136 159 119 177 123 166 156 ...
##  $ single          : int  111 105 83 100 101 73 106 84 112 91 ...
##  $ double          : int  34 29 32 17 33 21 35 20 35 35 ...
##  $ triple          : int  2 0 1 3 0 2 6 1 2 4 ...
##  $ home_run        : int  21 30 21 16 25 23 30 18 17 26 ...
##  $ strikeout       : int  174 120 150 216 91 236 122 176 125 149 ...
##  $ walk            : int  50 36 55 58 55 36 48 45 50 41 ...
##  $ k_percent       : num  21 17.2 22.7 28.6 14.5 34.1 18.2 27.5 18.1 22.3 ...
##  $ bb_percent      : num  6 5.2 8.3 7.7 8.8 5.2 7.2 7 7.2 6.1 ...
##  $ batting_avg     : num  0.22 0.252 0.235 0.203 0.285 0.185 0.288 0.209 0.265 0.252 ...
##  $ slg_percent     : num  0.352 0.434 0.402 0.309 0.478 0.331 0.511 0.338 0.408 0.447 ...
##  $ on_base_percent : num  0.275 0.291 0.316 0.281 0.349 0.239 0.341 0.267 0.321 0.303 ...
##  $ on_base_plus_slg: num  0.627 0.725 0.718 0.59 0.827 0.57 0.852 0.605 0.729 0.75 ...
##  $ woba            : num  0.273 0.309 0.313 0.264 0.352 0.248 0.361 0.264 0.317 0.321 ...
##  $ X               : logi  NA NA NA NA NA NA ...

データフレームのサイズを確認するには

# dim:行数と列数, nrow:行数, ncol:列数
dim(datb); nrow(datb); ncol(datb)
## [1] 262  22
## [1] 262
## [1] 22
dim(datp); nrow(datp); ncol(datp)
## [1] 204  24
## [1] 204
## [1] 24

それでは、いよいよ特定の条件を満たす選手を見つけていきましょう。

# 打率が3割を超えている選手を列挙
datb$last_name[datb$batting_avg>0.3]
##  [1] "Frazier"      "Harper"       "Marte"        "Anderson"     "Soto"        
##  [6] "Castellanos"  "Brantley Jr." "Riley"        "Reynolds"     "Gurriel"     
## [11] "Turner"       "Guerrero Jr." "Freeman"      "Alvarez"      "Benintendi"  
## [16] "Judge"        "Goldschmidt"  "Bogaerts"     "Lowe"         "McNeil"      
## [21] "Abreu"        "Arraez"
# ホームランを40本以上売っている選手の名前を列挙
datb$last_name[datb$home_run>=40]
## [1] "Tatis Jr."    "Ohtani"       "Perez"        "Guerrero Jr." "Semien"      
## [6] "Schwarber"    "Judge"        "Alonso"
# ホームランを40本以上売っている選手の成績を抽出(1〜5列のみ)
# (データフレームとして出力されます)
datb[datb$home_run>=40,1:5]
##        last_name first_name player_id year player_age
## 15     Tatis Jr.   Fernando    665487 2021         22
## 27        Ohtani     Shohei    660271 2021         26
## 110        Perez   Salvador    521692 2021         31
## 125 Guerrero Jr.   Vladimir    665489 2021         22
## 131       Semien     Marcus    543760 2021         30
## 170    Schwarber       Kyle    656941 2022         29
## 189        Judge      Aaron    592450 2022         30
## 252       Alonso       Pete    624413 2022         27
# ホームランを40本以上の記録を列挙
# (アトミックベクトルでの出力)
datb$home_run[datb$home_run>=40]
## [1] 42 46 48 48 45 46 62 40
# ホームランを40本以上の選手の名前・成績年・本塁打数を
# データフレームで抽出
datb[datb$home_run>=40,c("last_name","year","home_run")]
##        last_name year home_run
## 15     Tatis Jr. 2021       42
## 27        Ohtani 2021       46
## 110        Perez 2021       48
## 125 Guerrero Jr. 2021       48
## 131       Semien 2021       45
## 170    Schwarber 2022       46
## 189        Judge 2022       62
## 252       Alonso 2022       40

ブール演算子

  • A & B(AかつBが真のとき真)
  • A | B(AまたはBが真のとき真)
  • xor(A,B)(AとBのうち1つだけが真のとき真)
  • !A(Aが偽のとき偽)
  • any(A,B,C,...)(いずれかが真のとき真)
  • all(A,B,C,...)(いずれも真のとき真)
# 再び、打率が3割を超えている選手を列挙
datb$last_name[datb$batting_avg>0.3]
##  [1] "Frazier"      "Harper"       "Marte"        "Anderson"     "Soto"        
##  [6] "Castellanos"  "Brantley Jr." "Riley"        "Reynolds"     "Gurriel"     
## [11] "Turner"       "Guerrero Jr." "Freeman"      "Alvarez"      "Benintendi"  
## [16] "Judge"        "Goldschmidt"  "Bogaerts"     "Lowe"         "McNeil"      
## [21] "Abreu"        "Arraez"
# 2021年に限定(&は論理和)
bat2021 = datb$last_name[datb$year==2021 & datb$batting_avg>0.3]
print(bat2021)
##  [1] "Frazier"      "Harper"       "Marte"        "Anderson"     "Soto"        
##  [6] "Castellanos"  "Brantley Jr." "Riley"        "Reynolds"     "Gurriel"     
## [11] "Turner"       "Guerrero Jr."
# 2022年に限定
bat2022 = datb$last_name[datb$year==2022 & datb$batting_avg>0.3]
print(bat2022)
##  [1] "Freeman"     "Alvarez"     "Benintendi"  "Judge"       "Goldschmidt"
##  [6] "Bogaerts"    "Lowe"        "McNeil"      "Abreu"       "Arraez"

以下では、2年連続3割を超えた打者を探します。
そのための準備として、intersectの使い方を覚えます。

# intersectは2つのベクトルの中の共通の要素を取り出します。
intersect(1:50,30:80)
##  [1] 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
# 2の倍数と3の倍数の共通要素(0〜50)は6の倍数
intersect(seq(0,50,by=2),seq(0,50,by=3))
## [1]  0  6 12 18 24 30 36 42 48

# 2年連続の3割打者はいない
intersect(bat2021,bat2022)
## character(0)
# 2年連続、2割8部超えは8人(ジャッジ、ボガーツ、ゴールドシュミット他)
tmp2021 = datb$last_name[datb$year==2021 & datb$batting_avg>0.28]
tmp2022 = datb$last_name[datb$year==2022 & datb$batting_avg>0.28]
intersect(tmp2021,tmp2022)
## [1] "Marte"       "Judge"       "Bogaerts"    "Rosario"     "Goldschmidt"
## [6] "Turner"      "Freeman"     "Bichette"
# 同様に、2年連続40本以上の選手は?
tmp2021 = datb$last_name[datb$year==2021 & datb$home_run>=40]
tmp2022 = datb$last_name[datb$year==2022 & datb$home_run>=40]
intersect(tmp2021,tmp2022) #いない
## character(0)
# 2年連続34本以上の選手は?
tmp2021 = datb$last_name[datb$year==2021 & datb$home_run>=34]
tmp2022 = datb$last_name[datb$year==2022 & datb$home_run>=34]
intersect(tmp2021,tmp2022) #(4人いた)
## [1] "Ohtani" "Judge"  "Alonso" "Olson"
# 2年連続n本以上の選手を取り出す処理を関数化します。(BASICの復習)
getHomerunBatter = function(n){
  tmp2021 = datb$last_name[datb$year==2021 & datb$home_run>=n]
  tmp2022 = datb$last_name[datb$year==2022 & datb$home_run>=n]
  intersect(tmp2021,tmp2022)
}

この関数を使うと、、

getHomerunBatter(40)
## character(0)
getHomerunBatter(39)
## [1] "Judge"
getHomerunBatter(38)
## [1] "Judge"
getHomerunBatter(37)
## [1] "Judge"  "Alonso"

二刀流選手がいるかどうか調べましょう。。

# 打撃成績にも投手成績にもどちらにも顔を出している選手のIDは?
# (規定打席と規定投球回数のいずれも満たしている選手です)
intersect(datb$player_id,datp$player_id)
## [1] 660271
#誰だ?
#名前、成績年、打席数
datb[datb$player_id==660271,c("last_name","year","ab")]
##     last_name year  ab
## 27     Ohtani 2021 537
## 216    Ohtani 2022 586
#名前、成績年、投球回数
datp[datp$player_id==660271,c("last_name","year","p_formatted_ip")]
##     last_name year p_formatted_ip
## 84     Ohtani 2021          130.1
## 179    Ohtani 2022          166.0