R の基礎

2023年10月26日

R とは

R は統計計算のための環境である。ここでは、プログラミング言語としての R について整理する。

動作環境

ミラーサイトから動作環境を入手する。日本の場合は数理統計研究所 のサイトがある。

インストール

Ubuntu (20.04 LTS) の場合だと ここ にあるとおりである。

# ふつうにアップデート
$ sudo apt update

# なんか必要なパッケージがあるらしい
$ sudo apt install --no-install-recommends software-properties-common dirmngr

# キーの取得
$ wget -qO- https://cloud.r-project.org/bin/linux/ubuntu/marutter_pubkey.asc | sudo tee -a /etc/apt/trusted.gpg.d/cran_ubuntu_key.asc

# レジストリの追加
$ sudo add-apt-repository "deb https://cloud.r-project.org/bin/linux/ubuntu $(lsb_release -cs)-cran40/"

ここまでが準備で、いよいよ R のインストール。

$ sudo apt install --no-install-recommends r-base

R の実行

端末で R と打てば起動する。

$ R

R version 4.3.1 (2023-06-16) -- "Beagle Scouts"
Copyright (C) 2023 The R Foundation for Statistical Computing
Platform: x86_64-pc-linux-gnu (64-bit)

R は、自由なソフトウェアであり、「完全に無保証」です。 
一定の条件に従えば、自由にこれを再配布することができます。 
配布条件の詳細に関しては、'license()' あるいは 'licence()' と入力してください。 

R は多くの貢献者による共同プロジェクトです。 
詳しくは 'contributors()' と入力してください。 
また、R や R のパッケージを出版物で引用する際の形式については 
'citation()' と入力してください。 

'demo()' と入力すればデモをみることができます。 
'help()' とすればオンラインヘルプが出ます。 
'help.start()' で HTML ブラウザによるヘルプがみられます。 
'q()' と入力すれば R を終了します。 

> a <- 1
> a + 1
[1] 2

終了するには Ctrl-D か q() を入力。

> q()
Save workspace image? [y/n/c]: n

なにか聞いてくるが、とりあえず n でよい。

ふつうは、作業用ディレクトリを作ってその中で作業するようにし、作業途中の状態を保存しておいたりする。

コードをファイルに記述しておくこともできる (これをスクリプトという)。拡張子は .r、.R などが使われる。

hello.r

print("Hello")

次のように実行する。

$ Rscript hello.r

R のコマンドラインの中でも読み込める。

> source("hello.r")

ヘルプ

R のコマンドラインではヘルプが使える。

> help(solve)

ページャーが開くので、矢印で移動、q で終了する。?solve でも同じ。

help.start() でマニュアルなどが見れる。

> help.start()

ブラウザで表示されるはず。

作業ディレクトリとワークスペース

R には作業ディレクトリという考え方がある。現在の作業ディレクトリは getwd() で得られる。

> getwd()
[1] "/home/adelie/work"

作業ディレクトリを変更するには、setwd() を使う。

> setwd("~/work/r")
> getwd()
[1] "/home/adelie/work/r"

一通り作業して、終了しようとしたときに、質問をしてくる。

> q()
Save workspace image? [y/n/c]: n

これは、要するに作業の中で作ったオブジェクトとかの情報を保存するか、と聞いている。y とすると、作業ディレクトリに ".RData" と ".Rhistory" が作成される。

たとえば、オブジェクトを 1 つ作って保存終了する。

> a <- 1
> ls()
[1] "a"

> q()
Save workspace image? [y/n/c]: y

再度 R を立ち上げると、ワークスペースが復帰された旨が表示され、先程作成したオブジェクトが存在している。

 [以前にセーブされたワークスペースを復帰します] 

> ls()
[1] "a"

明示的にワークスペースのイメージを保存するには、save.iamge() を用いる。

> save.image("backup.RData")

読み込むには load() を使う。

> rm(list=ls())
> ls()
character(0)

> load("backup.RData")
> ls()
[1] "a"

上の例では、一旦オブジェクトをすべて消して、イメージを読み込んで、オブジェクトが復帰されていることを確認している。

保存されたワークスペースを復帰したくない場合は、作業ディレクトリにある ".RData" などを削除すればよい。

コマンドと式

R のコードはコマンドからなる。一番簡単なものは式や代入である。コマンドの終わりは勝手に認識されるようである。

> a <-
+ 1 +
+ 2

ここで、2、3 行目の行頭の "+" は継続を意味するプロンプトで、実際の入力は "a <- 1 + 2" である。

1 行で複数のコマンドを書くには、セミコロンで区切ればよい。

> a <- 1; b <- 2; a + b
[1] 3
> 

コメント

# 以降は行末までコメントである。

# hello.r
print("Hello")

オブジェクト

R の中で作られ、操作される実体のことをオブジェクトという。オブジェクトはデータの名前付けによって作られる。それには代入 (assign) <- を用いる。

> a <- 1

逆でもよい。

> 1 -> a

最近のバージョンでは = でもよいらしい。

> a = 1

オブジェクトを文字列 "..." として指定しても同じことになる。

> "a" <- 1

オブジェクトの名前は、大文字と小文字が区別される。"a" と "A" は別のオブジェクトである。

作成されたオブジェクトは、objects() で確認できる。

> objects()
[1] "a"

代わりに ls() でもよい。

> ls()
[1] "a"

ここでの "a" は (公式ドキュメントでは) あくまでオブジェクトと考えられていて、変数とは呼ばないようである (assign も本当は bind と呼びたいところなのかもしれない)。

オブジェクトを削除するには、rm() を用いる。

> rm(a)

すべてのオブジェクトを削除したい場合は、次のようにする。

> rm(list=ls())

オブジェクトの変更禁止

いわゆる "定数" を作りたい場合はどうしたらよいか。厳密にいうと、オブジェクトは変数ではなく、そもそも「箱の中の値を変更する」という考え方がない (構造を持ったオブジェクトの内部情報を書き換えることはできるが)。ここでの問題は、オブジェクトを同じ名前で再定義 (bind) できてしまうことである。

オブジェクトの定義をロックする方法はあり、以下のようにする。

> a <- 1
> lockBinding("a", globalenv())
> a <- 2
Error: cannot change value of locked binding for 'a'

lockBinding() を使うと、名前と値のつながり (bind) がロックされて、別の値を代入できなくなる。これは、オブジェクトの内部情報の変更も禁止する (ベクトルの要素を書き換えたりできなくなる)。

a <- c(1, 2, 3)
lockBinding("a", globalenv())
a[3] <- 4

データ型とデータ構造

整数

数字の末尾に L をつけると、整数と認識される。

> a <- 1L
> typeof(a)
[1] "integer"

ただの 1 では整数にならない。

> typeof(1)
[1] "double"

浮動小数点数

通常の実数表現は、倍精度浮動小数点数とみなされる。

> a <- 1.2
> typeof(1.2)
[1] "double"

> a <- 3
> typeof(a)
[1] "double"

pi で円周率を表す。

> pi
[1] 3.141593

ただし、予約語ではないためオブジェクト名に使えてしまうので注意。

e はない。ほしければ exp(1) を割り当てればよい。

> e <- exp(1)
> e
[1] 2.718282

複素数

虚数を表すには i を用いる。複素数を表すには次のようにする。

> typeof(1 + 2i)
[1] "complex"

> 1i^2
[1] -1+0i

> sqrt(-1 + 0i)
[1] 0+1i

> Re(1 + 2i)
[1] 1

> Im(1 + 2i)
[1] 2

> Conj(1 + 2i)
[1] 1-2i

文字列

二重引用符 "..." で文字列になる。

> a <- "Hello"
> typeof(a)
[1] "character"

> a <- "123"
> typeof(a)
[1] "character"

引用符 '...' でも同じ結果になる。

"\u" で Unicode の番号を指定できる。

> "\u0153"
[1] "œ"

文字列に二重引用符を入れたい場合は、バックスラッシュをつける。

> "str = \"xxx\""
[1] "str = \"xxx\""

あるいは、'...' の中ならそのまま二重引用符を使える。

> 'str = "xxx"'
[1] "str = \"xxx\""

バックスラッシュはエスケープと呼ばれる特別なものである。上で見た "\u" や改行 "\n" のようなものはエスケープ文字と呼ばれ、特別な文字とみなされる。バックスラッシュ自体を表示したい場合は、"\\" とする。

あるいは、r"(...)" という形にすると、ナマ (raw) の文字列になり、エスケープされない。

> r"(c:\Program Files\R)"
[1] "c:\\Program Files\\R"

各文字のアクセス

R の文字列は配列のようにはアクセスできないらしい。文字を取り出すなら、substr() で部分文字列として取り出すことができる。

> a <- "Hello, world."
> substr(a, 1, 1)
[1] "H"

引数の数字は、1 文字目から 1 文字目までを取り出すという意味である。

文字列の長さ

文字列の長さを知るには、nchar() を用いる。

> nchar(a)
[1] 3

文字列の連結

文字列を連結するには paste() を用いる。

> paste("Hello", "World")
[1] "Hello World"

引数 sep であいだの文字列を指定できる。

> paste("Hello", "World", sep=", ")
[1] "Hello, World"

論理値

論理値として FALSE と TRUE、それに NA (not available) がある。

> b <- TRUE
> typeof(b)
[1] "logical"

FALSE、TRUE は F、T と書いてもよい。

> typeof(T)
[1] "logical"

F、T はそれぞれ FALSE、TRUE の値が入った変数らしい。予約語ではないためオブジェクト名として使えてしまうので注意。

FALSE、TRUE はそれぞれ 0、1 と同じ意味である。

> FALSE + 1
[1] 1

> TRUE + 1
[1] 2

NA は足しても掛けても NA である。

> NA + 1
[1] NA

> NA*2
[1] NA

ベクトル

c(...) でベクトルを作れる。

> a <- c(1, 2, 3)
> a
[1] 1 2 3

続きの数値なら、1:10 のような書き方もできる。

> a <- 1:10
> a
 [1]  1  2  3  4  5  6  7  8  9 10

大小反対にしたければ、実際に反対にするだけでよい。

> a <- 10:1
> a 
 [1] 10  9  8  7  6  5  4  3  2  1

ステップ指定がしたければ、seq() が使える。

