R

トップページ
Google
WWWを検索
サイト内を検索

Rのデータフレームは、リストのように異なる型をまとめることができ、行列のように参照や代入ができる、統計解析によく適したデータ構造です。表の型式でデータを保持できます。標準で使えるため、ビルトイン関数などで広く用いられています。

1 リストとの違い

データフレームの便利さを理解するために、リストの不便さを理解しましょう。リストをつくって、i行j列を参照してみるとします。

lst01 <- with(new.env(), {
    n <- 100
    x <- runif(n)
    z <- runif(n)
    kn <- round(runif(n, min = 0.5, max = 3.5))
    k <- as.factor(letters[kn])
    y <- 1 + x - z + (kn==2)*1 + rnorm(n)
    r <- list(y, x, z, k)
    names(r) <- c("y", "x", "z", "k")
    r
})

慣れていれば

i <- 2; j <- 3
lst01[[j]][i]
[1] 0.3167571
lst01$z[i]
[1] 0.3167571

行列のようにlst01[i, j]と言う風には参照できません。\(i\)行目のサブセットをつくろうと思うと、lst01[i, ]と言う風には参照できないので、

sapply(names(lst01), \(cn){
    lst01[[cn]][i]
})
        y         x         z         k 
0.2543368 0.2747747 0.3167571 1.0000000 

と書くことになります。代入も同様で、気軽にできません。

X <- c(0.78, 0.92, 0.19, "c")
for(j in 1:length(lst01)){
    lst01[[j]][i] <- X[j]
}

データフレームは内部的にはリストの拡張となっているので、リストは簡単にデータフレームに変換できます。変換すると

df01 <- as.data.frame(lst01)
df01[i, j]
[1] c
Levels: a b c
df01[i, ]
     y    x    z k
2 0.78 0.92 0.19 c
df01[i, ] <- X

という風に、行列と同様に簡潔に操作ができるようになります。

ウィンドウ・アプリケーション開発などではリスト構造のデータが便利だったりするのですが、そのままでは統計解析では煩雑で、データフレームでは便利です。1990年代ではSとそのクローンであるRぐらいでしか見られないものですが、近年ではR以外のプログラミング言語でも統計解析を行うパッケージなどではデータフレームを実装するようになっており、コンセプトの正しさが示されています。

1.1 データフレームの欠点

リストは要素としてリストやデータフレームを入れられますが、データフレームはデータ構造を入れることができません。

2 行列との違い

行列と同様に操作ができるようになっており、nrowncolrbindcbindといった関数もそのまま使えます。一方、行列は一次元ベクトルに属性で次元の情報を加えたものなので、一次元ベクトルとして操作することもできますが、データフレームはリストに属性をつけたものなのでそれは出来ません。

3 デーフレームの作成

上の例ではリストをas.dataframeでデータフレーム化しましたが、ベクトルをつないで作ることもできます。

df02 <- data.frame(k = c("a", "b", "c"), v = c(1, 10, 100))

4 ファイルI/O

データフレームは表なので、CSVファイルやタブ区切りのファイルでの保存が可能です1。逆に、CSVファイルやタブ区切りのファイルを読み込むのに過不足ない機能を提供します。リストは複雑な構造にもなりうるためできません。行列もデータ型が一種類になるためできません。統計データの多くは表形式のテキストファイル型式で提供されることもあり、RのファイルI/Oではデータフレームが多く用いられることになります2

4.1 テキストファイルに保存

テキストファイルとしてデータフレームを保存する場合は、write.tableを使います。

write.table(df01, file = "df01.csv", sep = ",", row.names = FALSE)

タブ区切りの場合はsep = "\t"となります。標準ではrownamesで参照できる行名を出力しようとするので、row.names = FALSEで抑制しています。

旧いシステムだと標準ではutf-8での出力にならないことには注意しましょう。

4.2 テキストファイルから読み込む

読み込むときは、read.tableを使います。

