トップ 差分 一覧 ソース 検索 ヘルプ RSS ログイン

Rと競馬データで学ぶ統計学 第2回 Rの基礎と競馬データの読み込み

キーワード

この記事はRと競馬データで学ぶ統計学シリーズの一部です。


はじめに

Rと競馬データで学ぶ統計学シリーズ第2回は、第1回で紹介した競馬データを、オープンソースの統計解析ソフト「R」に読み込み、さまざまに操作してみます。

この記事では、はじめにRの概要と基本的な操作を紹介します。次に、実際に競馬データを読み込んで、基本統計量などの指標を算出します。

統計解析ソフトRとは

このコンテンツでは、競馬データを分析するために、オープンソースの統計解析ソフトウェア「R」を使います。Rは1990年代はじめから開発が始まり、現在でも世界中の開発者、研究者が参加して活発に開発が続けられています。


また、RはR「言語」とも呼ばれ、基本的にはコマンド関数という文字列を画面に入力すると、処理結果が返ってくるという操作形態のソフトウェアです。なお、ここからは、筆者の好みで「関数」という表記を使います。以下に、RでJRAの競馬データを読み込み、2015年度の騎手リーディングを集計する処理を行っている様子を示します。

RでJRAの競馬データを読み込み、2015年の騎手リーディングを集計した様子


使っている関数などは、このあと紹介していきますが、Rというのはこんな感じで使うのか、というイメージを持っていただければと思います。また、Rは分析結果を数字だけでなく、多種多様なグラフで表現できます。以下に、(1)芝レースとダートレースの勝ち馬の馬体重の分布と、(2)主要4場(東京、中山、京都、阪神)における芝2000mレースの勝ちタイムと上り3Fの関係をそれぞれヒストグラムと散布図であらわしたものを示します。

(1)2013年〜2016年の芝コースとダートコースの勝ち馬の馬体重の分布

(2)2013年〜2016年の主要4場の芝2000mにおける勝ち馬のタイムと上り3Fの関係


ヒストグラムや散布図の作り方、使い方についても今後説明をしていきますので、ここでは「Rはいろんなことができる」ということを理解していただければよいでしょう。Rは、高価な商用ソフトにひけを取らない、高度な統計解析ができるソフトウェアです。

補足:Rファミリー

Rの周辺には、プログラミング作業を支援する機能を持ったIDE(統合開発環境)であるRStudioや、主要な分析関数がGUIから呼び出せるRcommandar、分析フローを部品のドラッグアンドドロップで構築し、実行できるR Analytic Flowなど、さまざまな関連ソフトウェアがあります。活用すると便利ですが、今回は触れません。

 Microsoft R Openについて

Microsoft R Open(MRO)は、Microsoft社(が買収したRevolution Analytics社)が独自にカスタマイズしたRです。外見はオリジナルのRと同一ですが、複数CPUで並列計算するためのIntel Math Kernel Library(IMKL)が組み込まれており、一部の関数で自動的に並列処理が行われます。

Microsoft R Open公式サイト https://mran.microsoft.com/open/


実は、オリジナルのRは、ユーザーが明示的に並列処理の設定をしない限り、シングルスレッド(CPUコア1つを使用)で処理を行います。そのため、マルチコアのCPUを搭載したハイスペックなPCであっても、その性能を活かしきれない部分があります。Microsoft R Openでは、IMKLにより自動で並列処理が行われるため、基本的にオリジナルのRよりも処理性能は高くなります。

https://mran.microsoft.com/assets/img/RelativePerformanceGraphic.a13797f3.png
Microsoft R Openのベンチマーク結果

また、もう1つの特徴として、Revolution Analytics社がある時点で凍結(フリーズ)したパッケージレポジトリを使用するという点があります。パッケージについて詳細は後述しますが、Rの機能を拡張する仕組みです。パッケージは、上記のCRAN(Comprehensive R Archive Network)というサイトに集積されます。しかし、随時パッケージ開発者による更新がされることで、例えば共同作業を行う際に、自分のPCのRにインストールしたパッケージのバージョンと、相手のPCにインストールされたパッケージのバージョンが異なるといったことがあり、場合によってはプログラムが正常に動作しないことがあります。そのようなことを防ぐため、Microsoft R OpenではRevolution Analytics社がある時点で凍結したレポジトリを提供します。これにより、複数の開発者が全員Microsoft R Openを導入すれば、その間でパッケージのバージョン不整合などは起こらなくなります。

以上のように、外見、使い勝手はオリジナルのRと変わらず、並列処理やレポジトリの保守などのメリットがあるため、このコンテンツではMicrosoft R Openを導入し、使用していきます。

 Microsoft R Openのダウンロード・インストール

Microsoft R Openは、公式サイトからダウンロードできます。2016年12月現在の最新バージョンは、3.3.2です。なお、Microsoft R Openは64bit版のみ提供されています。32bit OSを使用している場合は、CRANからオリジナルのRをダウンロードしてください。Downloadページに、各OSごとのインストーラとソースがあります。少し前までは、Microsoft R OpenとIMKLが別のファイルとして配布されていたため、2つのインストーラをダウンロードする必要がありましたが、現在はIMKLがMicrosoft R Openに組み込まれているため、OSごとに用意されたリンクをクリックするだけで必要なファイルが揃います。

Microsoft R Openのダウンロードページ


インストーラをダウンロードしたら、基本的には「OK」や「Next」を押していけばよいです。32bit OSの場合のオリジナル版も同様です。ただし、途中「コンポーネントの選択」メニューでは必ず「Message translations」にチェックするようにしてください。ここにチェックが入っていないと、メニュー項目やメッセージがすべて英語になってしまいます。[1]

「Message translations」にチェックを入れる(32bit OSのみ)


インストールができたら、Rを起動してみましょう。Microsoft R Openはデスクトップにショートカットを作らないので、スタートメニューから[Microsoft R Open]を探し、Rのアイコンをクリックして起動します。ちなみに、Windows 10ではこのアイコンをデスクトップにドラッグアンドドロップすれば、ショートカットが作成できます。

