2020/08/20

R - 入門本編 2章 数字とベクトル

R を使うにあたって、ベクトルの理解は必須です。ベクトルを基本的なデータ構造とし、様々な演算子や関数がベクトルに対して定義されることにより、データ分析のためのプログラミングが非常に簡潔に記述できるようになっています。
An Introduction to R の2章に沿いつつ (古い版ですが……)、若干の+αがあります。

ベクトルと付値

R の最も基礎的なデータ構造は「ベクトル」。順序付きの、値の列。ベクトルを作るには関数 c() を用いる (combine の c)。
> c(3.1, 4.1, 5.9, 2.6, 5.3)
とやれば、5つの数値からなるベクトルが作られる。このように式だけを書いて実行すると、結果の値が表示され、捨てられる (実際には次の式が評価されるまでは .Last.value という名前で参照できる)。
単独で現れた数値は、長さ1のベクトルと見なされる。
値に名前を付けるには、「付値」を行う。付値には演算子「<-」または「->」を用いる。
> x <- c(3.1, 4.1, 5.9, 2.6, 5.3)
または
> c(3.1, 4.1, 5.9, 2.6, 5.3) -> x
で、5つの数値からなるベクトルに x という名前が付く。ちなみに付値の式にも結果の値は存在する (付値された値になる) が、付値を行ったときは、結果の値は表示されない。
「<-」も「->」も意味は同じで、左辺と右辺の役割が逆になるだけ。
ところで、他の言語では「値 v を変数 x に代入する」と言うところを、R では「値 v を名前 x に付値する」と言っている。関数型言語ではよく「束縛」という言葉も使われる。
「変数への代入」よりも、「名前への付値・束縛」の方が抽象度が高い。「代入」では、同じメモリ空間を占め続けていることが暗に仮定されているが、「付値・束縛」ではそうではない。
R では、「変数」と「名前」の違いや、「代入」と「付値」の違いはあまり意識する必要はなさそうだが、for 構文のカウンタは変数と呼ばれていたりするので、同じメモリ空間を占め続けて値が変化していくものは変数と呼び、そうでないものは単に名前と呼んでいるようだ。
なお、演算子「=」は別の役割があるため、付値には使えない。
付値は関数 assign() を用いて行うことも可能。上記の5要素のベクトルの付値と同じことを assign() を用いて行うと以下のようになる。
> assign("x", c(3.1, 4.1, 5.9, 2.6, 5.3))
assign() を用いると、構文解釈上、識別子になれない名前に付値することも可能。
> assign("a b", 7)
値を参照するには get() を用いる。
> get("a b")
[1] 7
`` で括っても識別子とみなされるようだ。
> `a b`
[1] 7
上記のように x に付値してある状態で、以下のような式を評価してみる。
> 1/x
[1] 0.3225806 0.2439024 0.1694915 0.3846154 0.1886792
ベクトルの各要素の逆数が計算され、付値ではない式の評価なので、結果の値が表示された。結果もまた5要素のベクトルである。ベクトル演算については次節で詳しく見る。
c() はベクトルの連結も行える。
> y <- c(x, 0, x)
> y
 [1] 3.1 4.1 5.9 2.6 5.3 0.0 3.1 4.1 5.9 2.6 5.3
c() の機能はもっと奥が深そうだが、深堀りは後の章にて。

ベクトル演算

算術式内にベクトルを記述すると、その演算はベクトルの各要素に対して行われる。ベクトルの要素数は異なっていてもよく、最も要素数の多いベクトルと同じ要素数のベクトルが返り値となる。要素数の少ないベクトルは足りない分必要なだけ要素が再利用される。
例えば、
> x <- c(3.1, 4.1, 5.9, 2.6, 5.3)
> y <- c(x, 0, x)
> v <- 2*x + y + 1
とすると、一番要素数の多い y の 11 にあわせて、x は2回と1要素繰り返され、1 (これは要素数 1 のベクトル) は 11 回繰り返される。
と、あるのだが、実際やってみると、
  警告メッセージ: 