df01 <- read.table("df01.csv", sep = ",", header = TRUE)

標準では先頭行が列名として認識しないので、header = TRUEをつけています。

オプションは多様に取れますが、欠損値はNAが入ること、#からはじまる行はコメントとして無視されること、fileEncodingで文字コード指定ができること3、引数skipで先頭から何行かを飛ばすことぐらいを知っておくと、?read.tableでヘルプを見なくても済むことが多いと思います。

4.2.1 数値が文字列として認識されたとき

表計算で保存したテキストファイルは、数値データがダブルコーテーションでくくったカンマ付き文字列になっているときがありますが、

df01$y <- as.numeric(gsub(",", "", df01$y))

と言うようにgsubなどで置換をしてカンマを消して、as.numericで型を変換してしまえばよいです。

なお、引数colClassesで型指定ができますが、as.*関数群で変換できる程度しか融通が利かないです。

5 サブセットの作成

行列と同様に行番号や列番号を指定すれば、その番号のサブセットができます。

# yがゼロ以下の行を抽出
df01[df01$y <= 0, ]
# yがゼロより大きく1以下の行を抽出
df01[df01$y > 0 & df01$y <= 1, ]
# y列とk列を抽出
df01[, c("y", "k")]

subsetをつかうと、行の抽出がすっきり書けます。

subset(df01, y <= 0)

5.1 頭n行,末尾n行

ベクトルと行列でも使えるのですが、頭n行を抽出するheadと、末尾n行を抽出するtailデータフレームの状態確認に便利です。

head(df01, 11) # 頭から11行を抽出
tail(df01, 11) # 末尾から11行を抽出

5.2 欠損データの除外

欠損値がない行をTRUEとするcomplete.casesを使うと簡単です。

subset(df01, complete.cases(df01))

6 ソート

行列でソートすることは稀だと思いますが、データフレームは行列と同様にソートできます。添字で指定した行番号の順序で並び替えたデータフレームが得られるので、orderで順序をつくって指定します。

# k列で昇順ソート
df01[order(df01$k), ]
# k列で昇順、y列で降順ソート
df01[order(df01$k, df01$y, decreasing = c(FALSE, TRUE), method = "radix"), ]

7 列の追加

データフレームの行数と同じ長さのベクトルを、新たな列名を指定して代入すると列の追加になります。

df01$n <- 1:nrow(df01)

8 データフレームの結合

IDなどのキー列をつきあわせて、二つのデータフレームを結合させることができます。

標準では二つのデータフレームで同じ名前の列がキーとして使われます。

merge(df01, df02)

突き合せにつかうキー列の列名が二つのデータフレームで異なる場合は、それぞれ指定します。

df03 <- data.frame(m = c("a", "b", "c"), v = c(1, 10, 100))
merge(df01, df03, by.x = "k", by.y = "m")

9 データフレームの分割

ベクトルをキーにしたデータフレームの分割はsplitで簡単にできます。

データフレームの列を使う場合や、多次元に分割する場合はモデル式を使うのが簡単です。

df_split <- split(df01, ~k)

行数を確認すると、合計100行になっています。

for(i in 1:length(df_split)){
    print(sprintf("%sは%d行", names(df_split)[i], nrow(df_split[[i]])))
}
[1] "aは30行"
[1] "bは36行"
[1] "cは34行"

モデル式ではなく、ベクトルで指定する事もできます。偶奇でわけてみましょう。

oe <- c("even", "odd")[1 + (1:nrow(df01)) %% 2]
(df_oe <- split(df01, oe))

  1. saveRDSできないわけではないです。↩︎

  2. 表形式ではないテキストファイルや、バイナリー型式のファイルも扱えます。↩︎

  3. Windowsの標準であったshift-jisと、現在は一般的なutf-8以外を指定することはまず無いと思いますが、euc-jpなども取れます。扱えるエンコードはiconvluist()で確認できます。↩︎