> seq(1, 10, 2)
[1] 1 3 5 7 9

繰り返しを行う rep() というものもある。

> rep(c(1, 2), 3)
[1] 1 2 1 2 1 2

空の数値ベクトルがほしければ、numeric() が使える。

> x <- numeric(10)
> x
 [1] 0 0 0 0 0 0 0 0 0 0

complex()、character() でも同様。

要素のアクセス

添字で各要素にアクセスできる (1 から始まる)。

> b <- 2*a
> b
 [1]  2  4  6  8 10 12 14 16 18 20

> b[3]
[1] 6

1:2 の形で 1 要素目から 2 要素目までの部分ベクトルを取り出すことができる。

> a[1:2]
[1] 1 2

コロンを使うときの注意としては、足し算などする場合は括弧がいるということである。

> a <- 1:5
> a[3:5]
[1] 3 4 5

> a[3+1:5]
[1]  4  5 NA NA NA

3+1:5 は、3+(1:5) という意味になっている。(3+1):5 とするには括弧が必要。

> a[(3+1):5]
[1] 4 5

添字にベクトルを指定してもよい。

> a[c(1, 3)]
[1] 1 3

ベクトルの長さより大きな添字を指定すると、エラーではなく NA を返す。

> a <- 1:5
> a
[1] 1 2 3 4 5

> a[10]
[1] NA

しかも値を入れることができて、拡張された部分は NA で埋められる。

> a[10] <- 10
> a
 [1]  1  2  3  4  5 NA NA NA NA 10

ベクトルの長さ

長さを知りたければ length() を使う。

> length(c(1, 2, 3))
[1] 3

ベクトルでないものも、長さ 1 のベクトルという扱いらしい。

> length(1)
[1] 1

要素の追加や削除

append() でベクトルにベクトルをつなげることができる。

> a <- c(1, 2, 3)
> append(a, c(4, 5, 6))
[1] 1 2 3 4 5 6

a の値は変わっていないことに注意。

この場合、c() でもよかったりする。

> c(a, 4, 5, 6)
[1] 1 2 3 4 5 6

append() だと任意の位置に挿入できる。

> append(a, c(4, 5, 6), 2)
[1] 1 2 4 5 6 3

ある要素を除外したければ、添字にマイナスをつければよい。

> a[-2]
[1] 1 3

> a[-(2:3)]
[1] 1

ベクトルの代入

ベクトルの代入ではコピーが作成される (<- がオブジェクトの作成の意味だからか)。

> a <- c(1, 2, 3)
> b <- a
> b[1] <- 2
> a
[1] 1 2 3

> b
[1] 2 2 3

別のところで述べるが、コピーはすぐ行われるのではなく、必要になったときに行われているようである。

アトミック構造

ベクトルは、すべての要素の型が同じであるアトミック構造を想定している (アトミックベクトルという)。したがって、型が異なるものを混ぜると型が揃えられてしまう。

> a <- c(1, "2")
> a
[1] "1" "2"

> a <- c(1, 2 + i)
> a
[1] 1+0i 2+2i

強さ (?) は、文字列 > 数値 > 論理値のようである。

辞書

キーに対して値を持つ辞書 (連想配列) を作ることができる。

> a <- c("A" = 1, "B" = 2)
> a
A B 
1 2 

> a["A"]
A 
1 

代入の <- は = でもよいと上で述べたが、ではここの "A" = 1 は "A" <- 1 でもよいのかというと、ダメである。"A" というオブジェクトができて、その値が要素になるだけなので。

これは独立したデータ構造というより、ベクトルに名前の属性がついたものになっている。名前は names() で得られる。

> names(a)
[1] "A" "B"

データのほうだけほしければ、unname() で取り出せる。

> unname(a)
[1] 1 2

名前は属性にすぎないので、あとから追加できたりする。

> a <- c(1, 2)
> names(a) <- c("A", "B")
> a
A B 
1 2 

> a["A"]
A 
1 

逆に名前を消したかったら、NULL を入れる。

> names(a) <- NULL

ところで、属性というのはオブジェクトに付加された情報で、attributes() で確認できる。

> attributes(a)
$names
[1] "A" "B"

ファクタ

ファクタは、いわゆるカテゴリカルデータを扱うためのデータ構造である。たとえば、猫ちゃんのデータベースを作るとして、タイプが "sabatora"、"chatora"、"mike" であるとする。猫ちゃんが 4 匹いて、それぞれのタイプが "sabatora"、"chatora"、"mike"、"sabatora" である場合、ファクタを次のように定義できる。

> cat_type <- c("sabatora", "chatora", "mike")
> a <- factor(c("sabatora", "chatora", "mike", "sabatora"), cat_type)
> a
[1] sabatora chatora  mike     sabatora
Levels: sabatora chatora mike

取りうる値をレベルと呼び、levels() で確認できる。これは属性である。

> levels(a)
[1] "sabatora" "chatora"  "mike"

もし、1 匹目の猫ちゃんのタイプが間違っていた場合、"chatora" に変更することはできるが、レベルにない "white" に変更しようとすると怒られる。この場合、NA が入る。

> a[1] <- "chatora"
> a[1] <- "white"
 警告メッセージ: 
 `[<-.factor`(`*tmp*`, 1, value = "white") で: 
   不正な因子水準です。NA が発生しました 

レベルは数値に変換することができる。

> as.numeric(a)
[1] 1 2 3 1

逆もできる。

> a <- c(1, 2, 3, 1)
> a <- as.factor(a)
> levels(a) <- cat_type
> a
[1] sabatora chatora  mike     sabatora
Levels: sabatora chatora mike

順序を持ったレベルも扱える。

> a <- factor(c("Mar", "Feb", "Jan", "Jan"), levels=c("Jan", "Feb", "Mar"), ordered=TRUE)
> a
[1] Mar Feb Jan Jan
Levels: Jan < Feb < Mar

順序を持っている場合、値を比較できる。

> a[1] < a[2]
[1] FALSE

猫ちゃんデータで比較を行おうとすると怒られる。

> a[1] < a[2]
[1] NA
 警告メッセージ: 
 Ops.factor(a[1], a[2]) で:   ‘<’ は因子に対しては無意味です

ところで、ファクタの属性を見ると、以下のような感じになる。

> attributes(a)
$levels
[1] "sabatora" "chatora"  "mike"    

$class
[1] "factor"

また、型は次のようになっている。

> typeof(a)
[1] "integer"

属性にクラスというものがある。R のオブジェクトは、データ型とは別に、クラスによってオブジェクトの種類が区別されている場合がある。

> class(a)
[1] "factor"

行列

次のような形で行列を定義することができる。

> a <- matrix(c(1, 2, 3, 4), nrow=2)
> a
     [,1] [,2]
[1,]    1    3
[2,]    2    4

デフォルトではベクトルの値が列方向に入っていく。行方向に入れたければ次のようにする。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

行方向のほうが慣れているなら、関数を作っておくとよいかもしれない (よく使うなら短めの名前がよいかもしれない)。

> mat <- function(x, ...) matrix(x, byrow=TRUE, ...)
> a <- mat(c(1, 2, 3, 4), nrow=2)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

空の行列がほしければ、numeric() などを使えばよい。

> a <- matrix(numeric(4), nrow=2)
> a
     [,1] [,2]
[1,]    0    0
[2,]    0    0

値を 1 つ指定する方法もある。

> a <- matrix(0, nrow=2, ncol=3)
> a
     [,1] [,2] [,3]
[1,]    0    0    0
[2,]    0    0    0

要素にアクセスしたければ、カンマ区切りで添字を指定すればよい (1 から始まる)。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a[1, 2]
[1] 2

部分行列の値を得ることも可能。

> a[1:2, 1]
[1] 1 3

ある列全体がほしいといった場合は、添字を省けばよい。

> a[, 2]
[1] 2 4

サイズ (次元) がほしければ、dim() で得られる。

> dim(a)
[1] 2 2

サイズは属性にすぎないので、属性の値を変えることでサイズを変えられる。

> attributes(a)
$dim
[1] 2 2

> dim(a) <- c(4, 1)
> a
     [,1]
[1,]    1
[2,]    3
[3,]    2
[4,]    4

多次元配列

多次元配列は次のように作成する。

> a <- array(c(1:4, 5:8, 9:12), c(2, 2, 3))
> a
, , 1

     [,1] [,2]
[1,]    1    3
[2,]    2    4

, , 2

     [,1] [,2]
[1,]    5    7
[2,]    6    8

, , 3

     [,1] [,2]
[1,]    9   11
[2,]   10   12

次のようにアクセスする。

> a[1:2, 1:2, 2]
     [,1] [,2]
[1,]    5    7
[2,]    6    8

> a[, , 2]
     [,1] [,2]
[1,]    5    7
[2,]    6    8

> a[2, 2, 3]
[1] 12

リスト

ベクトル、行列、多次元配列はデータが同じ型である必要があるが、リストは別の型を一緒に持てるデータ構造である。

> a <- list(1, c("2", "3"), TRUE)
> a
[[1]]
[1] 1

[[2]]
[1] "2" "3"

[[3]]
[1] TRUE

> typeof(a)
[1] "list"

要素にアクセスするには、次のようにする。

> a[[2]][1]
[1] "2"

ここで、a[[2]] というのが出てきているが、a[2] とは当然意味が異なる。リストはサブリストのまとまりになっており、a[2] ではサブリストの指定、a[[2]] ではサブリストの中身の指定になっている。

要素の追加は、添字指定でできる。

> a[[4]] <- 1 + 2i
> a
[[1]]
[1] 1

[[2]]
[1] "2" "3"

[[3]]
[1] TRUE

[[4]]
[1] 1+2i

添字を飛ばして入れると、あいだは NULL になる。

> a[[6]] <- "xxx"
> a
[[1]]
[1] 1

[[2]]
[1] "2" "3"

[[3]]
[1] TRUE

[[4]]
[1] 1+2i

[[5]]
NULL

[[6]]
[1] "xxx"

ベクトルに変換するには、unlist() を使う。

> a <- list(1, 2, 3)
> a
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> unlist(a)
[1] 1 2 3

リストの要素の型がそろっていない場合は、ベクトルと同様の変換規則で型が揃えられる。

名前付きリスト

名前付きのリストを作ることができる。