In 2 * x + y :
   長いオブジェクトの長さが短いオブジェクトの長さの倍数になっていません 
と言われてしまう。確かに中途半端に再利用されてほしいことって実用上無いだろうから、倍数になっていないときは何らかミスってるよね。
しかし新しい An Introduction to R でも同じ説明なんだよなぁ。改訂しないのかな。
y の要素数を 10 にしてやりなおしてみる。
> y <- c(x, x)
> v <- 2*x + y + 1
> v
 [1] 10.3 13.3 18.7  8.8 16.9 10.3 13.3 18.7  8.8 16.9
無事計算できた。
+, -, *, /, ^(べき乗), log(), exp(), sin(), cos(), tan(), sqrt() なんかはみな同様にベクトルを受けとり、同じ要素数のベクトルを返す。
その他ベクトル演算の代表的なものとしては、max(), min() (それぞれ最大値、最小値)、range(x) (c(min(x), max(x))、length() (要素数)、sum()、prod() (全要素を掛けた値) などがある。
統計関数として、mean(x) (sum(x)/length(x) つまり標本平均)、var(x) (sum((x-mean(x))^2)/(length(x)-1) つまり標本分散) も用意されている。var() は n × p 行列を引数にとると、それを n 個の独立な p-変量標本ベクトルとみなし、p × p 標本共分散行列を返す。
sort() は昇順ソート。sort.list() はその拡張。order() は値の小さい順に1から順番に番号付けを行ったベクトルを返す。
max(), min() は引数に複数のベクトルを与えた場合、全体を通して最大、最小の値を返すが、pmax(), pmin() は引数に与えられた各ベクトルの要素位置ごとに最大値、最小値を求め、それを並べたベクトルを返す。
> pmax(c(0,1,0,1),c(1,0,1,0))
[1] 1 1 1 1
足りない要素は例によって補完され、結果ベクトルの要素数は引数のベクトルのうち最大の要素数のものと同じ。
> pmax(c(2,0),c(1,0,1,0))
[1] 2 0 2 0
数値には、整数、実数、複素数がある。数値リテラルは実数型 (double) になる。数値の後ろに i を付けると複素数の虚部として扱われ、虚部を含む数値は複素数型 (complex) になる。
整数型 (integer) は integer() (0 で初期化された整数型のベクトルを作る), as.integer() (引数の値を整数型に変換したものを返す) などで明示することによって利用できる。
内部的な計算は倍精度浮動小数点数または倍精度複素数で行われている。
複素数を取り扱いたい場合は、虚部を明示的に与えなければならない。例えば、複素数に拡張された sqrt() を使いたい場合は以下のようになる。
実数を引数にした場合:
> sqrt(-17)
[1] NaN
 警告メッセージ: 
In sqrt(-17) :  計算結果が NaN になりました 
複素数を引数にした場合:
> sqrt(-17+0i)
[1] 0+4.123106i

規則的な数列の生成

演算子「:」で数列を生成できる。1:30 は c(1, 2, 3, ..., 28, 29, 30) と同じ。
「:」は結合優先順位が最も高いので、2*1:15 は 2*(1:15) と評価され、c(2, 4, 6, ..., 26, 28, 30) を生成する。おそらく数列リテラルのように扱えることを想定して最強優先順位にしたのだろう。
> n <- 10
> 1:n-1
 [1] 0 1 2 3 4 5 6 7 8 9
> 1:(n-1)
[1] 1 2 3 4 5 6 7 8 9
30:1 とすれば降順の数列も生成できる (c(30, 29, 28, ..., 3, 2, 1))。
seq() はより一般的な数列生成関数。seq() は double 型で、seq.int() という integer 型のバージョンもある。全部で5つの引数を持ち、指定の仕方によって様々な振舞いがある。
seq(to) は 1:to と同じ。
> seq(10)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(from, to) は from:to と同じ。
> seq(3, 12)
 [1]  3  4  5  6  7  8  9 10 11 12
seq(from, to, by) は、from から to まで増分 by の数列を生成する。
> seq(3, 12, 2)
[1]  3  5  7  9 11
seq(length.out=length) は 1 から増分 1 で length 個の要素の数列を生成する。
> seq(length.out=10)
 [1]  1  2  3  4  5  6  7  8  9 10
seq(from, to, length.out=length) は、from から to までを length 個の要素で等間隔に変化させた数列を生成する。
> seq(2, 20, length.out=10)
 [1]  2  4  6  8 10 12 14 16 18 20
seq(along.with=x) は、1 から増分 1 で length(x) 個の要素の数列を生成する。
> seq(along.with=c(3.1, 4.1, 5.9, 2.6, 5.3))
[1] 1 2 3 4 5
ここで出てきた length.out, along.with は、タグと呼ばれるもので、呼び出し時にパラメータの名前を付けることができるもの。他の言語では名前付き引数などと呼ばれているもの。関数のオーバーロード機構がある言語では、引数の型、個数が異なるものは別の実装を持つことができるが、名前付き引数が無い場合、同じ型、同じ個数の引数で別の振舞いをさせたい場合は、違う関数名を付けるしかない (そうしろという話もあるが)。名前付き引数を使えば、同じ関数に相乗りできる (そのかわり当然その関数の仕様はややこしくなる)。
タグ名はタグが特定できる限り短縮して指定することもできる。seq(length.out=length) の場合、seq(length=length)、seq(len=length)、seq(l=length) まで縮めてもタグが特定できるため、正しく呼び出せる。
R での関数呼び出し時の引数の照合は以下のように行われている。
  1. タグに関する正確な照合
    • 各名前付き引数 (呼び出し側) に対して、名前が正確に一致する形式的引数 (宣言側) が探索される
    • 複数見つかった場合はエラー (同じタグが複数回使われている)
  2. タグに関する部分的な照合
    • 残りの各名前付き引数が残りの形式的引数に対し部分的照合により比較される
    • もし名前付き引数が、正確にある単独の形式的引数の最初の部分に一致すれば、両者は一致すると見なされる
    • 複数の名前付き引数に部分一致した場合はエラーになる
      • 例えば、fumble, fooey という2つの名前付き形式的引数を持つ関数 f() があったとする
      • f(f = 1, fo = 2) は2つ目の引数が fooey のみに一致するものの、1つ目の実引数は fumble, fooey 双方に部分一致するため、エラーとなる (fo が fooey のみに合致するので、f は fumble にあたるといった再帰的な照合はなされない)
      • f(f = 1, fooey =2) は2つ目の引数が正確に一致し、部分的照合の対象から外されるため、正しい構文になる
    • 形式的引数が ‘...’ を含む場合は、部分的照合はそれに先立つ引数に対してのみ適用される
  3. 位置に関する照合
    • 未照合の全ての形式的引数は、宣言の順に名前無しの引数と結びつけられる
    • ‘...’ を含む場合は、残りの引数全てが名前の有無にかかわらず引き渡される
seq() の場合は、from, to, by, length.out, along.with というタグ名になっているので、以下のような呼び出しもできる。
> seq(to=10, from=1)
 [1]  1  2  3  4  5  6  7  8  9 10
関数 rep() は様々な方法でベクトルを複製する関数である。
rep(x) は x そのまま。
> rep(x)
[1] 3.1 4.1 5.9 2.6 5.3
rep(x, times) は、x を times 回反復したものを返す。
> rep(x, times=2)
 [1] 3.1 4.1 5.9 2.6 5.3 3.1 4.1 5.9 2.6 5.3
rep(x, times, length.out) は、rep(x, times) の length.out 個目まで。
> rep(x, times=2, length.out=7)
[1] 3.1 4.1 5.9 2.6 5.3 3.1 4.1
rep(x, each) は、x の各要素を each 個ずつ反復。
> rep(x, each=3)
 [1] 3.1 3.1 3.1 4.1 4.1 4.1 5.9 5.9 5.9 2.6 2.6 2.6 5.3 5.3 5.3
rep(x, times, each) は rep(x, each) を times 回反復。
> rep(x, times=2, each=3)
 [1] 3.1 3.1 3.1 4.1 4.1 4.1 5.9 5.9 5.9 2.6 2.6 2.6 5.3 5.3 5.3 3.1 3.1 3.1 4.1 4.1 4.1 5.9 5.9 5.9 2.6 2.6 2.6 5.3 5.3 5.3

論理ベクトル

R では論理ベクトルという形で論理量を取り扱うことができる。論理ベクトルの要素は TRUE, FALSE, NA (欠損値) の3種類の値をとる (クリーネの三値論理相当)。
TRUE, FALSE のかわりにそれぞれ T, F と略記することが可能だが、T, F は既定の変数であって予約語ではない。上書き可能なので、なるべく使用しない方が良い。
<, <=, >, >=, ==, != といった条件演算子による演算結果が論理ベクトルとなる。また、論理演算子として &, |, ! もある。C 言語などと同じ。
論理ベクトルを算術演算の文脈で使用した場合は FALSE は 0、TRUE は 1 に強制変換される。

欠損値

論理値が「完全には知られていない」とか、「利用不能」とか、「欠損値である」とか言うとき、NA という識別子を用いることができる。一般的な処理は NA に対して NA を返す (分からないものは分からない、的な)。
そうではない例としては、is.na(x) は x と同じ要素数のベクトルを返し、対応する x の要素が NA のところには TRUE が、そうでないところには FALSE が入る。
> is.na(c(1,2,3,NA))
[1] FALSE FALSE FALSE  TRUE
NA は厳密には「値」ではないので、NA == NA も TRUE にはならず、NA である。だから is.na(x) と同じことをやろうとして x == NA としてもうまくいかない。
> c(1,2,3,NA) == NA
[1] NA NA NA NA
数値計算における欠損値として「非数 (Not a Number)」というものもある。いわゆる NaN である。
0/0 や Inf - Inf は NaN を返す (エラーにはならない)。
NaN に対する演算は基本的に全て NaN となる。is.nan() で NaN かどうかのチェックが可能。
is.na() は NA に対しても NaN に対しても TRUE となるが、is.nan() は NaN に対してのみ TRUE となる。

文字列ベクトル

文字列リテラルは "" (ダブルクオート) で括って表現する。'' (シングルクオート) も可。C 言語スタイルのエスケープシーケンスが使用可能。
c() を使えば複数要素の文字ベクトルにできるのは数値や論理値と同様。
paste() は、任意の数のベクトルを引数にとり、1つの文字列に連結する関数。数値も自明な形で (1 なら "1" に、等) 変換されて連結される。
> paste(c("A", "B"), 1:5)
[1] "A 1" "B 2" "A 3" "B 4" "A 5"
戻り値は一番長いベクトルと同じ要素数になっている。短い引数は要素が再利用されている。ここでは要素数が倍数になっている必要はないようだ。
デフォルトの区切り文字は半角スペースだが、名前付き引数 sep を用いると区切り文字を変えることができる。
> paste(c("A", "B"), 1:5, sep="-")
[1] "A-1" "B-2" "A-3" "B-4" "A-5"
collapse パラメータを指定すると、結果のベクトルをさらに指定した文字を挟んで連結する。
> paste(c("A", "B"), 1:5, sep="-", collapse="|")
[1] "A-1|B-2|A-3|B-4|A-5"
ちょっと興味があったので、A, B と 1, 2, 3, 4, 5 の全組み合わせができるか試してみた。
> paste(c(outer(c("A", "B"), 1:5, function(x, y) paste(x, y, sep="-"))), collapse="|")
[1] "A-1|B-1|A-2|B-2|A-3|B-3|A-4|B-4|A-5|B-5"
outer() とか匿名関数とか先取りしちゃってるのはすまない。

添字ベクトル

ベクトルの後ろに「[]」で括られた「添字ベクトル」をつけることで、ベクトルの一部分を切り出すことができる。
添字ベクトルには4パターンある。
  1. 論理ベクトル
    • 対象のベクトルと同じ要素数である必要がある
    • 添字ベクトル中で TRUE になっている箇所に対応する要素が選び出される
  2. 正の整数値ベクトル
    • 対象のベクトルを x としたときに、1:length(x) の部分集合が指定可能
    • 対応する要素をその順番で選び出す
    • 添字ベクトルの要素数は任意で、結果の要素数は添字ベクトルの要素数に等しい
  3. 負の整数値ベクトル
    • 正の整数値ベクトルでは選び出す要素を指定したが、負値にすると除外する要素を指定した意味になる
  4. 文字列ベクトル
    • 名前付きベクトル (後述) から要素を選び出すのに用いる
    • 指定された文字列に合致する名前を持つ要素が選び出される
    • あとは正の整数値ベクトルと同様
以下、使用例。
> x <- 101:110
> x
 [1] 101 102 103 104 105 106 107 108 109 110
x は 101~110 の数値ベクトル。
> x %% 2 == 0
 [1] FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE FALSE  TRUE
偶数のとき TRUE になる論理式。%% は剰余演算。
これを添字ベクトルとして使うと、次のように偶数の要素のみ選び出すことができる。
> x[x %% 2 == 0]
[1] 102 104 106 108 110
正の整数値ベクトルの場合。
> x[1]
[1] 101
これは C 言語などの配列の添字みたいな使い勝手。ただし 1 から始まる。
> x[2:4]
[1] 102 103 104
> x[c(1,3,5,7,9)]
[1] 101 103 105 107 109
複数要素のベクトルで指定した場合はこんな感じ。
次に負の整数値ベクトルの場合。
> x[-c(1,3,5,7,9)]
[1] 102 104 106 108 110
1, 3, 5, 7, 9 番目の要素が除外されている。
文字列ベクトルの場合。
まずはベクトルに名前を付加する。
> names(x) <- c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J")
> x
  A   B   C   D   E   F   G   H   I   J 
101 102 103 104 105 106 107 108 109 110 
names(x) に付値することで x に名前を付加できる。名前のベクトルは x より短かくても良い (足りない要素には名前が付かない) が、多いとエラーとなる。
名前を付けると、連想配列のような使い勝手になる。
> x["A"]
  A 
101 
複数要素の選び出しも可。
> x[c("D", "H")]
  D   H 
104 108 
添字ベクトルは、付値される側のベクトルにも付けることができ、該当する要素を入れ替えるような挙動をする。
> x <- -5:5 / -5:5
> x
 [1]   1   1   1   1   1 NaN   1   1   1   1   1
> x[is.na(x)] <- 0
> x
 [1] 1 1 1 1 1 0 1 1 1 1 1
> y <- -5:5
> y
 [1] -5 -4 -3 -2 -1  0  1  2  3  4  5
> y[y < 0] <- -y[y < 0]
> y
 [1] 5 4 3 2 1 0 1 2 3 4 5

他の型のオブジェクト

ベクトル以外にも以下のような型がある。
  • 行列、配列 (ベクトルの多次元化)
  • 因子 (カテゴリ化)
  • リスト (ベクトルの一般化で、異なる型の要素が混在できたり、ベクトルやリストを要素に持てたりする)
  • データフレーム (行列に似た構造だが、異なる型の列が混在でき、実験データの保持に向いたデータ構造になっている)
  • 関数 (関数自体もオブジェクトである)