Rではすべてがベクトル
多くの言語にはスカラー(単一の値)があり、それを複数まとめるためのコンテナ型があります。Rはスカラーを飛ばします。ベクトルとは、すべて同じ型を共有する順序付きの値の集まりで、他のあらゆるものが築かれる単位です。単独の数値に見えるものでさえ、長さ1のベクトルです。
c()——combine の略——が手作業でベクトルを組み立てる方法です。length() はいくつ要素を持っているかを教えてくれます。そして 5 単体は is.vector が TRUE、長さは1と報告されます。Rはベクトルを扱うことをやめません。ただ、ときどきとても短いものを扱っているだけです。
この設計こそ、RのコードがPythonやJavaScriptと違って見える理由です。他の言語ならループを書く操作が、Rでは1つの式でベクトル全体に適用されます。これは後ほど扱います。
いま体に入れておくべきルールがひとつ。ベクトルのすべての要素は同じ型を持ちます。 c() に混在した値を渡しても文句は言われず、存在する中で最も柔軟な型へすべてが静かに変換されます。
文字列が混ざればすべてが文字列("1"、"TRUE")になります。数値の中の論理値は 1 と 0 になります。この型強制は決まった序列——logical → integer → double → character——に従い、警告なしに起こります。数値の列に紛れ込んだ "7" ひとつで、データセット全体が静かにテキストになってしまう理由です。序列の全体像はデータ型で扱っています。本当に異なる型の値をまとめて保持したいなら、そのための道具がリストです。
数列の生成::、seq()、rep()
すべての要素を c() に打ち込むのはすぐに面倒になります。Rには規則的なパターンのための3つの近道があります。
1:10が日常的なものです。一方の端からもう一方まで、刻み幅1の整数を作ります。seq()はそれを一般化します。by =で刻み幅を選ぶか、length.out =で欲しい要素数を伝えて間隔はRに計算させます。seq_len(n)は1からnまでを作り、ループの添字を組み立てる安全な方法です。1:nと違ってnが0のときに1 0と逆向きに数えることなく、正しく空のベクトルを返します。rep()は繰り返します。times =はベクトル全体を続けて繰り返し(1 2 1 2 1 2)、each =は各要素をその場で繰り返します(1 1 1 2 2 2)。この2つは混同しやすいので、コードを実行して見比べてみてください。
添字アクセス:Rは1から数える
角かっこで要素を取り出します。最初の要素は [0] ではなく [1] です。PythonやJavaScriptから来た人にとって、これは最も多い初期のつまずきです。
ここで立ち止まる価値のある点が3つあります。
- 位置のベクトルで添字アクセスできます。
fruits[c(2, 4)]は2番目と4番目を一度に取り出します。 - 負の添字は「それ以外すべて」を意味します。
fruits[-1]は最初の要素を除いたベクトルです。-1が最後の要素を意味するPythonとはまったく異なります。同じ呼び出しで正と負の添字を混ぜることはできません。 fruits[0]はエラーではなく、空のcharacter(0)を返します。Rでは1つずれのバグがクラッシュではなく静かな空の結果を生むことが多いので、後段で不可解に空になっているときはlength()を確認する価値があります。
要素は名前を持つこともでき、これがラベルによる3つ目の添字アクセス方法になります。
名前付きベクトルは軽量な参照表のように働き、コードを読みやすく保ちます。prices["tea"] は意味を語りますが、prices[2] は語りません。
論理マスクと which()
最も強力な添字アクセスは論理ベクトル——データと同じ長さの TRUE/FALSE のマスク——を使うものです。ベクトルに対するすべての比較が、まさにそれを生みます。
temps > 24 は1つの答えではなく、要素ごとに1つずつ、計5つの答えを返します。そのマスクを [ ] の中に入れると、マスクが TRUE の要素だけが残ります。条件は &(かつ)と |(または)で組み合わせます。詳しくは演算子で扱っています。
which() はマスクを位置に翻訳します。ここでは 2 3 5、つまり暑い観測値の添字です。一致がどこにあるかを知る必要があるとき——それを報告するため、あるいは同じ位置で別のベクトルに添字アクセスするため——に使いましょう。単純な絞り込みなら、temps[which(temps > 24)] より temps[temps > 24] のほうが直接的です。
このマスクによる絞り込みのパターンは、Rのデータ処理のほぼすべてを支える背骨です。データフレームの行を絞り込むときにそのまま戻ってきます。
ベクトル演算とリサイクル規則
Rの算術はベクトル全体に要素ごとに適用されます。ループは不要です。
1行目が目玉の機能です。prices * 2 はすべての要素を倍にします。他の言語が for ループを必要とするところで、Rは演算子だけで済みます。しかもベクトル化された書き方は短く、かつ速いのです。繰り返しは最適化されたCの中で行われるからです。各ステップが前のステップに依存するときはforループを書き、要素ごとの計算はベクトル化しましょう。
3行目はリサイクル規則を示しています。ベクトルの長さが異なるとき、Rは短いほうを繰り返して合わせます。c(10, 20) は 10 20 10 20 にリサイクルされ、11 22 13 24 になります。prices * 2 が機能したのも、まさに長さ3のベクトルに 2 がリサイクルされたからです。スカラーとは、リサイクルされている長さ1のベクトルにすぎません。
長いほうの長さが短いほうのちょうど倍数であれば、リサイクルはきれいに働きます。そうでないときも、Rは答えを計算し、そして警告を出します。
11 22 13 が返り、「長いほうのオブジェクトの長さが短いほうの倍数になっていない」という警告が出ます。この警告はバグ報告だと考えてください。中途半端なリサイクルが意図どおりであることはまずなく、たいていは同じ長さであるべき2つのベクトルがそうなっていないことを意味します。
並べ替え・反転・重複除去
いつも使うことになる小さなユーティリティが3つあります。
sort() は値を並べ替え、rev() は並べ替えずに既存の順序を反転し、unique() は最初の出現を残して重複を落とします。どれも x を変更しません。Rのほぼすべてのものと同様、新しいベクトルを返して元をそのままにします。
この記事のまとめ
- ベクトルはRの基本単位です。順序があり、全体で1つの型を持ち、単一の値さえ長さ1のベクトルです。
c()で作り、:、seq()、rep()でパターンを生成します。c()は混在した型を静かに強制することを忘れずに。- 添字は 1 から始まります。負の添字は要素を落とし、名前を使えばラベルで添字アクセスできます。
- 論理マスク(
x[x > 5])は言語全体の絞り込みの定型で、which()はマスクを位置に変えます。 - 演算はベクトル化され、短いベクトルはリサイクルされます。中途半端なリサイクルの警告はバグの合図です。
次はリストです。値がすべて同じ型を共有しないときのためのコンテナを扱います。
よくある質問
Rでベクトルはどう作りますか?
c() 関数(combineの略)を使います。x <- c(10, 20, 30) のように書きます。規則的な数列には 1:10、seq(0, 1, by = 0.25)、rep(0, 5) を使います。すべての要素は最終的に同じ型になる必要があり、型を混ぜるとRはすべてを最も柔軟な型へ静かに変換します。
Rの添字は0始まりですか、1始まりですか?
1始まりです。x[1] が最初の要素、x[length(x)] が最後の要素です。Python、JavaScript、Cから来た人は必ずつまずきます。x[0] という要素は存在せず、要求してもエラーではなく空のベクトルが返るため、バグが静かになりがちです。
Rのリサイクル規則とは何をするものですか?
長さの異なる2つのベクトルで算術を行うと、Rは短いほうを長いほうに合うまで繰り返します。c(1, 2, 3, 4) + c(10, 20) は 11 22 13 24 になります。長いほうの長さが短いほうの倍数でない場合、Rは答えを計算しつつ警告を出します。その警告はバグだと考えてください。
Rのwhich()は何をしますか?
論理ベクトルを、その TRUE の位置に変換します。x > 5 が FALSE TRUE TRUE なら、which(x > 5) は 2 3 になります。位置そのものが必要なときに使いましょう。単純な絞り込みなら、論理ベクトルで直接添字アクセスする(x[x > 5])ほうが簡単です。