2020/05/27

R - 入門本編 7章 ファイルからデータを読み込む

実践的には、プロンプトから data.frame() だの c() だのを駆使してデータフレームを構築することはまずありません。当然ながら、外部ファイルからデータを読み込んでデータフレームを構成することができますので、その方法を習得しておきましょう。

read.table() 関数

read.table() は、特定の形をした外部ファイルを読み込んでデータフレームを生成する関数です。外部ファイルの形式としては、以下のようなことが要請されます。
  • 1行目が以下の条件を満たす場合、1行目はデータフレームの列名定義行になり、2行目以降がデータ行になる
    • 1行目の列数がデータ行の列数より1つ少ないこと
  • そうでない場合は、1行目もデータ行とみなされ、列名は V1, V2, ..., と自動的に割り当てられる
  • 各行はいわゆるホワイトスペース (1つ以上の半角スペース、タブ、CR, LF) で区切られた項目の集まりとして認識される
  • データ行の1列目は行ラベルとして認識される
例えば、houses.txt という外部ファイルがあるとしましょう。内容は以下のようなものです。
     Price    Floor     Area   Rooms     Age  Cent.heat
01   52.00    111.0      830     5       6.2      no
02   54.75    128.0      710     5       7.5      no
03   57.50    101.0     1000     5       4.2      no
04   57.50    131.0      690     6       8.8      no
05   59.75     93.0      900     5       1.9     yes
このファイルを読み込んでデータフレームを生成するには、以下のようにします。
> HousePrice <- read.table("houses.txt")
> HousePrice
   Price Floor Area Rooms Age Cent.heat
01 52.00   111  830     5 6.2        no
02 54.75   128  710     5 7.5        no
03 57.50   101 1000     5 4.2        no
04 57.50   131  690     6 8.8        no
05 59.75    93  900     5 1.9       yes
デフォルトの振舞いでは、行ラベルを除く数値項目は数値変量として読み込まれます。そうではない項目 (この例では Cent.heat) は文字列として読み込まれます。

(補足) バージョンによる違い

文字列として読み込まれるようになったのはバージョン4からです。過去のバージョンでは、因子として読み込まれていました。stringsAsFactors 引数を TRUE  と指定すれば、従来通り因子になります。options(stringsAsFactors = TRUE) とする方法もありますが、非推奨です。こちらも合わせてご覧ください

データフレームの実体はリストです。リストの各要素に class() を適用した結果と、全体を unclass() した結果を見てみましょう。
> lapply(HousePrice, class)
$Price
[1] "numeric"

$Floor
[1] "numeric"

$Area
[1] "integer"

$Rooms
[1] "integer"

$Age
[1] "numeric"

$Cent.heat
[1] "character"

> unclass(HousePrice)
$Price
[1] 52.00 54.75 57.50 57.50 59.75

$Floor
[1] 111 128 101 131  93

$Area
[1]  830  710 1000  690  900

$Rooms
[1] 5 5 5 6 5

$Age
[1] 6.2 7.5 4.2 8.8 1.9

$Cent.heat
[1] "no"  "no"  "no"  "no"  "yes"

attr(,"row.names")
[1] "01" "02" "03" "04" "05"
ファイルの1行目の列がリストのコンポーネント名になってています。lapply(HousePrice, class) の結果から、各列がどのクラスになったかが分かります。Cent.heat は character (文字列) になり、他の列は numeric (数値) になりました。データはベクトルで格納されています。行ラベルは row.names という属性に格納されていて、リストのコンポーネントにはなっていません。

行ラベルを付けていないデータも扱うことができます。houses.txt から行ラベルを取り除いた houses2.txt を用意して、データフレームを生成してみます。houses2.txt の内容は次の通りです。
Price    Floor     Area   Rooms     Age  Cent.heat
52.00    111.0      830     5       6.2      no
54.75    128.0      710     5       7.5      no
57.50    101.0     1000     5       4.2      no
57.50    131.0      690     6       8.8      no
59.75     93.0      900     5       1.9     yes
結論から先に言うと、この形式のファイルを期待通りの形でデータフレームに読み込むには、header=TRUE 引数を指定する必要があります。
> HousePrice2 <- read.table("houses2.txt", header=TRUE)
> HousePrice2
  Price Floor Area Rooms Age Cent.heat
