Rの基礎(4)

Rのプログラム開発に欠かせないデバッグ、OSとのインタラクション(作業ディレクトリの変更、ファイルの存在確認など)、いくつかのライブラリについて学びます

学習項目です

デバッグ: バグに悩まされなくなるように

Rも含めてどんなプログラミング言語でもプログラムを作るときは、それが複雑なプログラムになればなるほど、バグを免れえない。構文の間違いならコンピュータ(プログラミング言語の処理系)が指摘してくれるのでそこの場所を直せば良い。しかし、意味的な間違い(プログラムのアルゴリズムの間違いなど設計ミスや、文法的には間違いではない打ち間違いなど)は見つけるのが難しい。

プログラムを作るための最良の方法は「一遍に大きなプログラムを作らない」ことである。 つまり、プログラムを意味的に小さなカタマリに分割し、そのカタマリごとにちゃんと動くことを確かめ、最後にそのカタマリを組み合わせる、という方法である。

普通のプログラミング言語であれば、このプログラムの(小さな)カタマリはサブルーチンとか関数と呼ばれるものである。「関数」としてこのカタマリを作っていくことは価値がある。なぜなら、関数は、計算に必要なものが何かを「引数」として明示的に表し、それをどのように計算するかというアルゴリズムをコードとして表された本体があり、最後に何を値として返すか(出力、もしくは結果)がはっきりしているからである。

Rはインタプリタ型の言語である。そのため、そのカタマリや、それよりも小さな単位のコードを実際に試しながら、プログラムを作ることが容易である。
[インタプリタ型の言語とは]

例として(標本)標準偏差を求める関数s.sdを作る工程を示そう(Rの組み込みとして標準偏差を求める関数sdがあるが、sdは不偏分散の正の平方根であり、標本の標準偏差とは異なる)。その計算の過程は次のようになる:

s.sd <- function(x) {
          m <- xの平均
          v <- xの(標本)分散をxの平均を用いて求める
          s <- vの正の平方根
          return(s)
       }
この一行一行が「意味的なカタマリ」である。このプログラムを作るには、実際にデータを使いながら行うのがやりやすい。ここでは以下のデータを用いるものとする:
set.seed(0)			# 同じ乱数をいつも発生させるためのもの---例題なので同じ結果がいつも得られるようにするための措置
data <- rnorm(30,0,1)		# rnorm(n,m,d) は平均0、分散1の正規分布から乱数をn個生成する関数
このdataは作られ方から、平均が0で分散および標準偏差はともに1に近いと考えることができる。この事実は、試しに作った関数の値の検査に役に立つ。得られた結果がこれとかなり離れた値を返した場合に、意味的な問題が生じたことがわかるからである。

まず「xの平均」を求めてみよう。それには次のようにRを実行すればよい。

> x <- data			# xの値としてdataの値を代入
> ( m <- mean(x) )
[1] 0.08331833
yには0.08というかなり0に近い値が入ったので、平均値が求められたと考えられる(もちろん、meanという組み込み関数を使ったのだからそうでないと困るのだが)。

次に「v <- xの(標本)分散をxの平均を用いて求める」のコードを書く。「分散」とは、「xの要素と平均値との差」の自乗の総和から求めることを思い出す。 そこで次のように書いてみよう。