> a <- list(num=1, vec=c("2", "3"), log=TRUE)
> a
$num
[1] 1

$vec
[1] "2" "3"

$log
[1] TRUE

各名前のオブジェクトには、$ でアクセスできる。

> a$vec
[1] "2" "3"

あるいは

> a[["vec"]]
[1] "2" "3"

以下のようにすると、違った結果が出てくる。

> a["vec"]
$vec
[1] "2" "3"

理屈は数字の添字の場合と同じである。a["vec"] ではサブリストの指定、a[["vec"]] ではサブリストの中身の指定である。

名前付きリストは、いわゆる構造体のようなものとして使うことができる。

属性を用いた構造体

名前付きリストと似たようなものだが、属性を用いて構造体を作ることができる。

> p <- structure(list(), x=0, y=0)
> p
list()
attr(,"x")
[1] 0
attr(,"y")
[1] 0

属性にアクセスする場合は、attr() を用いる。

> attr(p, "x")
[1] 0

> attr(p, "x") <- 1
> p
list()
attr(,"x")
[1] 1
attr(,"y")
[1] 0

新しい属性を追加することもできる。

> attr(p, "z") <- 0
> p
list()
attr(,"x")
[1] 1
attr(,"y")
[1] 0
attr(,"z")
[1] 0

データフレーム

データフレームは、データ分析のための便利なデータ構造である。手で作る場合、次のようになる。

> df <- data.frame(name=c("omochi", "annko", "tamago"), type=c("sabatora", "chatora", "mike"), age=c(3, 4, 5))
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

> typeof(df)
[1] "list"

> class(df)
[1] "data.frame"

タイプとしては list だが、クラスは data.frame である。

データの個数

データの個数は length() で得られる。

> length(df)
[1] 3

要素のアクセス

要素のアクセスは、まず添字でできる。

> df[1, 2]
[1] "sabatora"

> df[1:3, 2]
[1] "sabatora" "chatora"  "mike"

ある列全体がほしいといった場合は、添字を省けばよい。

> df[, 2]
[1] "sabatora" "chatora"  "mike"

連続した要素はベクトルとして得られるが、同じデータフレームとしてほしければ、drop=FALSE を指定する。

 df[1:3, 2, drop=FALSE]
      type
1 sabatora
2  chatora
3     mike

名前による参照

名前による列の参照も行える。

> df$name
[1] "omochi" "annko"  "tamago"

> df["name"]
    name
1 omochi
2  annko
3 tamago

> df[c("name", "type")]
    name     type
1 omochi sabatora
2  annko  chatora
3 tamago     mike

部分がほしければ次のようにもできる。

> df[1:2, c("name", "type")]
    name     type
1 omochi sabatora
2  annko  chatora

データの追加

行データを追加したければ、最後の行の次に追加すればよい。

> df[4, ] <- c("momo", "mike", 3)
> df
    name     type age
1 omochi   white    3
2  annko chatora    4
3 tamago    mike    5
4   momo    mike    3

添字を飛ばしてしまうと、あいだに NA が入る。

> df[6, ] <- c("kinako", "chatora", 4)
> df
    name     type age
1 omochi   white    3
2  annko chatora    4
3 tamago    mike    5
4   momo    mike    3
5        
6 kinako chatora    4

列データは、名前を指定して入れることができる。

> df <- data.frame()
> df[1:3, "name"] <- c("omochi", "annko", "tamago")
> df["type"] <- c("sabatora", "chatora", "mike")
> df
    name     type
1 omochi sabatora
2  annko  chatora
3 tamago     mike

データの削除

添字にマイナスをつけると、それだけ取り除かれるので、それで新しいデータフレームを作ればよい。

> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

> df[-2]
    name age
1 omochi   3
2  annko   4
3 tamago   5

> df[-2, ]
    name     type age
1 omochi sabatora   3
3 tamago     mike   5

列名から添え字を知りたければ、match() などを使う。

> match("type", colnames(df))
[1] 2

型変換

型変換は、"as.型名" という名前の関数で行うことができる。

> as.character(1)
[1] "1"

> as.numeric("1")
[1] 1

 as.list(c(1, 2, 3))
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> as.matrix(list(1, 2, 3))
     [,1]
[1,] 1   
[2,] 2   
[3,] 3 

> as.vector(matrix(c(1, 2, 3)))
[1] 1 2 3

式と演算子

式はデータと演算子で構成される。

整数、浮動小数点数については、以下のような演算子が使える。

演算子例意味
+a + b加算
-a - b減算
*a * b乗算
/a / b除算
^, **a^b, a**b累乗
%%a %% b剰余
--a負符号

優先順位は一般的なものである。

波括弧が丸括弧と同じように機能する。

> 1 + 2 * 3
[1] 7

> (1 + 2) * 3
[1] 9

> {1 + 2} * 3
[1] 9

後述するが、波括弧部分が 1 つの値 (最後の式の値) をもつためと考えられる。次のようにすると違いがわかる。

> {1 + 3; 1 + 2} * 3
[1] 9

> (1 + 3; 1 + 2) * 3
 エラー:   予想外の ';' です  ( "(1 + 3;" の) 

制御構造

グループ

{...} で複数のコマンドをひとまとまりにすることもできる。

> {
+ a <- 1
+ b <- 2
+ a + b
+ }
[1] 3

最後の式の値がグループの値になる。

グループは、他の言語で言うところの独自のスコープを構成しないことに注意。以下の制御構文も同様である。

if...else 文

if...else 文で条件分岐を作れる。

> v <- 1
> if (v == 1) {
+ print("sabatora")
+ } else if (v == 2) {
+ print("chatora")
+ } else {
+ print("mike")
+ }
[1] "sabatora"

if...else 文は、文というより式 (値をもつ) なので、以下のような書き方もできる。

> a <- -1
> b <- if (a < 0) -a else a
> b
[1] 1

比較演算子

値を比較する演算子として、<、>、<=、>= がある。"等しい" を表すものは == である (= と間違いやすい)。"等しくない" は != である。

論理演算子

条件を複数記述するための論理演算子として、&& (AND)、|| (OR) がある。また、式の頭に ! をつけると否定になる。途中で結果が決まったらその先は評価しない (短絡という)。たとえば、&& で早々に FALSE になったり、|| で早々に TRUE になったりすると、その先は評価されない。関数を呼んでいたりすると、呼ばれないことになる。

たとえば、x が 0 以上でかつ y が 5 以下であるときになにかしたい、という場合は、次のようになる。

if (x >= 0 && y <= 5)
    ...

この場合、そもそも x が 0 より小さかったら、y の式はまったく評価されない。

for ループ

for ループは繰り返し処理に使う。

> for (i in 1:5) {
+ print(i)
+ }
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

上の例は、1 から 5 までループする。逆さまにしたい場合は以下のようにする。

> for (i in 5:1) {
+ print(i)
+ }
[1] 5
[1] 4
[1] 3
[1] 2
[1] 1

配列の要素をループさせることもできる。

> for (i in c("sabatora", "chatora", "mike")) {
+ print(i)
+ }
[1] "sabatora"
[1] "chatora"
[1] "mike"

seq_along() を使えば、添字をループさせることができる。

> for (i in seq_along(c("sabatora", "chatora", "mike"))) {
+ print(i)
+ }
[1] 1
[1] 2
[1] 3

while ループ

while ループは条件が成り立つあいだ繰り返す。

> i <- 1
> while (i <= 5) {
+ print(i)
+ i <- i + 1
+ }
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

next と break

next でループを次に進めることができる。

> for (i in 1:10) {
+ if (i %% 2 == 0) next
+ print(i)
+ }
[1] 1
[1] 3
[1] 5
[1] 7
[1] 9

break でループを抜けることができる。

> for (i in 1:10) {
+ if (i == 5) break
+ print(i)
+ }
[1] 1
[1] 2
[1] 3
[1] 4

repeat ループ

repeat は単純なループで、break で抜けるか、Ctrl-C しないと止まらない。

> repeat {
+ print(i)
+ i <- i + 1
+ if (i > 5) break
+ }
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5

プログラムの停止

プログラムは q() あるいは quit() で止まるが、そのままだと質問されるので、引数に質問への回答を入れておく。

quit("no")

エラーで止める場合は stop() がある。

stop("error!")

また、stopifnot() というものもある。条件が成り立っていなかったら止まる。

stopifnot(a > 0)

関数

関数は、引数を受け取って値を返す処理のまとまりである。次のように定義する。

> f <- function(x, y) {
+ x + y
+ }
> f(1, 2)
[1] 3

最後の式の値が返り値になる。上の例では、1 行で書いてもよい。

> f <- function(x, y) x + y

return も使えるが、関数のような使い方である (というか関数そのもの)。

> f <- function(x, y) {
+ return(x + y)
+ }

返り値は勝手に表示されるが、表示されると困る場合もある。print() では、画面に表示しつつ値も返している。値が表示されると 2 重で表示されることになるはずだが、そうなっていない。返り値を表示しないように指定しているためである。これは invisible() で実現できる。

> f <- function(x) invisible(x)
> f("test")
> 

何も表示されない。でも値はオブジェクトとして受け取れる。

> r <- f("test")
> r
[1] "test"

R の関数は、定義の方法からして明らかだが、同じ名前で引数が異なる関数を多重定義することは (ふつうの方法では) できない。

引数

関数の引数は、動きとしては基本的に値渡しである。ただ、実際には必要がなければコピーは行われないようである。

> a <- list(1, 2, 3)
> tracemem(a)
[1] "<0x55e089e40908>"

> f <- function(x) print(x)
> g <- function(x) {x[[4]] <- 4; print(x)}

> f(a)
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

> g(a)
tracemem[0x55e089e40908 -> 0x55e089e3fcd8]: g 
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

[[4]]
[1] 4

引数に対して何もしない f() ではコピーが起こっていないが、引数に対して操作する g() ではコピーが起こっている。

これは、関数に限った話ではないらしい。

> a <- list(1, 2, 3)
> tracemem(a)
[1] "<0x55ed8da2e908>"

> b <- a
> b[[4]] <- 4
tracemem[0x55ed8da2e908 -> 0x55ed8da2e778]:

b を編集したときにはじめてコピーが行われている。

名前付き引数

引数名を指定して呼び出すことができる。