Microsoft R Openのメニュー


以下の画面がRの基本的なインターフェースです。上部にいくつかメニューがありますが、ほとんど使うことはありません。多くの操作は、赤い不等号(>)が表示されたプロンプトに関数を入力することで行います。

Microsoft R Openのインターフェース


 参考:Rに関する情報源

このあと、Rの操作を学習していきますが、Rについて詳しく知りたい方にいくつかおすすめのWebサイト、書籍を紹介します。

Webサイト

Rに関するWebサイトや解説記事がたくさんあります。中でも、筆者がよく参考にする、広く情報がまとまったサイトをいくつか紹介します。

サイト名(リンク)コメント
R-Tips舟尾暢男氏によるRの基本的な操作を網羅的にまとめたサイト(公開は農林水産研究情報総合センター竹澤邦夫氏)
Rによる統計処理青木繁伸氏[2]による、目的別にさまざまなプログラムが掲載されているサイト
統計・データ解析三重大学・奥村晴彦氏によるサイト。さまざまな話題とRプログラムが掲載されている
R、R言語、R環境・・・・・・同志社大学・金明哲氏によるサイト。幅広い分野の解説記事があり、基礎統計から機械学習までカバーしている
RjpWiki東京大学・岡田昌史氏が管理する、Rに関する情報を集積したコミュニティサイト。膨大な情報量を誇るが、記事によっては古い情報もあり、更新日時を確認することが必要
Quick-R英語だが、「やりたいこと」別に記事がまとめられており、わかりやすい
seekRRに関するトピックに特化して検索できる検索エンジン

書籍

Rや統計学に関する書籍もたくさん出ていますが、「入門」と銘打つものの、想定するレベルが非常に高いような本もたくさんあります。[3]基本的には、書店などで実物を手に取り、ページをめくってみての“相性”で選ぶとよいと思いますが、ここでは本コンテンツの内容にとどまらず、データ分析一般について、筆者が相性がよかった本を紹介します。いちおう、1冊(大阪ガスの研修教材)を除き、すべて所有しています。その上でのおすすめです。[4]ただ、取り上げている書籍は若干古いものもありますので、やはりご自身で相性の良い参考書を選ぶとよいでしょう。

  • 「データ分析をすること」についての全般的な理解のための書籍

  • 統計学についての入門的な書籍

  • データマイニング、機械学習についての解説書

  • R言語についての解説書

Rの基本的な操作

ここからは、競馬データの分析を行うためにも知っておかなければいけない、Rの基本的な操作を紹介します。なお、ここからは以下に示すような書式でRのプログラムを記述していきます。

操作 # コメント
(例)
nums <- 1:10 # nums変数に1から10の数値を代入しベクトルを作る

# 以降は説明のためのコメントですので、入力は不要ですが、Rで複数行にわたるプログラミングを行う際には、処理の内容を簡潔に説明するコメントを含める習慣をつけるとよいでしょう。

それでは、競馬データを読み込む前に、まずはR単体でできる操作をもとに、R言語の特性や仕様を確認していきましょう。

 データと関数

Rでは、ファイルやプロンプトからデータを入力し、変数に格納します。そして、変数に対して関数を適用することで、所望の結果を得ます。変数の作り方やさまざまな関数についてはこの後紹介しますが、まずは言語仕様を知るという意味で、以下のプログラムを1つずつ、左側のウィンドウに入力してください。ここでは、# 以降に a <- 1:100 などと書いている部分がプログラムです。[5]

入力したら、左下の "Run" ボタンを押すと、入力がチェックされて、結果が右側に表示されます。わからない場合は、左下の "Solution" ボタンを押すと、解答が表示されます。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

# input 1 to 10 continuous number. # 1:10 # make object "a", and substitute 1 to 100 number. # a <- 1:100 # print object "a" # a # calculate the summation of the numbers in object "a". # sum(a) # calculate the maximum number of the numbers in object "a". # max(a) # calculate the average (mean) of the numbers in object "a". # mean(a) # input 1 to 10 continuous number. # 1:10 1:10 # make object "a", and substitute 1 to 100 number. # a <- 1:100 a <- 1:100 # print object "a" # a a # calculate the summation of the numbers in object "a". # sum(a) sum(a) # calculate the maximum number of the numbers in object "a". # max(a) max(a) # calculate the average (mean) of the numbers in object "a". # mean(a) mean(a) test_object("a") test_function("sum",args="...") test_function("max",args="...") test_function("mean",args="x") success_msg("Great job!")

上記の“はじめてのRプログラム”をベースに、R言語のごく基本的な仕様を以下にまとめます。

  • 入力した文字列がそのまま1行ずつ処理されるインタープリタ型言語である[6]
  • 変数(オブジェクト)を作成し、データを代入して利用できる
    • 代入の方法は、一般に矢印を模した <- の他に = や、逆向きの矢印 -> も使える。逆向きの矢印の場合は、変数名と代入する値の順序が逆になる[7]
  • 作成したオブジェクトに対し、関数を適用することで処理結果を得られる
    • Rでは 関数名(オブジェクト名) という書式で適用する
    • ここでは使用していないが、関数にオプションを付与できる。関数名(オブジェクト,オプション=値) という書式になる

他にもたくさんの特徴、仕様がありますが、まずは「値の代入の仕方(<-)」と「関数の適用方法(関数名(オブジェクト名))」を覚えておきましょう。また、PCにインストールしたRの操作法として、以下のようなことを知っておくと便利です。

  • 以前に入力、実行したプログラムは、矢印キーの上下で辿って再実行できる
  • 入力ミスなどで誤ったプログラムが実行され、長く時間がかかる場合はESCキーで中断できる
  • 入力ミスなどでエラーが出た場合は、上下キーで当該プログラムを表示し、左右キーでエラーの原因箇所に移動し、バックスペースで部分的に削除、修正して再実行できる[8]
  • 部分的にEmacsのキーバインドが利用できる(Ctrl-aで行頭、Ctrl-eで行末、Ctrl-kで行削除など)

 Rのデータ構造