1 52.00   111  830     5 6.2        no
2 54.75   128  710     5 7.5        no
3 57.50   101 1000     5 4.2        no
4 57.50   131  690     6 8.8        no
5 59.75    93  900     5 1.9       yes
> lapply(HousePrice2, class)
$Price
[1] "numeric"

$Floor
[1] "numeric"

$Area
[1] "integer"

$Rooms
[1] "integer"

$Age
[1] "numeric"

$Cent.heat
[1] "character"
header=TRUE は、1行目が列名を表す行であることを明示するための引数です。 houses2.txt は、全行の列数が等しいため、デフォルトの振舞いでは1行目もデータ行と見做され、期待した構造では読み込まれません。試しにやってみると、次のようになります。
> HousesNG <- read.table("houses2.txt")
> HousesNG
     V1    V2   V3    V4  V5        V6
1 Price Floor Area Rooms Age Cent.heat
2 52.00 111.0  830     5 6.2        no
3 54.75 128.0  710     5 7.5        no
4 57.50 101.0 1000     5 4.2        no
5 57.50 131.0  690     6 8.8        no
6 59.75  93.0  900     5 1.9       yes
> lapply(HousesNG, class)
$V1
[1] "character"

$V2
[1] "character"

$V3
[1] "character"

$V4
[1] "character"

$V5
[1] "character"

$V6
[1] "character"
1行目がデータ行になり、列名の指定が無いことになるので、列名は V1, V2, ..., V6 が割り当てられます。1行目がデータ行になったことによって、どの列も文字列要素が混じっていることになり、全列とも文字列扱いになってしまいました。

scan() 関数

scan() は、より低水準の外部ファイル読み込み関数です。データ行の項目数が均一で、各項目のモードも事前に明確であれば、scan() を用いて項目ごとのベクトルとして読み込むことができます。

例えば上の houses.txt を scan() で読み込むとしたら、以下のようになります。
> HousePriceScan <- scan("houses.txt", list("", Price=0, Floor=0, Area=0, Rooms=0, Age=0, Cent.heat=""), skip=1)
Read 5 records
> HousePriceScan
[[1]]
[1] "01" "02" "03" "04" "05"

$Price
[1] 52.00 54.75 57.50 57.50 59.75

$Floor
[1] 111 128 101 131  93

$Area
[1]  830  710 1000  690  900

$Rooms
[1] 5 5 5 6 5

$Age
[1] 6.2 7.5 4.2 8.8 1.9

$Cent.heat
[1] "no"  "no"  "no"  "no"  "yes"

skip=1 は先頭から1行読み飛ばす指定です。scan() では列名定義行が扱えないので、読み飛ばしています。2つ目の引数に与えているリストは、各項目のモードを scan() に教えるためのダミーリストです。このリストのコンポーネント名が、生成されるリストのコンポーネント名に引き継がれます。
リストではなくベクトルを与えることもでき、その場合は結果はベクトルで返ります (改行は関係なくなります)。ファイルに含まれる値の数の分、この引数のベクトルの要素が反復適用されて読み込まれます。例えばスカラーの 0 を指定すれば、全ての列が数値項目であることが期待され、その通りであれば数値ベクトルが返ります。

組込みデータセットにアクセスする

