Rのこと。

記事は引っ越し作業中。2023年中までに引っ越しを完了させてブログは削除予定

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が用意されている。NANA_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-271970-01-01から18104日後となる。

Sys.Date()
[1] "2019-07-27"

unclass(today)
[1] 18104

POSIXct

日時はPOSIXctPOSIXltの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"

POSIXct1970-01-01以降の秒数を表す。

unclass(now)
[1] 1564264800
attr(,"tzone")
[1] "UTC"

difftimes

日付間の時間または日付時刻を表す期間は、difftimesに格納される。Difftimesdoubleの上に構築され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