Advanced R: Vectors
はじめに
Advanced R 2nd EditionのPaperBook版が届いたので、Rについてのおさらい備忘録。また、書籍を読んでいるにも関わらず誤った解釈や用語の誤用もあるかもしれませんので、参考にする際は自己責任でお願いします。
この記事のライセンスはAdvanced R 2nd Editionと同様で下記の通りです。
Atomic vectors
Rのベクトルのおさらい。ベクトルという言葉はアトミックベクトルとリストを含む。
- 型:typeof:オブジェクトが何者なのかを教えてくれる。
- 長さ:length:オブジェクトの長さを教えてくれる。
- 属性:attributes:オブジェクトに何が帰属してるのかを教えてくれる。
Rにはアトミックベクトルという考え方がある。アトミック(atmic)のそのものの意味としては、「それ以上は分解できない、ごく小さなもの」。これをRに当てはめると、それ以上は分解できないレベルのベクトル、それがアトミックベクトルともいえる。つまり、分解できないという意味での最小単位がアトミックベクトル。c()
で何重にネストしてもフラットなデータ構造が維持される。
c(1, c(2, c(3, c(4, c(5, c(6, c(7, c(8, c(9, c(10)))))))))) [1] 1 2 3 4 5 6 7 8 9 10 c(1,2,3,4,5,6,7,8,9,10) [1] 1 2 3 4 5 6 7 8 9 10
一般的なアトミックベクトルは下記の通り。複素数型(complex)、バイト型(raw)は扱わない。
- 論理型:logical
- 整数型:integer
- 倍精度小数点型:double
- 文字型:character
アトミックベクトルなのかどうかはtypeof()
で調べられて、特定の型かどうかはis.**()
で調べられる。
logi <- c(TRUE, FALSE, T, F) int <- c(1L, 2L, 3L, 4L, 5L) dob <- c(1.0, 2.0, 3.0, 4.0, 5.0) cha <- c("1", "2", "3", "4", "5") is.logical(logi) [1] TRUE is.integer(int) [1] TRUE is.double(dob) [1] TRUE is.character(cha) [1] TRUE
is.numeric()
は、数値なのかどうかを教えてくれるが、整数型でも、倍精度小数点型でもTRUE
を返すので、is.integer()
やis.double()
の汎用的な関数と言える。また、typeof()
は「何の」アトミックベクトルなのかを教えてくれる。
typeof(logi) [1] "logical" typeof(int) [1] "integer" typeof(dob) [1] "double" typeof(cha) [1] "character"
アトミックベクトルに関連して、as.**()
というデータ型を変換できる関数群もある。as.**()
を使う際には、アトミックベクトルが混じると、その場合における最も柔軟な型に、型変更が強制的に行われる。
NG <-c(1L, 2L, "three") sum(NG) Error in sum(NG) : invalid 'type' (character) of argument typeof(NG) [1] "character"
この性質をうまく使えば、論理型を演算することも可能。
sum(as.integer(logi)) [1] 2
NA
NA
は欠落している、または不明な値を意味する。欠損値を含むほとんどの計算では、欠損値が返される。
NA + 1 [1] NA NA > 10 [1] NA
NA
かどうか判断するには、is.na()
を利用する。
x <- c(NA, 1:3) is.na(x) [1] TRUE FALSE FALSE FALSE
Rには、アトミックタイプにあわせてNA
が用意されている。NA
、NA_integer_
、NA_real_
、NA_character_
。NA
は必要に応じて自動的に型変換が行なわれる。
NULL
NULL
は、常に長さゼロであり、いかなる属性も持たない特殊な値。
length(integer(0)) == length(NULL) [1] TRUE typeof(NULL) [1] "NULL" length(NULL) [1] 0
NA
は論理型だが、NULL
は論理でもなければ何でもない。
is.logical(NA) [1] TRUE is.logical(NULL) [1] FALSE is.null(NULL) [1] TRUE
NULL
は特殊な値なので、ベクトルにもできない。
c(NA, NULL, 1) [1] NA 1
リスト
リストとアトミックベクトルの違いは、リストは異なるデータ型を許容できるという点。例えば、こんなの。
list(logi, int, dob, cha) [[1]] [1] TRUE FALSE TRUE FALSE [[2]] [1] 1 2 3 4 5 [[3]] [1] 1 2 3 4 5 [[4] [1] "1" "2" "3" "4" "5" list(logi, list(int), dob, list(cha)) [[1]] [1] TRUE FALSE TRUE FALSE [[2]] [[2]][[1]] [1] 1 2 3 4 5 [[3]] [1] 1 2 3 4 5 [[4]] [[4]][[1]] [1] "1" "2" "3" "4" "5"
リスト内でc()
を使うと、ベクトルをリストに変換できる。
str(list(logi, list(int), dob, list(cha))) List of 4 $ : logi [1:4] TRUE FALSE TRUE FALSE $ :List of 1 ..$ : int [1:5] 1 2 3 4 5 $ : num [1:5] 1 2 3 4 5 $ :List of 1 ..$ : chr [1:5] "1" "2" "3" "4" ... str(list(logi, c(int), dob, c(cha))) List of 4 $ : logi [1:4] TRUE FALSE TRUE FALSE $ : int [1:5] 1 2 3 4 5 $ : num [1:5] 1 2 3 4 5 $ : chr [1:5] "1" "2" "3" "4" ...
リストをフラットに戻すunlist()
という関数もあるが、この際にも型変換が行なわれる。
l <- list(logi, c(int), dob, c(cha)) unlist(l) [1] "TRUE" "FALSE" "TRUE" "FALSE" "1" "2" "3" "4" "5" "1" "2" "3" "4" "5" [15] "1" "2" "3" "4" "5"
データフレーム
データフレームは「リスト」を束ねたデータ構造。リストと異なるのは、あとで説明する長さが異なる場合、エラーが返される点。
logi <- c(TRUE, FALSE, TRUE) int <- c(1L, 2L, 3L, 4L, 5L) dob <- c(1.0, 2.0, 3.0, 4.0, 5.0) cha <- c("one", "two", "three") df <- data.frame(logi, int, dob, cha) Error in data.frame(logi, int, dob, cha) : arguments imply differing number of rows: 3, 5
長さを揃えることで、データフレームを作成できる。
logi <- c(TRUE, FALSE, TRUE) int <- c(1L, 2L, 3L) dob <- c(1.0, 2.0, 3.0) cha <- c("one", "two", "three") df <- data.frame(logi, int, dob, cha) df logi int dob cha 1 TRUE 1 1 one 2 FALSE 2 2 two 3 TRUE 3 3 three
データフレームは「リスト」を束ねたデータ構造と説明したが、実際にリストであることがわかる。
typeof(df) [1] "list"
属性
すべてのオブジェクトは属性を持つことができ、名前(name)、次元(dim)、クラス(class)が属性にあたる。x
は名前属性なし、y
は名前属性あり。
名前
x <- c(1, 2, 3, 4, 5) y <- c(a = 1, b = 2, c = 3, d = 4, e = 5) x [1] 1 2 3 4 5 y a b c d e 1 2 3 4 5 names(x) NULL names(y) [1] "a" "b" "c" "d" "e"
names()
を使えば、名前を付与したり、変更できる。
names(y) <- c("A", "B", "C", "D", "E") y A B C D E 1 2 3 4 5
長さ
そのままですが、長さはオブジェクトの長さのこと。
x <- c(1:100) m <- matrix(1:12, 4, 3) length(x) [1] 100 length(m) [1] 12 nrow(m) [1] 4 ncol(m) [1] 3 dim(m) [1] 4 3
length()
の下にあるnrow()
、ncol()
はlength()
の高次元版で、name()
はrownames()
、colnames()
、dimnames()
などがある。
クラス
クラス属性を持つと、オブジェクトはS3オブジェクトに変わる。つまり、ジェネリック関数に渡されると、通常のベクトルとは動作が異なるようになる。というかそうなっている。class
は、S3オブジェクトシステムの基礎。基本的なS3オブジェクトは下記の通り。
- factor
- Date
- POSIXct
- difftime
factor
Advanced R by Hadley Wickhamに"One important use of attributes is to define factors"(属性の最も大事な役割に因子を定義すること)とあるように、因子のために属性があるといってもよいかもしれない。因子を設定することで、値に順序をもたせることが可能となるclass()
とlevels()
で構成される。
fac <- factor(c("one", "two", "three"), levels = c("one", "two", "three")) class(fac) [1] "factor" levels(fac) [1] "one" "two" "three"
因子を使う際に1番注意しないといけないのが、「因子は文字にみえる整数」であること。
fac <- factor(c("one", "two", "three"), levels = c("one", "two", "three")) typeof(fac) [1] "integer" sum(as.numeric(fac)) [1] 6 fac2 <- factor(c("ten", "twenty", "thirty"), levels = c("ten", "twenty", "thirty")) typeof(fac2) [1] "integer" sum(as.numeric(fac2)) [1] 6
fac
では、うまく整数型に変換し、1+2+3の足し算がうまく行っているように見えますが、fac2
では、10+20+30の足し算がうまくいってない。当たり前ですが、文字列をうまくパースして、変換するわけでもなく、因子の順序を整数が制御しているから。str()
で確認すれば、「3つのレベルがあって、それは"ten", "twenty", "thirty"で、左から1番、2番、3番」となっている。
levels(fac2) [1] "ten" "twenty" "thirty" str(fac2) Factor w/ 3 levels "ten","twenty",..: 1 2 3
下記は実際に起こりそうなヒヤリハット事例。
jiko <- c(10L, 20L, 30L) typeof(jiko) [1] "integer" sum(jiko) [1] 60 jiko1 <- as.numeric(as.factor(jiko)) sum(jiko1) [1] 6
データフレームとして、型を設定せずに読み込んだときに、因子が設定されているに気づかず、10+20+30だと思って足し算したら、6が返ってくる。因子のレベルが1+2+3=6だから、このような場合、因子→文字→数値と変換すれば回避できる。つまり、オブジェクトのレベルを文字型にすることで、それを数値に変える。
jiko2 <- as.numeric(as.character(as.factor(jiko))) sum(jiko2) [1] 60
そして、因子はアトミックベクトルではなく、整数型が持つ属性のクラスが因子ということになる。多分、この解釈であっているはず。そして、メソッドディスパッチなどを行う際にクラスは重要になる。
Dates
Date
ベクトルはdouble
ベクトルのクラスDate
のこと。
today <- Sys.Date() typeof(today) [1] "double" attributes(today) $class [1] "Date"
クラスを削除すると1970-01-01以降の日数を表すことになる。つまり、2019-07-27
は1970-01-01
から18104日後となる。
Sys.Date() [1] "2019-07-27" unclass(today) [1] 18104
POSIXct
日時はPOSIXct
とPOSIXlt
の2つの方法で日時の情報を保持する。POSIXct
ベクトルは、double
ベクトルの上に構築される。
now <- as.POSIXct("2019-07-27 22:00:00", tz = "UTC") now [1] "2019-07-27 22:00:00 UTC" typeof(now) [1] "double" attributes(now) $class [1] "POSIXct" "POSIXt" $tzone [1] "UTC"
POSIXct
は1970-01-01
以降の秒数を表す。
unclass(now) [1] 1564264800 attr(,"tzone") [1] "UTC"
difftimes
日付間の時間または日付時刻を表す期間は、difftimesに格納される。Difftimes
はdouble
の上に構築されunits
という整数で解釈する属性を持つ。
one_week_1 <- as.difftime(1, units = "weeks") one_week_1 Time difference of 1 weeks typeof(one_week_1) [1] "double" attributes(one_week_1) $class [1] "difftime" $units [1] "weeks" one_week_2 <- as.difftime(7, units = "days") one_week_2 Time difference of 7 days typeof(one_week_2) [1] "double" attributes(one_week_2) $class [1] "difftime" $units [1] "days" now <- as.POSIXct("2019-07-27 22:00:00", tz = "UTC") pre <- as.POSIXct("2019-07-17 22:00:00", tz = "UTC") now - pre Time difference of 10 days