ここからは、Rで使用できるデータ構造について紹介します。Rではファイルやデータベースに格納されたデータを、変数あるいはオブジェクトと呼ばれる構造に格納して、さまざまな処理を適用します。また、オブジェクトに入るデータの特性によって、さまざまな“型”が存在します。

Rを使って競馬データを機械学習して一攫千金……と思っている方も、Rのオブジェクトを正しく理解し、操作できなければ、スターティングゲートに入ることすらできません。[9]ここでは、代表的なデータ構造について紹介し、実際にオブジェクトを作成してふるまい、操作を確認していきましょう。

スターティングゲート


Rで作成、使用するデータ構造として代表的なものに以下があります。

スカラー
単一の値が入ったオブジェクト。プログラム言語における、いわゆる「変数」
ベクトル
複数の値が1行に格納されたオブジェクト。いわゆる「配列」
行列
複数の値が縦横の2次元で格納されたオブジェクト。いわゆる「2次元配列」
データフレーム
複数の値が縦横の2次元で格納されたオブジェクト。各列(縦)の値は、データ型が異なってもよい。ファイルからデータを読み込む場合、一般的にはデータフレームに格納する
リスト
スカラーやベクトルやデータフレームなどをなんでも格納できるオブジェクト。自分で作ることは多くないが、統計関数の返り値がリストであることは多い

(競馬データに限らず)Rにおけるデータ分析では多くの場合、データフレームを基本の構造として、そこから必要に応じてベクトルなどを作成して関数を適用していきます。

 オブジェクトの作成

ここでは、実際に種々のオブジェクトを作成してみましょう。以下には、ブラウザで操作を試せる環境を用意していますが、手元にインストールしたRを使っても構いません。

スカラー型オブジェクト

スカラー型オブジェクトの作成は、もっともシンプルで、以下のようにします。

変数名 <- 値
(例)
a <- 1 # オブジェクト a に数値1を格納

b <- "馬" # オブジェクト b に文字列"馬"を格納

ここで示したように、スカラー型のオブジェクトには数値でも文字列でも格納できます。というよりも、数値でも文字列でも、何か値が1つだけ入ったオブジェクトがスカラーである、と捉えたほうがよいのかもしれません。なお、オブジェクトに格納した値の「型」を確認するには、class関数を使います。

class(オブジェクト名)
(例)
class(a)
[1] "numeric" # 数値型(整数型、浮動小数点型を包含)

class(b)
[1] "character" # 文字列型

同じスカラー型のオブジェクトであっても、格納した値の型(ややこしいですが)は異なることがわかります。では、実際に操作してみましょう。先ほどと同じく、左側のウィンドウにプログラムを記述し、左下の "Run" ボタンを押すと実行され、右側のウィンドウに結果が表示されます。エラーが出る場合は、記述を確認しましょう。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

# make numeric(integer) scalar # a <- 10 # make numeric(decimal) scalar # b <- 33.4 # print object "a" and "b" # a # b # check the class of object "a" # class(a) # will be "numeric" # check the class of object "b" # class(b) # same as "a" (numeric) # make character scalar # c <- "horse" # check the class of object "c" # class(c) # will be "character" # make character scalar (substitute a number as character) # d <- "100" # check the class of object "d" # class(d) # will be "character" # make numeric(integer) scalar # a <- 10 a <- 10 # make numeric(decimal) scalar # b <- 33.4 b <- 33.4 # print object "a" and "b" # a # b a b # check the class of object "a" # class(a) # will be "numeric" class(a) # check the class of object "b" # class(b) # same as "a" (numeric) class(b) # make character scalar # c <- "horse" c <- "horse" # check the class of object "c" # class(c) # will be "character" class(c) # make character scalar (substitute a number as character) # d <- "100" d <- "100" # check the class of object "d" # class(d) # will be "character" class(d) test_object("a") test_object("b") test_object("c") test_object("d") test_function("class",args="x") test_function("class",args="x") test_function("class",args="x") test_function("class",args="x") success_msg("Great job!")

なお、Rでは整数も小数も、同じ numeric 型として扱われますが、プログラムの中での条件分岐(整数しか受け付けない)など、もし厳密に両者を区別したい場合は、typeof 関数を使うと、整数は integer、小数は double と返ってきます。

ベクトル型オブジェクト

ベクトル型オブジェクトは、1つのオブジェクトの中に複数の値が格納されています。つまり、「第11レース」というオブジェクトの中に「サイレンススズカ、グラスワンダー、エルコンドルパサー...」[10]と出走馬の名前が格納されているようなイメージです。

ベクトルを作成するには、c 関数を使います。c は“Combine”の略で、引数に指定した値を結合してベクトルにします。

変数名 <- c(値1, 値2, 値3, ...)
(例)
a <- c(1, 2, 3, 4) # オブジェクト a に1, 2, 3, 4の値を格納

なお、ベクトルに格納した値の型は、すべて同一になります。つまり、数値と文字列を混在して格納した場合、2つの型の中で対応範囲が広い文字列(character 型)に統一(型変換)されますので、注意してください。

a <- c("ウオッカ", 33.5)

a
[1] "ウオッカ" "33.5"

class(a)
[1] "character" # 数値33.5は文字列型に型変換される

では、実際に操作してみましょう。先ほどと同じく、左側のウィンドウにプログラムを記述し、左下の "Run" ボタンを押すと実行され、右側のウィンドウに結果が表示されます。エラーが出る場合は、記述を確認しましょう。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

# make vector with numeric arguments # a <- c(34.0, 33.2, 33.6, 34.9) # print object "a" # a # check the class of object "a" # class(a) # will be "numeric", not a "vector" # check the object whether vector or not # is.vector(a) # will be "TRUE" # make vector with numeric arguments # a <- c(34.0, 33.2, 33.6, 34.9) a <- c(34.0, 33.2, 33.6, 34.9) # print object "a" # a a # check the class of object "a" # class(a) # will be "numeric", not a "vector" class(a) # check the object whether vector or not # is.vector(a) # will be "TRUE" is.vector(a) test_object("a") test_function("class",args="x") test_function("is.vector",args="x") success_msg("Great job!")