> f <- function(x, y) x + y
> f(y=2, x=1)
[1] 3

辞書のときと同じく、ここでも = の代わりに <- は使えないので注意。

デフォルト引数

引数にデフォルト値を設定しておくことができる。

> f <- function(x, y=1) x + y
> f(1)
[1] 2

可変長引数

"..." (ドット 3 つ) で可変長引数が扱える。

> f <- function(...) {
+ a <- c(...)
+ if (length(a) == 0) return(0)
+ else if (length(a) == 1) return(a[1])
+ else return(a[1] + f(a[-1]))
+ }

> f()
[1] 0

> f(1)
[1] 1

> f(1, 2, 3, 4, 5)
[1] 15

> f(1:10)
[1] 55

再帰呼び出し

ふつうにオブジェクト名 (関数名) で呼べばよい。

> fibonacci <- function(n) {
+ if (n == 0) return(0)
+ else if (n == 1) return(1)
+ else return(fibonacci(n - 2) + fibonacci(n - 1))
+ }

> for (i in 0:10) print(fibonacci(i))
[1] 0
[1] 1
[1] 1
[1] 2
[1] 3
[1] 5
[1] 8
[1] 13
[1] 21
[1] 34
[1] 55

無名関数

R の関数は、関数定義自体を関数の引数に渡したりできる。

> map <- function(x, f) {
+ a <- c()
+ for (i in seq_along(x)) a <- c(a, f(x[i]))
+ a
+ }

> map(c(1, 2, 3), function(x) 2*x)
[1] 2 4 6

演算子

演算子を関数として扱いたい場合がある (関数の引数に渡したい場合など)。その場合は、`...` とすればよい。

> `+`(1, 2)
[1] 3

演算子の定義

"%...%" の形で新しい演算子を定義できる。

> "%!%" <- function(x, y) x + y
> 1 %!% 2
[1] 3

これは一応関数の扱いらしい。

> "%!%"(1, 2)
[1] 3

パイプ

パイプというのは、UNIX 系のシェルで使われる、コマンドの処理結果をさらに別のコマンドに流し込む操作である。R では、dplyr や magrittr などのパッケージによって %>% の形で導入され、広く使われるようになったようである。最近の R では |> の形で実装されている。

> (1 + 2) |> sqrt()
[1] 1.732051

これは以下と同じである。

> sqrt(1 + 2)
[1] 1.732051

このページ によると、|> は F# に由来するらしい。

クロージャ

次のように、呼ぶたびに 1 増えた数値を返す関数を定義したい。

> f()
[1] 1

> f()
[1] 2

> f()
[1] 3

そのためには、ローカルの環境にオブジェクトを閉じ込める形で関数を定義すればよい。この仕組みをクロージャという。問題は、R の場合どうやったら閉じた環境ができるかである。一見ブロックに見える {...} であるが、これは独自の環境を持っていないという意味ではブロックではない。環境は environment() で確認できる。

> environment()
<environment: R_GlobalEnv>

> {environment()}
<environment: R_GlobalEnv>

> (function() environment())()
<environment: 0x55f41a141ea8>

つまり、{...} では環境が変わらないが、関数であれば変わる。環境はオブジェクトが作成される場なので、{...} の中でオブジェクトを作ると、グローバル環境に作られる。一方関数内では、関数の環境に作られることになる。

目的の関数 f() は、次のように作れそうに思うかもしれない。

> f <- (function() {
+ x <- 0
+ function() {
+ x <- x + 1
+ x
+ }
+ })()

関数を作って返す関数を作り、即実行させている。あいにく、外側の関数と内側の関数の環境が違うので、x <- x + 1 の右辺は外側の関数の x を見ているが、左辺の x は内側の関数の環境に x を作成していることになり、値は常に 1 になってしまう。

> f()
[1] 1

> f()
[1] 1

> f()
[1] 1

目的の関数 f() は、次のように作ることができる。

> f <- (function() {
+ x <- 0
+ function() {
+ assign("x", x + 1, parent.env(environment()))
+ x
+ }
+ })()

> f()
[1] 1

> f()
[1] 2

> f()
[1] 3

ここで、parent.env() は環境の親環境を返す。assign() は環境を指定した割り当て (オブジェクトの作成) である。つまり、内側の関数において、外側の関数の環境に x を作り直している。

しかし、これでは長ったらしいので、代わりに以下のように書く方法がある。

> f <- (function() {
+ x <- 0
+ function() {
+ x <<- x + 1
+ x
+ }
+ })()

<- の代わりに <<- を用いている。これは、左辺のオブジェクトが存在するところまで親の環境に向かって探しに行ってくれるようである。もし外側の関数に x <- 0 がなく、グローバルのほうにあったとしたら、グローバルのオブジェクトの値が変更される。

クロージャは、次のような使い方もできる。

> setup <- function(r) function(x) x*r

> g <- setup(1.2)
> g(1000)
[1] 1200

> h <- setup(1.5)
> h(1000)
[1] 1500

親の環境とは別に、親のフレームというものもある。

> f <- function() {
+ print(parent.env(environment()))
+ print(parent.frame())
+ }

> f()
<environment: R_GlobalEnv>
<environment: R_GlobalEnv>

> (function() f())()
<environment: R_GlobalEnv>
<environment: 0x55f9964b1ad0>

親の環境は関数を定義したときに決まるが、親のフレームのほうは、関数を呼び出した時点での呼び出し元の環境である。これを使うと、次のような「動的スコープ」(変数の値が呼び出し元の環境に依存する) を実現できる。

> f <- function(x) {
+ r <- get("r", parent.frame())
+ x*r
+ }

> r <- 1.2
> f(1000)
[1] 1200

> (function() {r <- 1.5; f(1000)})()
[1] 1500

ちなみに、グローバル環境を参照したければ、globalenv() で得られるが、.GlobalEnv でも参照できる。

> globalenv()
<environment: R_GlobalEnv>

> .GlobalEnv
<environment: R_GlobalEnv>

呼び出し側の値の変更

関数の呼び出し側の引数 (実引数) の値を変更する場合、一般的には参照渡しが使われる。しかし、R の関数の引数は値渡しである。ただ、呼び出し元のオブジェクトの値を強引に変更することはできる。

関数内で substitute() に引数 (仮引数) を渡すと、呼び出し側のオブジェクト名が得られる。これを使って、式を親の環境で実行すれば、呼び出し側のオブジェクトの値を変更できる。

たとえば、引数の値を 1 増やす関数を作りたいとする。以下のようにしても引数の値は変わらない。

> inc <- function(x) x <- x + 1
> a <- 0
> inc(a)
> a
[1] 0

以下のようにすると、値が変わる。

> inc <- function(x) eval.parent(substitute(x <- x + 1))
> a <- 0
> inc(a)
> a
[1] 1

inc(a) を呼ぶと、その場 (環境) で "a <- a + 1" が実行される。これではほとんどマクロではないか? 実際、それに近い。substitute() は呼び出し側の式そのものを得る。

> f <- function(x) substitute(x)
> f(1 + 2)
1 + 2

遅延評価

R は遅延評価 (lazy evaluation) を採用している。これは、必要になるまで式を評価しないというものである。具体的には、次のようになる。

> f <- function(x, y, z=0) if(z == 0) x else y
> f(print("x"), print("y"))
[1] "x"

> f(print("x"), print("y"), 1)
[1] "y"

関数 f() は、z の値に応じて引数 x、y のどちらかを使うが、呼び出し側で関数を実行するようにしていると、使われる側の関数しか実行されないことになる。

メソッド

R にはオブジェクト指向プログラミングの仕組みがいくつか実装されている。そのひとつが S3 で、R のもとになった言語 S のバージョン 3 という意味らしい。

なんでもよいが、とりあえずリストから構造体を作る。

> p <- structure(list(), x=0, y=0)

R のオブジェクトは属性を持ち、その中にはクラス情報がある。

> class(p)
[1] "list"

これは書き換えることができる。

> class(p) <- "point"
> class(p)
[1] "point"

ところで、print() はどんなオブジェクトでも機能するように見えるが、実はこれはジェネリック関数 (総称関数) である。

> print
function (x, ...) 
UseMethod("print")
<bytecode: 0x55c1adcddba8>
<environment: namespace:base>

UseMethod() が引数のクラスに応じて適切な関数を呼び出す。ジェネリック関数の実装のほうをメソッドという。print() がどのようなメソッド群で成り立っているかは methods() でわかる。

> methods(print)
  [1] print.acf*                                          
  [2] print.activeConcordance*                            
  [3] print.anova*                                        
  [4] print.aov*                                          
  [5] print.aovlist* 

  ...

[190] print.xtabs*

メソッドは "print.クラス名" という名前で定義されている。point クラスに対して print() を定義するなら、print.point(x, ...) を定義すればよい。

> print.point <- function(x, ...) {
+ s <- sprintf("(%g, %g)", attr(x, "x"), attr(x, "y"))
+ print(s)
+ }

> print(p)
[1] "(0, 0)"

クラスに対してどういうメソッドが定義されているか、以下のように調べることができる。

> methods(class="point")
[1] print

自分でジェネリック関数を作りたければ、次のようにする。

> to_string <- function(x) {
+ UseMethod("to_string")
+ }

> to_string.point <- function(x) {
+ sprintf("(%g, %g)", attr(x, "x"), attr(x, "y"))
+ }

> to_string(p)
[1] "(0, 0)"

メソッドが増えている。

> methods(class="point")
[1] print     to_string

仕組み上、同じ名前で引数違いのメソッドは定義できない。

S3 では、以上のような仕組みでオブジェクト指向プログラミングを行えるようになっている。コンストラクタというものはないので、以下のような関数を定義すればよい。

> new_point <- function() {
+ structure(list(), class="point", x=0, y=0)
+ }
> p <- new_point()

R でオブジェクト指向プログラミングをサポートする仕組みは、S3 の他に S4 などがある。S4 のほうがもっと洗練されているが、統計処理という目的には煩雑かもしれない。

コードの分割

source() で別ファイルのコードを取り込むことができる。

> source("geometry.r")

パッケージ

R では外部パッケージを利用できる。たとえば、グラフの描画などにパッケージの ggplot2 が利用される。パッケージを利用するには、インターネットを通じてインストールする必要がある。インストール先は .libPaths() で確認できる。

