備忘ログ

チラシの裏的備忘録&メモ

メモ:Rで長さの異なるlistをデータフレームに変換したい

Rで長さの異なるlistをデータフレームに変換したいということがあったので方法のメモ。

a <- list(A = 1,
          B = 1:2,
          C = 1:3,
          D = c(1, NA, 3:4),
          E = c(1, NA))

こんなリストがあったときに

次のようなデータフレームがほしいとき(データフレーム1、リストそれぞれ行になるような)

##   .id 1  2  3  4
## 1   A 1 NA NA NA
## 2   B 1  2 NA NA
## 3   C 1  2  3 NA
## 4   D 1 NA  3  4
## 5   E 1 NA NA NA

または(データフレーム2、リストそれぞれが列になるような)

##    A  B  C  D  E
## 1  1  1  1  1  1
## 2 NA  2  2 NA NA
## 3 NA NA  3  3 NA
## 4 NA NA NA  4 NA

こういうのがほしいと思ったときの方法のメモ。

要するに、長さが異なるリストをデータフレーム化したときに長さが短いところは長さが長いところに併せての頃の箇所をNAで埋めてほしいと思った。

長さが同じリストの場合は次のようにすると簡単にできる。

b <- list(a = 1:4,
          b = 5:8,
          c = 9:12)

データフレーム1の要な場合は

data.frame(do.call(rbind, b)) 
##   X1 X2 X3 X4
## a  1  2  3  4
## b  5  6  7  8
## c  9 10 11 12

あとは、必要なら行名を取り出してID列を作ってくっつければ良い気がする。必要ないなら列名を消しても良いと思う。

データフレーム2のような形なら、

data.frame(b)
##   a b  c
## 1 1 5  9
## 2 2 6 10
## 3 3 7 11
## 4 4 8 12

でできる。

しかし、これを長さが異なるリストで実行するとデータフレーム1の場合。

data.frame(do.call(rbind, a))
## Warning in (function (..., deparse.level = 1) : number of columns of result is
## not a multiple of vector length (arg 3)

##   X1 X2 X3 X4
## A  1  1  1  1
## B  1  2  1  2
## C  1  2  3  1
## D  1 NA  3  4
## E  1 NA  1 NA

と、長さが同じになるようにリサイクルされたりして勝手に足りない部分が埋められてしまう。

データフレーム2の場合はそもそも長さが異なるので実行できないとErrorがでる。

data.frame(a)
## Error in (function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE, :  引数に異なる列数のデータフレームが含まれています: 1, 2, 3, 4

調べるといろいろな手法があるが、とりあえずデータフレーム1のようなものの場合、今はあまり使われなくなった{plyr}パッケージのldply関数を使うと簡単な気がする 。

plyr::ldply(a, rbind)
##   .id 1  2  3  4
## 1   A 1 NA NA NA
## 2   B 1  2 NA NA
## 3   C 1  2  3 NA
## 4   D 1 NA  3  4
## 5   E 1 NA NA NA

データフレーム2のような場合は一発でできない?のかリストの長さを取り出して長さを整えたリストを作る必要がありそう。

max_l <- max(sapply(a, length))
data.frame(lapply(a, function(x){
  length(x) <- max_l
  x})
)
##    A  B  C  D  E
## 1  1  1  1  1  1
## 2 NA  2  2 NA NA
## 3 NA NA  3  3 NA
## 4 NA NA NA  4 NA

で作れる。ワンライナーで書こうと思えば書けるが……。

自作関数化してしまえばよいという説もある。

list2data.frame_cbind <- function(list){
  max_l <- max(sapply(list, length))
  data.frame(lapply(list, function(x){
    length(x) <- max_l
    x
  }))
}
list2data.frame_cbind(a)
##    A  B  C  D  E
## 1  1  1  1  1  1
## 2 NA  2  2 NA NA
## 3 NA NA  3  3 NA
## 4 NA NA NA  4 NA

この要領でリストの長さを統一する手法を用いれば、{plyr}パッケージを用いなくてもデータフレーム1も得られる。

max_l <- max(sapply(a, length))
data.frame(do.call(rbind, lapply(a, `length<-`, max_l)))
##   X1 X2 X3 X4
## A  1 NA NA NA
## B  1  2 NA NA
## C  1  2  3 NA
## D  1 NA  3  4
## E  1 NA NA NA

これも関数化すれば良い説はある。

list2data.frame_rbind <- function(list){
  max_l <- max(sapply(list, length))
  data.frame(do.call(rbind, lapply(list, `length<-`, max_l)))
}
list2data.frame_rbind(a)
##   X1 X2 X3 X4
## A  1 NA NA NA
## B  1  2 NA NA
## C  1  2  3 NA
## D  1 NA  3  4
## E  1 NA NA NA

{plyr}ldplyを使う手法もリストの長さを統一してやる手法もどちらも長さが異なるリストに対応しているということは、長さが同じリストでも大丈夫ということなので汎用性があり、リストの形を見ずにデータフレーム化できる。

{plyr}ldplyの方が列名じゃなく.idでリストのときの名前が出てくるので少しだけ嬉しい?用途によるかも。

参考

How to convert a list consisting of vector of different lengths to a usable data frame in R? - stackoverflow

Combining (cbind) vectors of different length - stackoverflow