データフレーム Tips 大全
R の多くの関数はデータフレームと呼ばれるオブジェクトを対象とする.データフレームは、同じ長さの複数の数値ベクトル,文字ベクトル等を成分とする、data.frame クラス属性を持つオブジェクト(実体はリスト)であるが,そのものとしては行列のような外見を持つ.各行・列はラベルを必ず持ち,それを用いた添字操作が可能である.データフレームの各行は一組の観測値(case)を表現する。データフレームの各列は一つの変数(項目)を表現する。
attach 関数はデータフレームの成分を現在の環境中に登録する。detach は逆に抹消する。
> data(swiss) # 組み込みデータ swiss を読み込み > swiss # swiss データは5成分からなるデータフレーム Fertility Agriculture Examination Education Catholic Courtelary 80.2 17.0 15 12 9.96 Delemont 83.1 45.1 6 9 84.84 ...... (以下略) .............................. > swiss$Fertility # 成分 Fertility の表示 [1] 80.2 83.1 92.5 85.8 76.9 76.1 83.8 92.4 82.4 82.9 87.1 64.1 66.9 68.9 61.7 [16] 68.3 71.7 55.7 54.3 65.1 65.5 65.0 56.6 57.4 72.5 74.2 72.0 60.5 58.3 65.4 [31] 75.5 69.3 77.3 70.5 79.4 65.0 92.2 79.3 70.4 65.7 72.7 64.4 77.6 67.6 35.0 [46] 44.7 42.8 > Fertility Error: Object "Fertility" not found # Fertility という変数は無いのでエラー > attach(swiss) # swiss データの各成分を成分ラベルで参照できるようにする > Fertility # Fertility という変数ができた [1] 80.2 83.1 92.5 85.8 76.9 76.1 83.8 92.4 82.4 82.9 87.1 64.1 66.9 68.9 61.7 [16] 68.3 71.7 55.7 54.3 65.1 65.5 65.0 56.6 57.4 72.5 74.2 72.0 60.5 58.3 65.4 [31] 75.5 69.3 77.3 70.5 79.4 65.0 92.2 79.3 70.4 65.7 72.7 64.4 77.6 67.6 35.0 [46] 44.7 42.8 > detach(swiss) # もし不要で邪魔なら成分名で参照できないようにする
attach 関数に代わる機能 with はデータフレーム等から専用の環境を作り、その中で評価を行なう。結果として attach 関数無しで、成分を参照できる。with 関数が終了すればこの環境もなくなる。
注意: ループ中等で同じデータフレームを何度も attach する場合は、一まとまりの処理が終り、不要になったらこまめに detach (例えば on.exit(detach(swiss)) を関数本体に入れておく)しておかないと処理時間が段々遅くなるという報告があるので注意(検索パスが段々長くなるためらしい)。with 利用ではそうした問題は起きない。
> library(MASS) # アドオンパッケージ MASS 読み込み > data(anorexia) # anorexia は三成分 Treat, Prewt, Postwt を持つデータフレーム > with(anorexia, { # データフレーム anorexia から専用の環境を作り、その中で以下を評価 anorex.1 <- glm(Postwt ~ Prewt + Treat + offset(Prewt), family = gaussian) summary(anorex.1) })
データフレーム変数を得る基本は、特別の形式をもつデータファイルを read.table() 関数で読み込むことである。
行ラベルの無いデータファイル "temp1.data" の内容
Price Floor Area Ch 52.0 111.0 830 yes 54.8 128.0 710 no 57.5 101.0 1000 yes 59.3 131.0 900 no
オプション header=TRUE 付きでデータフレームとして読み込む。最初の行は見出しで,列ラベルとされる。行ラベルは与えられておらず,自動的にヘッダー(通し番号 1,2,3,4)が付け加えられる
> HP <- read.table("temp1.data", header=TRUE) > HP Price Floor Area Ch # 形式的に 4x4 行列.行・ラベルが自動的に付加される 1 52.0 111 830 yes 2 54.8 128 710 no 3 57.5 101 1000 yes 4 59.3 131 900 no > HP$Price # 第一列.HP[[1]] でも良い。 [1] 52.0 54.8 57.5 59.3 # 数字は数値として読み込まれる > HP[["Ch"]] # これも可.第4列 [1] yes no yes no Levels: no yes # 文字列は因子として読み込まれる > HP[1,] # 第一行.列ラベルが付く Price Floor Area Ch 1 52 111 830 yes > HP[2,] Price Floor Area Ch 2 54.8 128 710 no > HP[2,2] # 第2列第2行の要素 [1] 128 > HP[2,"Floor"] # この形式も可 [1] 128 > dim(HP) # 行列と同様次元を持つ [1] 4 4
行ラベル "Am", "St", "Yo", "Ht" 付きのデータファイル "temp2.data" の内容
Price Floor Area Ch Am 52.0 111.0 830 yes St 54.8 128.0 710 no Yo 57.5 101.0 1000 yes Ht 59.3 131.0 900 no
(既定の) header=FALSE で読み込み
> HP <- read.table("temp2.data") > HP Price Floor Area Ch Am 52.0 111 830 yes St 54.8 128 710 no Yo 57.5 101 1000 yes Ht 59.3 131 900 no > HP[2,2] # 第2行第2列成分 [1] 128 > HP["St","Area"] # 第2行第3列成分 [1] 710 > HP["St","Ch"] # 第2行第4列成分 [1] no Levels: no yes
もう一つの行ラベル付きデータファイル "temp3.data"。行ラベルは文字列 "32", "13", "5", "71" と見なされる
Price Floor Area Ch 32 52.0 111.0 830 yes 13 54.8 128.0 710 no 5 57.5 101.0 1000 yes 71 59.3 131.0 900 no
> HP <- read.table("temp3.data") # 既定値の header=FALSE で読み込み > HP Price Floor Area Ch 32 52.0 111 830 yes 13 54.8 128 710 no 5 57.5 101 1000 yes 71 59.3 131 900 no > HP[32,2] # エラー [1] NA > HP["32",2] # 正しい.HP[1,2] でも良い [1] 111
a 1 2 yes # 列ラベルの無いデータファイル "temp.data" b 2 3 no c 5 7 yes
> x=read.table("temp.data") # 既定の header=FALSE で読み込み > x V1 V2 V3 V4 # 列(変数名)ラベルが自動的につけられる 1 a 1 2 yes 2 b 2 3 no 3 c 5 7 yes > y=read.table("temp.data",header=TRUE) > y a X1 X2 yes # ナンセンスな結果 1 b 2 3 no 2 c 5 7 yes
> data.frame(vx = 1:4, vy = rnorm(4)) # 4x2 のデータフレーム。列ラベル vx,vy vx vy 1 1 -0.8481768 2 2 0.8831475 3 3 0.2178012 4 4 0.2753476
> x [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 > data.frame(x) X1 X2 X3 1 1 5 9 2 2 6 10 3 3 7 11 4 4 8 12 > data.frame(x,row.names=c("a","b","c","d")) # 行ラベルを指定 X1 X2 X3 # 列ラベルは後から変更するしかない(?) a 1 5 9 # x が列ラベルを持つ行列なら、それを継承する b 2 6 10 c 3 7 11 d 4 8 12 > x=matrix(1:4, c(2,2), dimnames=list(c("Ca","Cu"),c("Ag","Pt"))) > x # 行・列ラベル付き行列 Ag Pt Ca 1 3 Cu 2 4 > data.frame(x) # データフレームは行・列ラベルを継承する Ag Pt Ca 1 3 Cu 2 4
MASS ライブラリ中の write.matrix 関数も write.table 関数と同様の機能を持つ。
> x <- matrix(1:12, ncol=3) # 数値行列 > x [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 > write.table(x, file="temp.dataframe") # x をファイルに書き出す(同時にデータフレームに強制変換される) > xx <- read.table("temp.dataframe") # 再び読み込み > xx # データフレームになっている X1 X2 X3 # 変数ラベルが付け加えられている 1 1 5 9 2 2 6 10 3 3 7 11 4 4 8 12
ファイル temp.dataframe の中身は以下のようになっている.
"X1" "X2" "X3" "1" 1 5 9 "2" 2 6 10 "3" 3 7 11 "4" 4 8 12
read.table() に対して関数 write.table() や write() で実現できる. 例えば以下の様なデータ data.txt が( C:/ に)あったとする.
0.9, 1.2, 1.9 1.3, 1.6, 2.7 2.0, 3.5, 3.7 1.8, 4.0, 3.1 0.9, 1.2, 1.9 1.3, 1.6, 2.7 2.0, 3.5, 3.7 1.8, 4.0, 3.1 2.2, 5.6, 3.5 2.2, 5.6, 3.5 3.5, 5.7, 7.5 1.9, 6.7, 1.2 2.7, 7.5, 3.7 2.1, 8.5, 0.6 3.6, 9.7, 5.1
このデータ data.txt を read.table() で読み込んで, write.table() でファイル output.txt に出力してみる.
x <- read.table("C:/data.txt") x V1 V2 V3 1 0.9, 1.2, 1.9 2 1.3, 1.6, 2.7 3 2.0, 3.5, 3.7 4 1.8, 4.0, 3.1 5 0.9, 1.2, 1.9 6 1.3, 1.6, 2.7 7 2.0, 3.5, 3.7 8 1.8, 4.0, 3.1 9 2.2, 5.6, 3.5 10 2.2, 5.6, 3.5 11 3.5, 5.7, 7.5 12 1.9, 6.7, 1.2 13 2.7, 7.5, 3.7 14 2.1, 8.5, 0.6 15 3.6, 9.7, 5.1 write.table(x,"C:/output.txt") # quote=F にしないと,要素に "" がついてしまう # append = F にするとファイルに上書きする write.table(x,"C:/output.txt", append=T, quote=F, col.names=F)
データフレームを出力する際は write.table() が使いやすいが,リストをファイルに出力する場合は write() が使いやすい.それぞれの関数の書式は以下のようになっている.『出力するファイルのパス』 は,空文字列 "" なら標準出力に出力される・
write(リスト名, "出力するファイルのパス", append = T, ncolumns = 一行の要素の数) write.table(データフレーム名, "出力するファイルのパス", append=T, quote=F, col.names=F)
x <- matrix(1:12, ncol=3) x [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 write(x, file="x.data") scan("x.data") # scan 関数で再読み込み(結果はベクトルになっている) Read 12 items [1] 1 2 3 4 5 6 7 8 9 10 11 12 xx <- matrix(scan("x.data"), ncol=3) # また行列に変換 Read 12 items xx [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 # ファイルそのものを直接編集したいのでなければ次のようにする方が # x の構造を直接記録してくれるので世話がない save(x, file="temp.data") # ファイル temp.data はテキストデータではない! rm(x) load("temp.data") # オブジェクト x が復元される
リストに入ったデータを,例えばカンマ ,で区切ってファイルに出力する場合,{データ ,カンマ,データ ,カンマ,・・・} というリストを作っておいてファイルに出力すればよい.
x <- c(1:9) out <- NULL for (i in 1:(length(x)-1)) { out <- cbind(out, x[i]) out <- cbind(out, ",") } out <- cbind(out, x[length(x)]) write(out, file="
外部から取り込んだデータフレームを使ったときの原因不明エラー時に
> df x y z 1 l 4 7 2 2 5 8 3 3 6 9 > sum(df$x) Summary.factor(c(3L, 1L, 2L), na.rm = FALSE) でエラー: ‘sum’ は因子に対しては無意味です > sum$x #何だこのエラーは? > str(df) #str()で確認 'data.frame': 3 obs. of 3 variables: $ x: Factor w/ 3 levels "2","3","l": 3 1 2 #1に見えたのはlだったんだ。 $ y: num 4 5 6 $ z: num 7 8 9 > summary(df) #summary()で確認してもよい x y z 2:1 Min. :4.0 Min. :7.0 3:1 1st Qu.:4.5 1st Qu.:7.5 l:1 Median :5.0 Median :8.0 Mean :5.0 Mean :8.0 3rd Qu.:5.5 3rd Qu.:8.5 Max. :6.0 Max. :9.0
欠損値を除いたデータフレームを作るということ
> x <- c(1,3,2,1,NA,4) > y <- c(2,NA,3,2,5,4) > z <- c(5,3,2,3,2,1) > df <- data.frame(x,y,z) > df x y z 1 1 2 5 2 3 NA 3 3 2 3 2 4 1 2 3 5 NA 5 2 6 4 4 1 > dfc <- subset(df, complete.cases(df)) > dfc x y z 1 1 2 5 3 2 3 2 4 1 2 3 6 4 4 1 > dfc2 <- df[complete.cases(df),] > dfc2 x y z 1 1 2 5 3 2 3 2 4 1 2 3 6 4 4 1
追記(2012-01-01)
> dfc3 <- na.omit(df) > dfc3 x y z 1 1 2 5 3 2 3 2 4 1 2 3 6 4 4 1
条件の指定はいろいろあるが,
抽出されるデータフレーム <- 元のデータフレーム[条件, ]
とする。条件の後の「,」を忘れないように注意。
その意味では,上の例や次の例のように subset 関数を使うのがよい。
subset には,次に示すような別の利点もある。
条件としては例えば,
元のデータフレーム[項目] == 値
とか,単に 10:15 とかでもよい。
# テスト用のデータフレームを作る > x <- c(1,3,2,1,NA,4) > y <- c(2,NA,3,2,5,4) > z <- c(5,3,2,3,2,1) > df <- data.frame(x,y,z) > df # 以下のような内容になる x y z 1 1 2 5 2 3 NA 3 3 2 3 2 4 1 2 3 5 NA 5 2 6 4 4 1 # z が 3 であるデータ(行)を抽出 > z.eq.3 <- df[df["z"] == 3,] > z.eq.3 x y z 2 3 NA 3 4 1 2 3 # z が 3 以外のデータ(行)を抽出 > z.ne.3 <- df[df["z"] != 3,] > z.ne.3 x y z 1 1 2 5 3 2 3 2 5 NA 5 2 6 4 4 1
# テスト用のデータフレームを作る > x <- c(1,3,2,1,NA,4) > y <- c(2,NA,3,2,5,4) > z <- c(5,3,2,3,2,1) > df <- data.frame(x,y,z) # このような内容 > df x y z 1 1 2 5 2 3 NA 3 3 2 3 2 4 1 2 3 5 NA 5 2 6 4 4 1 # y が 2 である行を取り出そうとすると > df[df["y"]==2,] x y z 1 1 2 5 NA NA NA NA # わお,変なものも取り出される。しかも元の行とは内容が違うし。 4 1 2 3 # subset を使うと書き方もわかりやすいし,変なものも選ばれない # つまり NA は NA として扱われる > subset(df, df["y"]==2) x y z 1 1 2 5 4 1 2 3
subsetを使ってデータセットの一部を取り出すと、以下のように空の因子レベルが残ってしまう。これを取り除くにはfactor()を使う。
IS2$spray <- droplevels(IS2$spray) でもよい。droplevels( ) は実際には factor( ) なのだけど。(覚えるためには,機能を表す関数名 droplevels の方がよいのだろう)
> IS2 <- subset(InsectSprays, InsectSprays$spray != "D") > IS2$spray [1] A A A A A A A A A A A A B B B B B B B B B B B B C C C C C C [31] C C C C C C E E E E E E E E E E E E F F F F F F F F F F F F Levels: A B C D E F > summary(IS2$spray) A B C D E F 12 12 12 0 12 12 > IS2$spray <- factor(IS2$spray) > summary(IS2$spray) A B C E F 12 12 12 12 12
> aq <- transform(airquality, Month = factor(Month, labels = month.abb[5:9])) > aq <- subset(aq, Month != "Jul") > table(aq$Month) May Jun Jul Aug Sep 31 30 0 31 30 > table(droplevels(aq)$Month) May Jun Aug Sep 31 30 31 30
抽出したものがベクトルかデータフレームか把握してないと、以後の処理を間違える。
> x <- c(1,2,3) > y <- c(4,5,6) > df <- data.frame(x,y) > df x y 1 1 4 2 2 5 3 3 6 > df$x [1] 1 2 3 #ベクトル > df[,1] [1] 1 2 3 #ベクトル > df[1,] x y 1 1 4 #データフレーム。以下も1列だけれどデータフレーム。 > df[1] x 1 1 2 2 3 3 > df['x'] x 1 1 2 2 3 3
データフレームに新しいケースを加えたり、既存のケースを置き換える際には一寸注意がいる。各ケースに付値するオブジェクトは原則としてデータフレームと同じ構造を持つリストである必要がある。
> Df <- data.frame(A=1:3, B=c("a","b","c")) > Df A B 1 1 a 2 2 b 3 3 c > str(Df) 'data.frame': 3 obs. of 2 variables: $ A: int 1 2 3 $ B: Factor w/ 3 levels "a","b","c": 1 2 3 # 文字変数は既定では因子(整数値コード)化される > levels(Df$B) [1] "a" "b" "c" > Df[2,] <- list(4,"a") # 第2ケースを置き換え > Df A B 1 1 a 2 4 a # 問題無し 3 3 c
もし因子水準に無い文字列を含むリストで置き換えると問題が起きる。
> Df[2,] <- list(4,"d") # 既存のケースを置き換える Warning message: 無効な因子水準です。NA が発生しました in: `[<-.factor`(`*tmp*`, iseq, value = "d") > Df A B 1 1 a 2 4 <NA> # 第2ケースは意図したようにはならない("d" は因子水準中にないから) 3 3 c
一つの解決策は因子水準を拡大してやること。
> levels(Df$B) <- c(levels(Df$B),"d") > levels(Df$B) [1] "a" "b" "c" "d" > Df[2,] <- y > Df A B 1 1 a 2 4 d # 成功! 3 3 c
もう一つは、「そのまま関数」I() を用いて、因子化しないように指示すること。
> Df <- data.frame(A=1:3, B=I(c("a","b","c"))) # 文字列変数を因子化しないように指示 > str(Df) 'data.frame': 3 obs. of 2 variables: $ A: int 1 2 3 $ B:Class 'AsIs' chr [1:3] "a" "b" "c" # 文字列そのままが記録されている > Df[2,] <- list(6,"e") > Df A B 1 1 a 2 6 e 3 3 c
もしデータフレームの全ての変数が同じ型(数値、文字列、論理値)ならば、リストでなくベクトルで置き換えることができる。
> Df <- data.frame(A=1:3, B=runif(3)) > Df A B 1 1 0.3963373 2 2 0.6295562 3 3 0.2628651 > Df[2,] <- c(4, 3.1415) # 既存のケースを変更 > Df A B 1 1 0.3963373 2 4 3.1415000 3 3 0.2628651 > Df[4,] <- c(5, 2.71828) # 新しいケースを加える > Df A B 1 1 0.3963373 2 4 3.1415000 3 3 0.2628651 4 5 2.7182800
文字列の場合は先の因子化問題に注意する必要がある。
> Df <- data.frame(A=I(c("x","y","z")), B=I(c("a","b","c"))) > Df[2,] <- c("foo", "bar") # 既存のケースを置き換え > Df[4,] <- c("yes", "no") # 新しいケースを加える > Df A B 1 x a 2 foo bar 3 z c 4 yes no > Df[6,] <- c("male", "female") # 第5列無しに第6列を加えると: > Df A B 1 x a 2 foo bar 3 z c 4 yes no 5 <NA> <NA> # (文字型の)NA値からなるケースが挿入される 6 male female
> x <- data.frame(a=1:3, b=letters[1:3]) > x["c"] <- c(TRUE,TRUE,FALSE) > x a b c 1 1 a TRUE 2 2 b TRUE 3 3 c FALSE
> x <- data.frame(a=1:3, b=letters[1:3]) > x <- transform(x, c=c(TRUE,TRUE,FALSE)) > x a b c 1 1 a TRUE 2 2 b TRUE 3 3 c FALSE
> x <- data.frame(a=1:3, b=letters[1:3]) > z <- cbind(x, c(TRUE,TRUE,FALSE)) > names(z) <- c("a","b","c") # 変数ラベルを調整 > z a b c 1 1 a TRUE 2 2 b TRUE 3 3 c FALSE
> x <- data.frame(a=1:3, b=letters[1:3]) > y <- data.frame(c=c(TRUE,TRUE,FALSE)) > cbind(x,y) a b c 1 1 a TRUE 2 2 b TRUE 3 3 c FALSE
最後のに似ているけど,以下のようにやれば1ステップでできる(... 河童の屁 2009/10/20)
> x <- data.frame(a=1:3, b=letters[1:3]) > x <- data.frame(x, c=c(TRUE,TRUE,FALSE)) > x a b c 1 1 a TRUE 2 2 b TRUE 3 3 c FALSE
> x <- data.frame(a=1:3, b=letters[1:3]) > y <- data.frame(c=c(TRUE,TRUE,FALSE), d=LETTERS[1:3]) > cbind(x,y) a b c d 1 1 a TRUE A 2 2 b TRUE B 3 3 c FALSE C > data.frame(x,y) a b c d 1 1 a TRUE A 2 2 b TRUE B 3 3 c FALSE C > c(x,y) # リストとして結合される $a [1] 1 2 3 $b [1] a b c Levels: a b c $c [1] TRUE TRUE FALSE $d [1] A B C Levels: A B C
> df<-data.frame(var1=c(4,1),var2=c(NA,2.0),var3=c("yes",NA)) # オリジナルのデータフレーム > df[1,] # 変数を確認 var1 var2 var3 1 4 NA yes > df<-data.frame(var1=df$var1,var3=df$var3) # データフレーム再定義:var2の列を削除 > df[1,] # 変数を確認 var1 var3 1 4 yes
> df<-data.frame(var1=c(4,1),var2=c(NA,2.0),var3=c("yes",NA)) # オリジナルのデータフレーム > df[1,] # 変数を確認 var1 var2 var3 1 4 NA yes > df$var2 <- NULL > df[1,] # 変数を確認 var1 var3 1 4 yes
特別に,妙な解を工夫して記載しているのだろうか(... 河童の屁)
2008.09.18 の方法は,元のデータフレームに列がいっぱいあったら,大変なタイプ量
2009.10.19 の方法は,列を削除して別のデータフレームを作りたいときに,まえもってコピーをして,そのデータフレームの特定の列を削除するということになり,面倒
オーソドックスな解がなぜ紹介されていないのか,気になるが以下のようにすれば問題なし
> df<-data.frame(var1=c(4,1),var2=c(NA,2.0),var3=c("yes",NA)) # オリジナルのデータフレーム > df[1,] # 変数を確認 var1 var2 var3 1 4 NA yes > df <- df[, -2] # 要するに,2 列目を除くということ > df[1,] # 変数を確認 var1 var3 1 4 yes
何番目の列というのを数えるのが面倒だというなら,以下のようにも
> df<-data.frame(var1=c(4,1),var2=c(NA,2.0),var3=c("yes",NA)) # オリジナルのデータフレーム > df[1,] # 変数を確認 var1 var2 var3 1 4 NA yes > df <- df[setdiff(colnames(df), "var2")] # "var2" ではない列を全部ということ > df[1,] # 変数を確認 var1 var3 1 4 yes
> df x y 1 1 3 2 2 4 > colnames(df) <- c("a","b") #全部を変更 > df a b 1 1 3 2 2 4 > colnames(df)[1] <- "A" #1つだけ変更 > df A b 1 1 3 2 2 4