> .libPaths()
[1] "/usr/local/lib/R/site-library" "/usr/lib/R/site-library"      
[3] "/usr/lib/R/library"

たとえば、作業ディレクトリ (カレントディレクトリ) の lib にインストールしたい場合は、次のようにする方法がある。

$ R_LIBS=`pwd`/lib R

もしくは、R 起動後に

> .libPaths(sprintf("%s/lib", getwd()))

自分で打つのがめんどくさければ (ふつうそうだが)、~/.Rprofile に書いておけばよい。

~/.Rprofile

libPaths(sprintf("%s/lib", getwd())) 

R が起動するときに自動で実行される。

あるいは、.bashrc などでユーザーディレクトリ内にパッケージのインストール先を指定しておく方法もある。

~/.bashrc
R_LIBS=~/.R/lib

インストール

インストール先の設定が終わったら、以下のようにインストールする。

> install.packages("ggplot2")

パスのアクセス権に問題がなければ、インストールが始まる。

依存関係にあるパッケージもインストールするには、dependencies=TRUE を指定する。

パッケージの利用

パッケージを利用するには、ロードする必要がある。

> library("ggplot2")

たとえば、次のように使う。

> x <- seq(0, 2*pi, 2*pi/50)
> y <- sin(x)
> qplot(x, y)

グラフが表示されれば OK。

コマンドライン引数

コマンドライン引数を得るには、commandArgs() を用いる。args.r

commandArgs(TRUE)
$ Rscript args.r 1 2 3
[1] "1" "2" "3"

commandArgs() の引数で TRUE を指定しないと、次のような結果になる。

[1] "/usr/lib/R/bin/exec/R" "--no-echo"             "--no-restore"         
[4] "--file=args.r"         "--args"                "1"                    
[7] "2"                     "3"

ファイル名が必要なら、この方法になる。この場合、"--args" を探せば引数だけを抜き出せる。

args2.r

arglist <- commandArgs()
i <- which(arglist == "--args")
if (length(i) != 0) arglist[(i+1):length(arglist)]

文字列

部分文字列を取り出す

部分文字列を取り出すには、substr() を用いる。

> a <- "abcde"
> substr(a, 3, 5)
[1] "cde"

文字列を繰り返す

文字列を繰り返すには次のようにできる。

> paste(rep("=", 20), collapse="")
[1] "===================="

連結と分割

ベクトルの要素を連結するには次のようにする。

> paste(c("sabatora", "chatora", "mike"), collapse=",")
[1] "sabatora,chatora,mike"

文字列を分割するには、次のようにする。

> strsplit("sabatora,chatora,mike", ",")
[[1]]
[1] "sabatora" "chatora"  "mike"

正規表現も使える。

> strsplit("sabatora\tchatora mike", "\\s")
[[1]]
[1] "sabatora" "chatora"  "mike"

文字列を探す

文字列のベクトルから指定の文字列を含むものを探す場合は、grep() を用いる。

> grep("n", c("penguin", "cat", "lion"))
[1] 1 3

> grep("m", c("penguin", "cat", "lion"))
integer(0)

指定の文字列を含む位置を得たい場合は、regexpr() を用いる。

> regexpr("n", c("penguin", "cat", "lion"))
[1]  3 -1  4
attr(,"match.length")
[1]  1 -1  1
attr(,"index.type")
[1] "chars"
attr(,"useBytes")
[1] TRUE

文字列のベクトルから指定の文字列と完全に一致するものを探す場合は、match() を用いる。

> match("lion", c("penguin", "cat", "lion"))
[1] 3

> match("panda", c("penguin", "cat", "lion"))
[1] NA

which() でもよい。

> which("lion" == c("penguin", "cat", "lion"))
[1] 3

> which("panda" == c("penguin", "cat", "lion"))
integer(0)

候補が複数ある場合、match() は最初に見つかった添字だけを返す。

> match("lion", c("penguin", "cat", "lion", "lion"))
[1] 3

> which("lion" == c("penguin", "cat", "lion", "lion"))
[1] 3 4

文字列の置換

文字列の置換は sub() で行える。

> sub("n", "N", c("penguin", "cat", "lion"))
[1] "peNguin" "cat"     "lioN"

適合するものすべてを置換する場合は、gsub() を用いる。

> gsub("n", "N", c("penguin", "cat", "lion"))
[1] "peNguiN" "cat"     "lioN"

1 文字ずつ置換する chartr() というものもある ("n" -> "m"、"t" -> "s" など)。

> chartr("nt", "ms", c("penguin", "cat", "lion"))
[1] "pemguim" "cas"     "liom" 

正規表現も使える。

> gsub("\\s", " ", "sabatora\tchatora\nmike")
[1] "sabatora chatora mike"

ベクトル

大きさだけ確保する

とりあえずベクトルの大きさだけ確保するには、次のようにする。

> a <- vector("numeric", 3)
> a
[1] 0 0 0

rep() でもよい。

> a <- rep(0, 3)
> a
[1] 0 0 0

ある値で埋める

全要素を特定の値にしたい場合は、添字なしで値を割り当てればよい。

> a[] <- 1
> a
[1] 1 1 1

最初と最後を取り出す

ベクトルから最初と最後の要素を取り出すには、それぞれ head()、tail() がある。

> head(1:100, 3)
[1] 1 2 3

> tail(1:100, 3)
[1]  98  99 100

要素が含まれるかどうか

要素が含まれるかどうかは、%in% で調べられる。

> "a" %in% c("a", "b", "c")
[1] TRUE

> "1" %in% c("a", "b", "c")
[1] FALSE

含まれていないかどうかを調べるには、! で逆にすればよい。

> !("1" %in% c("a", "b", "c"))
[1] TRUE

要素の並び替え

要素を並び替えるには sort() を用いる。

> a <- c(1, 2, 3, 5, 6, 4)
> sort(a)
[1] 1 2 3 4 5 6

> a
[1] 1 2 3 5 6 4

ただし、元のオブジェクトの値には影響しない。

逆順に並び替えたければ "decreasing=TRUE" を指定する。

> sort(a, decreasing=TRUE)
[1] 6 5 4 3 2 1

単純に逆にするだけなら rev() でよい。

> rev(a)
[1] 4 6 5 3 2 1

重複要素の削除

重複要素を削除するには unique() を使う。

> a <- c(1, 2, 2, 5, 4, 6, 3)
> unique(a)
[1] 1 2 5 4 6 3

最大値と最小値

要素の最大値、最小値を得るには、それぞれ max()、min() を用いる。

> a <- runif(3)
> a
[1] 0.6684421 0.5690569 0.9888023

> max(a)
[1] 0.9888023

> min(a)
[1] 0.5690569

絶対値

abs() で絶対値を得られる。

> a <- c(1, -2, 3, -4, 5)
> abs(a)
[1] 1 2 3 4 5

合計

sum() で合計を取ることができる。

> a <- c(1, 2, 3, 4, 5)
> sum(a)
[1] 15

足していく途中がほしい場合は cumsum() を使う。

> cumsum(a)
[1]  1  3  6 10 15

積

> a <- c(1, 2, 3, 4, 5)
> prod(a)
[1] 120

積の途中がほしい場合は cumprod() を用いる。

> cumprod(a)
[1]   1   2   6  24 120

演算の積算

Reduce() で、ある演算子を要素に適用して積算した結果が得られる。

> a <- c(1, 2, 3, 4, 5)
> Reduce(`+`, a)
[1] 15

> Reduce(`*`, a)
[1] 120

初期値を指定できる。

> Reduce(`*`, a, init=-1)
[1] -120

階差

diff() で階差を作る。

> diff(cumprod(c(1, 2, 3, 4, 5)))
[1]  1  4 18 96

ベクトル同士の演算

ベクトルの要素同士を足し合わせるには、+ でよい。

> a <- c(1, 2, 3)
> b <- c(4, 5, 6)
> a + b
[1] 5 7 9

- や * も同様である。片方がスカラーでも機能する。

各要素への関数の適用

別のベクトルを作る

ベクトルの各要素へ関数を適用して別のベクトルを作るには、sapply() を用いる。

> sapply(1:5, function(x) 2*x + 1)
[1]  3  5  7  9 11

パイプでもよい。

> 1:5 |> sapply(function(x) 2*x + 1)
[1]  3  5  7  9 11

リスト版もある。

> lapply(list(1:5, 2:6), function(x) 2*x + 1)
[[1]]
[1]  3  5  7  9 11

[[2]]
[1]  5  7  9 11 13

Map() というものもある。これもリストで返す。

> Map(function(x) 2*x + 1, list(1:5, 2:6))
[[1]]
[1]  3  5  7  9 11

[[2]]
[1]  5  7  9 11 13

関数が引数を複数取る場合は、mapply() が使える。

> mapply(function(x, y) 2*x + y, 1:5, 5:1)
[1]  7  8  9 10 11

単純な適用

単純に各要素に関数を適用したい場合は、for を使えばよい。

> b <- c()
> for (x in 1:5) b <- c(b, 2*x + 1)
> b
[1]  3  5  7  9 11

見かけ上 for を使わないで書けるだろうか。sapply() だと、途中のリストが返ってきてしまうので、次のような関数を定義したらよいかもしれない。

> foreach <- function(f, x) for (xi in x) f(xi)

次のように使う。

> b <- c()
> foreach(function(x) b <<- c(b, 2*x + 1), 1:5)
> b
[1]  3  5  7  9 11

抽出

Filter() で条件に合った要素だけを取り出すことができる。

> a <- 1:10
> Filter(function(x) x %% 2 == 0, a)
[1]  2  4  6  8 10

この場合、もっと直接的な方法がある。

> a[a %% 2 == 0]
[1]  2  4  6  8 10

条件に合う場所の添字がほしい場合は、which() を用いる。

> a <- 2:10
> a
[1]  2  3  4  5  6  7  8  9 10

> which(a %% 2 == 0)
[1] 1 3 5 7 9

最初に条件に合う要素を探すだけでよければ、Find() を使う。

> Find(function(x) x %% 2 == 0, a)
[1] 2

添字の大きい方から探したければ、right=TRUE を指定すればよい。

> Find(function(x) x %% 2 == 0, a, right=TRUE)
[1] 10

