データフレームは「各列を要素とするリスト」として振る舞いますが「各行を要素とするリスト」として扱いたい場合もあります.そこで,データフレームを「各行を要素とするリスト」に変換します.また,変換したリストをデータフレームに戻します.
ここでは iris
データセットを例に説明するので,先に読み込んでおきます.
data(iris)
> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
データフレーム ⇒ リスト
データフレームの各行を要素とするリストに変換します.
Zip <- function(...) Map(list, ...)
iris.list <- do.call(Zip, iris)
それぞれの行は異なる型の値から構成されているので,行自身もリストで表現しています.そのため,iris.list
にはリストのリストが代入されます.
> iris.list[[1]]
$Sepal.Length
[1] 5.1
$Sepal.Width
[1] 3.5
$Petal.Length
[1] 1.4
$Petal.Width
[1] 0.2
$Species
[1] setosa
Levels: setosa versicolor virginica
この方法でリスト化すると,列名は保存されますが,行名は失われます.
リスト ⇒ データフレーム
リストに変換したデータフレームを,元に戻します.
Unzip <- function(...) rbind(data.frame(), ...)
iris.frame <- do.call(Unzip, iris.list)
単純に do.call(rbind, iris.list)
とすると,データフレームではなく,行列(リストに dim
属性を与えて行列にしたもの)になります.今回はデータフレームとして扱いたいので,空のデータフレームに rbind
しています.
この方法でデータフレーム化すると行名が自動的に割り振られてしまうので,必要ない場合は rawnames(iris.frame) <- NULL
で削除してください.
動機
関数型プログラミング?を少し勉強したので「R でも高階関数を使ってそれっぽくコーディングするぜ!」と思ったのですが,サンプル(データフレームの各行)をフィルタリングする処理を書こうとするとデータフレームのままではやりづらかったのでリストに変換しました.
data(iris)
Zip <- function(...) Map(list, ...)
iris.list <- do.call(Zip, iris)
f <- function(sample) sample[["Sepal.Length"]] > 6
iris.list.sub <- Filter(f, iris.list)
Unzip <- function(...) rbind(data.frame(), ...)
iris.sub <- do.call(Unzip, iris.list.sub)
が,無理せずデータフレームの機能を使うほうが簡単ですね.
iris.sub <- iris[which(iris[["Sepal.Length"]] > 6), ]
今まで通り書くか(´・ω・`)