| Top Page | プログラミング | R 自動化 目次 | 索引 | 前へ | 次へ |

R でプログラミング:データの一括処理とグラフ描き

11. データファイルの順次処理:ファイル名のいろいろな決め方

まずは前の章の復習です.いくつものデータファイルを読んで処理する場合の基本的な 流れは,

for (...){  #  ファイルごとの繰り返し
    ファイル名を設定する.
    ファイルの内容を読み込む.
    読み込んだデータを使ってなんらかの処理をする.
}

とうものでした.前の章では,読み込むデータファイルの名前を プログラム中に書き並べるという素朴な方法をためしてみました. この章では,ほかの方法を紹介します.

ファイル名を規則的に生成する

ファイル名が規則的についているなら,プログラムのなかで生成することができます. こういうプログラムが書きやすいように,データファイルを作る段階からファイル名に 工夫をしておくとよいでしょう.

プログラム中でのファイル名生成には, sprintf が活躍します. 使い方を復習しておいてください.

まずは思いっきり簡単な例.site_a_01.txt と site_a_02.txt の二つのファイル名を プログラム中で作ってみます.

n <- 2  # 通し番号は1番から2番まで(site_a_01.txt から site_a_02.txtまで).

pdf('figures.pdf')    #  デバイスの用意

for (i in 1:n) {      # i は 1, 2 が順次代入される
    file.name <- sprintf("site_a_%02d.txt", i)     # site_a_01.txt のような文字列ができる
    d <- read.table (file.name, header = TRUE)     # ファイルをデータファイルに読み込む
    plot(main = file.name, d$height, d$diameter, type = 'p')   #  散布図を描く
}

dev.off()             #  デバイスを閉じる

最初に n に 2 を代入しているので,for 構文の {} の中の処理は 2回繰り返されます.file.name の値は繰り返しごとに site_a_01.txt, site_a_02.txt と変化します.

2つだけのファイルではファイル名自動生成のありがたみはほとんどないですが, site_a_01.txt から site_a_10.txt までの10個だったらけっこうありがたいでしょうし, 100個だったらものすごくありがたいはずです.

<練習>

組み合わせワザ

今度は、上の規則的生成方法と、前の章のファイル名列挙方式とを組み合わせてみます.

sites <- c('site_a', 'site_b')  # サイト名のベクトル
n <- 2    # 各サイトで1番から2番までのデータがある.

pdf('figures.pdf')                          #  デバイスの用意

for (site in sites) {  # sites の要素が順次 site に代入される
    for (i in 1:n) {  # i は 1, 2 が順次代入される
        file.name <- sprintf("%s_%02d.txt", site, i)     # サイト名と番号からファイル名を生成
        d <- read.table (file.name, header = TRUE)       # ファイルをデータファイルに読み込む
        plot(main = file.name, d$height, d$diameter, type = 'p')   #  散布図を描く
    }
}

dev.off()       #  デバイスを閉じる

for ループ(繰り返し)を二重にしています.外側のループは,最初に ベクトルで与えてあるサイト名を順番に使うためのもの, 内側のループは,各サイトごとにつくられた01番から02番までのファイルを 順番に指定するためのものです.これで,4個のファイル名が生成されます.

この例で,各サイトのデータファイル数がきっちり揃っていればよいのですが, サイトごとにファイル数がちがっていたらどうしたらよいでしょうか? サイトごとのファイル数をプログラム中に書いておいて…などというのは とても面倒ですね.

簡単な方法は,データファイル数(上のプログラムなら最初に設定している n) を大きめにしておいて,読み込むまえにファイルの有無をチェックし, 存在しなかったらスキップすることです. ファイルのチェックには file.access を使えばよい, というのはすでに前の章で紹介しました.

<練習>

ファイル名を書き並べたファイルを読む

処理したいデータファイルの名前を書き込んだファイルがあれば, これを利用してファイル名を設定していくことができます.

read.table はデータフレームという特別なデータ構造を作ってくれる 関数でしたが,この場合はデータフレームは必要ありません. ただのベクトルのほうが扱いが簡単です. そこで,readLines という関数を利用します. この関数は,引数としてファイル名を渡すと, 一行の内容をひとつの文字列として並べたベクトルを作ってくれます.