条件判定

どれかの要素で条件が成り立つかを調べるには any() が使える。また、すべての要素で条件が成り立つかどうかは all() でわかる。

> a <- c(1, 3, 5, 8, 9)
> any(a %% 2 == 0)
[1] TRUE

> all(a %% 2 == 1)
[1] FALSE

> a <- c(1, 3, 5, 7, 9)
> all(a %% 2 == 1)
[1] TRUE

複雑な条件の場合、つまり関数で判定したい場合は、sapply() を使えばよい。

> all(a |> sapply(function(x) x %% 2 == 1))
[1] TRUE

ベクトル用の論理演算子として & (AND) や | (OR) がある。

> a <- 1:10
> a[a %% 2 == 1 & a <= 5]
[1] 1 3 5

xor() もある。

> xor(c(TRUE, TRUE), c(FALSE, TRUE))
[1]  TRUE FALSE

条件に合うものが何個あるかを数えるには、sum() を使えばよい。

> a <- c(1, 3, 5, 8, 9, 10)
> sum(a %% 2 == 0)
[1] 2

要素の置き換え

replace() で条件に合った要素を別の値で置き換えることができる。

> a <- 1:5
> b <- replace(a, a %% 2 == 0, NA)
> b
[1]  1 NA  3 NA  5

関数で判定したければ、sapply() を用いればよい。

> replace(b, b |> sapply(is.na), 0)
[1] 1 0 3 0 5

この場合はふつうに以下のようにすればよい。

> replace(b, is.na(b), 0)
[1] 1 0 3 0 5

あるいは直接以下のようにしてもよい。

> a <- 1:5
> b <- a
> b[a %% 2 == 0] = NA
> b
[1]  1 NA  3 NA  5

> b[is.na(b)] <- 0
> b
[1] 1 0 3 0 5

単純に sapply() でも同じことができる。

> a <- 1:5
> b <- sapply(a, function(x) if (x %% 2 == 0) NA else x)
> b
[1]  1 NA  3 NA  5

> sapply(b, function(x) if (is.na(x)) 0 else x)
[1] 1 0 3 0 5

行列

大きさだけ確保する

とりあえず行列の大きさだけ確保するには、次のようにする。

> a <- matrix(0, nrow=2, ncol=3)
> a
     [,1] [,2] [,3]
[1,]    0    0    0
[2,]    0    0    0

0 行列を作る関数を用意しておくと便利かもしれない。

> zeros <- function(m, n=1) matrix(0, nrow=m, ncol=n)
> zeros(2, 3)
     [,1] [,2] [,3]
[1,]    0    0    0
[2,]    0    0    0

ある値で埋める

全要素を特定の値にしたい場合は、添字を指定しないで値を割り当てればよい。

> a[] <- 1
> a
     [,1] [,2] [,3]
[1,]    1    1    1
[2,]    1    1    1

形状を変える

属性の次元 (dim) を変更すれば、形状を変えられる。

> a <- matrix(c(1:3, 4:6), nrow=3)
> a
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6

> dim(a)
[1] 3 2

> dim(a) <- c(1, 6)
> a
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    2    3    4    5    6

結合する

行列は rbind()、cbind() で結合できる。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

> b <- matrix(c(5, 6, 7, 8), nrow=2, byrow=TRUE)
> b
     [,1] [,2]
[1,]    5    6
[2,]    7    8

> rbind(a, b)
     [,1] [,2]
[1,]    1    2
[2,]    3    4
[3,]    5    6
[4,]    7    8

> cbind(a, b)
     [,1] [,2] [,3] [,4]
[1,]    1    2    5    6
[2,]    3    4    7    8

ループ

> a <- matrix(c(1:3, 4:6), nrow=3)
> a
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6

行ごとのループ

> for (i in 1:nrow(a)) print(a[i, ])
[1] 1 4
[1] 2 5
[1] 3 6

列ごとのループ

> for (j in 1:ncol(a)) print(a[, j])
[1] 1 2 3
[1] 4 5 6

要素ごとのループ

> for (e in a) print(e)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6

> for (i in seq_along(a)) print(a[i])
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6

要素ごとのループの順番からすると、列方向が自然な動きのようである。添字 (i, j) でアクセスするなら、次のようになる。

> for (j in 1:ncol(a)) for (i in 1:nrow(a)) print(a[i, j])
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6

といいつつ、そもそもできるだけループは使わないほうがよいと思う。実行時間を計測してみるとよい (後述)。

転置

行列の転置は t() で得られる。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

> t(a)
     [,1] [,2]
[1,]    1    3
[2,]    2    4

複素共役行列

conj() で複素共役行列が得られる。

> a <- matrix(c(1 + 2i, 3 + 4i))
> a
     [,1]
[1,] 1+2i
[2,] 3+4i

> Conj(a)
     [,1]
[1,] 1-2i
[2,] 3-4i

随伴行列がほしければ、以下のようにすればよい。

> t(Conj(a))
     [,1] [,2]
[1,] 1-2i 3-4i

関数を定義しておけばよいかもしれない。

> adjoint <- function(x) t(Conj(x))
> adjoint(a)
     [,1] [,2]
[1,] 1-2i 3-4i

行列同士の演算

行列同士の和は + で計算できる。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

> b <- matrix(c(5, 6, 7, 8), nrow=2, byrow=TRUE)
> b
     [,1] [,2]
[1,]    5    6
[2,]    7    8

> a + b
     [,1] [,2]
[1,]    6    8
[2,]   10   12

アスタリスク (*) だと要素同士の掛け算となる。

> a*b
     [,1] [,2]
[1,]    5   12
[2,]   21   32

累乗も要素ごとの累乗になることに注意。

> a^2
     [,1] [,2]
[1,]    1    4
[2,]    9   16

行列の積を計算したい場合は、%*% を用いる。

> a %*% b
     [,1] [,2]
[1,]   19   22
[2,]   43   50

%*% はベクトルの場合は "内積" になる。

> a <- c(1, 2, 3)
> b <- c(4, 5, 6)
> a %*% b
     [,1]
[1,]   32

これは以下の計算と同じである。

> t(as.matrix(a)) %*% as.matrix(b)
     [,1]
[1,]   32

スカラーにしたければ drop() を使う。

> drop(a %*% b)
[1] 32

%o% でベクトルの直積を計算できる。

> a <- c(1, 2, 3)
> b <- c(4, 5, 6)
> a %o% b
     [,1] [,2] [,3]
[1,]    4    5    6
[2,]    8   10   12
[3,]   12   15   18

直積というのは以下の計算と同じものである。

> as.matrix(b) %*% t(as.matrix(a))
     [,1] [,2] [,3]
[1,]    4    8   12
[2,]    5   10   15
[3,]    6   12   18

※R では、%*% を "cross product"、%o% を "outer product" と呼んでいるようである。crossprod() で %*% と同じ計算ができる。tcrossprod() と outer() で %o% と同じ計算ができる。

単位行列

単位行列を作る方法は直接的にはないが、diag() に行数だけ与えると単位行列になる。

> diag(3)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

もう少しわかりやすく名前を付けておいたほうがよいかもしれない。

> eye <- function(n) diag(n)
> eye(3)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

ノルム

ノルムは norm() でよいが、計算方法がいくつかある。

> a <- matrix(c(1, 2, 3))
> norm(a, "2")
[1] 3.741657

"2" を指定すると、L2 ノルムを計算する。 以下の計算と同じである。

> sqrt(drop(crossprod(a)))
[1] 3.741657

ベクトル用に関数を定義しておいたほうがよいかもしれない。

> norm2 <- function(x) norm(x, "2")
> norm2(a)
[1] 3.741657

正規化

ベクトルの正規化の方法は特に用意されていないようなので、次のようにする。

> a <- c(1, 2, 3)
> normalize <- function(x) x/norm2(x)
> normalize(a)
[1] 0.2672612 0.5345225 0.8017837

内積

上で見たように、"内積" は %*% で計算できるのだが、ベクトル用に関数を定義しておいたほうがよいかもしれない。

> a <- c(1, 2, 3)
> b <- c(4, 5, 6)
> dot <- function(x, y) drop(x %*% y)
> dot(a, b)
[1] 32

外積

ベクトルの "外積" は定義されていないので、用意する必要がある。

> cross <- function(x, y) {
+ x2 <- rep(0, 3)
+ x2[1:length(x)] <- x
+ y2 <- rep(0, 3)
+ y2[1:length(y)] <- y
+ i <- 1:3
+ j <- function(i) (i - 1) %% 3 + 1
+ x2[j(i+1)]*y2[j(i+2)] - x2[j(i+2)]*y2[j(i+1)]
+ }

> cross(a, b)
[1] -3  6 -3

トレース

トレースを計算する方法は特に用意されてなさそうなので、次のようにする。

> a <- matrix(c(1, 2, 3, 4, 5, 6, 7, 9, 8), nrow=3, byrow=TRUE)
> tr <- function(a) sum(diag(a))
> tr(a)
[1] 14

対角成分

上ですでに使っているが、diag() で行列の対角成分を得られる。

> diag(a)
[1] 1 5 8

また、ベクトルを与えるとそれを対角成分にもつ行列を作る。

> diag(1:3)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    2    0
[3,]    0    0    3

数値を 1 つだけ指定すると、行数と解釈され、単位行列を作る。

> diag(3)
     [,1] [,2] [,3]
[1,]    1    0    0
[2,]    0    1    0
[3,]    0    0    1

行列式

det() で行列の行列式を計算する。

> det(a)
[1] 9

逆行列と連立方程式の解決

solve() で行列の逆行列を計算する。

> solve(a)
           [,1]       [,2]       [,3]
[1,] -1.5555556  1.2222222 -0.3333333
[2,]  1.1111111 -1.4444444  0.6666667
[3,]  0.1111111  0.5555556 -0.3333333

solve() は連立方程式を解く。

> b <- matrix(c(1, 2, 3))
> x <- solve(a, b)
> x
           [,1]
[1,] -0.1111111
[2,]  0.2222222
[3,]  0.2222222

> a %*% x
     [,1]
[1,]    1
[2,]    2
[3,]    3

行列の累乗

行列の累乗を計算する方法は標準では準備されてなさそうなので、以下のようなものを用意しておくとよいかもしれない。