上記のコードにあるように、あるオブジェクトがベクトルかどうかを確認するには、is.vector 関数を使います。ただし、スカラーも「要素数1のベクトル」として TRUE と判定されます。

ベクトルでは、1つのオブジェクトの中に複数の値を格納できるため、オブジェクト名を入力、実行すると、格納されているすべての値が出力されます。一方、ベクトルの中の特定の値だけを取り出したい場合は、オブジェクト名[要素番号]とすることで、1番目の値、2番目の値というように位置を指定して取り出せます。

a <- c("ウオッカ", 33.5)

a[1]
[1] "ウオッカ"

a[2]
[1] "33.5"

また、1番目から5番目、というように連続した要素を取り出したい場合は、コロン : を使い、始点:終点 と指定できます。1番目と3番目と5番目、というように任意の位置の値を取り出す場合は、少し複雑になりますが、要素番号を指定する大カッコの中で c 関数を使ってベクトルを作成し、引数として与えることになります。

a <- c(39.9, 2.3, 161.4, 35.0, 33.1)

a[1:3] # 1番目から3番目の要素を取り出し
[1]  39.9   2.3 161.4

a[c(1, 3, 5)] # 1番目、3番目、5番目の要素を取り出し
[1]  39.9 161.4  33.1

では、実際に操作してみましょう。先ほどと同じく、左側のウィンドウにプログラムを記述し、左下の "Run" ボタンを押すと実行され、右側のウィンドウに結果が表示されます。エラーが出る場合は、記述を確認しましょう。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

# make vector with character arguments # a <- c("Dream Journey", "Victoire Pisa", "Orfevre", "Gold Ship", "Orfevre", "Gentildonna", "Gold Actor") # print object "a" # a # extract the 4th element of the object "a" # a[4] # will be "Gold Ship" # extract 5th to 7th elements of the object "a" # a[5:7] # will be "Orfevre", "Gentildonna" and "Gold Actor" # extract 1st, 3rd, 4th elements of the object "a" # a[c(1, 3, 4)] # will be "Dream Journey", "Orfevre" and "Gold Ship" (They has same father "Stay Gold") # make vector with character arguments # a <- c("Dream Journey", "Victoire Pisa", "Orfevre", "Gold Ship", "Orfevre", "Gentildonna", "Gold Actor") a <- c("Dream Journey", "Victoire Pisa", "Orfevre", "Gold Ship", "Orfevre", "Gentildonna", "Gold Actor") # print object "a" # a a # extract the 4th element of the object "a" # a[4] # will be "Gold Ship" a[4] # extract 5th to 7th elements of the object "a" # a[5:7] # will be "Orfevre", "Gentildonna" and "Gold Actor" a[5:7] # extract 1st, 3rd, 4th elements of the object "a" # a[c(1, 3, 4)] # will be "Dream Journey", "Orfevre" and "Gold Ship" (They has same father "Stay Gold") a[c(1, 3, 4)] test_object("a") success_msg("Great job!")

※無理やりにでも、ちょこちょこと競馬要素を入れていきます。

行列型オブジェクト

行列型オブジェクトは、ベクトルの概念を複数行に拡張したものです。統計学や機械学習などを理論的に理解するうえでは避けては通れない概念ですが、少なくとも競馬データ分析や一般的なビジネス統計の文脈では、自分で作成することはほとんどないと思います。ただし、統計関数の返り値が行列であるということは多いので、行列の操作の仕方は理解しておく必要があります。ここでは、説明のために matrix 関数を使って行列を作成し、操作をします。

オブジェクト名 <- matrix(要素ベクトル, nrow=行数, ncol=列数, byrow=TRUE / FALSE)
(例)
# 4行4列からなる行列の作成
a <- matrix(c("ウオッカ", "ダイワスカーレット", "アストンマーチャン", "ローブデコルテ",
1304876000, 786685000, 248997000, 209673000,
26, 12, 11, 18,
10, 8, 5, 3), nrow=4) # それぞれ、馬名、獲得賞金額、出走数、勝利数

a
     [,1]                 [,2]         [,3] [,4]
[1,] "ウオッカ"           "1304876000" "26" "10"
[2,] "ダイワスカーレット" "786685000"  "12" "8" 
[3,] "アストンマーチャン" "248997000"  "11" "5" 
[4,] "ローブデコルテ"     "209673000"  "18" "3"

# 行列の操作
a[1,] # 1行目を表示
[1] "ウオッカ"   "1304876000" "26"         "10"

a[,1] # 1列目を表示
[1] "ウオッカ"           "ダイワスカーレット" "アストンマーチャン"
[4] "ローブデコルテ"

a[1,3] # 1行3列の“セル”を表示
[1] "26"

# 列にラベルを付与する
colnames(a) <- c("馬名", "獲得賞金額", "出走数", "勝利数")
a
     馬名                 獲得賞金額   出走数 勝利数
[1,] "ウオッカ"           "1304876000" "26"   "10"  
[2,] "ダイワスカーレット" "786685000"  "12"   "8"   
[3,] "アストンマーチャン" "248997000"  "11"   "5"   
[4,] "ローブデコルテ"     "209673000"  "18"   "3"

# ラベルで列指定をする
a[,"獲得賞金額"]
[1] "1304876000" "786685000"  "248997000"  "209673000"

上記のように行列では オブジェクト名[行番号,列番号] として任意の要素を取り出せます。行番号または列番号を省略すると、1行または1列をすべて出力します。また、行番号や列番号にマイナス(-)を付けて指定すると、その行(列)を除いて出力されます。

行列型オブジェクトには、行や列を指定しやすくするために、行名(rownames 関数)や列名を付与できます。なお、出力を見てわかるように、行列型オブジェクトでは、オブジェクトに含まれる値の型のうち、もっとも対応範囲が広い型(ここでは文字列型)に型変換されますので、注意が必要です。