たとえば,一行にひとつづつデータファイル名が書いてある files.txt というファイルが あったとします.

files <- readLines('files.txt')

とすれば,各ファイル名を要素とするベクトルが files に代入されます. さっそくこれを利用したプログラムを書いてみましょう.

files <- readLines('files.txt')  #  ファイル名がならんだベクトルを作る

pdf('figures.pdf')    #  デバイスの用意

for (file.name in files) {
    d <- read.table (file.name, header = TRUE)     # ファイルをデータファイルに読み込む
    plot(main = file.name, d$height, d$diameter, type = 'p')      #  散布図を描く
}

dev.off()             #  デバイスを閉じる

readLines を使っているほかは,何も新しいことはありませんね.

<練習>

ディレクトリ中のファイルを調べる

とにかくいろんな名前のファイルがあるんだという場合, ディレクトリ(フォルダ)中のファイル名を調べて片っ端から読んでしまうという方法も あります.これには,list.files という関数を使います. ディレクトリ中の全ファイルの名前が並んだベクトルを返してくれる関数です. 調べたいディレクトリの名前をパラメータに指定することができます. とくに何も指定しないと,現在の作業ディレクトリの中を調べます.

ためしに,入力画面で

list.files()

としてみてください. ファイル名がずらずら表示されたはずです. これらをベクトルにしまってしまえば,あとはこれまでと同様です.

files  <- list.files()    # 現在の作業ディレクトリのファイル一覧を代入

pdf('figures.pdf')                          #  デバイスの用意

for (file.name in files) {
    d <- read.table (file.name, header = TRUE)  # ファイルをデータファイルに読み込む
    plot(main = file.name, d$height, d$diameter, type = 'p')      #  散布図を描く
}

dev.off()                                #  デバイスを閉じる

作業ディレクトリ中にデータファイルしかないのならこれでよいのですが, そのほかにもプログラムファイルだの,画像ファイルだのも入っているかもしれません. そういうものを read.table で読んでもしょうがありません.

そこで,特定の条件にあう名前のファイル(たとえばファイル名の末尾が .txt のもの) だけを読み込み,それ以外は無視するためのコードを書き加えます.

文字列の形式をチェックするには,regexprという関数を利用します. 意味不明の名前かもしれません.これは regular expression, 日本語で正規表現というもの に由来する関数です.なんらかの「パターン」と,調べる対象の文字列とを渡すと, 文字列中に指定された「パターン」が見つかるかどうかを調べてくれるものです. もし見つからなかったら -1, 見つかったら文字列中の何文字めに見つかったかを返します.

…といっても,正規表現というものを知らない人には なんだかよく分かりませんね.私の Perl 入門ページ 正規表現とパターンマッチ のところを見ていただけば,もう少しイメージが掴めるかもしれません.

ここでは,とにかくこう書けばこういうことができる,という例を挙げておきます.

files  <- list.files()    # 現在の作業ディレクトリのファイル一覧を代入

pdf('figures.pdf')                          #  デバイスの用意

for (file.name in files) {
    if (regexpr('\\.txt$', file.name)  < 0) { # ファイル名の最後が '.txt'か?
        next                                 # そうでなければスキップ.
    }
    d <- read.table (file.name, header = TRUE)  # ファイルをデータファイルに読み込む
    plot(main = file.name, d$height, d$diameter, type = 'p')      #  散布図を描く
}

dev.off()                                #  デバイスを閉じる

上の例では,拡張子が .txt となっているファイルだけを読むようにしています. 以下の詳しい説明は,面倒だったら読み飛ばしてください.

regexpr('\\.txt$', file.name) で, ファイル名変数 file.name にしまわれている文字列(ファイル名)に, \\.txt$ というパターンが含まれるかをチェックします.このパターンの 意味するところを解釈してみます.

まず,\\. というのはピリオドを表します.単に . (ピリオド)を書くと, repexpr が解釈する際に「任意の一文字」という特別の意味を持って しまうのですが,その前に \ を書くことで,特別の意味ではなく 「ピリオドそのもの」を表すことができます.ただし,R のプログラム中に引用符で囲まれた 文字列のなかに \ が現れると,\ は特別の意味を持つもの (エスケープ文字)として R が解釈し,関数に渡す以前に 変換してしまいます. regexpr に間違いなく \. という文字列を渡すには, R に勝手なことをさせないために,\ を \ そのものとして扱ってくれ, という指示をします.それが二つ重なっている \ の最初のほうの働きです (なんとややこしいことか).

