今回はバスケットボールの話ではなく技術系のネタです。
はじめに
実際のBリーグの選手データ(名前、所属チーム、身長、体重など)を使ってRの使い方やデータ分析の考え方の初歩に触れてみたいと思います。ちなみに以下の記事はその選手データを用いて書いたものです(データを取得したタイミングは違いますが。)
ひとつ注意事項です。この選手データは2018/10/13時点で私がBリーグの公式ページから取得してきたものですが、Bリーグさんはもちろん私も内容の正誤については責任を持てませんので、あくまで練習用のデータだとご理解頂ければと思います。
この記事で必要なもの
この記事ではRを使います。またRのプログラムを書き実行するのにはR Studioというソフトウェアが便利なので、それも使用します。
この記事でやること
この記事で以下のことをしたいと思います。
- Rに選手データを読み込む
- 選手データを確認のために閲覧する
- dplyrというパッケージを使ってデータの集計を行う(チームの身長について集計する)
- ggplot2というパッケージを使ってデータを視覚化する
ではやってみましょう。まずはR Studioを起動して下さい。R Studioの細かい操作についてはここでは説明しませんが、下記画像の赤枠のテキストエリアにコマンドを書き、テキストエリアの該当行にカーソルがある状態でCtrl + ENTERと押すと青枠のコンソールエリアでそのコマンドが実行されます。それが唯一の使い方ではありませんが、私はそれを繰り返すことで分析作業をすることが多いです。
データを読み込む
データはgithub上に上げてあります。以下のようにするとそのデータをR上に読み込むことが可能なはずです。
url <- "https://github.com/rintaromasuda/bleaguebydata/raw/master/blog/2018101301/bleaguers_20181013.csv" df <- read.csv(url)
read.csvというのはその名の通りCSVファイルを読み込むためのものです。読み込んだ後にdata.frameというすごく便利な型にデータを変換してくれます。data.frame型というのは、イメージとしてはExcelのテーブルみたいなもので、行(Row)があり、列(Column)があり、列には名前が付いていたりします。
データを見てみる
データを読み込んだら、ざっとそのデータを見てみることが多いです。*1
dim(df)
dimを使うとそのdata.frameの行数と列数が表示できます。
str(df)
strを使うとそのdata.frameの各列の名前と、その列自体の型、そしてそこにどんな値が存在しているのかを一部表示できます。今回のデータを上記のコマンドで読み込んだ場合、列はint型(整数)かFactor型のいずれかになっていると思います。
Factor型というのは「選択肢が限定された文字列」のようなものです。例えばチーム名であればB1、B2合わせて36個の選択肢しかないのでFactor型に適しています。その意味では選手の名前はFactor型に適したものではないのですが、ここではそのまま進みます。
summary(df)
strと似ていますが、こちらは各列に格納されている数値型の値の統計値を表示してくれます。例えば選手の身長が入っているHeightという列がありますが、最小値、最大値、中央値、平均などの値が表示されるはずです。
View(df)
これはデータをそれこそExcelのように別のタブで表示してくれます。実際に目で見て確認したいときに便利です。
head(df)
データ量が多い場合はheadで最初の数行を表示するのも便利です。ちなみに正確に言うのであれば、headはdata.frame型のデータの最初の数行を別のdata.frame型として返してくれるものです。
tail(df)
headと同じですが、こちらは最後の数行を返してくれます。
どうでしょうか。ざっと見た感じでB1とB2のチームに属する選手データが入っていたのが分かったのではないかと思います。
集計をしてみる(チームごとに人数を数える)
まずは各チームの選手の人数を見てみましょう。ここはいくつか新しい概念が同時に出てきますが、頑張ってひと通り説明した糸思います。
まずはdplyrというパッケージを読み込みます。パッケージというのはRの拡張機能のことで実に様々なものがあるのですが、このdplyrや後で出てくるggplot2というパッケージはもはやRの標準機能であるかのごとくよく使われていると思います。
if (!require(dplyr)) { install.packages("dplyr") library(dplyr) }
これは日本語で言うと「dplyrパッケージが既にインストールされていたら読み込んでね。なかったらインストールした後に読み込んでね。」ということをやっています。Rのバージョンなどによっては警告など出るかもしれませんが、エラーでなければ問題なく使えると思います。
では各チームの人数を見てみましょう。まずは説明の前に以下のコマンドを実行してみて下さい。
df %>% group_by(Team) %>% summarise(NumOfPlayers = n()) %>% as.data.frame()
そうするとこんな感じのアウトプットがあったはずです。なんとなく各チームの選手の数が数えられていそうですね。
Team NumOfPlayers 1 A東京 12 2 FE名古屋 13 3 SR渋谷 13 4 愛媛 11 5 茨城 12 6 横浜 14 ...
順を追って説明します。
まず%>%
という記号が出てきましたが、これはdplyrをロードすると使えるようになるパイプと呼ばれる機能です。意味は簡単で、「この記号の左のものを右のものに入力してね」という意味になります。改行して書いていますけれど、左から右にデータが流れていく感じで理解してもらえればと思います。例えば先ほどhead(df)
というコマンドを書きましたが、パイプを使ってdf %>% head()
と書けます。さらにdf %>% head() %>% summary()
と書くと最初の数行だけに対してsummary()が実行されます。
お察しの通り別にパイプを使わなくても書けるのですが、dplyrを使ってやりがちなことはこのパイプを使うときれいに整理できるため、使うことが多いです。
続いてgroup_by
です。これはSQLの知識がある人にはお馴染みかと思いますが、要するに集計する際の束ね方を指定しています。今は「各チームの」人数を集計しようとしているのでTeam列を指定していますが、例えばポジションごとに集計したいならPosition列を指定しますし、B1、B2ごとにしたければLeague列を指定します。
続いてsummarise()
です。ここでは具体的な集計方法と、集計結果を格納する列の名前を指定しています。n()
というのは「数を数えてね」という意味の、集計の中ではもっとも単純な関数です。ここで例えばチームごとの平均身長を求めたい場合はMeanHeight = mean(Height)
などのようにHeight列に対して``mean()```を実行したりします。他にも最大値、最小値を求める、中央値を求める等々、要件によって色々な集計関数を使用します。
as.data.frame()
というのは、summarise()
の時点では結果がdata.frame型ではないため、冒頭に述べた通りdata.frame型が便利なのでそれに変換している処理です。
集計をしてみる(チームごとの最低身長、最高身長、平均身長、身長の中央値を求める)
では以上を応用して、チームごとに身長の最低、最高、平均(mean)、中央値(median)をそれぞれ求めてみましょう。あとおまけとして、今回はB2のチームは除いてみましょう。その為にはこのように書きます。
df %>% filter(League == "B1") %>% group_by(Team) %>% summarise(MinHeight = min(Height), MaxHeight = max(Height), MeanHeight = mean(Height), MedianHeight = median(Height)) %>% as.data.frame()
こんなアウトプットになったと思います。
Team MinHeight MaxHeight MeanHeight MedianHeight 1 A東京 171 211 193.0000 192.5 2 SR渋谷 174 213 192.6154 193.0 3 横浜 173 208 192.3571 190.5 4 京都 173 208 188.4545 188.0 5 三遠 169 206 189.2308 187.0 ...
まずfilter(Leauge == "B1"
というのが出てきましたが、これは想像の通り「この条件に当てはまるデータだけしかいりません」という意味です。当てはまらないデータは以降の処理からは除外されます。
またご覧の通りsummarise()
にはこうしていくつもの集計関数を一気に指定することが可能です。
データを可視化する(身長のヒストグラムを作ってみる)
さて最後に身長のヒストグラムを作ってみましょう。でもその前にデータを扱う人のちょっとした小言です。
世の中「平均」が多く使われます。平均身長や平均年収など、データが「集計」されるときは大抵の場合に平均を使ってデータが集計され、説明されることがほとんどです。平均は世の中の人のほとんどが理解しているという意味では大変便利なものですが、集計されるもとになったデータを正しく、というか誤解なく表しているかというとそうでない場合も多いです。
有名な例、でかつバスケットの例なんでちょうどいいのですが、アメリカのノースカロライナ大学で卒業生の平均年収を学部ごとに集計していたところ、ある年に地理学の平均年収がすさまじく上がったことがあったらしいのです。でもそれは地理学の学生の年収がすべからく上昇したわけではなく、実はマイケル・ジョーダンが地理学専攻だった、というオチだったと。中央値を集計していればこのようなことはないですが、平均だととんでもない人に引きずられてしまうことになります。
収入を語るときに平均は上記の理由で適切ではない場合がほとんどですが、世の中ではまだまだ年収と言えば平均で集計されており、ジョーダンのときの問題はいまだに残っていることになります。
はい、小言は終わりです。何が言いたかったかというと、集計するのであれば適切なものを選ぶべきであるし、データ担当者はなるべく集計する前のデータそのままを扱ってそのデータを理解するように努めるべきだということです。そしてその為の有効な手段のひとつがグラフを作ることです。
私はこのブログで箱ひげ図を多用しているのでそのうち箱ひげ図についても説明を書きたいと思っているのですが、今回はヒストグラムを作りたいと思います。
ggplot2
Rにはデフォルトで可視化の為の関数が色々と用意されていますが、上述の通りこのggplot2というパッケージが広く使われていて、もはや標準機能くらいの存在感があると思います。今回はこれを使いたいと思います。
ggplot2もdplyrと一緒でちょっと書き方が特殊に感じる部分はあるのですが、慣れてくるとすごくよくデザインされていると感じるようになります。ではまずは単純にヒストグラムを作成します。さっきまで%>%
でしたが、ここでは+
を使っていることに注意してください。
if (!require(ggplot2)) { install.packages("ggplot2") library(ggplot2) } ggplot() + geom_histogram(data = df, binwidth = 2, aes(x = Height))
パッケージのロードについては先ほどと一緒です。
ggplot()
というのはggplotを使う前のおまじないみたいなものです。ggplotはグラフをいくつも重ねて描いたりできるのですが、このggplot()
に+
を使って表示したいグラフやその他の装飾処理をどんどん追加していくイメージで使います。
今回の場合はヒストグラムをひとつだけ追加したいので、geom_histogram()
というヒストグラム描画用の関数を使い、dfデータを使うこと、棒の一本の幅が2(つまり身長2cm分)を表すこと、x軸に使う列は何であるかなどを指定しています。これを実行すると以下のようなヒストグラムが描かれたはずです。
先ほど平均が正しく元のデータを表さないときがあると言いましたが、このデータの平均身長はmean(df$Height)
と書くと求められますが190cmくらいなんですね。しかしヒストグラムを見ると、190台後半の選手は少ないけれど、206-8cmくらいの選手はかなりたくさんいるなということが分かります。これはご存知の通り外国人ビッグマンによるものですが、例えばこういう感覚は平均身長だけ見ていれば分からなかったものです。
ではこの記事の最後にB1とB2の身長ヒストグラムを別々に作り、一緒にプロットしてみたいと思います。
ggplot() + geom_histogram(data = subset(df, League == "B1"), binwidth = 2, aes(x = Height, fill = League, alpha = 0.5)) + geom_histogram(data = subset(df, League == "B2"), binwidth = 2, aes(x = Height, fill = League, alpha = 0.5))
するとこんなヒストグラムができるはずです。
なんとなくいつもこのブログに載っているようなものに見た目が近くなってきました。このヒストグラムは色分けや透明化など見やすくするために少しだけ努力をしていますが、ここでは細かく説明することはやめておきたいと思います。実際この部分はケースバイケースで対応しなければならないことも多く、本質的ではなかったりするのですが、人に誤解なく伝わるように結構な時間がかかる部分だったりします。毎回少し違う要件があったりするので、いつもウェブを検索しては対応していたりします。
さて、今回はこの辺にしておきたいと思います。コードもすべてGitHubに上がっていますので、必要があれば参照してみてください。
*1:ちなみに、データの分析を担当することになった場合、このようにいきなり分析を対象するデータが用意されていてそれを分析する、ということはあまりないと思います。このデータそのものを用意することから仕事である場合がほとんどで、かつそこが一番の労力がかかる部分だったりします。この選手データも公式サイトから取得した後に、分析しやすいように少し加工などしています。