データフレーム型オブジェクト

データフレーム型オブジェクトは、行列型オブジェクトと同じように、データを縦横の2次元で格納します。行列型オブジェクトと異なり、データフレーム型オブジェクトでは、各列ごとに、型が異なるデータを混在して格納できます。一般に、データ分析の対象となるデータには、文字列(馬名、レース名)、整数(賞金額、距離)、小数を含む値(上り3F、オッズ)など、複数の異なる型が混在して含まれることが多いでしょう。データフレーム型オブジェクトでは、それらを列単位で「文字列型の列」、「数値型の列」といったように区別して、1つのオブジェクトの中に格納できます。

データフレーム型オブジェクトは、一般にファイルからデータを読み込んで生成することが多いですが、data.frame 関数を使って作成することもできます。ここでは実際に小さなデータフレームを作って、操作を確認してみましょう。

a <- data.frame(馬名=c("ウオッカ", "ダイワスカーレット", "アストンマーチャン", "ローブデコルテ"),
獲得賞金額=c(1304876000, 786685000, 248997000, 209673000),
出走数=c(26, 12, 11, 18),
勝利数=c(10, 8, 5, 3))

a
                馬名 獲得賞金額 出走数 勝利数
1           ウオッカ 1304876000     26     10
2 ダイワスカーレット  786685000     12      8
3 アストンマーチャン  248997000     11      5
4     ローブデコルテ  209673000     18      3

a[,"馬名"] # 行列型と同様の列指定
[1] ウオッカ           ダイワスカーレット アストンマーチャン
[4] ローブデコルテ    
4 Levels: アストンマーチャン ウオッカ ... ローブデコルテ

a$馬名 # "$列名"による列指定
[1] ウオッカ           ダイワスカーレット アストンマーチャン
[4] ローブデコルテ    
4 Levels: アストンマーチャン ウオッカ ... ローブデコルテ

a$馬名 <- as.character(a$馬名) # "馬名" 列を文字列型に変換
max(a$獲得賞金額) # "獲得賞金額" の最大値を算出する
a[which.max(a$獲得賞金額),] # "獲得賞金額" の最大値を”含む行”を抽出する

sapply(a, class)
       馬名  獲得賞金額      出走数      勝利数 
"character"   "numeric"   "numeric"   "numeric"

少しプログラムが長くなってきましたが、順に説明していきます。はじめに、data.frame 関数でデータフレームを作成しています。data.frame 関数の引数に、c 関数で作成したベクトルが指定されています。つまり、この一連のプログラムは、「c 関数で作成したベクトルを、data.frame 関数でデータフレームに変換する」という処理をあらわしています。また、data.frame 関数では 馬名= というように列名も同時に与えています。

次に、作成したデータフレームの各列について、[,列名] として列ごとに値を取り出しています。この書式は行列と同じですが、データフレームではもう1つ、$列名 という指定もできます。結果には変わりがないので、好きなほうを使えばよいでしょう。

また、max 関数を使い、引数に与えた「獲得賞金額」列の最大値を算出しています。さらに、少し応用になりますが、「最大値の要素番号を返す」which.max 関数を使い、「最大値を含む行」を抽出しています。

そして、「各列ごとに、型が異なるデータを混在して格納」できることを確認するために、「馬名」列にベクトルの型変換を行う関数 as.character を適用しています。なお、「馬名」列は、当初は factor という型になっています。factor 型は“水準”型とも呼ばれ、データをグループ化するためのインデックスという扱いです。データの型について、詳細は後述します。

最後に、sapply 関数で class 関数を各列に適用しています。sapply 関数は、現段階では「引数に指定したオブジェクトに、同じく引数に指定した関数を適用する」ものと捉えておけばよいでしょう。この結果から、データフレームでは列ごとに型が異なるデータを格納できることがわかります。

データフレームの操作は、この後実践として数多く行うことになりますが、ここで基本を確認しておきましょう。先ほどと同じく、左側のウィンドウにプログラムを記述し、左下の "Run" ボタンを押すと実行され、右側のウィンドウに結果が表示されます。エラーが出る場合は、記述を確認しましょう。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

a <- read.csv("http://dokoka.org/R_Keiba/en_keiba_data_small_sample.csv",header=TRUE,stringsAsFactors=FALSE) # print all data of the object "a" # a # print "HorseName" column of the object "a" # a$HorseName # returned values will be the names of horses # check the class of each rows of the object "a" # sapply(a, class) # convert the data type of several rows of the object "a" # a$Course <- as.factor(a$Course) # a$Track <- as.factor(a$Track) # a$Round <- as.factor(a$Round) # a$Condition <- as.factor(a$Condition) # check again the class of each rows of the object "a" # sapply(a, class) # print all data of the object "a" # a a # print "HorseName" column of the object "a" # a$HorseName # returned values will be the names of horses a$HorseName # returned values will be the names of horses # check the class of each rows of the object "a" # sapply(a, class) sapply(a, class) # convert the data type of several rows of the object "a" # a$Course <- as.factor(a$Course) # a$Track <- as.factor(a$Track) # a$Round <- as.factor(a$Round) # a$Condition <- as.factor(a$Condition) a$Course <- as.factor(a$Course) a$Track <- as.factor(a$Track) a$Round <- as.factor(a$Round) a$Condition <- as.factor(a$Condition) # check again the class of each rows of the object "a" # sapply(a, class) sapply(a, class) test_object("a") success_msg("Great job!")

ここまでで、ひととおりRのデータ構造と操作方法について紹介しました。このあと、実際に行う機会の多いファイルからのデータ読み込みと、データフレームへの各種関数の適用について紹介します。

 ファイルの読み込み

Rにファイルからデータを読み込む方法は複数あります。ファイル形式や構造に合わせて、適切な関数を使い分けます。なお、前提として、Rは標準ではMicrosoft Excelなどのソフトウェア独自のファイル形式(.xlsx.xls など)を読み込むことができません。後述するパッケージを使用すれば可能ですが、基本的には、プレインテキスト(スペース区切り、タブ区切り、カンマ区切りなど)でデータを用意します。

