リストには何でも入る
ベクトルには厳しいルールがひとつあります。すべての要素が同じ型でなければならない、というものです。リストにはそのルールがありません。リストの各要素は、異なる型でも、異なる長さでも、別のリストでもかまいません。Rの汎用コンテナです。
length() は4と報告します。そのうちの1つ(scores)がそれ自体3つの数値を含んでいても、要素は4つです。リストは中身ではなく、仕切りの数を数えます。str()(structure)は、リストが実際に何を保持しているかを見るいちばん速い方法です。要素ごとに1行、名前と型とプレビューを表示します。str() を反射にしましょう。関数から何かを受け取って、それが何かわからないときは、とりあえず str() です。
名前は省略できますが(list(1, "a", TRUE) は合法です)、ほぼ常に付ける価値があります。名前付きのリストはそれ自体がドキュメントになります。
中へ入る3つの方法:[、[[、$
リストで誰もが一度は間違えるところなので、正確に理解しておきましょう。アクセス演算子は3つあり、それらは2種類の仕事をします。
[はより小さなリストを返します。同じ種類の入れ物で、選んだものを保持しています。[[は要素そのものを返します。仕切りの中にある実際の値です。$はリテラルな名前を伴う[[の略記です。person$ageはperson[["age"]]と同じです。
軽く表示している間はこの違いは見えませんが、計算しようとした瞬間にはっきり見えてきます。
person["scores"] のクラスは "list" です。中にスコアのベクトルが入った、長さ1のリストです。person[["scores"]] のクラスは "numeric" で、mean() にそのまま渡せるベクトルそのものです。単一角かっこ版に mean() を呼ぶと、引数が数値でないというエラーが出ます。そのエラーメッセージは、ほぼ常に [[ のつもりで [ を使ったサインです。
覚えやすい比喩があります。リストは列車です。[ は短い列車をくれます——まだ列車です。[[ は車両を開けて、積み荷を渡してくれます。
では [ が正しい道具になるのはいつでしょうか。複数の要素が欲しくて、まとめたまま保ちたいときです。
2つの要素を「そのもの」として同時に取り出すことはできません。2つの値には入れ物が必要だからです。したがって複数要素の選択は常に単一角かっこであり、常にリストを返します。
もうひとつ違いがあります。$ は部分一致を使い(曖昧でなければ person$sc が scores を見つけます)、コンソールでは便利ですがスクリプトでは危険です。動き続ける必要のあるコードでは、完全な名前を伴う [[ を選びましょう。変数に格納された名前も受け取れますが、$ にはそれができません。
追加・変更・削除
リストは普通の代入だけで伸び縮みします。専用のappendメソッドは不要です。
- まだ存在しない名前に代入すると要素が追加されます。
- 既存の名前に代入すると置き換えられます。
- 位置
length(x) + 1に代入すると、名前なしの要素が末尾に追加されます。 NULLを代入すると要素は完全に削除され、リストが短くなります。(NULLが削除命令として働く唯一の場所です。本当に「何もない」を仕切りに格納したいならx["k"] <- list(NULL)を使います。)
2つのリストをつなげるには、ベクトルと同じように c() が使えます。c(list_a, list_b) はより長い1つのリストを返します。
入れ子のリスト
リストの要素は別のリストにできるので、リストは任意の深さに入れ子にできます。そのため、パースしたJSONやグループ化された結果のような構造化データに対する、Rの自然なかたちになっています。
掘り下げるのはアクセサをつなげるだけです。$ や [[ のたびに1段深く進みます。入れ子構造がわからなくなったら、str(company) が木構造全体を一度に見せてくれますし、str(company, max.level = 1) なら最上層だけを見せてくれます。
unlist() による平坦化
unlist() は、どれだけ深く入れ子になっていてもリストを1つのベクトルにつぶします。
注目すべき点が2つあります。1つ目は、unlist() が名前を組み立ててくれること(math1、math2、art)。各値がどこから来たのかを追えます。2つ目——そしてこちらが罠です——ベクトルは1つの型しか持てないため、型の混ざったリストをunlistするとすべてが強制変換され、その中で最も柔軟な型になります。数値の 1 は文字列の "1" として返ってきました。unlistして数値がテキストに変わったなら、そのリストは思っていたほど均質ではなかったということです。
リストが重要な理由
リストは、いずれ卒業する初心者向けの話題のように感じられるかもしれません。実際は逆で、言語全体を支える構造材です。
- 複数のものを返す関数はリストを返します。 Rの関数は1つのオブジェクトを返します。リストがあれば、その1つのオブジェクトに当てはめ値と係数表の両方を運ばせられます。回帰を実行して返ってくるモデルオブジェクトは、(大きくてクラスの付いた)リストです。
str()をかければ確かめられます。 - データフレームは列のリストです。 等しい長さのベクトルに、いくらか追加の振る舞いを載せたものです。いま学んだこと(
$、[[、要素を落とすためのNULL代入)は、そのままデータフレームの列に当てはまります。 - apply系 ——
lapply()とその仲間——はリストを取り、各要素に関数を適用し、リストを返します。リストを入れてリストを出す、というのがRにおける反復計算の標準的なかたちです。
この記事のまとめ
- リストには何でも入ります。型の混在、長さの不揃い、別のリスト、データフレームまるごと。
[はより小さなリストを返し、[[は要素そのものを返します。$はリテラルな名前を伴う[[です。計算に使いたい? それなら[[か$です。- 新しい名前への代入で追加し、
NULLの代入で削除し、c()で結合します。 unlist()はベクトルへ平坦化し、その途中で型を強制します。結果のクラスを確認しましょう。- データフレームは列のリストなので、この知識はそのまま転用できます。
次は行列です。すべてが同じ型で、行と列を持つ、リストの対極にあるものを扱います。
よくある質問
Rの [ と [[ の違いは何ですか?
単一の角かっこ [ は常に、選んだ要素を含むリスト——同じ種類の、より小さな入れ物——を返します。二重の角かっこ [[ は中に手を伸ばして、要素そのものを返します。x$scores が数値ベクトルを保持しているなら、x["scores"] は長さ1のリストで、x[["scores"]] は数値ベクトルです。実際にその値を扱いたいときは [[(または $)を使いましょう。
Rでリストに要素を追加するには?
まだ存在しない名前に代入します。x$email <- "rosa@example.com" や x[["email"]] <- ... のように書けば、リストは自動的に伸びます。名前なしで末尾に追加するには次の位置に代入します(x[[length(x) + 1]] <- value)。要素を削除するには NULL を代入します(x$email <- NULL)。
Rでリストをベクトルに変換するには?
unlist(x) はリストを(入れ子も含めて)1つのベクトルに平坦化します。ベクトルは1つの型しか持てないため、すべてがその中で最も柔軟な型に強制されます。数値と文字列のリストは、すべてが文字列のベクトルになります。unlistした後は結果の class() を確認しましょう。