> "%^%" <- function(x, n) {
+ m <- abs(n)
+ r <- diag(nrow(x))
+ if (n < 0) x <- solve(x)
+ for (i in 1:m) r <- r %*% x
+ r
+ }

> a <- matrix(c(1, 2, 3, 4, 5, 6, 7, 9, 8), nrow=3, byrow=TRUE)

> a %^% 2
     [,1] [,2] [,3]
[1,]   30   39   39
[2,]   66   87   90
[3,]   99  131  139

> a %^% -2
           [,1]       [,2]       [,3]
[1,]  3.7407407 -3.8518519  1.4444444
[2,] -3.2592593  3.8148148 -1.5555556
[3,]  0.4074074 -0.8518519  0.4444444

固有値

行列の固有値は eigen() で計算できる。

> a <- matrix(c(1, 2, 3, 4), nrow=2, byrow=TRUE)
> a
     [,1] [,2]
[1,]    1    2
[2,]    3    4

> f <- eigen(a)
> f$values
[1]  5.3722813 -0.3722813

> f$vectors
           [,1]       [,2]
[1,] -0.4159736 -0.8245648
[2,] -0.9093767  0.5657675

分解

Cholesky 分解

> a <- matrix(c(4, 12, -16, 12, 37, -43, -16, -43, 98), nrow=3, byrow=TRUE)
> a
     [,1] [,2] [,3]
[1,]    4   12  -16
[2,]   12   37  -43
[3,]  -16  -43   98

> chol(a)
     [,1] [,2] [,3]
[1,]    2    6   -8
[2,]    0    1    5
[3,]    0    0    3

QR 分解

> a <- matrix(c(12, -51, 4, 6, 167, -68, -4, 24, -41), nrow=3, byrow=TRUE)
> a
     [,1] [,2] [,3]
[1,]   12  -51    4
[2,]    6  167  -68
[3,]   -4   24  -41

> f <- qr(a)
> f$qr
            [,1]         [,2] [,3]
[1,] -14.0000000  -21.0000000   14
[2,]   0.4285714 -175.0000000   70
[3,]  -0.2857143    0.1107692  -35

特異値分解

> a <- matrix(rep(0, 4*5), nrow=4)
> a[1, 1] = 1
> a[1, 5] = 2
> a[2, 3] = 3
> a[4, 2] = 2
> a
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    0    0    0    2
[2,]    0    0    3    0    0
[3,]    0    0    0    0    0
[4,]    0    2    0    0    0

> svd(a)
$d
[1] 3.000000 2.236068 2.000000 0.000000

$u
     [,1] [,2] [,3] [,4]
[1,]    0    1    0    0
[2,]    1    0    0    0
[3,]    0    0    0   -1
[4,]    0    0    1    0

$v
     [,1]      [,2] [,3] [,4]
[1,]    0 0.4472136    0    0
[2,]    0 0.0000000    1    0
[3,]    1 0.0000000    0    0
[4,]    0 0.0000000    0    1
[5,]    0 0.8944272    0    0

データフレーム

データフレームを作る

データフレームをベクトルから作るには、次のようにすればよい。

> name <- c("omochi", "annko", "tamago")
> type <- c("sabatora", "chatora", "mike")
> age <- c(3, 4, 5)

> df <- data.frame(name=name, type=type, age=age)
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

行列から作ったほうが楽かもしれない。

> data <- cbind(name, type, age)
> data
     name     type       age
[1,] "omochi" "sabatora" "3"
[2,] "annko"  "chatora"  "4"
[3,] "tamago" "mike"     "5"

> df <- data.frame(data)
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

問題は、行列がアトミック構造のため、この場合は数値が文字列化してしまうことである。

> df$age
[1] "3" "4" "5"

個別に型変換はできる。

> df$age <- as.numeric(df$age)
> df$age
[1] 3 4 5

空の状態から作っていく方法もある。

> df <- data.frame()
> df[1:length(name), "name"] <- name
> df["type"] <- type
> df["age"] <- age
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

最初のデータだけ、行の範囲を指定する必要がある。

列の名前を変更する

列の名前は colnames() で得られる。

> colnames(df)
[1] "name" "type" "age" 

colnames() の値を変えれば、列の名前を変更できる。

> colnames(df) <- c("名前", "タイプ", "年齢")
> df
    名前   タイプ 年齢
1 omochi sabatora    3
2  annko  chatora    4
3 tamago     mike    5

実は names() でも同じ結果になる。

文字列をファクタにする

文字列データをファクタと解釈させるには、stringsAsFactors に TRUE を指定する。

> df <- data.frame(data, stringsAsFactors=TRUE)
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

type に変な値を入れようとすると怒られる。

> df$type[1] = "white"
 警告メッセージ: 
 `[<-.factor`(`*tmp*`, 1, value = c(NA, 1L, 2L)) で: 
   不正な因子水準です。NA が発生しました 
> df$type[1] = "chatora"
> 

ただ、この場合は name もファクタになってしまう (実は age もファクタになっている)。ファクタにしたいものは個別に変換したほうがよいかもしれない。

> df <- data.frame(data)
> df$age <- as.numeric(df$age)
> df$type <- as.factor(df$type)
> df$type
[1] sabatora chatora  mike    
Levels: chatora mike sabatora

あとからレベルを追加したければ、levels() で変更すればよい。

> levels(df$type) <- c(levels(df$type), "white")
> df$type
[1] sabatora chatora  mike    
Levels: chatora mike sabatora white

> df$type[1] = "white"
> 

結合

データフレームを結合するには、rbind() を用いる。

> df <- data.frame()
> df[1:5, "height"] <- c(147.9, 163.5, 159.8, 155.1, 163.3)
> df["weight"] <- c(41.7, 60.2, 47.0, 53.2, 48.3)

> df2 <- data.frame()
> df2[1:5, "height"] <- c(158.7, 172.0, 161.2, 153.9, 161.6)
> df2["weight"] <- c(59.7, 58.5, 49.0, 46.7, 44.5)

> rbind(df, df2)
   height weight
1   147.9   41.7
2   163.5   60.2
3   159.8   47.0
4   155.1   53.2
5   163.3   48.3
6   158.7   59.7
7   172.0   58.5
8   161.2   49.0
9   153.9   46.7
10  161.6   44.5

最初と最後を取り出す

データフレームの最初と最後を取り出すには、head()、tail() を使う。

> df <- data.frame()
> df[1:10, "height"] <- c(147.9, 163.5, 159.8, 155.1, 163.3, 158.7, 172.0, 161.2, 153.9, 161.6)
> df["weight"] <- c(41.7, 60.2, 47.0, 53.2, 48.3, 59.7, 58.5, 49.0, 46.7, 44.5)
> head(df, 3)
  height weight
1  147.9   41.7
2  163.5   60.2
3  159.8   47.0

> tail(df, 3)
   height weight
8   161.2   49.0
9   153.9   46.7
10  161.6   44.5

値が含まれるかどうか

値が含まれているかどうかは、%in% で確認できる。

> df <- data.frame()
> df[1:3, "name"] <- c("omochi", "annko", "tamago")
> df["type"] <- c("sabatora", "chatora", "mike")
> df["age"] <- c(3, 4, 5)
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

> "annko" %in% df$name
[1] TRUE

あるいは、== で調べる方法もある。

> df$name == "annko"
[1] FALSE  TRUE FALSE

並び替え

値で並び替えるには、order() を用いる。

> df <- data.frame()
> df[1:10, "height"] <- c(147.9, 163.5, 159.8, 155.1, 163.3, 158.7, 172.0, 161.2, 153.9, 161.6)
> df["weight"] <- c(41.7, 60.2, 47.0, 53.2, 48.3, 59.7, 58.5, 49.0, 46.7, 44.5)

> df[order(df$height),]
   height weight
1   147.9   41.7
9   153.9   46.7
4   155.1   53.2
6   158.7   59.7
3   159.8   47.0
8   161.2   49.0
10  161.6   44.5
5   163.3   48.3
2   163.5   60.2
7   172.0   58.5

> df[order(-df$height),]
   height weight
7   172.0   58.5
2   163.5   60.2
5   163.3   48.3
10  161.6   44.5
8   161.2   49.0
3   159.8   47.0
6   158.7   59.7
4   155.1   53.2
9   153.9   46.7
1   147.9   41.7

重複データの削除

重複したデータを削除するには、duplicated() を用いる。

> df2 <- rbind(df, head(df, 3))
> df2
   height weight
1   147.9   41.7
2   163.5   60.2
3   159.8   47.0
4   155.1   53.2
5   163.3   48.3
6   158.7   59.7
7   172.0   58.5
8   161.2   49.0
9   153.9   46.7
10  161.6   44.5
11  147.9   41.7
12  163.5   60.2
13  159.8   47.0

> df2[!duplicated(df2),]
   height weight
1   147.9   41.7
2   163.5   60.2
3   159.8   47.0
4   155.1   53.2
5   163.3   48.3
6   158.7   59.7
7   172.0   58.5
8   161.2   49.0
9   153.9   46.7
10  161.6   44.5

duplicated() は重複しているものを検出するものだが、デフォルトでは最初に出てきたものを採用する形になる。最後に出てきたものを採用するなら、次のようにする。

> df2[!duplicated(df2, fromLast=TRUE),]
   height weight
4   155.1   53.2
5   163.3   48.3
6   158.7   59.7
7   172.0   58.5
8   161.2   49.0
9   153.9   46.7
10  161.6   44.5
11  147.9   41.7
12  163.5   60.2
13  159.8   47.0

インデックスが変わる。インデックスを振り直すなら、インデックスのラベルを無効化すればよい。

> df3 <- df2[!duplicated(df2, fromLast=TRUE),]
> row.names(df3) <- NULL
> df3
   height weight
1   155.1   53.2
2   163.3   48.3
3   158.7   59.7
4   172.0   58.5
5   161.2   49.0
6   153.9   46.7
7   161.6   44.5
8   147.9   41.7
9   163.5   60.2
10  159.8   47.0

最大値と最小値

要素の最大値、最小値を得るには、それぞれ max()、min() を用いる。

> max(df$height)
[1] 172

> min(df$height)
[1] 147.9

総和と平均

要素の総和や平均値を得るには、それぞれ sum()、mean() を用いる。