スペース区切りファイルの読み込み: read.table

列(フィールド)がスペースで区切られたデータファイルをスペース区切りファイルと呼びます。Microsoft Excelでスペース区切りファイルを出力すると、標準で拡張子は .prn となりますが、一般的には .txt の拡張子を使うことが多いでしょう。

スペース区切りのファイルを読み込むには、read.table 関数を使います。

read.table("ファイル名", header=TRUE / FALSE, stringsAsFactors=TRUE / FALSE)
(例)
data <- read.table("http://dokoka.org/R_Keiba/keiba_data_small_sample.txt", ,header=TRUE)

上記のように、read.table 関数では引数にローカルのファイルパスだけでなく、URLを指定することもできます。[11]ここまで使ってきたDataCamp Lightによる実習環境では日本語を含むデータを扱えないため、上記の操作はぜひ手元のPCにインストールしたRでやってみてください。

read.table 関数にはいくつかのオプションがありますが、上では header オプションと stringsAsFactors オプションを取り上げています。header オプションは1行目が見出し行である場合に指定します。デフォルト値は TRUE です。ですから、1行目からデータが始まっている場合には header=FALSE と指定する必要があります。また、stringsAsFactors オプションは、Rがデータファイルを読み込む際に文字列を factor 型として扱うかどうかを制御します。多くの場合、あまり意識する必要はない[12]ですが、Rにおいて factor 型のデータは処理に時間がかかるため、文字列が水準を意味しない場合は、オプションで FALSE を指定しておくとよいでしょう。

なお、ここでTRUE / FALSEの値を取るオプションを紹介しましたが、これらの値は大文字で指定する必要があります。また、省略してT / Fとすることもできます(header=T など)が、プログラムの可読性のために、省略しない記法が推奨されているようです。

カンマ区切りファイルの読み込み: read.csv

フィールドがカンマで区切られたデータファイルをカンマ区切りファイル(CSV; Comma Separated Values)と呼びます。Microsoft Excelをはじめ、さまざまなソフトウェアで作成できます。

カンマ区切りのファイルを読み込むには、read.csv 関数を使います。

read.csv("ファイル名", header=TRUE / FALSE, stringsAsFactors=TRUE / FALSE)
(例)
data <- read.csv("http://dokoka.org/R_Keiba/keiba_data_small_sample.csv", header=TRUE)

用意しているデータファイルの中身は同一(2016年ジャパンカップのレース結果)です。ここまで使ってきたDataCamp Lightによる実習環境では日本語を含むデータを扱えないため、上記の操作はぜひ手元のPCにインストールしたRでやってみてください。

タブ区切りファイルの読み込み: read.delim

フィールドがタブで区切られたデータファイルをタブ区切りファイル(TSV; Tab Separated Values)と呼びます。Microsoft Excelをはじめ、さまざまなソフトウェアで作成できます。

タブ区切りのファイルを読み込むには、read.delim 関数を使います。

read.delim("ファイル名", header=TRUE / FALSE, stringsAsFactors=TRUE / FALSE)
(例)
data <- read.delim("http://dokoka.org/R_Keiba/keiba_data_small_sample.tsv", header=TRUE)

用意しているデータファイルの中身は同一(2016年ジャパンカップのレース結果)です。ここまで使ってきたDataCamp Lightによる実習環境では日本語を含むデータを扱えないため、上記の操作はぜひ手元のPCにインストールしたRでやってみてください。

固定長ファイルの読み込み: read.fwf

列が一定の長さによって区切られたデータファイルを固定長ファイル(Fixed Width Format)と呼びます。第1回で紹介したJRA-VANやJRDBのデータは、固定長で提供されています。ここでは、JRDBのサンプルデータを読み込んでみます。データの仕様書に記述されている列幅を widths オプションで指定します。

data <- read.fwf("http://www.jrdb.com/program/Sed/SED080913.txt",
widths=c(2,2,1,1,2,2,8,8,18,4,1,1,1,2,2,2,3,1,1,25,2,4,2,1,4,3,6,
6,6,2,3,3,3,3,3,3,3,3,3,3,3,1,1,2,1,1,1,1,5,5,5,5,6,3,3,3,24,2,6,
6,6,2,2,2,2,3,3,5,5,3,3,1,1,1,7,7,5,5,2,2,1,4))
colnames(data) <- c("場コード", "年", "回", "日", "R", "馬番",
"血統登録番号", "年月日", "馬名", "距離", "芝ダ障害コード", "右左", "内外",
"馬場状態", "種別", "条件", "記号", "重量", "グレード", "レース名",
"頭数", "レース名略称", "着順", "異常区分", "タイム", "斤量", "騎手名",
"調教師名", "確定単勝オッズ", "確定単勝人気順位", "IDM", "素点",
"馬場差", "ペース", "出遅", "位置取", "不利", "前不利", "中不利", "後不利",
"レース", "コース取り", "上昇度コード", "クラスコード", "馬体コード",
"気配コード", "レースペース", "馬ペース", "テン指数", "上がり指数",
"ペース指数", "レースP指数", "1(2)着馬名", "1(2)着タイム差", "前3Fタイム",
"後3Fタイム", "備考", "予備", "確定複勝オッズ下", "10時単勝オッズ",
"10時複勝オッズ", "コーナー順位1", "コーナー順位2", "コーナー順位3",
"コーナー順位4", "前3F先頭差", "後3F先頭差", "騎手コード", "調教師コード",
"馬体重", "馬体重増減", "天候コード", "コース", "レース脚質", "単勝",
"複勝", "本賞金", "収得賞金", "レースペース流れ", "馬ペース流れ",
"4角コース取り", "予備")

