2020/08/23

R - 入門本編 3章 オブジェクト、そのモードと属性

R の型システムの入門編。型をいちいち明示しない言語では、型システムをちゃんと理解しておかないとちょいちょいつまずくので、しっかり把握しておきたい。

本質的属性 - モードと要素数

R では、処理の対象は全て「オブジェクト」である。オブジェクトには様々な種類があり、オブジェクトは「モード」と「要素数」という2つの本質的属性を持つ。

ベクトルのモード

まずアトミックな構造として、「数値 (numeric)」、「複素数 (complex)」、「論理値 (logical)」、「文字列 (character)」の各ベクトルがある。この numeric, complex, logical, character を「モード」と呼んでいる。ベクトルは全て同じモードの値からなる。空のベクトルもモードを持つ。

mode() は引数に与えられたオブジェクトのモードを返す。
> mode(1)
[1] "numeric"
> mode(1i)
[1] "complex"
> mode(TRUE)
[1] "logical"
> mode("1")
[1] "character"

空のベクトルは以下のように作れる。引数の 0 は要素数 0 を意味する。
> mode(double(0))
[1] "numeric"
> mode(complex(0))
[1] "complex"
> mode(logical(0))
[1] "logical"
> mode(character(0))
[1] "character"

NA や NaN, Inf もモードを持つ。
> mode(NA)
[1] "logical"
> mode(NaN)
[1] "numeric"
> mode(Inf)
[1] "numeric"

しかし、NA は numeric モードのベクトルの要素に記述できる。
> mode(c(1, 2, NA))
[1] "numeric"

リスト、関数、式のモード

R には、「リスト」と呼ばれる種類のオブジェクトがある。任意のモードの値を複数持つ順序付きの列で、モードは list である。ベクトルがアトミックな構造であるのに対し、リストは再帰的な構造を持つ。簡単に言えば、リストの要素としてリストを持つことができる。
他の再帰的な構造としては、「関数 (function)」、「式 (expression)」がある。式がオブジェクトであるのは、R の大きな特徴である。式の一種としてモデル記述の際に用いられる「モデル式 (model formula)」というものがある。

モードの変換

モードは変換することができる。例えば numeric モードのベクトルを character モードに変換すると、次のようになる。
> z <- 0:9
> z
 [1] 0 1 2 3 4 5 6 7 8 9
> z <- as.character(z)
> z
 [1] "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
as.integer() を用いれば整数型に変換され、モードは numeric に戻る。
> z <- as.integer(z)
> z
 [1] 0 1 2 3 4 5 6 7 8 9
他にも as.xxx() 形式のモード変換関数がいろいろある。

要素数

オブジェクトの本質的属性のもう1つが「要素数 (length)」である。length() で取得できる。オブジェクトの要素数は、今の要素数を越える添字値の位置に値を入れることで変化する。

まず、numeric モードの空のベクトルを作る。
> e <- numeric()
> length(e)
[1] 0

添字値 3 に値を入れる。
> e[3] <- 17
> e
[1] NA NA 17
> length(e)
[1] 3
要素数は 3 になり、値を入れていない箇所には NA が入った。ファイルからベクトルやリストに値を読み込む scan() 関数などでも内部的にこのような要素数調整が行われている。
要素数を切り詰めるには、添字ベクトルで切り取って再付値すれば良い。

属性

オブジェクトには、本質的属性であるモードと要素数以外にも、属性を付加することができる。非本質的属性は、attributes() で取得したり更新したりすることができる。例えば自分で定義した関数には srcref という属性がついていて、関数の定義式を保持している。名前を付けたベクトルには names という属性に名前を保持している。

> g <- function(x, y) x + y
> attributes(g)
$srcref
function(x, y) x + y
> fruit <- c(5, 10, 1, 20)
> names(fruit) <- c("orange", "banana", "apple", "peach")
> attributes(fruit)
$names
[1] "orange" "banana" "apple"  "peach"

属性の取得・更新は、attributes(x) で x の全属性を名前付きリストとして取得し、これを操作すれば良い。また、attr() という関数で指定した名前の属性を操作することもできる。
names() で付けた名前が names という属性として保持されているなど、R の体系の一部をなしているので、付加したり除去したりする際には注意が必要である。例えば、行列オブジェクトは dim という属性で行数、列数を保持しているので、dim を変更することで行数、列数が変化する。
> z <- matrix(nrow=4, ncol=4)
> z
 [,1] [,2] [,3] [,4]
[1,]   NA   NA   NA   NA
[2,]   NA   NA   NA   NA
[3,]   NA   NA   NA   NA
[4,]   NA   NA   NA   NA
> attr(z, "dim") <- c(2, 8)
> z
 [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
[1,]   NA   NA   NA   NA   NA   NA   NA   NA
[2,]   NA   NA   NA   NA   NA   NA   NA   NA
> attr(z, "dim") <- c(8, 8)
 以下にエラー attr(z, "dim") <- c(8, 8) :
 dims [product 64] はオブジェクト  [16] の長さに整合しません
要素数は本質的属性でありこの方法では変更できないため、要素数が変化するような指定はエラーになっている。

オブジェクトのクラス

R ではオブジェクト指向スタイルのプログラミングが可能となっている。class という特別な属性を用いる。例えばあるオブジェクトが data.frame というクラスを持っている場合、print(), plot(), summary() 等の総称的関数が data.frame というクラスを意識した振舞いをする。unclass() 関数によって一時的にクラスの影響を取り外すことができる。

例として、データフレームを作ってみる。
> df <- data.frame(cbind(x=1, y=1:10))
> df
 x  y
1  1  1
2  1  2
3  1  3
4  1  4
5  1  5
6  1  6
7  1  7
8  1  8
9  1  9
10 1 10
cbind() で列名 x と y の2列を持つ10行の行列を作り、そこからデータフレームを作っている。表示すると、行列らしく綺麗に表示されている。

> class(df)
[1] "data.frame"
class() でクラス名を見ると、data.frame となっている。

> attr(df, "class")
[1] "data.frame"
class という属性に格納されていることがわかる。

> unclass(df)
$x
 [1] 1 1 1 1 1 1 1 1 1 1
$y
 [1]  1  2  3  4  5  6  7  8  9 10
attr(,"row.names")
 [1]  1  2  3  4  5  6  7  8  9 10
unclass() でクラスの影響を取り外して表示してみると、上記のようにリストとして取り扱われて表示される。データフレームの正体は列ごとのベクトルの名前付きリストに row.names という属性がついたものだと分かる。