| Top Page | プログラミング | R 自動化 目次 | 索引 | 前へ | 次へ |
この文書の最終的な目標は,いくつものデータセットを一括処理するためのプログラムを 書くことですが,そのまえに,ひとつのデータファイルを読み込んでの処理を ごくごく簡単に練習してみます.
すでに前のページの練習のためにディレクトリは作ってあるかもしれません. それならそれを使いましょう.まだ作ってないなら,さっそく用意してください. 名前はなんでもけっこうです. そのディレクトリの中に,最初に使う練習用データ len_width.txt を入れます.
>データファイル len_width.txt
クリックするとそのまま内容が表示される場合,全体をコピーしてから エディタの新規文書に貼りつけるとか,「対象をファイルに保存」「リンク先を保存」 (ブラウザによる)というようなメニュー項目を選ぶとかしてみてください.
このファイルに,以下のように3列のデータが見出し行 + 50行,あわせて51行 並んだものです. 中身は仮想的なもので,なにかの長さ(length),幅(width),種(species) のデータが50個体分並んだもの,と思ってください.
len width sp 58.2 7.1 Sp2 54.7 12.8 Sp1 47.4 18.4 Sp1 38.6 13.7 Sp1 ...
前のページで紹介した R で扱いやすいデータファイルのかたちになっていますね.
R は,練習に使えるように,いくつものデータセットが組み込まれています. 前のページでちょっとふれた irisもそのひとつです. 統計計算だのグラフ描きだのの練習には,これらを使えば簡単です. でも,このページは自分が持ってるたくさんのデータをプログラムを書いて一括処理 することを目的にしているので,あえてデータファイルを読み込むというところから はじめることにしました.
R でデータを扱うとき,データフレームというものが中心になります. これは,オブジェクトであり,データ構造のひとつ,というようなことを 言っても,最初はなんのことやら分かりませんね.
プログラミングの世界で使う「オブジェクト」は,データがひとつ以上あつまって, なんらかの「こと」や「もの」を表現しているものを指します. R では,変数はみなオブジェクトだと考えます. また,計算処理を定義した関数もオブジェクトだと考えます. 処理のしかたをデータとしてもっている,というイメージでしょうか. データフレームもオブジェクトの一種で、 たくさんのデータをまとめて管理するためのデータ構造をもっています (データフレームは リストの一種ですが、そのことにはここでは触れません).
データ構造とは、複数のデータをまとめて記録・管理するときの,まとめ方の構造のことです。 たとえば,
ある変数になにかを代入して… という作業をすると,その変数 がひとつのオブジェクトとして作り出されることになります. R がもともと用意しているものをのぞいて,自分がどんなオブジェクトを 作ったを見るには,objects という関数を使います. さっき作った変数はどんな名前だっけ,と思い出すのにも使えます. objects() と入力してためしてみましょう.
データフレームがどのようなものなのか感じをつかむため,さっそく作ってみます. R の練習用に作製して len_width.txt を入れたディレクトリを作業ディレクトリにしてから, 入力画面で以下のように read.table という関数を呼び出します.
d <- read.table('len_width.txt', header = TRUE)
read.table 関数は,最初の引数としてファイル名を与えます.
この関数には,そのほかにもいろいろな引数を渡せます. とはいえ,5個も10個もある引数を,関数が期待する通りの順序で 全部指定するのは大変です. そのようなめんどうを避けるため,引数にはそれぞれ名前がついていて, 呼び出す側では値を設定したい引数についてだけ 「引数名 = 値」 という形で書けばよいことになっています. デフォルトの設定でかまわない引数についてはなにも指定しません. 上の例では,header という名前の引数に TRUE (真偽の真)という値を設定しています. こうすると,データファイルの一行めはデータそのものではなく,各列の名前が並んだ 見出し行だと解釈してくれます.
真偽値は、TRUE(真), FALSE(偽)で表現されます。これらはそれぞれ T, F と書くこともできますが、、 きちんと TRUE, FALSE と書くほうが安全です。 初期状態では T には TRUE, F には FALSE が代入されているのですが、他の値を代入することができるので、 真、偽以外の値を持つことがあり得るからです。
read.table 関数は,読み込んだファイルの内容にもとづいてデータフレームを作ります. できあがったものを,d という変数に, 代入演算子 '<-' で代入しています.
d <- read.table('len_width.txt', header = TRUE)
とだけ入力すると,
len width sp 1 58.2 7.1 Sp2 2 54.7 12.8 Sp1 3 47.4 18.4 Sp1 4 38.6 13.7 Sp1 ...
というように,読み込んだデータがそのまま表形式で表示されます.左端の数字は 行番号(見出し含まず)です.
実際のデータは,一部が欠けていたり,測定回数がちがっていたりして, きれいな行列にならない場合も多々あります.R は,あるべきデータが 欠けている場合には NA (欠測値)をあてて,そのあとの処理を適当に進めてくれます.
read.table でタブ区切りデータを読み込む場合, オプション指定で区切りがタブであることを明示的に示せば, タブのあいだ/あとの空欄は,NA として読み込みます. データが不揃いでも,タブの数が全部の行で揃っていればエラーとならずに 読み込めます.
read.table とほぼ同じ動作をする read.csv は,データの区切りを特に指定しないと sep = ',' が指定されたものとしてファイルを読み込みます. その際,コンマのあいだ/あとの空欄は,NA として読み込みます. この場合も,全部の行でコンマの数が揃っていることは必要です.
# タブ区切り,空欄があるデータファイルの読み込み d <- read.table('xxx.txt', header = TRUE) # データ数が合わずエラーとなる # タブ区切り,空欄があるデータファイルの読み込み d <- read.table('xxx.txt', header = TRUE, sep = '\t') # 空欄は NA と認識 # カンマ区切りのデータファイルの読み込み d2 <- read.table('yyy.csv', header = TRUE, sep = ',') # 空欄があれば NA と認識 # カンマ区切りのデータファイルの読み込み d2 <- read.csv('yyy.csv', header = TRUE) # 空欄があれば NA と認識
(さらに補足) read.table で、空欄があるデータを読む場合、sep = '\t' を 指定しないと正しく読めないのは、なにも指定しない場合には連続する空白文字(スペース、 タブ、改行)をひとつの区切りと認識する設定になっているからです。空欄がひとつあると 2つのタブが連続することになりますが、これをまとめてひとつの区切りと認識するため、 一行中のデータ数が少なくなってしまいます。明示的に sep = '\t'と指定すると、 ひとつのタブがひとつの区切りと解釈されるので、空欄部分は空欄として読み込まれます。
(もうひとつ補足) 上で、 「R は,あるべきデータが 欠けている場合には NA (欠測値)をあてて,そのあとの処理を適当に進めてくれます」 と書きました。この「適当」は、どちらかというと安全めになっています。 たとえば、いくつものデータの平均を求める関数 mean は、 NA を含むデータを与えられると、平均値として NA を返します。 これは、一部にNAが含まれているのに気付かないであとの処理を進めてしまう危険を 回避しやすい振る舞いです。 NA を無視して、それ以外のデータの平均が知りたい場合は、そのことを na.rm = TRUE という オプションで指定します。詳しくは ?mean としてヘルプを確認してください。 分散を求めるvar、最大・最小をもとめる max, minも同様の振る舞いをします。
データフレーム内のデータは,いろいろな形で取り出すことができます. 下のプログラムをコピーして入力画面に貼り付けてためしてみてください.
変数名を入力画面に入力するとその内容が表示されますが、 変数名だけ書いたプログラムを実行してもてもなにも表示されません. プログラムの中でなにかを表示したり記録したるする方法はあとで出てきます.
d <- read.table('len_width.txt', header = TRUE) # データフレームの生成 ncol(d) # 何列(column)あるか nrow(d) # 何行(row)あるか colnames(d) # 列の名前の一覧 # データフレームの特定の列を名前で指定するときは, # データフレーム名のあと,$ に続いて列の名前を書く. d$len # d の,len という名前の列の内容 (ベクトル) d$width # d の,width という名前の列の内容 (ベクトル) d[['len']] # d の,len という名前の列の内容.d$len と同じもの (ベクトル) d[['width']] # d の,width という名前の列の内容.d$width と同じもの (ベクトル) d[[2]] # d の,2番めの列の内容 (ベクトル) d[10,2] # 10番めの行の,2番めの値(ひとつの値) d[,2] # 2番めの列の内容. ',' の前の行番号を省略している.d[[2]]と同じ (ベクトル) d[3,] # 3番めの行. ',' のあとの列番号を省略している (ベクトル) d[2] # 2番めの列 (厳密には1列からなるデータフレーム) d['len'] # d の,len という名前の列 (厳密には1列からなるデータフレーム)
上の例で,データフレーム中のどのデータというのを数字(列番号,行番号)や文字列(名前) で指定しているところは,数字や文字列そのものでなく, 数値や文字列を記憶している変数を書くこともできます.
ただし,$ のうしろには,d$width のように名前そのものしか書けません. 名前が入った変数の名前や,引用符でくくった文字列は,[] や [[]]の中にのみ書くことができます.
item <- 'width' d[[item]] # width という名前の列の内容. column <- 2 d[, column] # 2番めの列の内容
プログラム中ででデータフレームを処理するとき,どの列やどの行に注目するのか, あらかじめ決まっているとは限りません. 読み込んだデータによってプログラム中で判断して指定したくなることもよくあります. また,そういうことができるプログラムのほうが汎用性があるはずです. 変数を介して行や列の指定をできるので,このような汎用性を持ったプログラムを 書くことが容易になります.
ひとつややこしいのは,d[2] と d[[2]] とd[,2] の関係, また,d$width と とd[['width']] と d['width'] の関係です.
これらの6つの表現のうち d[2] とd['width'] がほかとは別の意味を持っています. ほかのものは,2番目の(あるいは 'width' という名前の)列に含まれるデータ を並べたベクトルを意味します. ベクトルとは,同じ型(かた.数値とか,文字列とか)のデータが並んだものです. 入力画面に d[,2] と入力すると,要素が横にならんで表示されます.
一方,d[2] とd['width']は,d というデータフレームの2番めの列,ないしは width という 名前の列だけからなるデータフレームを意味します.ちいさいけど, これでもデータフレームです. 入力画面に d[2] や d['width'] と入力すると,要素が縦に並んで表示されるはずです. これは,列が1個の表という形です.
ベクトルとデータフレームの区別は,とくに関数にデータを渡すときに注意する必要があります. 引数としてベクトルを求めているものにデータフレームを渡したり,あるいは その逆だったりすると,正しく処理してもらえません.
たとえば,データフレームが何列からなっているかを調べる ncol 関数に d[2] を渡せば 1列ですよという答えがかえってきます.でも,ベクトルである (データフレームではない)d[[2]] やd[,2] を ncolに渡しても, まともに答えてもらえません.
colnames関数は,データフレームの各列の名前をベクトルにまとめて 返します.ベクトルのn 番めの要素を表示させるには,ベクトル名に続けて [n] のように書きます. ですから,たとえば以下のように書けば各列の名前をひとつづつ表示させることができます.
item.names <- colnames(d) # データフレーム d の各列の名前からなるベクトルを item.namesに代入 item.names[1] # 最初の列の名前 item.names[2] # 2番めの列の名前
データフレームを,各行がひとつのサンプル,各列が測定項目と見るとします. この全部のサンプルでなく,なんらかの条件を満たす一部のサンプルだけを 処理したい場合ことがあります.幅が 10未満のものだけとか,特定の種の サンプルだけ,といった具合です.
'データフレーム名 [行番号, ]' という書式の行番号のところを 条件式に置き換えれば,このような一部のサンプルの抽出ができます.
d.sub <- d[d$sp == 'Sp1',]
とすれば,種名の列のデータが Sp1 という文字列になっている行だけを 取り出したデータフレームが d.sub に代入されますし,
d.sub <- d[d$width < 10.0,]
とすれば,width の列のデータが10未満の行だけを取り出したデータフレームが d.subに代入されます. 複数の条件を, & (かつ)や | (または)で結びつけることもできます.
たとえば,
d.sub <- d[10 <= d$width & d$width < 20,]
とすれば,width が10以上という条件とおなじくwidthが20未満という条件を & で結びつけていますので, width が 10以上で20未満の行だけからなるデータフレームができます.
条件式で使われる比較演算子や論理演算子(と呼ぶ)の詳細は, R-Tips の 該当ページ を見てください.
前に,欠測値は NA という特殊な値で表現されることを説明しました. ある値がNA かどうかは,専用の関数 is.na を使って調べます. この関数に渡した値が NA なら TRUE, NA でないなら FALSE が返ってきます. この関数を利用すると,NA が含まれている行を取り除くことができます.
# タブ区切り,空欄があるデータファイルの読み込み d <- read.table('xxx.txt', header = TRUE, sep = '\t') # 空欄は NA と認識 dd <- d[is.na(d$len) == FALSE, ] # len という名前の列の値が NA でない行だけ選ぶ
前のところで,行番号を指定するかわりに条件を書けることを紹介しました. なんだか,ずいぶんつごうのよい書き方です.こういう書き方がどうして許されるのか, 簡単に解説してみます.読み流すだけでけっこうです.
データを読み込んだデータフレームが d に代入されているとします.入力画面で
x <- d$len < 50 x
と入力すると,
[1] FALSE FALSE TRUE TRUE TRUE FALSE TRUE FALSE TRUE FALSE FALSE TRUE [13] TRUE FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE FALSE TRUE FALSE [25] TRUE TRUE FALSE TRUE FALSE TRUE FALSE FALSE FALSE FALSE TRUE TRUE [37] TRUE TRUE FALSE TRUE FALSE TRUE TRUE TRUE TRUE TRUE FALSE FALSE [49] TRUE TRUE
のように,TRUE(論理値の真) と FALSE(論理値の偽)が50個ずらずらと表示されます. x には,50個の論理値からなるベクトルが代入されています.
これは,d というデータフレームの len という名前の列を最初から順番に見ていって, データの値が 50 未満なら TRUE,50以上なら FALSE と評価していった結果です.だから,
d[d$len < 50, ]
と書くと,行指定のかわりに,(この場合は)50個の論理値が並んだベクトルを 書いたことになります. 行指定ではなく,このような論理値のベクトルが渡されると,真に対応する行のみを 抽出したデータフレームが作られます.それが,さきほど
d.sub <- d[d$width < 10.0,]
などと書いて作ったものでした.
なお,x <- d$len < 50 で論理値が50個並んだベクトルが作られたのは, R ではベクトルを含む演算式を書くと,ベクトルの各要素について計算した結果を 並べたてベクトルを返すことになっているからです. たとえば
d$width ^ 2 * d$len
と書くと,データフレームの一行ごとに width の二乗と len とをかけた 結果が並んだベクトルが作られます. '^' はべき乗を計算する演算子です.
d$volume <- d$width ^ 2 * d$len
と書けば,データフレーム d にあらたに volume という名前の列が作られて,そこに 各行ごとにwidth の二乗と len とをかけた結果がしまわれます.
論理値について,もうちょっと寄り道します. 数値を論理値として評価すると, ゼロは偽(F, FALSE),それ以外の値はすべて真(T, TRUE) となります. 文字列を論理値として評価しようとするとエラーになります. 一方,論理値を数値として評価すると,TRUE は 1,FALSE は 0となります.
このことを使って,ある条件を満たすデータの数を簡単に求めることができます. 使うのは sum という関数です.これは引数に 与えたベクトルの合計を求めます.そこで,
sum(d$len)
と書けば d$len の全要素を合計した値を求めることになりますし,
sum(d$len < 50)
と書けば,上に示したような50個の論理値が並んだベクトルを sumに渡したことになり,このうち TRUE は 1,FALSE は 0 と評価 されるので,sumは TRUE の数を返します.これはすなわち d$lenのうち 50未満の要素数に相当します.
もうひとつだけ、条件を指定してのデータフレームのいじりかたを紹介します。 len_width.txt のデータをデータフレームに読み込んでから、以下のようにして len の大きさによって、10刻みでクラス分けをすることにします。 まず、それぞれのデータがどのクラスに属するかを記録する列を新たに作ってみます。
d <- read.table('len_width.txt', header = TRUE) d$len.class <- as.integer(d$length / 10)
d の中身を見てみると、len.classという列ができていて、そこには length の 値に応じて 2 から 7 の値が入っているはずです。
length width sp len.class 1 53.4 18.6 Sp1 5 2 47.6 17.5 Sp1 4 3 62.3 8.9 Sp2 6 4 47.2 10.3 Sp2 4 5 58.6 19.2 Sp1 5 ....
ここで、len.class は最大 5 までとし、6や7のものはみな 5 のクラスとして扱いたい とします(値によって色分けしたグラフを描く場合などにありそうなことです)。
すでに紹介したように、
d[d$len.class > 5]
と書けば、len.class が 5 より大きい行だけが表示されます。また、
d$len.class[d$len.class > 5]
と書けば、len.class のうち、 5 より大きい要素だけ (6 や 7) が表示されます。 やりたいことは、len.class の値が 5より大きい場合(6や7の場合)にこれを 5 に 書き換えることですが、以下のように書けば一括して置換できます。
d$len.class[d$len.class > 5] <- 5
d の内容を確かめてみると、たとえば3行めの len.class の値が 6から 5に 置き換わっていることが分かります。
length width sp len.class 1 53.4 18.6 Sp1 5 2 47.6 17.5 Sp1 4 3 62.3 8.9 Sp2 5 4 47.2 10.3 Sp2 4 5 58.6 19.2 Sp1 5 ....
一般的に言うと、「オブジェクト名[...] <- 値」 という書き方は、オブジェクトの [...] で指定される要素(この例では条件式で指定)を、 <- の後ろに書いた値(この例では 5という数値)で置換せよと言う意味になります。
上で紹介したのはほんとうに基礎の基礎だけですが,これだけでも 理解していれば,たいていのことはできます. そのうえで,さらにいくつか関数を覚えると,よりスマートに作業ができます. R-Tips の, データの加工と抽出の章などを参考にしてください.