はじめに

R において欠損値を表す NA は非常に便利です。 NA は普通の R ユーザにとって自然に取り扱うことのできる概念ですが、それを支える内部の仕組みはわりと複雑です。 例えば、NA の型は論理値型ですが、一体それはなぜでしょうか? 今日は R における強制型変換の話とからめてその謎をひも解いていきます。

ベクトルと型

R のベクトルは型を持ちます。

例えば、1から3までの数値ベクトルを作成してみます。

x <- 1:3
x
#> [1] 1 2 3

ベクトルの型を確認するには typeof() 関数を使います。

typeof(x)
#> [1] "integer"

ベクトル x の型は整数型(integer)であることがわかります。

他にも、例えば次のようなベクトルを作成してみます。

x <- c("U", "NA", "DON")
x
#> [1] "U"   "NA"  "DON"
typeof(x)
#> [1] "character"

この場合、ベクトル x は文字列型(character)になります。

このように、Rのベクトルは必ず型を持ちます。

では、1つのベクトルの中に異なる型の要素を含めるとどうなるでしょうか。

x <- c("U", 2, "DON")
x
#> [1] "U"   "2"   "DON"
typeof(x)
#> [1] "character"

この場合、ベクトル x 中に含まれる数値の 2 は、文字列型の "2"強制型変換されます。

強制型変換

R のベクトルは、1つだけしか型を持つことができません。1

したがって、異なる型の要素を結合してベクトルを作成しようとすると、型を統一するために、型の自動変換が行われます。これが強制型変換です。

強制型変換にはルールがあります。結合しようとする要素の型の中で、最も柔軟性の高い型に変換されます。

型の柔軟性は次の通りです。

logical < integer < double < complex < character
(論理値型 < 整数型 < 倍精度小数点型 < 複素数型 < 文字列型)

先ほどの例で言うと、"U" は character、2 は double、"DON" は character なので、最も柔軟性の高い character 型に変換されたと言うわけです。

他の例を挙げると、例えば、整数型と論理値型を結合すると、整数型に強制型変換されます。2

x <- c(1L, 2L, 3L, TRUE, FALSE)
x
#> [1] 1 2 3 1 0
typeof(x)
#> [1] "integer"

複素数型と倍精度小数点型を結合すると、複素数型に強制型変換されます。

x <- c(1 + 1i, 2 + 2i, 3, 4)
x
#> [1] 1+1i 2+2i 3+0i 4+0i
typeof(x)
#> [1] "complex"

おわかりのように、型の柔軟性は、低い方から高い方に自然に変換できるように決められています。

どんな型でも文字列に変換することができるので、文字列の型が最も柔軟性が高いことがわかります。

x <- c(TRUE, 1L, 1.0, 1+0i, "hoge")
x
#> [1] "TRUE" "1"    "1"    "1+0i" "hoge"
typeof(x)
#> [1] "character"

強制型変換における NA の取り扱い

R には値が欠測していることを表すために NA という特別な値が用意されています。

x <- c(1, NA, 3)
x
#> [1]  1 NA  3

しかし、この NA の型を調べてみると、logical になっています。

typeof(NA)
#> [1] "logical"

したがって、NA は論理値型であると思われるかもしれませんが、それは違います。

実は、Rでは、全部の型に対して NA が用意されています。

type NA
論理値型 logical NA
整数型 integer NA_integer_
倍精度小数点型 double NA_real_
複素数型 complex NA_complex_
文字列型 character NA_character_

その理由は強制型変換と深いつながりがあります。

まず、NA は最も柔軟性の低い論理値型で定義されています。

すなわち、論理値型のベクトルに NA が含まれている場合、強制型変換は行われません。

x <- c(TRUE, NA, FALSE)
x
#> [1]  TRUE    NA FALSE
typeof(x)
#> [1] "logical"

次に、整数型のベクトルに NA を含めたいとします。 NA は論理値型ですので、整数型に強制型変換しなければなりません。 そこで使用されるのが整数型の NA である NA_integer_ というわけです。

x <- c(1L, NA, 3L)
x
#> [1]  1 NA  3
typeof(x)
#> [1] "integer"

表面上は NA と表示されますが、NA_integer_ に変換されていることは次のようにして確認できます。3

identical(x[2], NA)
#> [1] FALSE
identical(x[2], NA_integer_)
#> [1] TRUE

他の型でも同様に、NA は強制型変換のルールに従い、ベクトル中で最も柔軟性の高い型の NA に変換されます。

例えば、文字列型に含まれる NA は NA_character_ に変換されます。

x <- c("U", NA, "DON")
x
#> [1] "U"   NA    "DON"
identical(x[2], NA_character_)
#> [1] TRUE

デフォルトの NA が論理値型である理由は、ここにあります。

NA を柔軟性の最も低い論理値型とすることで、強制型変換のルールがうまく働き、最も自然な形で型変換を行うことができるのです。

まとめ

デフォルトの NA が論理値型であることで、強制型変換のルールとうまく組み合わさり、自然な動きをすることがわかりました。

すべての型の NA は、is.na() を適用すると TRUE となります。

sapply(list(NA, NA_integer_, NA_real_, NA_complex_, NA_character_), is.na)
#> [1] TRUE TRUE TRUE TRUE TRUE

したがって、ユーザは特に型を意識することなく NA を取り扱うことができます。

このように、便利な機能の裏側には様々な仕組みが動いています。

Rのこのような仕組みに興味を持った方は、参考文献に挙げた『R言語徹底解説』を読んでみてください。 Rの世界が広がると思います。

参考文献


  1. もし、複数の型を持つベクトルが必要ならば、リスト を使うことになります。

  2. 細かいことですが、R で 1 と書くと通常は倍精度小数点型とみなされます。整数型リテラルを表現するには 1L のように数字の後に L を加える必要があります。

  3. identical() は、オブジェクトが同じかどうかを判定する関数です。