> (v <- (x - m)^2
 [1] 1.0905112707 2.9173870696 0.5924946087 0.7780870630 0.3488318806
 [6] 2.5206025164 0.5554994293 4.0051943705 0.7155737916 0.2136054231
[11] 0.1414342549 2.2786344191 1.1288472969 0.9186234518 0.7475653825
[16] 0.1477034281 0.4797374218 0.0047967739 2.0255688416 0.3688306885
[21] 1.2633099540 0.0173108802 0.4092461570 0.0003777597 0.6897168796
[26] 0.7149848505 1.1755287558 0.7508797520 1.0007665306 0.7592551308
あれあれ、分散は1個の数値のはずなのに、ベクトルの結果が出てしまった。これは「xの要素と平均値の自乗」は求めたのだけど、「その和」を求めるのを忘れていたからである。そこで、次のように修正する。
> (v <- sum( (x - m)^2 )			# 先の値に関数sumを適用
[1] 28.76091
今度はちゃんと一個の数値が求まったけれど、28.8というのは分散1と比べてとても大きな数である。何かがおかしい…

そこで「分散」の定義を調べてみると、分散は「「xの要素と平均値との差」の自乗の総和」を要素数(不偏分散は「要素数−1」)で割った値」であったことがわかる。 そこで次のように修正する:

> (v <- sum( (x - m)^2 )/length(x) )			# 先の値にxの要素数(length(x))で割る
[1] 0.9586969
今度はかなり1に近い値になった。これは正解に近づいたことが確信できる。 そこで最後に、この平方根を求めるコードを書く:
> (s <- sqrt(v) )				# vの値に関数sqrtを適用
[1] 0.9791307
これも予想通り1に近い値になった。そこで、今まで書いてきたコードを改めてs.sdの関数定義の中に書き入れて、s.sdを定義する。
s.sd <- function(x) {
          m <- mean(x)				# xの平均
          v <- sum( (x - m)^2 )/length(x)	# xの(標本)分散をxの平均を用いて求める
          s <- sqrt(v) 				# vの正の平方根
          return(s)
       }
実はこのコードの正しさを確認するには数式の定義やアルゴリズムの検証が必要になるが、簡単にできるが重要な方法としては、例題にあげたデータ以外のデータをいくつか与えてみて、その値が正しい値を返すことを確認する、ということがある。とても重要なことなので是非実践してほしい。

以上が正道なのだが、こういう方法が正しいとわかっていても、いきなり関数の定義を書いてからバグに悩まされる人も少なくない(かくいう著者も時々これをやってしまう)。そのためのバグ除去の手段として、Rではdebug、およびbrowser関数が用意されている。

debugもbrowserも「デバッグモード」という特殊なモードで関数の実行を可能にしている。両者の違いは、その関数が呼び出された時にデバッグモードで実行するか(debugの場合)、それとも関数実行の途中でデバッグモードに入るのか(実行途中でbrowser関数を呼び出す。バグがありそうなところがわかっている場合に有効)、という違いである。

デバッグモードでは、次の命令が使える:

debugの使い方は次の実行例をみてほしい:
> debug(s.sd)				# s.sdが呼ばれたらdebug開始を指示
> s.sd(data)
debugging in: s.sd(data)	# debugが始まる。s.sdの関数定義が表示される
 #1 の debug: {
    m <- mean(x)
    v <- sum((x - m)^2)/length(x)
    s <- sqrt(v)
    return(s)
}
Browse[2]>  n				# 次の行を実行
 #1 の debug: m <- mean(x)
Browse[2]>  n				# 次の行を実行
 #2 の debug: v <- sum((x - m)^2)/length(x)
Browse[2]> ls()				# どのような変数に値が入ったかを確認
[1] "m" "x"			# 関数内の局所変数だけが表示される
Browse[2]> m				# mの値の確認
[1] 0.08331833
Browse[2]>  c				# 関数の最後まで実行
exiting from: s.sd(data)
[1] 0.9791307
> undebug(s.sd)				# s.sdに対するデバッグを終了

演習1

ファイルからデータフレーム型のデータ(例えばサンプルデータ)を読み込み、各列の平均と分散を表示するプログラムを作れ。ただし(できれば)、debugかbrowserを用いて、プログラムの動きを確認しながら、そのプログラムを作るものとする。

OSとの関わり

Rではプログラムコードやデータなどいろいろなファイルを使って作業を行う。 このようなファイルをRに取り込むには、そのファイルが有る場所(ファイルパス, path)を指定しなければならないが、例えば "C:\Users\sirai\Documents\Classes\R"というようなパスを指定するのは大変である。
[ファイルパス(path)]

そこでRではファイルメニューから「ディレクトリの変更」(ディレクトリとはフォルダのこと)を選んで、「作業ディレクトリ」を設定しておくことができる。これにより、例えば read.table("sample.csv") というコードを実行したときは、sample.csvというファイルはその作業ディレクトリから読み込まれる。またRで実行するプログラム(スクリプトという)も、ファイルメニューから「スクリプトを開く」を選ぶと、作業ディレクトリを表示してくれ、そこからRで実行するプログラムを指定することができる。

残念なことに、「ディレクトリの変更」はいつもコンピュータの一番上の階層から始まる。 もしも探すべきフォルダがかなり下にある場合には、これはなかなか面倒な操作が必要になる。それに対して、Rの次のコマンドを行うと、「スクリプトを開く」ことも、Rの中からデータファイルを読み込むのも楽になることが期待できる。使用するのは次の関数である: getwd, dir, setwd, unlink 。これらは、WindowsOSに働きかける命令も含んでおり、いわばRの環境とOSとの調整を果たしている。

> getwd()			# 現在の作業ディレクトリを確認
[1] "C:/Users/sirai/Documents"				# 作業ディレクトリの表示

> dir()			# 作業ディレクトリの下にあるファイルやフォルダを表示
> dir(pattern="Ro")		# 作業ディレクトリの下の名前にRoを含むファイルやフォルダを表示
[1] "HandbookOfRobotics"    "ProbabilisticRobotics" "Robot"                
[4] "RobotArm"              "RobotBasic"            "Roomba"               
> dir(pattern="ro")		# 作業ディレクトリの下の名前にroを含むファイルやフォルダを表示
[1] "Android"                 "ProbabilisticRobotics"  		# Roとroでは違うファイルが表示される
[3] "Processing"              "roman"                  
[5] "RubyInternetProgramming"

> setwd("R")		# 作業ディレクトリを、今のフォルダの下のRフォルダに変更
> getwd()		# 現在の作業ディレクトリを確認
[1] "C:/Users/sirai/Documents/R"	# 変更された
> dir()		# 作業ディレクトリの下にあるファイルやフォルダを表示
 [1] "3067_Code.zip"                            
 [2] "anovakun_433.txt"                         
 [3] "DataMiningWithR-Torgo2011"                
 (以下省略)
> setwd("../Robot")		# 作業ディレクトリを現在より上のフォルダの下にあるRobotに変更---..は上の階層を表す
> dir()		# 作業ディレクトリの下にあるファイルやフォルダを表示
[1] "AutonomousMobileRobot"             "RobotMotionPlanningAndControl.pdf"
> cat("this is a dummy file.\n",file="dummy.txt")		# cat関数によりdummy.txtを作成
> dir()		# 作業ディレクトリの下にあるファイルやフォルダを表示
[1] "AutonomousMobileRobot"             "dummy.txt"      		# dummy.txtが作成された
[3] "RobotMotionPlanningAndControl.pdf"

> readLines("dummy.txt")		# ファイルの読み込み---作業ディレクトリのファイルが読み込まれる
[1] "this is a dummy file."
> unlink("dummy.*")		# 名前にdummyという文字を含むファイルを削除(unlinkは削除を意味)

> dir()		# 作業ディレクトリの下にあるファイルやフォルダを表示
[1] "AutonomousMobileRobot"             "RobotMotionPlanningAndControl.pdf"

演習2

setwdコマンドを用いて自分の「ドキュメント」フォルダの下の「R」フォルダに作業ディレクトリを移動し、その下にある"test.txt"からreadLines関数により5行分読み込んで画面に表示するプログラムを作れ。 ただし、上記に必要なフォルダやファイルはあらかじめ(適切に)用意しておくこと。

ライブラリの使用

Rにはいろいろなタスクのためのライブラリが用意されている。ここでライブラリとは、Rに初めから組み込みで入っているものではないが、必要な物を選んで取り込み、そのライブラリで定義されている関数やデータを使えるようにしたものを指す。

CRANというRの情報源のサイトの、 Task Viewsというパッケージの紹介ページには、次のような分野のライブラリがあることが紹介されている(ここでは、ごく一部のみにとどめている):

ライブラリを使うには、まずRにそのライブラリが入っている「パッケージ」をインストールする必要がある。Windowsでは、次の操作が必要になる:
  1. 「パッケージ」メニューから「CRANミラーサイトの設定」を選択。Tokyo など、日本のサイトを選択(これはRのセッションあたり一回だけでよい)
  2. 「パッケージ」メニューから「パッケージのインストール」を選択。たくさんのパッケージが出てくるので、欲しいものだけを選んでクリック
    ちなみに、Rの関数としてinstall.packagesが用意されており、これでもパッケージのインストールは可能である。
既に以上の操作により、パッケージがインストールされていれば、そのライブラリを使うには、library関数を実行すればよい。

以下はdeepnetパッケージをインストールし、ライブラリdeepnetを使えるようにした様子である。ただし、ここでは既にミラーサイトTokyoを設定してあるものとする。

> install.packages("deepnet")
Installing package into ‘C:/Users/sirai/Documents/R/win-library/3.0’
(as ‘lib’ is unspecified)
 URL 'http://cran.md.tsukuba.ac.jp/bin/windows/contrib/3.0/deepnet_0.7-8.zip' を試しています 
Content type 'application/zip' length 282099 bytes (275 Kb)
 開かれた URL 
downloaded 275 Kb

 パッケージ ‘deepnet’ は無事に展開され、MD5 サムもチェックされました 

 ダウンロードされたパッケージは、以下にあります 
        C:\Users\sirai\AppData\Local\Temp\RtmpUXgLxG\downloaded_packages 
> library("deepnet")

演習3 (難しいテーマを含んでいるが、できるところまで頑張ってみよ)

人工知能の機械学習の一つであるニューラルネットを体験する。 (注意: ニューラルネットについては、 機械学習や人工知能概論で学んでいる思うが、知らない人は調べておくこと) それには、Rのnnetパッケージをインストールし、nnetライブラリを読み込んで使う。 (この解説の「パッケージのインストール」について読み返しておくこと)
アヤメの花びらと萼片の幅と長さのデータから種別を推定するシステムを作ろう。 なおこれは「教師あり学習」の一つである。
  1. sample関数を用いてirisデータを半分ずつに分割し、片方のデータを用いて学習し、 できあがった推定システムの評価を残りの半分のデータで行う。 どのようにこれを実現したかとともに、その評価値を答えよ。
    [ニューラルネットを作って走らせる...]
    irisデータとは150個の(3種類のアヤメ、それぞれ50個のデータが連続して入っている)データである。これはRを立ち上げた時に既にセットされている。 このirisデータをsample関数を用いて2つに分けるのは次のようにすればできる(iris.trainとiris.testにそれぞれ元のirisデータの半分ずつが入っている): (なおsample関数は乱数を用いて数を生成する関数である)
    samples <- sample(1:150,75)       # 1から150までの範囲で75個選ぶ
    iris.train <- iris[samples,]
    iris.test  <- iris[-samples,]
    
    次に、iris.trainデータを用いてニューラルネットを作る:
    library(nnet)		# nnetライブラリの読み込み
    iris.nnet <- nnet(Species~., size=3, decay=0.1,data=iris.train)
    
    ここでnnet(Species~., 中略, data=iris.train)とは、種別(Species)を出力(目的変数)、他のデータを入力(説明変数)とし、iris.trainデータで学習させてニューラルネットを作ることを意味する。またsize=3は中間層のノード数が3であることを意味する。 これにより作成されたニューラルネットがiris.nnetの値になっているので、これを次のようにiris.testに適用し(predict関数による。iris.predictにその結果が代入される)、その結果をtable関数を用いて表示する:
    iris.predict <- predict(iris.nnet, iris.test[,-5],type="class")
    table(iris.test[,5], iris.predict)
    
    上でiris.test[,-5]によりSpeciesを除くデータが入力となり、type="class"により数値ではなく種の判別を結果として返すことを意味する。 詳しくは金(2007)「Rによるデータサイエンス」や、豊田(2008)「データマイニング入門」を見てほしい。
  2. irisデータを、乱数を用いて5等分し (それぞれに3種類のアヤメのデータが均等に入るよう工夫せよ)、それらを 第1グループ、第2グループ、...、第5グループとする。
    まず第2グループから第5グループをひとまとめにしたデータ (つまり元データから第1グループを除いたもの)で学習し、 それによって得られた推定システムで第1グループのデータでテストする。 次に元データから第2グループを除いたデータで学習させ、 得られた推定システムを第2グループで評価を行う。 今度は元データから第3グループを除いたデータで学習させ、 得られた推定システムを第3グループで評価を行う...
    このように学習と評価を別々のデータを用いて合計5回行い、 すべての評価値の平均を答えよ。 なお、これは「交差検証」と呼ばれており、 特にデータが少ない時のシステムの評価や、 システムのパラメタの最適値を求めるのに用いられる「標準的な」方法である。

Rの解説(3)         トップページに戻る        Rで統計学(1)