なお、多くの固定長データについての仕様書では、データ項目の幅がバイト単位で書かれていますが、Rの read.fwf 関数では、文字列の長さで幅を指定します。そのため、日本語を含むデータの場合、Shift_JISでは2バイト=1文字、UTF-8では3バイト=1文字と読み替えてオプションを指定する必要がありますので注意してください。ここまで使ってきたDataCamp Lightによる実習環境では日本語を含むデータを扱えないため、上記の操作はぜひ手元のPCにインストールしたRでやってみてください。

任意の文字で区切られたファイルの読み込み: read.table

データファイルの中には、ここまで挙げたスペース、カンマ、タブ(、固定長)以外の区切り文字を使うものもあります。競馬とは何の関係もないですが、例えばUNIX / Linuxの設定ファイルの1つ /etc/passwd ファイルはコロン : でデータを区切ります。このような任意の文字で区切られたデータを読み込むには、read.table 関数を使います。sep オプションに区切り文字を指定することで、どのような区切り文字であっても、それをフィールド区切りとして処理できます。

data <- read.table("http://dokoka.org/R_Keiba/keiba_data_small_sample.psv", header=TRUE, sep="|")

ここでは、これまでと同じジャパンカップのデータを、パイプ(|)で区切ったデータを使いました。パイプ区切りのデータ(ここでは仮にPSV; Pipe Separated Valuesとしました)も実際に存在します。

実のところ、上で紹介した read.csv 関数も read.delim 関数も、内部では read.table 関数に sep="," または sep="\t" を指定して呼び出しています。Rでは、カッコを付けずに関数を実行すると、その関数の定義が返ってきますので、確認するとよいでしょう。

read.csv # カッコを付けずに実行すると関数の定義が返ってくる
function (file, header = TRUE, sep = ",", quote = "\"", dec = ".", 
    fill = TRUE, comment.char = "", ...) 
read.table(file = file, header = header, sep = sep, quote = quote, 
    dec = dec, fill = fill, comment.char = comment.char, ...)

ここまで、一般的なデータファイルの形式と、Rでそれを読み込むための方法を紹介しました。以降では、先ほど使用した少量のサンプルデータ(ジャパンカップの結果)について、さまざまなデータ操作や統計関数を適用する方法を紹介していきます。

 データフレームの操作

データフレームの行や列を指定してデータを抽出する方法はすでに紹介しましたが、ここでは行数、列数をカウントしたり、行や列の値の合計・平均を算出するための関数を紹介します。また、データフレームから条件を指定してデータを抽出する方法についても紹介します。

行数、列数のカウント

データフレームの行数や列数をカウントするには、nrow 関数や ncol 関数を使います。

data <- read.csv("http://dokoka.org/R_Keiba/keiba_data_small_sample.csv", header=TRUE)

nrow(data) # 行数をカウントする
[1] 17 # 出走頭数は17頭

ncol(data) # 列数をカウントする
[1] 30 # 1頭当たりの属性数は30

これらの関数は、例えば後述する条件指定によるデータ抽出の結果、「〇〇という条件に当てはまる馬は□□頭いた」というようなサマリを得るために使うことがあります。

行または列の値の合計・平均を算出する

データフレームの特定の行や列の合計・平均を算出するには、sum 関数や mean 関数などの基本統計関数を使います。

data <- read.csv("http://dokoka.org/R_Keiba/keiba_data_small_sample.csv", header=TRUE) # すでに読み込んでいるなら不要

sum(data$賞金) # ジャパンカップの総賞金を算出する(単位: 万円)
[1] 57000 # 5億7000万円
mean(data$タイム) # ジャパンカップ出走馬の平均走破タイムを算出する(単位: 秒)
[1] 146.7 # 2分26秒7

複数行または複数列の値の合計を算出するには、rowSums 関数や colSums 関数、平均を算出するには rowMeans 関数や colMeans 関数を使います。

data <- read.csv("http://dokoka.org/R_Keiba/keiba_data_small_sample.csv", header=TRUE) # すでに読み込んでいるなら不要

colSums(data[,c("賞金", "馬体重")]) # ジャパンカップの総賞金と出走馬の合計体重を算出する
  賞金 馬体重 
 57000   8162

colMeans(data[,c("タイム", "上り3F")]) # ジャパンカップの総賞金と出走馬の合計体重を算出する
   タイム    上り3F 
146.70000  34.97647

条件を指定してデータを抽出する

大量のデータから何らかの知見を得るために、一部のデータを抽出することがよくあります(単純に、ある1レースの結果を見るような場合など)。Rでデータを抽出する方法はさまざまにありますが、ここでは基本的な2つの方法を紹介します。

  • 演算子を使う

Rでは、数値の大小や真偽判断などに、演算子を使うことができます。代表的な演算子と機能として、以下のようなものがあります。

    • ==: 左辺と右辺の値が等しい場合にTRUEを返す
    • !=: 左辺と右辺の値が異なる場合にTRUEを返す
    • >: 左辺の値が右辺の値よりも大きい場合にTRUEを返す
    • <: 左辺の値が右辺の値よりも小さい場合にTRUEを返す
    • >=: 左辺の値が右辺の値以上である場合にTRUEを返す
    • <=: 左辺の値が右辺の値以下である場合にTRUEを返す
    • & / &&: 左辺と右辺の条件が両方ともTRUEである場合にTRUEを返す(AND条件)
    • | / ||: 左辺と右辺の条件のいずれかがTRUEである場合にTRUEを返す(OR条件)
      • ||| の違いについても上記リンクを参照

他にもさまざまな演算子がありますが、条件を指定したデータ抽出に使うのは上記のようなものが主です。これらの演算子を使って、実際にデータを抽出してみましょう。

data <- read.csv("http://dokoka.org/R_Keiba/keiba_data_small_sample.csv", header=TRUE) # すでに読み込んでいるなら不要

data[data$着順 <= 5,] # 着順が5着以内(掲示板内)の馬だけを抽出
      開催日 ... 着順 ...               馬名 ...       騎手