> sum(df$height)
[1] 1597

> mean(df$height)
[1] 159.7

要素への関数の適用

列ごとの平均をとるには次のようにできる。

> apply(df, 2, function (x) mean(x))
height weight 
159.70  50.88 

apply() の第 2 引数は軸の番号である。1 は行、2 は列になる。

行ごとに計算したい場合は、次のようにできる。

> df["BMI"] <- apply(df, 1, function (x) round(x["weight"]/(x["height"]/100.)^2, 1))
> df
   height weight  BMI
1   147.9   41.7 19.1
2   163.5   60.2 22.5
3   159.8   47.0 18.4
4   155.1   53.2 22.1
5   163.3   48.3 18.1
6   158.7   59.7 23.7
7   172.0   58.5 19.8
8   161.2   49.0 18.9
9   153.9   46.7 19.7
10  161.6   44.5 17.0

要素ごとに処理を行いたい場合は、次のようにする。

> apply(df["height"], 1, function (x) if (x > 150) x else 150)
    1     2     3     4     5     6     7     8     9    10 
150.0 163.5 159.8 155.1 163.3 158.7 172.0 161.2 153.9 161.6 

抽出

「身長が 160 cm 以上」といった条件に合うデータを得るには、次のようにする。

> df[df$height >= 160,]
   height weight  BMI
2   163.5   60.2 22.5
5   163.3   48.3 18.1
7   172.0   58.5 19.8
8   161.2   49.0 18.9
10  161.6   44.5 17.0

条件判定

どれかの要素で条件が成り立つかを調べるには any() が使える。また、すべての要素で条件が成り立つかどうかは all() でわかる。

> any(df$height > 160)
[1] TRUE

> any(df$height < 150)
[1] TRUE

> all(df$height > 160)
[1] FALSE

要素の置き換え

条件に合った要素を別の値で置き換えるのは apply() でできる。

> apply(df["height"], 1, function (x) if (x < 160) 160 else x)
    1     2     3     4     5     6     7     8     9    10 
160.0 163.5 160.0 160.0 163.3 160.0 172.0 161.2 160.0 161.6 

ファイルの書き出しと読み込み

データフレームを CSV ファイルとして書き出すには、次のようにする。

> name <- c("omochi", "annko", "tamago")
> type <- c("sabatora", "chatora", "mike")
> age <- c(3, 4, 5)
> df <- data.frame(name=name, type=type, age=age)
> write.csv(df, "cats.csv", row.names=FALSE)

cats.csv

"name","type","age"    
"omochi","sabatora",3    
"annko","chatora",4    
"tamago","mike",5 

もし "row.names=FALSE" がなかったら、以下のような結果になる。

"","name","type","age"    
"1","omochi","sabatora",3    
"2","annko","chatora",4    
"3","tamago","mike",5

書き出した CSV ファイルを読み込むには、次のようにする。

> df2 <- read.csv("cats.csv")
> df2
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

時間の計測

たとえば、配列の要素の絶対値を計算して返す関数を考えよう。以下の 2 つの実装を考える。

> myabs <- function(x) {
+ r <- x
+ for (i in 1:length(r)) if (r[i] < 0) r[i] <- -r[i]
+ r
+ }

> myabs2 <- function(x) {
+ r <- x
+ neg <- r < 0
+ r[neg] <- -r[neg]
+ r
+ }

> a <- rep(c(1, -2), 3)
> a
[1]  1 -2  1 -2  1 -2

> myabs(a)
[1] 1 2 1 2 1 2

> myabs2(a)
[1] 1 2 1 2 1 2

どちらが速いだろうか?

関数の実行時間は、system.time() で計測できる。

> a <- rep(c(1, -2), 100000000)
> system.time(myabs(a))
   ユーザ   システム       経過  
     9.807      0.344     10.172 

> system.time(myabs2(a))
   ユーザ   システム       経過  
     1.768      0.980      2.749 

> system.time(abs(a))
   ユーザ   システム       経過  
     0.251      0.326      0.577

myabs() よりも myabs2() のほうが速い。つまり、この場合は for を使わないほうがよいことになる。組み込みの abs() はもっと速い。

abs() を覗いてみると、次のようになっている。

> abs
function (x)  .Primitive("abs")

.Primitive() などとなっているものは、外部の (コンパイルされた) プログラムを呼び出しているようである。

ファイル

テーブルデータ

統計データはだいたいテーブル型と考えられるが、テーブル型のファイルを読み込むには、read.table() がある。

> read.table("cats.csv", sep=",", header=TRUE)
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

sep は区切り文字、header はヘッダーがあるかどうかである。"cats.csv" の内容は以下のようになっている。

"name","type","age"                                                              
"omochi","sabatora",3                                                            
"annko","chatora",4                                                              
"tamago","mike",5 

一番上がタイトルになっているので、header を指定しておかないと変なことになる。

> read.table("cats.csv", sep=",")
      V1       V2  V3
1   name     type age
2 omochi sabatora   3
3  annko  chatora   4
4 tamago     mike   5

これは一般的な関数で、CSV に特化したものが read.csv() である。

> read.csv("cats.csv")
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

Excel で作られた CSV など、文字コードが Shift-JIS になっているものを読み込む場合は、以下のようにする。

> read.csv("data.csv", fileEncoding="cp932")

オブジェクト

既存のデータを保存する方法として、ワークスペースごと保存する方法と、オブジェクトごとに保存する方法がある。

ワークスペースの保存と読み込みは、save.image() と load() を用いる。ファイルの拡張子は .RData とする。

> save.image("ws.RData")
> load("ws.RData")

存在するオブジェクトをすべて保存できるという便利さはあるが、既存のものと名前がかぶると読み込むときにめんどくさいところがある (上書きされてしまう)。

また、指定したオブジェクトだけを保存することもできる。

> save(a, b, c, file="objs.RData")
> load("objs.RData")

これもワークスペースの読み込みと同じ問題がある。

オブジェクトごとに保存・読み込みしたい場合は、.RDS ファイルを用いる。

> saveRDS(a, file="a.RDS")
> a <- readRDS("a.RDS")

これのよさは、readRDS() はオブジェクトを作らないので、好きな名前のオブジェクトでデータを受け取ることができるところである。

一般的なファイル

一般的なファイルを読み込む場合は、次のようになる。

> f <- file("cats.csv", "r")
> lines <- readLines(f)
> close(f)

file() でファイルを読み込み指定で開き、readLines() で行単位で読み込み、close() でファイルを閉じている。

CSV からデータフレームを手動で作るとすると、次のようになる。

> lines
[1] "\"name\",\"type\",\"age\"" "\"omochi\",\"sabatora\",3"
[3] "\"annko\",\"chatora\",4"   "\"tamago\",\"mike\",5" 

> data <- lapply(lines |> strsplit(","), function(x) gsub("\"", "", x))
> data
[[1]]
[1] "name" "type" "age" 

[[2]]
[1] "omochi"   "sabatora" "3"       

[[3]]
[1] "annko"   "chatora" "4"      

[[4]]
[1] "tamago" "mike"   "5"     

> title <- data[[1]]
> data <- matrix(unlist(data[-1]), ncol=length(data[[1]]), byrow=TRUE)
> data
     [,1]     [,2]       [,3]
[1,] "omochi" "sabatora" "3" 
[2,] "annko"  "chatora"  "4" 
[3,] "tamago" "mike"     "5" 

> df <- data.frame(data)
> df
      X1       X2 X3
1 omochi sabatora  3
2  annko  chatora  4
3 tamago     mike  5

> colnames(df) <- title
> df
    name     type age
1 omochi sabatora   3
2  annko  chatora   4
3 tamago     mike   5

書き出す場合は、次のようにする。

> data <- as.matrix(df)
> title <- colnames(df)
> f <- file("out.txt", "w")
> cat(sprintf("%s\n", paste(title, collapse=",")), file=f)
> for (i in 1:nrow(data)) {
+ cat(sprintf("%s\n", paste(data[i, ], collapse=",")), file=f)
+ }
> close(f)

file() で書き込み指定でファイルを開き、cat() でファイルに書き込んでいる。

out.txt

name,type,age
omochi,sabatora,3
annko,chatora,4
tamago,mike,5 

書式

sprintf() で C 言語の sprintf() のような書式が使える。

> a <- 1.23456
> sprintf("a = %.3f", a)
[1] "a = 1.235"

データセット

R には最初から用意されたデータセットがいくつかある。たとえば Iris データセットなどである。

> iris
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1            5.1         3.5          1.4         0.2     setosa
2            4.9         3.0          1.4         0.2     setosa
3            4.7         3.2          1.3         0.2     setosa
4            4.6         3.1          1.5         0.2     setosa
5            5.0         3.6          1.4         0.2     setosa
...

どういうデータセットが用意されているかの情報は、以下のコマンドで得られる。

> help(package="datasets")

付録: .Rprofile

今回の作業で作った .Rprofile を参考として示す。

.Rprofile

.libPaths(sprintf("%s/lib", getwd()))

foreach <- function(f, x) for (xi in x) f(xi)

mat <- function(x, ...) matrix(x, byrow=TRUE, ...)
zeros <- function(m, n=1) matrix(0, nrow=m, ncol=n)
adjoint <- function(x) t(Conj(x)) 
eye <- function(n) diag(n)
norm2 <- function(x) norm(x, "2")
normalize <- function(x) x/norm2(x)
dot <- function(x, y) drop(x %*% y)
cross <- function(x, y) {                                                      
    x2 <- rep(0, 3)                                                                
    x2[1:length(x)] <- x                                                           
    y2 <- rep(0, 3)                                                                
    y2[1:length(y)] <- y                                                           
    i <- 1:3
    j <- function(i) (i - 1) %% 3 + 1                                                
    x2[j(i+1)]*y2[j(i+2)] - x2[j(i+2)]*y2[j(i+1)]                                  
}
tr <- function(a) sum(diag(a))
inv <- function(x) solve(x)
"%^%" <- function(x, n) {
    m <- abs(n)
    r <- diag(nrow(x))
    if (n < 0) x <- solve(x)
    for (i in 1:m) r <- r %*% x
    r
}

参考

  • Garrett Grolemund, RStudio ではじめる R プログラミング入門, オライリー・ジャパン (2015).