| Top Page | プログラミング | R 自動化 目次 | 索引 |
updated on 2012-09-21
ベクトルは、同じ型のデータをまとめて並べたデータ構造です。 これに対し、リストはどのような型のデータでもしまえるデータ構造です。 ベクトルでもリスト自身でもデータフレームでも統計解析関数が返す複雑なオブジェクトでも、なんでも格納できます。 自動化した処理の結果をいくつもまとめておいて、最後にまとめて処理したいなどという場合にも便利でしょう。 なお、データフレームもリストの一種です。
このページでは、リストの使い方の基本を紹介します。 柔軟なデータ処理に活かしてください。 プログラム例はいずれもそのまま動作するように書いてあります。 ぜひ R のコンソールにコピーして試してみてください。
リストを作るには、 関数 list を使います。 引数に、リストにしまいたいオブジェクトをいくつでも並べて書きます。 オブジェクトの型がそろっている必要はありません。
x <- list('a', 'b') # 要素が2つのリスト。要素は文字列(正確には、長さが1のベクトル) x <- list(1, 2, 4) # 要素が3つのリスト。要素は整数(正確には、長さが1のベクトル) x <- list(1:10)) # 要素が1つのリスト。要素は整数のベクトル。 x <- list(1:10, 100:200) # 要素が2つのリスト。要素は2つとも整数のベクトル。長さは不揃い。 x <- list(1:10, 100:200, c('a', 'b')) # 要素が3つのリスト。要素は整数のベクトル2つと、文字列のベクトル1つ。長さも型も不揃い。 d1 <- data.frame(1:10, 11:20) # データフレーム d2 <- data.frame(101:110, 111:120) # データフレーム x <- list(d1, d2) # 要素が2つのリスト。要素はいずれもデータフレーム x <- list(d1, d2, c('a', 'b')) # 要素が3つのリスト(2つのデータフレームと、文字列のベクトル1つ)
作ったリストの構造を確認するには、オブジェクトの構造を表示する関数 str が便利です。 たとえば x <- list(1:10, 100:200, c('a', 'b')) を実行したあとで str(x) とすると、
List of 4 $ : int [1:10] 1 2 3 4 5 6 7 8 9 10 $ : int [1:101] 100 101 102 103 104 105 106 107 108 109 ... $ : chr [1:2] "a" "b" $ : num 3.14
のように表示されます。4つの要素があって最初は要素数 10の整数のベクトル、次は要素数 101 の整数のベクトル、 3番めは要素数 2 の文字列のベクトル、4番めは数値がひとつ、ということが分かります。
リストの要素は、[ ] や [[ ]] に要素の位置を与えて指定できます。 [ ]を使った場合は「指定した要素を含むリスト」、 [[ ]]を使った場合は「要素そのもの」を指すことに注意が必要です。
x <- list(1:3, 101:105, c('a', 'b')) # 要素が3つのリスト。要素は整数のベクトル2つと、文字列のベクトル1つ。 print( x[1] ) # x の1つめの要素を含むリスト print( x[c(1, 3)] ) # x の1つめと3つめのの要素を含むリスト print( x[[2]] ) # x の2つめの要素そのもの(要素数5のベクトル) print( x[2][1] ) # x の2つめの要素を含むリストの、1つめの要素(すなわちベクトル 101:105) print( x[[2]][4] ) # x の2つめの要素そのもの(ベクトル)の、4つめの要素(104)
リストの各要素に名前をつけ、その名前で要素を指定することもできます。
x <- list(1:3, 101:105, c('a', 'b')) # 要素が3つのリスト。要素は整数のベクトル2つと、文字列のベクトル1つ。 names(x) <- c('foo', 'bar', 'baz') # 各要素に名前をつける。 print( x$foo ) # 'foo' という名前の要素そのもの print( x[['foo']] ) # x$foo と同じ(要素そのもの) print( x['foo'] ) # 'foo'という名前の要素を含むリスト
[ ] や [[ ]]、$ による要素の指定方法は、 データフレームの場合と同様ですね。 これはデータフレーム自体がリスト(の一種)なので当然です( is.list(data.frame()) は TRUE を返す)。
型が異なるデータを束ね、それぞれに名前をつけられることを利用して、 オブジェクト指向的なプログラムを書くこともできるでしょう。
obj <- list(NULL, NULL, NULL) # 要素が3つのリスト。 names(obj) <- c('Name', 'Height', 'Weight') # 各要素に名前をつける。 obj$Name <- 'Taro' obj$Height <- 182.1 obj$Weight <- 72.0
統計関数が返すような、たくさんの情報を含んだオブジェクトは、まさにこの方法を使っています。
[ ] や [[ ]]、$ により指定した要素に オブジェクトを代入すれば、リストの内容を変更できます。 その際、代入するオブジェクトの型と、要素の指定方法との対応に注意が必要です。 リストを指定したらリストを、要素そのものを指定したら要素そのものを代入します (たまたま要素そのものがリストであっても、そのことは気にする必要はありません)。
x <- list(1:10, 100:200, c('a', 'b')) # 要素が3つのリスト。要素は整数のベクトル2つと、文字列のベクトル1つ。長さも型も不揃い。 x[[1]] <- c(11:20) # 最初の要素を、別のベクトルで上書きする。 x[2] <- list(c(200:300)) # 2番めの要素を含むリストに、ベクトルひとつを含むリストを代入。これでベクトル自体が上書きされる x <- list(1:3, 101:105, c('a', 'b')) # 要素が3つのリスト。要素は整数のベクトル2つと、文字列のベクトル1つ。 names(x) <- c('foo', 'bar', 'baz') # 各要素に名前をつける。 x$foo <- c(11:13) # 'foo' という名前の要素を、別のベクトルで上書き x[['foo']] <- c(11:13) # 上と同様 x['foo'] <- list(c(11:13)) # 'foo' という名前の要素を含むリストに、別のベクトルひとつを含むリストを代入。 # これでベクトル自体が上書きされる。
なお、まだ存在しない要素にオブジェクトを代入すると、対応する要素が作られます。 また、2つの要素を持つリストに5番めの要素を作ると、すきまの3番め、4番めの要素も自動的に作られ、 NULL が しまわれます。
x <- list(1:3, 10:12) # 要素が2つのリスト。 x[[5]] <- 'A' # 5番めの要素として文字列(がひとつのベクトル)を代入
上のコードを実行したあと str(x) とすると、以下のような出力が得られます。
List of 5 $ : int [1:3] 1 2 3 $ : int [1:3] 10 11 12 $ : NULL $ : NULL $ : chr "A"
たしかに 3番目、4番めの隙間要素が NULL で埋められていることが分かります。
最初からリストにしまうオブジェクトが揃っているなら、list (....) の ... にそれらのオブジェクトを並べれはよいのですが、 たとえばプログラム中で生成されてくるオブジェクトを順次しまいたいといった場合には、 既存のリストに要素を追加していく必要があります。
そのためには、関数 c を使います。 リスト x にオブジェクト y を追加したいなら、以下のように書きます。
x <- list(1:10) # 要素がひとつ(整数 10 個のベクトル)のリスト y <- 11:20 # 整数が10個のベクトル x <- c(x, list(y)) # リスト x の後ろに y を追加したリストを x に上書き
これで、x のうしろに y を追加したリストが作られ、x に上書されます。 この例では y は配列ですが、どのような型のオブジェクトでも、これでリストに追加できます。 追加するオブジェクト y をそのまま渡すのではなく list(y) とするのがポイントです。
以下は、正しい書き方を知ってしまえばあえて知る必要はない補足です。
y を list () で囲まないと、y の型によってさまざまなことが起こります。たとえば、y がベクトルだと、 ベクトル全体がリストの要素になるのではなく、ベクトルの要素ひとつひとつが、リストの新たな要素として追加されます。 y がデータフレームの場合は、データフレーム全体ではなく、その1列ごとがリストの要素として追加されます。 cor.test, glm などの統計関数が返す、さまざまな情報を持ったオブジェクトなら、そのオブジェクトにしまわれた様々な情報が それぞれリストの新たな要素として追加されます。 いずれにせよ、追加したオブジェクトをあとでまとめて取り出すことができなくなってしまいます。
また、リスト x にオブジェクト y を加えたリストを作るというので list(x, y) としたらどうなるでしょうか。 こう書くと、 「1つめの要素が x というリスト、2つめの要素が y」という新しいリストが作例されます。 つまり、リストの入れ子ができてしまいます。 これを何度も繰り返せば、どんどんリストの入れ子が深くなってしまいます。
リストへの要素の追加は、c の代わりに append という関数を使ってもできます。
x <- list(1:10) # 要素がひとつ(整数 10 個のベクトル)のリスト y <- 11:20 # 整数が 10個のベクトル x <- append(x, list(y)) # リスト x の後ろに y を追加したリストを x に上書き
プログラム中で生成するオブジェクトを並べたリストを作るには、最初に空のリストを作成し、これに要素を足していきます。 以下はその簡単な例です。
x <- list() # 空の(要素数ゼロの)リストを作る for (i in 1:4) { # 4回の繰り返し y <- rnorm(10) # 10個の正規乱数が並んだベクトルを作る x <- c(x, list(y)) # 作成したベクトルを x の末尾に追加する } str(x) # 要素数が4つ(それぞれ数値のベクトル)のリストを表示
要素の位置を指定してオブジェクトを代入することで、リストの末尾に追加する方法もあります。 現在のリストのサイズを関数 length で調べ、 その次の要素にオブジェクトを代入すれば、リストを追加できます。 リスト x の末尾にオブジェクト y を追加するには以下のように書きます。
x[[ length(x) + 1 ]] <- y # 最後の次の要素を作って y を代入
c や append ではなくこちらの方法を使いたいケースはちょっと思いつきませんが、参考まで。
リストをまるごと渡して処理してくれる関数がいくつかあります。 値の範囲(最小と最大)を探す関数 range は、リストをまるごと処理してくれる関数です。
x <- list(1:10, 100:200) # 要素が2のリスト。要素はいずれも整数のベクトル x.r <- range(x) # x に含まれるすべての数値データの最小値・最大値が並んだベクトルを返す cat ("min, ", x.r[1], "\n") # 結果を出力 cat ("max, ", x.r[2], "\n")
いくつものオブジェクトをまとめてグラフ化するときの軸の設定などに使えそうですね。 なお、リストに文字列のデータも含まれていると期待したようには動いてくれませんので注意が必要です。
一方で、リストを渡しても処理してくれない関数も多いので注意が必要です。 最小値を求める min や 最大値を求める max にリストを渡しても処理してくれません。 引数の型が不正だと怒られます。mean や sd、sum もだめです。
range も内部では min や max を呼んでいるのですが、 渡されたオブジェクトがリストだったら、その中を辿りながら全部の要素を連結したベクトルを作り、これを min や max に渡しているようです。
リストの中を辿って要素を連結したベクトルを自分で作りたいときには、 unlist という関数にリストを渡します。unlist が返すベクトルを min や max に 渡せば、リスト全体の最小値、最大値を求められます。 データフレームもリストの一種ですので、 unlist で全要素のベクトルを得られます。 なお、リストの要素のなかに文字列が混ざっていると、数値データも含めて文字列に変換した ベクトルになってしまうので注意が必要です。
個々のオブジェクトをそれぞれ処理するには、繰り返し構文を使う方法と lapply を使う方法があります。lapply は次項にゆずり、 まずは for による繰り返し処理です。
x <- list() # 空の(要素数ゼロの)リストを作る for (i in 1:4) { y <- rnorm(10) # 10個の正規乱数が並んだベクトルを作る x <- c(x, list(y)) # 作成したベクトルを x の末尾に追加する } for (vec in x) { # リスト x の各要素(すなわち数値のベクトル)を、順次 vec に代入する print (mean(vec)) # ベクトル vec の成分の平均を出力する。 } for (vec in x) { # リスト x の各要素(すなわち数値のベクトル)を、順次 vec に代入する print (vec[2]) # ベクトル vec の2番めの要素を出力する }
for 構文は、 for (変数名 in データの集合) {繰り返し処理} という書き方をします。「データの集合」のところには、 ベクトルでもリストでも置くことができます。いずれの場合も、各要素が順次、 in の前に指定した名前のオブジェクトに代入されていきます。
はじめにリストの要素数を調べ、要素の位置指定を for の中で変化させることでも、 リストの各要素へアクセスできます。
x <- list() # 空の(要素数ゼロの)リストを作る for (i in 1:4) { y <- rnorm(10) # 10個の正規乱数が並んだベクトルを作る x <- c(x, list(y)) # 作成したベクトルを x の末尾に追加する } n <- length(x) for (i in 1:n) { # リスト x の各要素を順次指定する print ( mean(x[[i]]) ) # i 番目の要素であるベクトルの成分の平均を出力する。 } for (i in 1:n) { # リスト x の各要素を順次指定する print ( x[[i]][2] ) # i 番目の要素であるベクトルの2番めの要素を出力する。 }
lapply は、apply 系の関数のひとつです (apply 系関数に慣れていない場合は、 apply系関数の使い方 のページも参照してください)。
lapplyの 最初の 'l' は list の 'l' です。 リストの要素を順次関数に渡して処理をするのに使います。 lapply に渡す引数は、リストのオブジェクト、要素を渡す関数の名前、そして必要ならその関数に渡す引数です。 以下は、上の for を使った例と同様の処理を lapply で書いたものです。 lapply は各要素の処理結果が並んだリストを返します。
x <- list() # 空の(要素数ゼロの)リストを作る for (i in 1:4) { y <- rnorm(10) # 10個の正規乱数が並んだベクトルを作る x <- c(x, list(y)) # 作成したベクトルを x の末尾に追加する } means <- lapply(x, mean) # リスト x の各要素(すなわち数値のベクトル)を、順次関数 mean に渡す str(means) # mean には結果が並んだリストが代入される seconds <- lapply(x, '[', 2) # リスト x の各要素に [2] を適用し。各ベクトルの2番めの要素を得る str(seconds) # seconds には結果が並んだリストが代入される
最後の例がちょっとトリッキーですね。 要素を指定する [2] は、実は '[' という名前の関数に、位置を指定する引数(この場合は 2) を与えているのです。 関数と引数が分かっていれば、そのまま lapply に渡せます。それで、 lapply(x, '[', 2) というわけです。 '[[' でも同様のことができます。
自分で定義した関数も lapply に渡せます。
x <- list() # 空の(要素数ゼロの)リストを作る for (i in 1:4) { y <- rnorm(10) # 10個の正規乱数が並んだベクトルを作る x <- c(x, list(y)) # 作成したベクトルを x の末尾に追加する } my.func <- function(w, title) {plot(w, main = title)} # 関数の定義 (指定のタイトルをつけて plot するだけ) par(mfcol = c(2, 2)) # 1画面に4つのグラフを描く準備 lapply(x, my.func, 'test') # グラフを描く # リスト x の要素が my.func に渡す最初の引数、文字列 'test' が2つ目の引数となる
なるべく for などの繰り返し構文を使わず、ベクトルはベクトルのまま処理したり、 apply 系の関数を上手に使うのが R っぽい書き方だとされています。 必ず apply を使わなければいけないとは思いませんが、この例で、雰囲気だけでも感じてみてください。