R には組み込みデータセットというものが用意されています。基本的なものは datasets パッケージに含まれており、他のパッケージも組み込みデータセットを持っていることがあります。datasets パッケージ内のデータセットの一覧は、data() で見ることができます。他のパッケージのデータセットの一覧であれば data(package = "boot") のようにパッケージを指定すれば確認できます。data(package = .packages(all.available = TRUE)) とすれば、全ての利用可能なパッケージからデータセットを探して一覧にしてくれます。
データセットを読み込むには、data(infert) のようにデータセット名を指定します。datasets 以外のパッケージから読み込む場合は、data(amis, package="boot") のように package タグを指定してください。データセットを読み込むと、1つ以上の R のオブジェクトが読み込まれます。通常はデータセットと同名のデータフレーム1つであることがほとんどですが、そうでないこともあります。何が読み込まれるかはデータセット名でヘルプを引けばわかるはずです。なお、library() 関数を用いてパッケージを検索リストに入れれば、そのパッケージ内のデータセットは package タグで指定することなく読み込めます。

さらに言えば、検索リスト上に存在するデータセットは、読み込まなくてもその名称で参照することは可能です。data() での読み込みは、データセットの複製をデフォルトの名前空間上に作っていることになります。変更を加えるような付値を行っても自動的に読み込まれます。
> ls()
character(0)
> airmiles
Time Series:
Start = 1937
End = 1960
Frequency = 1
 [1]   412   480   683  1052  1385  1418  1634  2178  3362  5948  6109  5981
[13]  6753  8003 10566 12528 14760 16769 19819 22362 25340 25343 29269 30514
> ls()
character(0)
> airmiles[1] <- 0
> airmiles
Time Series:
Start = 1937
End = 1960
Frequency = 1
 [1]     0   480   683  1052  1385  1418  1634  2178  3362  5948  6109  5981
[13]  6753  8003 10566 12528 14760 16769 19819 22362 25340 25343 29269 30514
> ls()
[1] "airmiles"
> rm(airmiles)
> ls()
character(0)
> airmiles
Time Series:
Start = 1937
End = 1960
Frequency = 1
 [1]   412   480   683  1052  1385  1418  1634  2178  3362  5948  6109  5981
[13]  6753  8003 10566 12528 14760 16769 19819 22362 25340 25343 29269 30514

最初の ls() で、デフォルトの名前空間には何も付値されていないことが分かります (1〜2行目)。airmiles という名前を参照すると、ts クラスのデータが参照されました (3〜9行目)。これは datasets パッケージに定義されています。datasets パッケージは検索リスト上にあるので、直接参照することができます。参照後に ls() をしても、やはり付値されていません (10〜11行目)。参照しただけでは複製は作られません。12行目で最初の要素を 0 に書き換えてみました。airmiles を参照してみると、書き変わっていることが分かります (13〜19行目)。ここで改めて ls() してみると、airmiles という名前で付値されていることが確認できます (20〜21行目)。rm(airmiles) として消してみると、デフォルトの名前空間から消えます (22〜24行目)。もう一度 airmiles を参照してみると、最初の要素が元に戻っています (25〜31行目)。これはデフォルト名前空間から airmiles が消えたことにより、また datasets パッケージの airmiles を参照するようになったためです。

データを編集する

edit() はテキストエディタを呼び出す関数ですが、データフレームや行列に対して呼び出されたときは、スプレッドシートが起動するようになっています。
例えば Windows の場合は、以下のようになります。
> HousePrice4 <- edit(HousePrice)

edit() にデータフレームを渡すと、このようにスプレッドシートが起動します。このスプレッドシート上で Cent.heat[1] を yes に変更して、ウィンドウを閉じてみましょう。

edit() の結果を HousePrice4 に付値するようにしていたので、HousePrice4 を参照してみます。
> HousePrice4
   Price Floor Area Rooms Age Cent.heat
01 52.00   111  830     5 6.2       yes
02 54.75   128  710     5 7.5        no
03 57.50   101 1000     5 4.2        no
04 57.50   131  690     6 8.8        no
05 59.75    93  900     5 1.9       yes

確かに変更されていることが確認できました。
例えば、DF <- edit(data.frame()) のようにすれば、新しいデータフレームをスプレッドシートから定義することもできます。