1 2016-11-27 ...    1 ...   キタサンブラック ...      武 豊
2 2016-11-27 ...    2 ... サウンズオブアース ... M.デムーロ
3 2016-11-27 ...    3 ...   シュヴァルグラン ...  福永 祐一
4 2016-11-27 ...    4 ...   ゴールドアクター ...  吉田 隼人
5 2016-11-27 ...    5 ...   リアルスティール ...   R.ムーア

data[data$上り3F <= 34.5, "馬名"] # 上り3Fのタイムが34秒5以内の馬の馬名を抽出
[1] サウンズオブアース シュヴァルグラン   レインボーライン  
[4] イキートス

data[data$調教評価 == "A", c("馬名", "調教コメント")] # 調教評価がAの馬の馬名とコメントを抽出
                 馬名 調教コメント
1    キタサンブラック     態勢万全
4    ゴールドアクター     迫力満点
5    リアルスティール       本調子
13 ディーマジェスティ       本調子

  • subset 関数を使う

条件を指定してデータを抽出するもう1つの方法として、subset 関数を使うことができます。

subset(オブジェクト名, 条件)
(例)
subset(data, 上り3F <= 34.5) # 結果は同じため省略

subset(data, 人気 <= 5 & 調教評価=="A")[,c("着順", "人気", "馬名", "騎手")]
   着順 人気               馬名      騎手
1     1    1   キタサンブラック     武 豊
4     4    3   ゴールドアクター 吉田 隼人
5     5    2   リアルスティール  R.ムーア
13   13    4 ディーマジェスティ 蛯名 正義

subset 関数を使うと、複雑な条件もすっきりと書けます。また、次回紹介しますが、よりモダンな方法として、dplyr パッケージの filter 関数を使うと、データの抽出がより柔軟にできます。

さて、ここまでの説明を踏まえて実際に操作してみましょう。先ほどと同じく、左側のウィンドウにプログラムを記述し、左下の "Run" ボタンを押すと実行され、右側のウィンドウに結果が表示されます。エラーが出る場合は、記述を確認しましょう。

※スマートフォン向けブラウザでは正しく表示されないかもしれません。その場合は、「PC向けモード」などで表示してください。

data <- read.csv("http://dokoka.org/R_Keiba/en_keiba_data_small_sample.csv",header=TRUE,stringsAsFactors=FALSE) # print all data of the object "data" # data # count rows of the object "data" # nrow(data) # count columns of the object "data" # ncol(data) # calculate the purse of the race # sum(data$Prize) # calculate the average (mean) time of the race # mean(data$Time) # extract the horses who placed the 1st to 5th # data[data$Result <= 5,] # extract the horses who runs faster than 34.5 sec in the last 3F # data[data$Last3F <= 34.5, "HorseName"] # extract the horses who looks good condition and gaining popularity # subset(a, Popularity <= 5 & TrainingJudge=="A")[,c("Result", "Popularity", "HorseName", "Jockey")] # print all data of the object "data" # data data # count rows of the object "data" # nrow(data) nrow(data) # count columns of the object "a" # ncol(data) ncol(data) # calculate the purse of the race # sum(data$Prize) sum(data$Prize) # calculate the average (mean) time of the race # mean(data$Time) mean(data$Time) # extract the horses who placed the 1st to 5th # data[data$Result <= 5,] data[data$Result <= 5,] # extract the horses who runs faster than 34.5 sec in the last 3F # data[data$Last3F <= 34.5, "HorseName"] data[data$Last3F <= 34.5, "HorseName"] # extract the horses who looks good condition and gaining popularity # subset(data, Popularity <= 5 & TrainingJudge=="A")[,c("Result", "Popularity", "HorseName", "Jockey")] subset(data, Popularity <= 5 & TrainingJudge=="A")[,c("Result", "Popularity", "HorseName", "Jockey")] test_object("data") test_function("nrow", args="x") test_function("ncol", args="x") test_function("sum", args="...") test_function("mean", args="x") test_function("subset", args="x") success_msg("Great job!")


さて、ここまでR言語の基本仕様を詳しく見てきました。上記のような方法で、競馬データを読み込み、操作できます。ところで、実際に競馬データに対して何をするかといえば、統計関数や高度な機械学習ロジックを適用し、競走馬1頭1頭の特性を把握したり、何らかの基準に基づく比較を行い、予想の根拠とするなどでしょう。次回以降の記事で、今回使用したよりも大規模な競馬データに対して、データ分析を行っていきます。

あまりにも長くなったのでこのへんで。


脚注

  • [1]まぁ、メッセージはそもそもほとんど英語ですが。
  • [2]ホームページの自己紹介によれば、2016年3月に群馬大学を定年退職されたそうです。
  • [3]大学の先生、優秀なエンジニアにとっては、このレベルが入門なんでしょうが……。
  • [4]本当は、アフィリエイトにして小銭稼ぎ(どうせ馬券に消えるんですが)をしたかったのですが、「サイトが未完成である」とのことで却下されてしまいました。いちおう、6年くらい運用実績があるサイトなんですが。
  • [5]日本語でコメントが書ければもっとわかりやすいのでしょうが、使っているJavaScriptライブラリが、(おそらくUTF-8以外の)日本語を通さないので、英語表記にしています。
  • [6]処理を高速化させるために、プログラムをコンパイルしたり、実質的にC++言語を呼び出して処理することも可能ではありますが、高度な内容ですのでここでは触れません。
  • [7]というか、そんな代入の仕方をしているプログラムを見たことはないですが。
  • [8]コンピュータに詳しい方は、そんなのわかりそうなものだと思うかもしれませんが、初心者は、間違ったら最初から1文字ずつ再度入力しようとするのです。
  • [9]無理やり競馬っぽいたとえをしてみました。
  • [10]出てくる名前が古いですね。そこらへんの世代なもので。
  • [11]ただし、ネットワーク環境やRのインストール状況によってはHTTPSプロトコルが使用できなかったり、制約がある場合もあります。
  • [12]あるいは、実験計画に基づいて収集したデータの文字列はたいてい水準のラベルなので、factor型が望ましい場合が多いでしょう。