\\. のうしろの txt という文字列はそのまんま,txt という3文字が並んでいる ことを意味します.そして$ は文字列の最後を表します.これより後ろには, もう文字が無い,ということを表現するためのものです.

これらをつなげて考えると,末尾が .txt となっている文字列が, パターン \\.txt$ にマッチし,regexpr は -1 よりも大きい値を返す,という ことになります.

なお,regexpr は大文字と小文字を区別します.Windows ではファイル名の大文字,小文字は あまり意識しないので,.txt と .TXT が混在していたりするかもしれません. その場合,file.name をそのままチェックするのではなく,大文字を小文字に置き換える 関数 tolowerを使い, regexpr('\.txt$', tolower(file.name)) とすればよいでしょう.


おまけ:拡張子を取りのぞく・置換する

もうひとつ、おまけで正規表現をつかった技を紹介します.

ディレクトリ内のデータファイル名を調べてから読み込む場合,個々のデータファイルごとに 画像ファイルを作るとなると,画像ファイル名はどうしたらよいでしょうか. データファイル名から末尾の .txt などを取り除き,かわりに .jpg とか .png とかをくっつけた文字列が欲しくなります. また,グラフ中には.txt や .dat がついていない文字列を書き込んで おきたいかもしれません.

以下はそのためのプログラムです. 文字列置換のための sub という関数を使っています。 sub は、探すパターン、該当部分を置き換える文字列、対象データを受けとります。 下のプログラムでは、「ピリオド+それ以外の文字」というパターンで拡張子を探し、 その部分を他の文字列に置き換えています。

ff <- '20051022.dat'   # .dat という拡張子がついている。
ff.base <- sub('\\.[^.]*', "", ff)     # ピリオド以降を切り取る("" に置換)
ff.jpg  <- sub('\\.[^.]*', ".jpg", ff) # ピリオド以降を ".jpg" に置換。

ff2.jpg  <- sub('(.*)\\.[^.]*', "\\1.jpg", ff) # 別法。\\1は、( ) で囲まれたバターン とマッチした文字列

入力画面に上のプログラムを貼り付けて実行したあと,ff.base や ff.jpg がどうなっているかを確認してください. ff.base では末尾の .dat がなくなっており, ff.jpg や ff2.jpg では .dat が .jpg に置き換わっているはずです.

ここでは正規表現の詳しい説明はしません. これは使えるな,と思ったらそのままご利用ください.


いくつものデータファイルの解析結果をファイルに記録する

最後に、多量のデータファイルの解析結果をまとめて出力しようという節を作って見ましたが、 実のところ、新しく説明することはありません。これまでに説明したことを組み合わせれば できますよ、というだけです。

前の章とこの章では、いくつものデータファイルを読み込み、それぞれをグラフにすることを 中心に説明してきました。繰り返し構文を使い、繰り返す処理としてファイルを読むことと 描画することを練習しました。

ファイルを読んだあとにやりたい処理は、グラフ作成に限りません。 統計解析用の関数を利用した処理を行いたいこともしばしばあるでしょう。 その場合は、 本章の一番最初に示した擬似コードの、「読み込んだデータを使ってなんらかの処理をする」 のところに、解析と結果出力のコードを書けばよいだけです。

for (...){  #  ファイルごとの繰り返し
    ファイル名を設定する.
    ファイルの内容を読み込む.
    読み込んだデータを統計解析関数に渡し、解析結果を受けとる。
    受けとった解析結果から、必要な部分をファイルなどに書き出す
}

解析結果をファイルに書き出すやりかたは、3章の 計算結果をファイルに記録するのところで説明しました。 6章の グラフ中に式を書き込む で説明した低水準描画関数 text を利用すれば、 描いたグラフのなかに計算結果まで書込むこともできます。

このように、手持ちのワザを組み合わせて使うことで、 できることの範囲は飛躍的に広がります。 それこそがプログラミングの醍醐味と言ってもよいかもしれません。 ぜひ柔軟に頭を使って世界を広げてください。


| Top Page | プログラミング | R 自動化 目次 | 索引 | 前へ | 次へ |