本ツールで使用しているオープンソースライブラリ

本ツールのコードには 1 個のオープンソースライブラリが含まれています。

R クイックリファレンス — 簡潔なリファレンス

R 4.4の構文、データ構造、最もよく使われるbase R / tidyverseのクイックリファレンスマニュアルで、日常シーンの約80%をカバーする。

R

R R 4.4

R(GNU R / 対話型インタプリタ) · 関数型 · ベクトル化 · オブジェクト指向 (S3 / S4 / R6) · 動的

おすすめの学習パス

まずRscriptの実行と代入(<-)を学ぶ → ベクトルやdata.frameなどのデータ構造を習得する → if/for/applyファミリーでフローを書く → 関数、クロージャ、パイプ |> を学ぶ → applyによる集合操作を深く学ぶ → S3/R6オブジェクト指向とエラー処理を理解する → 必要に応じてファイルI/O、並列処理、ネットワーク、正規表現を学ぶ。FAQセクションは後でつまづきを避けるのに適している;パッケージ管理与renvはビルドの章を参照。

1.Hello Worldと実行環境

Rscriptの実行、REPL対話、印刷出力とヘルプドキュメント。

最小プログラム

Rは式単位で評価する。printはオブジェクトを表示し、catは直接出力する。スクリプトはRscriptで実行できる。

1
2
3
4
5
print('Hello, world!') # コンソールに出力
cat('Hello, world!\n') # 直接出力、引用符なし
# hello.R として保存して実行:
# $ Rscript hello.R
# または R / RStudio で source('hello.R')

代入と出力

代入は<-または=を使う。printはオブジェクトの構造を表示し、catは連結出力に適する。messageはstderrに書き出す。

1
2
3
4
5
x <- 42 # 代入(推奨は <-)
y = 42 # = も有効だが、スタイル上は控えめに
print(x) # [1] 42 と表示
cat('x =', x, '\n') # 連結出力:x = 42
message('这是提示') # stderr に書き込む

Rscriptの実行

Rscriptは非対話方式でスクリプトを実行し、バッチ処理やコマンドラインに適する。sourceはセッション内でスクリプトファイルを実行する。

1
2
3
4
5
6
7
8
9
10
# スクリプト hello.R:
# print('hello')
# スクリプトの実行:
# $ Rscript hello.R
# 引数付きで実行:
# $ Rscript hello.R a b
# セッション内でファイルを実行:
source('hello.R')
# 文字列を直接実行:
eval(parse(text = '1 + 1'))

コマンドライン引数

commandArgs(trailingOnly = TRUE)でスクリプトに続くコマンドライン引数を取得する。引数はすべて文字列である。

1
2
3
4
5
6
7
8
9
# 実行:Rscript app.R a b c
args <- commandArgs(trailingOnly = TRUE)
print(args) # [1] "a" "b" "c"
# スクリプト名を含むか(FALSE なら args[1] がスクリプトパス):
commandArgs() # Rscript とスクリプトパスを含む
# args[1] を入力パスとして使う:
if (length(args) > 0) {
input <- args[1]
}

出力の印刷

printはオブジェクトと構造を表示し、catは引用符なしで連結し、sprintfは書式化する。invisibleは自動印刷を抑制する。

1
2
3
4
5
6
7
8
x <- c(1, 2, 3)
print(x) # [1] 1 2 3
cat(x, sep = ', ', '\n') # 1, 2, 3
sprintf('Pi = %.2f', pi) # "Pi = 3.14"
# 式は自動で出力(トップレベルでのみ):
1 + 1 # [1] 2
# 関数内の最後の式は自動で返して出力:
invisible(1) # 出力しない

ヘルプドキュメント

?はヘルプを開き、??は全文検索する。argsは関数の仮引数を表示し、exampleはサンプルを実行する。

1
2
3
4
5
6
7
8
?mean # mean のヘルプページ
??regression # 全文検索
args(mean) # 仮引数リスト
example(mean) # 組み込み例を実行
apropos('dist') # dist を含むオブジェクトを検索
help.search('聚类') # トピックで検索
# ソースを確認:
mean # 関数本体を表示

パッケージの読み込み

libraryはパッケージを検索パスに読み込み、requireは論理値を返す。::はパッケージ内の関数を明示的に取得する。

1
2
3
4
5
6
7
8
9
10
11
library(dplyr) # パッケージを読み込む
library('ggplot2') # 文字列形式も可
if (!requireNamespace('pkg', quietly = TRUE)) {
install.packages('pkg')
}
# 明示的に名前空間を指定して呼ぶ:
dplyr::select(df, x)
# 読み込み済みを確認:
search()
# インストール済みパッケージの一覧:
rownames(installed.packages())

スクリプトの実行

sourceは現在のセッションでスクリプトファイルを実行し、関数と変数を定義できる。echoは実行過程を表示する。

1
2
3
4
5
6
7
source('utils.R') # utils.R を実行
source('utils.R', encoding = 'UTF-8')
# 各行と結果を表示:
source('demo.R', echo = TRUE)
# よく使う:自作関数セットを読み込む:
# utils.R 内で f <- function(x) x * 2 を定義
f(5) # 10

2.変数と定数

代入、基本型、NA/NULL、型変換について。

代入記号

<-が慣用的な代入で、=も使える。<<-は関数内で外側の変数に書き込む。グローバル代入はassignを使う。

1
2
3
4
5
6
7
8
9
10
11
x <- 5 # 推奨
y = 5 # こちらも有効
x <- y <- z <- 1 # 連鎖代入、すべて 1
# 関数内で外側の変数を変更:
f <- function() {
g <<- 99 # グローバル環境に書き込む
}
# 名前で代入:
assign('name', 'Rex')
name # "Rex"
# = との違い:= はネストした関数環境に入らない

基本型

原子ベクトルにはnumeric、integer、character、logicalがある。整数はL接尾辞を使う。typeofは格納型を返す。

1
2
3
4
5
6
7
8
9
10
x <- 3.14 # numeric (double)
i <- 1L # integer(L 接尾辞)
s <- 'hi' # character
b <- TRUE # logical
typeof(x) # "double"
typeof(i) # "integer"
# 長さ:すべてのスカラは長さ 1 のベクトル:
length(s) # 1
# 複合型:
typeof(1:5) # "integer"

NAとNULL

NAは欠損値を表し、NULLは空オブジェクトを表す。is.na / is.nullで判定する。NaNは非数値、Infは無限大。

1
2
3
4
5
6
7
8
9
10
11
12
13
v <- c(1, NA, 3)
is.na(v) # FALSE TRUE FALSE
any(is.na(v)) # TRUE
# NULL はオブジェクトが存在しないことを表す:
x <- NULL
is.null(x) # TRUE
# 数値の特殊値:
0 / 0 # NaN
1 / 0 # Inf
is.nan(NaN) # TRUE
is.finite(Inf) # FALSE
# 統計時に NA を無視:
mean(c(1, NA), na.rm = TRUE) # 1

型変換

as.*は明示的な変換。c()で異なる型を連結すると最も汎用的な型に自動変換される。判定にはis.*を使う。

1
2
3
4
5
6
7
8
9
10
11
12
as.numeric('42') # 42
as.character(42) # "42"
as.logical(1) # TRUE
as.factor(c('a', 'b')) # ファクター
# 混在連結は自動変換:
c(1, 'a') # "1" "a"(character に変わる)
c(1, TRUE) # 1 1(numeric に変わる)
# 変換失敗は NA になる:
as.numeric('abc') # NA(警告あり)
# 安全な変換 + チェック:
x <- suppressWarnings(as.numeric('abc'))
is.na(x) # TRUE

ベクトルの作成

cは連結、コロンは連続生成、seqは歩幅指定、repは繰り返し。namesは要素に名前を付ける。

1
2
3
4
5
6
7
8
9
10
c(1, 2, 3) # 連結
1:10 # 1 2 ... 10
seq(0, 1, by = 0.25) # 0 0.25 ... 1
seq(1, 10, length.out = 5) # 等間隔で 5 点
rep(1, 3) # 1 1 1
rep(c('a', 'b'), each = 2) # a a b b
# 名前付き要素:
x <- c(a = 1, b = 2)
names(x) # "a" "b"
x['a'] # 1

命名規則

変数名には英字、数字、ドット、アンダースコアが使え、数字で始められない。ドットはRでは演算子ではない。

1
2
3
4
5
6
7
8
9
10
11
12
my_var <- 1 # アンダースコア
my.var <- 2 # ドットは有効
.var <- 3 # ドットで開始
# 数字で開始できない:
# 1var <- 1 # 構文エラー
# 予約語は変数名にできない:
# if <- 1 # エラー
# キャメルでもスネークでも可、一貫させる:
totalCount <- 10
total_count <- 20
# R ではドットは通常の文字:
a.b <- 1 # 有効

変数の管理

lsは変数を列挙し、rmは削除する。existsは存在を判定する。rm(list = ls())は環境を空にする。

1
2
3
4
5
6
7
8
9
10
11
x <- 1; y <- 2
ls() # "x" "y"
exists('x') # TRUE
rm(x) # x を削除
rm(list = ls()) # 現在の環境をクリア
# 特定のパターンのみ削除:
rm(list = ls(pattern = '^tmp'))
# オブジェクトの構造を確認:
str(y) # num 2
# 環境情報:
ls(all.names = TRUE) # 隠しオブジェクトを含む

組み込み定数

Rにはpi、letters、month.nameなどのよく使う定数と、.Machineというプラットフォーム詳細がある。

1
2
3
4
5
6
7
8
9
10
11
pi # 3.141593
letters # a b ... z
LETTERS # A B ... Z
letters[1:3] # "a" "b" "c"
month.name # January ...
month.abb # Jan Feb ...
# 機械精度など:
.Machine$double.eps # 2.22e-16
.Machine$integer.max # 2147483647
# その他の組み込み:
date() # 現在の日時文字列

スコープの基礎

Rはレキシカルスコープを持つ。関数内では変数を外側へ順に探す。グローバルと局所で同名だと局所が覆う。getは環境を指定して取得する。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- 'global'
f <- function() {
x <- 'local' # グローバルを隠蔽
x
}
f() # "local"
# 代入しなければグローバルを参照:
g <- function() x
g() # "global"
# 明示的に環境から取得:
get('x', envir = .GlobalEnv)
# 環境チェーン:
parent.env(globalenv())

3.データ型とデータ構造

ベクトル、ファクター、行列、リスト、データフレーム、tibbleなど、Rのデータ構造の体系。

ベクトル

原子ベクトルは同質で一次元であり、Rの最も基本的な構造である。c()で作成し、インデックスは1から始まる。

1
2
3
4
5
6
7
8
9
10
11
12
v <- c(1, 2, 3)
v[1] # 1(インデックスは 1 から)
v[c(1, 3)] # 1 3
v[-1] # 先頭を除去
v > 1 # FALSE TRUE TRUE
v[v > 1] # 論理フィルタ:2 3
length(v) # 3
# 整数列:
1:5
# 名前付きアクセス:
x <- c(a = 1, b = 2)
x['a']

ファクター

ファクターはカテゴリ変数を保持し、levelsでカテゴリを固定する。順序付きファクターは順序を表す。as.factor / factorで作成する。

1
2
3
4
5
6
7
8
9
10
11
f <- factor(c('低', '中', '高'))
levels(f) # "低" "中" "高"
table(f) # カウント
# カテゴリ順を固定:
f2 <- factor(c('低', '高'),
levels = c('低', '中', '高'))
# 順序付きファクター:
of <- ordered(c('低', '高'),
levels = c('低', '中', '高'))
of[2] > of[1] # TRUE
# 数値をファクターに変換して再取得する際は注意(FAQ 参照):

行列

matrixは二次元の同質構造。byrowは埋め込み方向を制御する。行名・列名はdimnamesで扱う。

1
2
3
4
5
6
7
8
9
10
11
12
m <- matrix(1:6, nrow = 2, ncol = 3)
m # 2 行 3 列
m[1, 2] # 1 行目 2 列目
m[1, ] # 1 行目
# 行方向に埋める:
matrix(1:6, nrow = 2, byrow = TRUE)
# 行/列の演算:
rowSums(m); colMeans(m)
# 次元:
dim(m) # 2 3
# 名前付け:
dimnames(m) <- list(c('r1', 'r2'), c('a', 'b', 'c'))

配列

arrayは任意の次元の同質構造である。dimで各次元の長さを指定する。多次元テンソルに適する。

1
2
3
4
5
6
7
8
9
10
11
a <- array(1:24, dim = c(2, 3, 4))
dim(a) # 2 3 4
a[1, 2, 3] # スライスで取得
# インデックス後は次元が下がる:
a[1, , ] # 3x4 行列
# 次元方向に合計:
apply(a, 3, sum) # 第 3 次元の各スライスの合計
# 次元変更:
dim(a) <- c(6, 4) # 6x4 に再形成
# matrix との関係:
is.matrix(a) # TRUE(dim が 2 のとき)

リスト

listは異種でネストできる。$と[[は要素を取り、[はサブリストを返す。lengthは要素数を返す。

1
2
3
4
5
6
7
8
9
10
11
l <- list(name = 'Rex', age = 5, tags = c('a', 'b'))
l$name # "Rex"
l[[2]] # 5
l[1] # サブリスト(name を含む)
length(l) # 3
names(l) # "name" "age" "tags"
# ネストしたリスト:
l2 <- list(a = list(x = 1), b = 2)
l2$a$x # 1
# 再帰的に平坦化:
unlist(l2) # ベクトルに平坦化

データフレーム

data.frameは表形式データで、列は異種でもよい。既定ではstringsAsFactors=FALSE(R 4.0以降)。str / headで確認する。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(
name = c('alice', 'bob'),
age = c(30, 25),
admin = c(TRUE, FALSE)
)
df$age # 列を取得(ベクトル)
df[2, ] # 2 行目
df[['name']] # 列を取得(正確な名前)
nrow(df); ncol(df) # 2 3
str(df) # 構造の概要
head(df, 2) # 先頭 2 行
# 列を追加:
df$score <- c(88, 92)

tibble

tibbleはtidyverseの現代的なデータフレームである。遅延評価の列、印刷の見やすさ、文字列をファクターにしないなどの特徴を持つ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(tibble)
tb <- tibble(
name = c('a', 'b'),
value = c(1, 2)
)
tb # 見やすく、型を表示
# 列は前の列を参照できる:
tb2 <- tibble(x = 1:3, y = x * 2)
# ファクターにしない:
class(tb$name) # character
# data.frame と相互変換:
df <- as.data.frame(tb)
tb3 <- as_tibble(df)
# 抽出:
tb$value; tb[['value']]

属性

attributesはメタ情報を保持する。names、dim、classなど。attrは個別に読み書きする。structureは一度に構築する。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 1:3
attr(x, 'unit') <- 'cm' # カスタム属性
attributes(x) # すべて列挙
attr(x, 'unit') # "cm"
# structure で一括構築:
y <- structure(1:3, unit = 'cm')
# よく使う組み込み属性:
dim(m); names(df); class(x)
# 属性を削除:
attributes(x) <- NULL
# オブジェクトのクラスを確認:
class(c(1, 2)) # "numeric"

型の判定

is.*で型を判定し、class / typeof / modeで型を見る。as.*で変換する。is.data.frameなどは分岐に使う。

1
2
3
4
5
6
7
8
9
10
11
12
is.numeric(1) # TRUE
is.integer(1L) # TRUE
is.character('a') # TRUE
is.logical(TRUE) # TRUE
is.list(list()) # TRUE
is.data.frame(df) # TRUE
is.matrix(m) # TRUE
# class と typeof:
class(data.frame()) # "data.frame"
typeof(list()) # "list"
# null / NA の判定:
is.null(NULL); is.na(NA)

4.参照とオブジェクトのセマンティクス

Rには生のポインタはない。copy-on-modifyの値セマンティクスとenvironmentの参照セマンティクスがある。

コピーオンライト

Rはコピー時に変更する(copy-on-modify)方式である。代入はデータを共有し、実際に変更されたときにのみ複製される。大きなオブジェクトに別名を付けてもコストはほぼゼロである。

1
2
3
4
5
6
7
8
9
10
11
x <- 1:1e6
y <- x # コピーせず、データを共有
# y を変更するとコピーが発生:
y[1] <- 99
# このとき x は影響を受けない:
x[1] # 1
# tracemem でコピーのタイミングを観察:
tracemem(x)
y2 <- x # 共有、コピーなし
y2[1] <- 1 # ここでコピー発生
untracemem(x)

tracememによるコピー追跡

tracememはオブジェクトをマークし、Rがそのオブジェクトを実際にコピーするときにアドレスの変化を表示する。想定外のコピーやメモリ使用量の診断に使う。

1
2
3
4
5
6
7
8
9
10
x <- 1:1e6
tracemem(x) # 追跡開始
y <- x # 出力なし:コピーなし
y[1] <- 0 # tracemem: コピーが出力される
untracemem(x) # 追跡停止
# 大きなデータフレームの列を変更するとコピー:
df <- data.frame(a = 1:1e5, b = rnorm(1e5))
tracemem(df)
df$a <- df$a + 1 # フレーム全体がコピーされる
untracemem(df)

環境の参照

environmentはRの参照オブジェクトである。関数に渡してもコピーされず、関数内で変更すればそのまま反映される。Rの「ポインタ」に相当する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
e <- new.env() # 環境を新規作成
e$x <- 1
e[['y']] <- 2
# 関数内で環境を変更(<<- 不要):
inc <- function(env) {
env$x <- env$x + 1
}
inc(e)
e$x # 2(参照セマンティクス有効)
# 読み取り:
get('x', envir = e)
ls(e) # "x" "y"
# 環境はハッシュテーブルでもある:
e$name <- 'Rex'

可変状態のコンテナ

environmentを可変状態のコンテナとして使う。カウンタ、キャッシュ、アキュムレータなど。グローバル変数の汚染を避け、状態を明示的に受け渡す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
counter <- function() {
e <- new.env()
e$n <- 0
function() {
e$n <- e$n + 1 # クロージャが捕捉した環境を変更
e$n
}
}
next_num <- counter()
next_num() # 1
next_num() # 2
# キャッシュ:key で結果を保存
cache <- new.env(hash = TRUE)
cache$key <- list(result = 42)

シャローコピーとディープコピー

リストの代入はシャローコピーであり、サブオブジェクトを共有する。ネストした要素を変更するとその層だけがコピーされる。ディープコピーは明示的に実装する必要がある。

1
2
3
4
5
6
7
8
9
10
11
12
l1 <- list(x = 1:3, y = list(a = 1))
l2 <- l1 # 浅いコピー、子オブジェクトを共有
# l1 のトップレベルを変更するとその層でコピー:
l1$x[1] <- 99 # x はコピー、y は共有のまま
# 深いコピーの例(再帰コピー):
deep_copy <- function(obj) {
if (is.list(obj)) lapply(obj, deep_copy)
else obj
}
l3 <- deep_copy(l1)
# data.table には明示的な copy() がある:
# dt2 <- copy(dt)

参照クラスR6

R6は参照セマンティクスのクラスである。オブジェクトは参照で渡され、メソッドはコピーではなく元のオブジェクトを変更する。状態管理に適する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(R6)
Counter <- R6Class('Counter',
public = list(
n = 0,
increment = function() self$n <- self$n + 1
)
)
c1 <- Counter$new()
c2 <- c1 # 参照、同じオブジェクトを指す
c2$increment()
c1$n # 1(c1 も変更された)
# R5 / setRefClass も同様の参照セマンティクス:
# setRefClass で field + methods を定義
# S3/S4 と違い、R6 は変更時に copy-on-modify なし

data.tableの参照

data.tableは:=でインプレースに変更する(参照セマンティクス)。data.frameはコピーセマンティクスである。setDTでインプレースにdata.tableに変換できる。

1
2
3
4
5
6
7
8
9
10
11
12
# library(data.table)
dt <- data.table(a = 1:3, b = 4:6)
dt[, c := a + b] # その場で列 c を追加、コピーなし
dt
# data.frame の変更はコピー:
df <- as.data.frame(dt)
df$d <- 1 # コピーを生成
# その場で設定/削除:
dt[, d := NULL] # 列を削除
setDT(df) # その場で data.table に変換
# 明示的なコピー:
dt2 <- copy(dt) # data.table::copy

オブジェクトのサイズ

object.sizeは個々のオブジェクトの使用量を返し、gcはメモリ全体を見る。lobstr::obj_sizeは共有量も計算できる。

1
2
3
4
5
6
7
8
9
object.size(1:1e6) # ~8 MB
format(object.size(1:1e6), units = 'MB')
# メモリ全体:
gc() # 使用/ピーク(MB)
memory.size() # Windows 専用
# 共有オブジェクトの実占用:
# lobstr::obj_size(x, y) # 重複部分は 1 回だけ数える
# 大規模データ:
# data.table でコピーを減らせる

明示的なコピー

独立したコピーが必要な場合は明示的に複製する。リストはlapplyで再帰的に、data.tableはcopyで、ベクトルは[]でコピーできる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# リストの深いコピー:
l <- list(a = 1:3, b = list(x = 1))
l_copy <- lapply(l, function(x) {
if (is.list(x)) lapply(x, function(y) y)
else x
})
# data.table のコピー:
# dt2 <- copy(dt)
# ベクトルのコピー:
v <- c(1, 2, 3)
v2 <- v[] # 明示的なコピー
# v2 を変更しても v に影響しない:
v2[1] <- 99
v[1] # 1
# 共有かどうか確認:
# lobstr::ref(v, v2)

5.制御フロー

if / else、ベクトル化されたifelse、for / while、switch、そして論理演算について。

if / else

ifの条件は長さ1でなければならない。1以外の場合は先頭要素が使われ警告が出る。elseはifと同じ行の閉じ括弧の位置を揃える。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 5
if (x > 0) {
print('正数')
} else if (x == 0) {
print('零')
} else {
print('负数')
}
# 条件は長さ 1 で安全:
if (c(TRUE, FALSE)) print('x') # 警告し先頭要素を採用
# 代入式:
y <- if (x > 0) 'pos' else 'neg'

ifelseのベクトル化

ifelseはベクトルの要素ごとに条件を判定し、条件と同じ長さの結果を返す。一括置換に適する。

1
2
3
4
5
6
7
8
9
10
11
x <- c(-1, 0, 1, 2)
ifelse(x > 0, 'pos', 'non-pos')
# ネスト:
ifelse(x > 0, 'pos',
ifelse(x == 0, 'zero', 'neg'))
# ifelse は戻り値の型を強制することに注意:
ifelse(x > 0, 1L, 0) # すべて double に変換
# NA を保持:
ifelse(x > 0, 'pos', NA_character_)
# tidyverse での代替:
# dplyr::case_when(x > 0 ~ 'pos', TRUE ~ 'neg')

forループ

forはベクトルやシーケンスを順に処理する。seq_alongでインデックスに沿って走査し、1:lengthの空シーケンスの罠を避ける。

1
2
3
4
5
6
7
8
9
10
11
for (i in 1:5) print(i)
# ベクトルの要素を反復:
for (ch in c('a', 'b')) print(ch)
# インデックスで反復:
x <- c(10, 20, 30)
for (i in seq_along(x)) {
x[i] <- x[i] * 2
}
x # 20 40 60
# x が空のとき 1:length(x) はエラーになるので避ける
# ループよりベクトル化を優先

whileとrepeat

whileは条件を判定してから実行する。repeatは無条件ループで、breakで抜ける。どちらも無限ループに注意する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
n <- 0
while (n < 3) {
n <- n + 1
}
n # 3
# repeat は最低 1 回実行:
total <- 0
repeat {
total <- total + 1
if (total >= 5) break
}
total # 5
# 無限ループ対策:最大回数を設定
# 条件が永遠に真にならない場合は break/return を使う

nextとbreak

nextは次の反復へ進み、breakはループを抜ける。スキップや早期終了に適する。

1
2
3
4
5
6
7
8
9
10
11
12
for (i in 1:10) {
if (i %% 2 == 0) next # 偶数をスキップ
if (i > 7) break # 7 より大きいと終了
print(i) # 1 3 5 7
}
# ネストしたループで break は内側のみ抜ける:
for (i in 1:3) {
for (j in 1:3) {
if (j == 2) break
cat(i, j, '\n')
}
}

switch

switchは位置または名前で分岐する。数値はn番目、文字は名前で一致する分岐を返し、一致しない場合はNULLを返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
switch(2, 'a', 'b', 'c') # "b"(位置指定)
op <- 'add'
switch(op,
add = 1 + 1, # 2
mul = 2 * 3,
'未知'
)
# デフォルトがなくマッチしないと NULL を返す:
switch('nope', add = 1)
# 数値 + 欠落は NULL を返す:
switch(5, 'a', 'b')
# match.arg で引数を検証:
# match.arg(choice, c('a', 'b'))

ループよりベクトル化を優先する

R では明示的なループの代わりにベクトル化を使う:ベクトル全体を一度に計算するので速くて簡潔だ。ループはベクトル化できない場面のために取っておく。

1
2
3
4
5
6
7
8
9
10
11
x <- 1:1e6
y <- x * 2 # ベクトル全体の乗算、速い
z <- x^2 + sqrt(x)
# 同等のループ(遅い、非推奨):
for (i in seq_along(x)) x[i] * 2
# よく使うベクトル化関数:
sum(x); mean(x); cumsum(x)
which(x > 5) # 条件を満たすインデックス
x[x > 5] # 値の絞り込み
# 比較のベクトル化:
any(x > 5); all(x > 0)

論理演算

& | はベクトル化された論理演算で、&& || は最初の要素だけを評価する(短絡評価)。any/all で集約する。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(TRUE, FALSE, TRUE)
y <- c(FALSE, TRUE, TRUE)
x & y # FALSE FALSE TRUE
x | y # TRUE TRUE TRUE
!x # 反転
# 短絡版(単一値):
TRUE && FALSE # FALSE
TRUE || stop('不执行') # 短絡、エラーなし
# 集約:
any(x); all(x)
# 比較の連結は逐一書く:
# x > 1 && x < 5
# && || は最初の要素のみ取ることに注意

インデックス走査

seq_along / seq_len でインデックスを作り、rev で逆順にする。which で位置を取得する。split でグループごとにループする。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(3, 1, 4, 1, 5)
seq_along(x) # 1 2 3 4 5
seq_len(3) # 1 2 3
# 逆順に反復:
for (i in rev(seq_along(x))) print(x[i])
# which:
which(x == 1) # 2 4
which.max(x) # 5
# グループごとに反復:
grp <- split(x, x > 2)
lapply(grp, sum)
# 要素ごとに出力:
# sapply(x, function(v) ...)

6.関数とクロージャ

関数定義、引数、遅延評価、可変長引数、クロージャとパイプ。

関数定義

function で関数を定義し、本体の最後の式が返り値になる。無名関数はそのまま引数として渡せる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
square <- function(x) {
x * x
}
square(5) # 25
# 単一式なら短縮可:
add1 <- function(x) x + 1
add1(2) # 3
# 無名関数:
(function(x) x * 2)(10) # 20
# 関数はオブジェクトで、代入できる:
f <- add1
f(1) # 2
# 関数本体を確認:
body(square)

引数とデフォルト値

関数の引数にはデフォルト値を設定できる。呼び出し時は名前を省いて位置で渡すことも、名前付きで渡すこともできる。missing で引数が与えられたかを判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
greet <- function(name, greeting = '你好') {
paste(greeting, name)
}
greet('张三') # 你好 张三
greet('张三', '早上好')
greet(greeting = '嗨', name = '李四')
# 引数が省略されているか確認:
show <- function(x) {
if (missing(x)) return('未提供')
x
}
show() # 未提供
# ... の透過引数:
f <- function(x, ...) paste(x, ...)
f('a', 'b', 'c')

遅延評価

R の引数は遅延評価される:使うときに初めて評価され、使わなければエラーにもならない。force で先に評価を強制できる。これはクロージャで特に重要になる。

1
2
3
4
5
6
7
8
9
10
11
f <- function(a, b) a
f(1, stop('这行不执行')) # 1、b は未使用
# 強制評価:
g <- function(a, b) {
force(b) # すぐに b を評価
a
}
g(1, stop('会报错')) # エラーが発生
# 遅延評価とデフォルト引数で前の引数を参照:
h <- function(x, y = x * 2) y
h(5) # 10

可変長引数

... は任意個数の引数を受け取る。list(...) でまとめ、do.call でリストを使って関数を動的に呼び出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
sum_all <- function(...) sum(...)
sum_all(1, 2, 3) # 6
# リストに収集:
capture <- function(...) list(...)
capture(1, 'a', TRUE)
# do.call:動的に引数を渡す
args <- list(x = 1:3, y = 1:3)
do.call(pmax, args)
# ベクトル要素を分解して渡す:
vals <- c(1, 2, 3)
do.call(sum, as.list(vals)) # 6
# ... を他の関数に転送:
wrap <- function(...) plot(...)

返り値

関数は最後の式を返す。return で早めに返せる。invisible を使うと返り値が自動で表示されない。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
f <- function(x) x * 2 # 最後の式が戻り値
f(3) # 6
# return で早期終了:
g <- function(x) {
if (x < 0) return('负数')
x * 2
}
g(-1) # 负数
# 複数値はリストで返す:
h <- function(x) {
list(sq = x^2, rt = sqrt(x))
}
h(4) # リストに sq rt を含む
# 出力しない戻り値:
invisible(42)

クロージャ

関数は自分が作られた環境を捕捉するので、内部状態を持ち運べる。ファクトリ関数は状態を持つ新しい関数を生成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
make_adder <- function(n) {
function(x) x + n # クロージャが n を捕捉
}
add5 <- make_adder(5)
add5(3) # 8
# カウンタのクロージャ:
counter <- local({
n <- 0
function() {
n <<- n + 1 # 環境内の n を変更
n
}
})
counter(); counter() # 1 2
# 環境はクロージャの媒体:
environment(add5)

無名関数

無名関数は関数リテラルで、apply 系や purrr でよく使う。R 4.1 以降は \\(x) という短縮記法が使える。

1
2
3
4
5
6
7
8
9
10
11
lapply(1:3, function(x) x^2) # 1 4 9
sapply(1:3, function(x) x * 2)
# \\(x) の短縮記法(R >= 4.1):
lapply(1:3, \(x) x^2)
# 直接呼び出し:
(function(x) x + 1)(5) # 6
# purrr のパイプスタイル:
# purrr::map(1:3, ~ .x * 2)
# 無名関数をソートに使う例:
sort(c(3, 1, 2))
sort(c('b', 'a'), decreasing = TRUE)

高階関数

関数は引数として別の関数に渡せるし、関数を返すこともできる。Map / Reduce / Filter などが組み込みの高階関数だ。

1
2
3
4
5
6
7
8
9
10
11
12
13
Filter(is.numeric, list(1, 'a', 2))
Reduce(`+`, 1:5) # 15
Map(function(x, y) x + y, 1:3, 4:6)
# 関数を返す関数(カリー化):
curry_add <- function(a) {
function(b) a + b
}
curry_add(10)(5) # 15
# よくあるパターン:データフレームの各列に関数を適用
sapply(df, mean, na.rm = TRUE)
# 独自の高階関数:
apply_twice <- function(f, x) f(f(x))
apply_twice(function(x) x + 1, 5) # 7

パイプ

ネイティブパイプ |> は左側の結果を右側の関数の第一引数に渡し、入れ子の呼び出しを一直線に書けるようにする。%>% は magrittr 版のパイプ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
x <- 1:10
x |> sum() # 55
# パイプの連結:
1:10 |> mean() |> round(2)
# 先頭以外の引数へ渡すにはプレースホルダ:
1:10 |> paste(collapse = '-') # エラーの例
# ネイティブパイプのプレースホルダ:
1:10 |> paste(_, collapse = '-')
# tidyverse スタイル:
df |> dplyr::filter(age > 18) |>
dplyr::select(name) |>
dplyr::arrange(name)
# magrittr パイプは . でプレースホルダ:
library(magrittr)
1:10 %>% sum()

7.文字列

文字ベクトル、連結、部分取得、分割、正規表現とフォーマット。

文字列の基礎

文字ベクトルは引用符で囲み、ダブルクォートとシングルクォートは等価。nchar で長さを調べ、[] で要素を取り出す。バックスラッシュはエスケープが必要。

1
2
3
4
5
6
7
8
9
10
11
12
s <- 'hello'
"hi" # 等価
toupper('abc') # "ABC"
tolower('ABC') # "abc"
nchar('中文') # 2(文字数)
# バックスラッシュのエスケープ:
cat('a\tb') # a\tb
# 生文字列:
raw_s <- r"(C:\path\file)"
# 連結:
paste('a', 'b') # "a b"
paste0('a', 'b') # "ab"

連結

paste は既定で空白区切り、paste0 は区切りなし。collapse でベクトル全体を一つの文字列にまとめる。連結もベクトル化される。

1
2
3
4
5
6
7
8
9
10
11
12
paste('a', 'b', 'c') # "a b c"
paste0('a', 'b') # "ab"
paste('a', 'b', sep = '-') # "a-b"
# collapse:ベクトルを 1 つの文字列に
paste(1:3, collapse = ',') # "1,2,3"
# ベクトル化された組み合わせ:
paste(letters[1:3], 1:3) # "a 1" "b 2" "c 3"
# 循環再利用の組み合わせ:
paste(letters[1:2], 1:3) # 長さを 3 に補う
# 文字数とバイト数:
nchar('你好') # 2
nchar('你好', type = 'bytes') # 6

部分文字列

substr / substring は位置を指定して切り出し、[] では個々の要素を取り出す。strsplit で分割する。

1
2
3
4
5
6
7
8
9
10
s <- 'hello world'
substr(s, 1, 5) # "hello"
substring(s, 7) # "world"(末尾まで)
# 1 文字を取り出す:
strsplit(s, '')[[1]] # 文字単位
# 部分文字列を置換:
substr(s, 7, 11) <- 'R!'
s # "hello R!"
# 位置で複数区間を取得:
substr('abcdef', c(1, 4), c(2, 6))

分割

strsplit は区切り文字でリストに分解する。unlist で平坦化する。区切りには正規表現も使える。

1
2
3
4
5
6
7
8
9
10
11
s <- 'a,b,c'
strsplit(s, ',') # リスト ["a" "b" "c"]
unlist(strsplit(s, ',')) # ベクトル
# ベクトル化された分割:
strsplit(c('a-b', 'c-d'), '-')
# 正規表現で分割:
strsplit('a1b22c', '[0-9]+')
# 固定文字列でマッチ(正規表現でない):
strsplit('a.b.c', '.', fixed = TRUE)
# 元に戻す:
paste(unlist(strsplit(s, ',')), collapse = ';')

正規表現関数

grep / grepl でマッチし、gsub / sub で置換する。どれもベクトル化されており要素ごとに処理される。詳しくは正規表現の章を参照。

1
2
3
4
5
6
7
8
9
10
x <- c('apple', 'banana', 'cherry')
grepl('^a', x) # TRUE FALSE FALSE
grep('a', x) # インデックス 1 2
# 置換:
gsub('a', 'o', x) # opple bonono cherory
sub('a', 'o', x) # 最初のみ置換
# マッチ部分を抽出:
regmatches(x, gregexpr('a', x))
# 大文字小文字:
grepl('APPLE', x, ignore.case = TRUE)

フォーマット

sprintf は C の printf に似た書式化を行う。%s は文字列、%d は整数、%f は浮動小数点。formatC / round で数値の見せ方を調整する。

1
2
3
4
5
6
7
8
9
10
11
sprintf('%s is %d', 'R', 4) # "R is 4"
sprintf('%.2f', pi) # "3.14"
sprintf('%5.1f', pi) # " 3.1"
sprintf('%05d', 42) # "00042"
# パーセント記号のエスケープ:
sprintf('100%%')
# ベクトル化:
sprintf('x%d', 1:3) # "x1" "x2" "x3"
# 数値のフォーマット:
format(pi, digits = 3)
formatC(12345, big.mark = ',')

大文字小文字と空白除去

toupper / tolower で大文字小文字を変換し、trimws で空白を取り除く。chartr は文字単位の置換を行う。データの整形でよく使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
toupper('hello') # "HELLO"
tolower('HELLO') # "hello"
# 先頭を大文字に:
to_title <- function(s) {
paste0(toupper(substr(s, 1, 1)), substr(s, 2, nchar(s)))
}
to_title('hello') # "Hello"
# 空白を除去:
trimws(' hi ') # "hi"
trimws('\nhi\t', which = 'both')
# 文字レベルでの置換:
chartr('a-c', 'A-C', 'abc')
# パッケージ名をスネークケースに:
# gsub('([A-Z])', '_\\L\\1', s, perl = TRUE)

エンコーディング処理

R の文字列は基本的に UTF-8。Encoding でエンコーディングを確認し、enc2utf8 で変換する。iconv で文字コードを変換する。

1
2
3
4
5
6
7
8
9
10
11
12
13
s <- '你好'
Encoding(s) # "UTF-8"
enc2utf8(s) # UTF-8 に変換
enc2native(s) # ネイティブエンコーディングに変換
# エンコーディング変換:
iconv(s, from = 'UTF-8', to = 'GB18030')
# バイト表示:
charToRaw('A') # 41
rawToChar(as.raw(0x41)) # "A"
# 16 進数文字列:
paste(as.hexmode(charToRaw('R')), collapse = ' ')
# ファイル読み取りでエンコーディング指定:
# readLines('f.txt', encoding = 'UTF-8')

stringr

tidyverse の文字列処理パッケージ。str_ 接頭辞で命名が統一されており、str_detect / str_replace / str_extract などがある。

1
2
3
4
5
6
7
8
9
10
11
12
# library(stringr)
str_detect(c('a1', 'b2'), '[0-9]')
str_replace('ab-cd', '-', '_')
str_replace_all('a-b-c', '-', '')
str_extract('price 99', '[0-9]+')
str_extract_all('a1 b2', '[0-9]')
str_remove('xx-abc', 'xx-')
str_split('a,b', ',')
str_pad('5', 3, pad = '0') # "005"
str_trim(' hi ')
str_to_title('hello world')
str_length('你好') # 2

8.集合と apply ファミリー

apply / lapply / sapply による一括処理、ソート、重複除去、集合演算と dplyr のデータ操作。

lapply と sapply

lapply はリストやベクトルの各要素に関数を適用してリストを返す。sapply は結果をベクトルや行列に簡約しようとする。

1
2
3
4
5
6
7
8
9
10
l <- list(a = 1:3, b = 4:5)
lapply(l, sum) # リスト、$a $b を含む
lapply(l, function(x) x * 2)
# sapply で簡略化:
sapply(l, sum) # a=6 b=9 名前付きベクトル
sapply(1:4, sqrt)
# vapply で戻り値の型を指定(より安全):
vapply(1:4, sqrt, numeric(1))
# unlist でリスト結果を平坦化:
unlist(lapply(1:3, function(x) x^2))

apply

apply は行列や配列の次元に沿って一括計算する。MARGIN=1 は行方向、2 は列方向。返り値は次元に応じて組み立てられる。

1
2
3
4
5
6
7
8
9
10
m <- matrix(1:9, nrow = 3)
apply(m, 1, sum) # 各行の合計
apply(m, 2, sum) # 各列の合計
apply(m, 1, mean)
apply(m, 2, function(x) x / max(x))
# 3 次元配列で第 3 次元方向:
a <- array(1:24, c(2, 3, 4))
apply(a, 3, sum)
# インデックス付きのグループ化:
apply(m, 1, which.max)

mapply と複数引数

mapply は複数の引数に並行して関数を適用し、Map に対応する。短い引数はリサイクルされて長さが揃えられる。SIMPLIFY で簡約するかどうかを制御する。

1
2
3
4
5
6
7
8
9
mapply(paste, c('a', 'b'), c(1, 2))
# pmax などに対応:
mapply(max, c(1, 5), c(3, 2)) # 3 5
# Map は簡略化せず、常にリスト:
Map(paste, c('a', 'b'), c(1, 2))
# 複数引数のベクトル化:
mapply(function(x, y) x^y, 1:3, 1:3)
# 行列に簡略化:
mapply(function(x, y) c(x, y), 1:2, 3:4, SIMPLIFY = TRUE)

split でグループ化

split は因子に従ってベクトルをグループごとのリストに分ける。lapply と組み合わせればグループ集計ができ、group-by に相当する。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c(1, 2, 3, 4, 5)
g <- c('a', 'a', 'b', 'b', 'b')
split(x, g) # リスト $a $b
# グループごとの平均:
lapply(split(x, g), mean)
# データフレームを列でグループ化:
df <- data.frame(grp = c('a', 'a', 'b'), val = 1:3)
split(df, df$grp)
# 統計サマリ:
sapply(split(df$val, df$grp), summary)
# tapply:一発で実行
tapply(df$val, df$grp, mean)
# dplyr の group_by + summarise に対応

ソートと順位付け

sort はベクトルを並べ替え、order は並べ替え後のインデックスを返し、rank は順位を返す。decreasing で並び順を指定する。データフレームは列を指定して並べ替える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- c(3, 1, 2)
sort(x) # 1 2 3
order(x) # 2 3 1(インデックス)
x[order(x)] # ソート後
rev(sort(x)) # 降順
rank(c(3, 1, 2)) # 3 1 2
# データフレームのソート:
df <- data.frame(age = c(30, 20), name = c('b', 'a'))
df[order(df$age), ]
# 複数列でのソート:
df2 <- data.frame(a = c(1, 1, 2), b = c(2, 1, 3))
df2[order(df2$a, df2$b), ]
# dplyr:
# dplyr::arrange(df, desc(age))

重複除去

unique で重複を取り除き、duplicated で重複要素に印を付ける。重複した行は最初のものが残る。all.equal でベクトルを比較する。

1
2
3
4
5
6
7
8
9
10
11
12
x <- c(1, 2, 1, 3, 2)
unique(x) # 1 2 3
duplicated(x) # FALSE FALSE TRUE FALSE TRUE
!duplicated(x) # 最初の出現を保持
x[!duplicated(x)]
# データフレームの重複行を除去:
df <- data.frame(a = c(1, 1, 2), b = c(1, 1, 3))
unique(df)
# 重複のカウント:
table(x)
# dplyr:
# dplyr::distinct(df, a, .keep_all = TRUE)

集合演算

union / intersect / setdiff で集合演算を行う。%in% で要素の有無を判定する。unique した上で和・積・差をとる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a <- c(1, 2, 3); b <- c(2, 3, 4)
union(a, b) # 1 2 3 4
intersect(a, b) # 2 3
setdiff(a, b) # 1
setdiff(b, a) # 4
# メンバー判定:
2 %in% a # TRUE
# 重複を保持した結合:
c(a, b)
# 等価比較:
identical(a, b)
setequal(a, c(3, 2, 1)) # TRUE(順序を無視)
# %in% でフィルタ:
a[a %in% b] # 2 3

リスト操作

リストの結合、平坦化、まとめてのリネーム。unlist は再帰的に平坦化し、do.call で配列に組み立てる。purrr は型を意識した操作を提供する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
l1 <- list(a = 1); l2 <- list(b = 2)
c(l1, l2) # 結合
unlist(l1) # 名前付きベクトル
# リストの各要素に関数を適用:
lapply(l1, function(x) x + 1)
# リストの名前を変更:
names(l) <- c('x', 'y')
# 条件で要素を抽出:
l <- list(1, 'a', TRUE)
Filter(is.numeric, l)
# 1 層フラット化:
list_of_lists <- list(list(1, 2), list(3))
unlist(list_of_lists, recursive = TRUE)
# purrr:
# purrr::map(l, ~ .x * 2)
# purrr::keep(l, is.numeric)

dplyr のデータ操作

select/filter/mutate/arrange/summarise をパイプでつないでデータフレームを操作し、group_by でグループ化する。tidyverse の中核となる部分。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# library(dplyr)
df <- data.frame(
name = c('a', 'b', 'c'),
age = c(25, 35, 40),
salary = c(50, 60, 80)
)
df |> filter(age > 30)
df |> select(name, salary)
df |> mutate(salary2 = salary * 2)
df |> arrange(desc(age))
# グループ集計:
df |> group_by(age > 30) |>
summarise(avg = mean(salary))
# 重複除去:
df |> distinct(age)
# 計算列を追加して絞り込み:
df |> mutate(ratio = salary / age) |>
filter(ratio > 1.5)

9.メモリ管理

ガベージコレクション、オブジェクトサイズ、コピーコスト、事前確保と性能分析。

ガベージコレクション

R は参照カウントと定期的な GC を自動で行う。gc を呼べば手動で回収し、結果を報告させられる。大きなデータのループの後に呼ぶとよい。

1
2
3
4
5
6
7
8
9
10
11
gc() # 強制回収、統計を返す
# gc は Ncells/Vcells の使用とピークを返す:
gc(reset = TRUE) # ピーク統計をリセット
# 大きなオブジェクトは不要になったら NULL に:
big <- 1:1e8
big <- NULL # 回収可能
gc()
# gc のトリガーしきい値を確認:
gc(verbose = TRUE) # 回収の詳細を表示
# メモリ使用関数:
ls() # 現在のオブジェクトを確認

オブジェクトのサイズ

object.size で個々のオブジェクトのバイト数を確認する。format で読みやすい単位に変換する。大きなデータセットではメモリ使用量を見積もっておく必要がある。

1
2
3
4
5
6
7
8
9
10
11
object.size(1:1e6)
format(object.size(1:1e6), units = 'MB')
# リストのサイズ:
object.size(list(1:1e5, 1:1e5))
# データフレーム:
df <- data.frame(x = 1:1e5, y = rnorm(1e5))
format(object.size(df), units = 'MB')
# 共有オブジェクトの実占用:
# lobstr::obj_size(x, y)
# 大規模データの見積もり:
# 1e7 行のデータフレームは数百 MB 程度

コピーコスト

R は値セマンティクスなので変更時にオブジェクト全体をコピーし、大きなオブジェクトほど変更のコストが高い。大きなベクトルの要素を何度も書き換えるのは避ける。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- 1:1e7
# オブジェクト全体の演算は速い(ベクトル化):
y <- x * 2
# ループ内で要素ごとに変更:
for (i in 1:1000) x[i] <- x[i] + 1 # 大量のコピーを発生
# 事前割り当てで代替:
out <- numeric(1e5) # 先に割り当て
for (i in seq_along(out)) out[i] <- i
# ループ内で c() で伸ばすのは避ける:
# res <- c() # 遅い
# ベクトル化か事前割り当てを使う
# コピーされるか確認:
# tracemem(x)

事前確保

先に必要な長さの結果ベクトルを確保してからループで埋めれば、連結のたびにコピーが起きるのを防げる。numeric や character であらかじめ確保しておく。

1
2
3
4
5
6
7
8
9
10
11
12
13
n <- 1e4
# numeric ベクトルを事前割り当て:
out <- numeric(n)
for (i in 1:n) out[i] <- i^2
# 文字列を事前割り当て:
res <- character(n)
# リストを事前割り当て:
results <- vector('list', n)
for (i in seq_len(n)) results[[i]] <- i * 2
# 避けるべき方法(遅い):
# res <- c(); for (i in 1:n) res <- c(res, i)
# 理想:完全にベクトル化
sq <- (1:n)^2

ベクトル化と性能

できるだけベクトル全体で計算する。行の結合は rbind の繰り返しが遅いので、do.call(rbind, list) や data.table を使う。マイクロベンチマークで確かめる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- 1:1e6
# 速い:ベクトル全体
system.time(y <- x * 2 + sin(x))
# 遅い:ループ
# for (i in seq_along(x)) y[i] <- x[i] * 2
# 行結合の蓄積は遅い:
# for (...) df <- rbind(df, row) # 悪い
# 正しい方法:リストに集めて do.call
rows <- lapply(1:100, function(i) data.frame(i = i))
big <- do.call(rbind, rows)
# 計測:
system.time(mean(x))
# マイクロベンチマーク:
# microbenchmark::microbenchmark(a, b)

メモリ上限

memory.limit(Windows)で上限を確認・調整する。object.size で使用量を調べる。ulimit はセッション全体に影響する。

1
2
3
4
5
6
7
8
9
10
11
memory.limit() # Windows のメモリ上限 MB
# 必要な場合:
# memory.limit(size = 8192)
# 現在の使用量:
memory.size()
# 他のプラットフォームには memory.limit なし:
# システムのリソースツールで監視
# 大きなオブジェクトを確認:
ls() |> sapply(function(x) object.size(get(x)))
# 並べて最大を確認:
sort(sapply(ls(), function(x) object.size(get(x))), decreasing = TRUE)[1:5]

性能分析

Rprof は関数呼び出しにかかった時間を記録する。summaryRprof で集計する。system.time はおおまかな計測に使う。profvis で可視化する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Rprof('prof.out')
slow <- function() {
for (i in 1:1e5) sqrt(i)
}
slow()
Rprof(NULL)
summaryRprof('prof.out')
# 簡単な計測:
system.time(slow())
# 詳細な計測:
# tictoc::tic(); slow(); tictoc::toc()
# 可視化:
# profvis::profvis(slow())
# ベンチマーク比較:
# bench::mark(a, b)

data.table のメモリ

data.table は参照セマンティクスでコピーを減らす::= でその場を書き換え、setDT で変換し、copy で明示的に複製する。大きなデータセットほどメモリを節約できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(data.table)
dt <- data.table(a = 1:1e5, b = rnorm(1e5))
# その場で列を追加/変更(コピーなし):
dt[, c := a * 2]
# set 系:
setkey(dt, a) # ソートキー
setorder(dt, a) # その場でソート
# サブセット取得時にコピー回避:
sub <- dt[a > 100] # サブセットをコピー
# 明示的なコピー:
copy(dt)
# 列参照の共有:
# set(dt, j = 'd', value = dt$a)
# メモリ比較:
# df は列変更でフレーム全体がコピー、dt はしない

10.オブジェクト指向 (S3 / S4 / R6)

S3 の簡易ジェネリック、S4 の形式的なクラス、R6 の参照クラスという 3 つの OO システムがそれぞれの役割を担う。

S3 クラスの基礎

S3 は R の簡易的な OO で、class 属性とジェネリック関数からなる。unclass で内部の構造を確認できる。最もよく使われ、最も軽量。

1
2
3
4
5
6
7
8
9
10
11
12
x <- 1:3
class(x) # "integer"
# 独自クラス:
obj <- structure(list(a = 1), class = 'myclass')
class(obj) # "myclass"
# ジェネリックのディスパッチ:
print(obj) # print.myclass を呼ぶ
# ジェネリック関数の一覧:
methods('print')
# ディスパッチ対象を確認:
class(1:3)
unclass(1:3) # クラス属性を除去

S3 メソッド

class.method という名前の関数を定義するだけでジェネリックを実装できる。UseMethod がディスパッチを行う。NextMethod で親のメソッドを呼ぶ。

1
2
3
4
5
6
7
8
9
10
11
12
shape <- function(x) UseMethod('shape')
shape.default <- function(x) paste('default:', class(x)[1])
shape.circle <- function(x) paste('圆,半径', x$r)
# オブジェクトを構築:
c1 <- structure(list(r = 3), class = 'circle')
shape(c1) # 圆,半径 3
shape(1:3) # default
# print を上書き:
print.circle <- function(x) cat('Circle r =', x$r, '\n')
print(c1)
# 全メソッドを確認:
methods('shape')

S3 のコンストラクタ

自作クラスにはコンストラクタと検証の仕組みを用意する。structure を使えば一度にクラスを設定できる。print / summary を定義して出力をカスタマイズする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new_point <- function(x, y) {
stopifnot(is.numeric(x), is.numeric(y))
structure(list(x = x, y = y), class = 'point')
}
p <- new_point(1, 2)
# ジェネリック print:
print.point <- function(obj, ...) {
cat('point(', obj$x, ',', obj$y, ')\n')
}
print(p)
# ジェネリック summary:
summary.point <- function(obj, ...) {
cat('均值:', mean(c(obj$x, obj$y)), '\n')
}
summary(p)
# ジェネリック + 検証:
# new_ プレフィックスは tidyverse の慣習

S4 クラス

S4 は形式的な OO で、setClass でスロットを定義し、validity で検証し、setGeneric / setMethod でジェネリックを定める。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
setClass('Person',
slots = c(name = 'character', age = 'numeric'),
validity = function(obj) {
if (obj@age < 0) 'age 不能为负' else TRUE
}
)
p <- new('Person', name = 'Rex', age = 5)
p@age # 5
slot(p, 'name') # "Rex"
# ジェネリック:
setGeneric('describe', function(x) standardGeneric('describe'))
setMethod('describe', 'Person', function(x) paste(x@name, x@age))
describe(p)
# 確認:
isS4(p)
# 継承:
# setClass('Student', contains = 'Person')

R6 クラス

R6 は参照セマンティクスのカプセル化クラスで、メソッド内では self$ でアクセスする。オブジェクト指向プログラミングが他の言語により近くなる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# library(R6)
Animal <- R6Class('Animal',
public = list(
name = NULL,
initialize = function(name) self$name <- name,
speak = function() paste(self$name, '叫')
)
)
a <- Animal$new('狗')
a$speak() # "狗 叫"
# プライベートメンバー:
Class <- R6Class('Class',
public = list(init = NULL),
private = list(hidden = 42)
)
# 継承:
Dog <- R6Class('Dog',
inherit = Animal,
public = list(speak = function() paste(self$name, '汪汪'))
)
d <- Dog$new('旺财')
d$speak()

ジェネリック dispatch

dispatch はオブジェクトの class 属性に基づき、UseMethod が class に対応するメソッドを探す。旧クラス (S3) は階層的に試行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
f <- function(x) UseMethod('f')
f.default <- function(x) '默认'
f.numeric <- function(x) '数字'
f.factor <- function(x) '因子'
f(1L) # 数字
f(factor('a')) # 因子
f('a') # 默认
# 多引数ディスパッチ(S4):
# setMethod(..., signature = c('x', 'y'))
# ディスパッチメソッド表を確認:
methods('mean')
# class はベクトルにできる:
class(x) <- c('myclass', 'numeric')

継承

S3 は class 属性で継承し (NextMethod チェーン)、S4 は contains、R6 は inherit を使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
# S3 の継承:
structure(list(a = 1), class = c('sub', 'base'))
# NextMethod で親クラスのメソッドを呼ぶ:
f.base <- function(x) 'base 实现'
f.sub <- function(x) paste('sub:', NextMethod())
f(structure(list(), class = c('sub', 'base')))
# R6 の継承は r6 トピックを参照:
# super$ で親クラスのメソッドにアクセス
# S4 の継承:
# setClass('Base')
# setClass('Derived', contains = 'Base')
# 継承の確認:
# inherits(obj, 'base')

三方式の比較

S3 は軽量で大半のパッケージに使用、S4 は厳密な定義向け、R6 の参照セマンティクスは状態を持つオブジェクトに適する。実際には多くが併用される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# S3:クラス属性 + ジェネリック、宣言不要
# 高速、強制なし、多くのパッケージで使用
# S4:setClass で厳密なスロットと検証
# 正式な API と大規模フレームワーク向き
# R6:参照セマンティクス、メソッドのカプセル化
# 可変状態、モデリングオブジェクト向き
# 混在の例:
# ggplot2 は S3;
# Bioconductor は S4;
# 新しいパッケージの多くは R6
# 選択の指針:
# 単純なデータオブジェクトは S3,
# 複雑な正式インターフェースは S4,
# 元オブジェクトを変更したい場合は R6

class と属性

class は特殊な属性で、ジェネリックはこれを見て dispatch する。attr で独自の属性を追加できるが、dispatch には影響しない。class<- で直接 class を設定できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x <- 1:3
class(x) # "integer"
class(x) <- 'myclass' # 直接クラスを変更
x
# 全属性を確認:
attributes(x)
# カスタム属性:
attr(x, 'note') <- '说明'
attr(x, 'note')
# クラスの判定:
inherits(x, 'myclass') # TRUE
is.numeric(1:3)
# ジェネリックは class の順序で探す:
# 最初に class[1]、次に class[2]...、最後に default
# クラスを除去:
unclass(x)

11.エラー処理

stop でエラー、warning で警告、tryCatch で捕捉、条件システムを扱う。

stop でエラー

stop はエラーを投げて実行を停止する。stopifnot は条件を手早く検証する。エラーメッセージは原因を明確に書くのが望ましい。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
divide <- function(a, b) {
if (b == 0) stop('除数不能为 0')
a / b
}
divide(1, 0) # エラーが発生
# stopifnot:
check <- function(x) {
stopifnot(is.numeric(x), length(x) > 0)
x
}
check('a') # エラーが発生
# カスタムエラークラス:
stop('自定义错误', call. = FALSE)
# 条件オブジェクトを付与:
# stop(simpleError('msg'))

warning で警告

warning は致命的でない通知を出して中断しない。suppressWarnings で黙らせる。options(warn=2) でエラーに変換できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
warn_if <- function(x) {
if (any(is.na(x))) warning('存在 NA')
x
}
warn_if(c(1, NA))
# 黙らせる:
suppressWarnings(warn_if(c(NA)))
# すべての警告をエラーにする:
options(warn = 2)
# 元に戻す:
options(warn = 0)
# 一度だけ警告を捕まえる:
withCallingHandlers(
expr,
warning = function(w) print('有警告')
)

tryCatch

tryCatch はエラーや警告を捕捉して処理結果を返す。error、warning、finally の三つで構成される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
safe_div <- function(a, b) {
tryCatch(
a / b,
error = function(e) paste('错误:', conditionMessage(e)),
warning = function(w) paste('警告:', conditionMessage(w)),
finally = cat('完成\n')
)
}
safe_div(1, 0) # 错误: 除数不能为 0
safe_div(1, 2) # 0.5
# エラーのみを見る:
tryCatch(stop('boom'), error = function(e) '捕获')
# 結果とエラーの両方を保持:
tryCatch(list(ok = TRUE, val = 1), error = function(e) list(ok = FALSE))

try でフォールトトレラント

try は式の結果を返し、エラー発生時には try-error クラスのオブジェクトを返して全体は中断しない。バッチ処理でよく使われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
out <- try(log('a'), silent = TRUE)
class(out) # "try-error"
inherits(out, 'try-error') # TRUE
# エラーに寛容なループ:
res <- vector('list', 3)
for (i in 1:3) {
res[[i]] <- try(log(i - 2), silent = TRUE)
}
# 失敗した項目を除外:
failed <- sapply(res, inherits, 'try-error')
# 成功した項目:
res[!failed]
# エラー時にデフォルト値を与える:
out2 <- tryCatch(log(-1), error = function(e) NA)
out2

条件システム

R のエラー・警告・メッセージはすべて条件オブジェクトである。signalCondition で発火し、withCallingHandlers で捕捉して続行する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
signalCondition(simpleError('出错了'))
# 条件クラス:
stopifnot(identical(conditionMessage(simpleError('x')), 'x'))
# メッセージ:
message('普通提示')
# メッセージを捕まえる:
withCallingHandlers(
message('hi'),
message = function(m) cat('捕获:', conditionMessage(m), '\n')
)
# 条件は呼び出しスタックを含む:
f <- function() stop('deep')
# tryCatch で捕捉:
tryCatch(f(), error = function(e) conditionCall(e))
# 条件オブジェクトには独自フィールドを載せられる:
# simpleError('msg', call = sys.call())

警告のインターセプト

withCallingHandlers は警告を捕捉するが実行は止めず、記録して続行できる。invokeRestart と組み合わせる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
log_warnings <- function(expr) {
ws <- list()
res <- withCallingHandlers(
expr,
warning = function(w) {
ws[[length(ws) + 1]] <<- conditionMessage(w)
invokeRestart('muffleWarning')
}
)
list(result = res, warnings = ws)
}
out <- log_warnings({ warning('w1'); 42 })
out$result # 42
out$warnings # "w1"
# tryCatch との違い:
# withCallingHandlers は捕まえた後も元の式を続行できる

restarts 復元ポイント

シグナル処理の上級者向け仕組みで、呼び出し側が復元動作を提供できる。invokeRestart を捕捉側で呼び出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 再開ポイントを定義:
withRestarts(
{ signalCondition(simpleError('x')); '继续' },
my_restart = function() '恢复动作'
)
# 捕まえて再開:
withCallingHandlers(
withRestarts(stop('err'), abort = function() 'aborted'),
error = function(e) invokeRestart('abort')
)
# よくある再開ポイント:
# muffleWarning、muffleMessage
# カスタム:ユーザーが error handler で選択できる
# 対話的なリトライロジック向き

ループ内のエラー

バッチ処理では一つの失敗が全体を中断させてはならない。tryCatch で個別にフォールトトレラントにして結果を保持する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
items <- list(1, 'a', 3, -1)
process <- function(x) if (x < 0) stop('负数') else x * 2
# 安全に処理:
out <- lapply(items, function(x) {
tryCatch(process(x), error = function(e) NA)
})
unlist(out) # 2 NA 6 NA
# 失敗理由を記録:
out2 <- lapply(items, function(x) {
tryCatch(list(ok = TRUE, v = process(x)),
error = function(e) list(ok = FALSE, msg = conditionMessage(e)))
})
# 処理を継続:
# purrr::possibly / safely が使える

カスタムエラー

カスタムエラークラスは追加フィールドを持てる。simpleError や structure で構築し、class で型を表す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
my_error <- function(msg, code = 500) {
structure(
list(message = msg, call = NULL, code = code),
class = c('my_error', 'error', 'condition')
)
}
stop(my_error('余额不足', code = 400))
# 捕まえてフィールドを読み取る:
fail <- function() stop(my_error('boom'))
tryCatch(
fail(),
my_error = function(e) paste('自定义:', e$code),
error = function(e) '其他错误'
)
# 条件クラスの作成:
# condition <- structure(list(), class = c('x', 'condition'))

12.ファイルとデータ I/O

CSV、readr、行単位読み込み、RDS、JSON、コネクションオブジェクト、バイナリを扱う。

CSV の読み書き

read.csv / write.csv で CSV を読み書きする。stringsAsFactors=FALSE で因子化を防ぐ。check.names で列名を処理する。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(a = 1:3, b = c('x', 'y', 'z'))
write.csv(df, 'out.csv', row.names = FALSE)
# 読み戻す:
d <- read.csv('out.csv')
# ファクターを避ける:
read.csv('out.csv', stringsAsFactors = FALSE)
# カスタム区切り:
write.table(df, 'out.tsv', sep = '\t', row.names = FALSE)
read.delim('out.tsv')
# ヘッダーなし:
read.csv('f.csv', header = FALSE)
# エンコーディング指定:
# read.csv('f.csv', fileEncoding = 'UTF-8')

readr での読み書き

readr は tidyverse の高速 CSV 読み込みパッケージである。read_csv は型を自動推論し、write_csv は高速に書き出す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(readr)
write_csv(df, 'out.csv')
read_csv('out.csv')
# 型の自動推定、より高速:
read_csv('out.csv', col_types = 'di') # d=double i=integer
# 大規模データ:
read_csv('big.csv', show_col_types = FALSE)
# 列名を指定:
read_csv('f.csv', col_names = c('x', 'y'))
# 書き戻す:
write_csv(df, 'out2.csv')
# その他:
# read_tsv / write_tsv タブ区切り
# read_delim(file, delim = '|')
# 進捗と型は README 参照

行単位読み込み

readLines で行ごとに文字ベクトルとして読む。writeLines で書き出す。大ファイルはチャンク単位で読む。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
writeLines(c('第一行', '第二行'), 'f.txt')
lines <- readLines('f.txt')
lines # "第一行" "第二行"
# エンコーディング指定:
readLines('f.txt', encoding = 'UTF-8')
# 先頭の数行だけ読む:
readLines('f.txt', n = 1)
# 大きなファイルは分割:
con <- file('f.txt', 'r')
while (length(chunk <- readLines(con, n = 10)) > 0) {
cat('块大小', length(chunk), '\n')
}
close(con)
# エンコーディング付きで書き出す:
writeLines(lines, 'out.txt', useBytes = FALSE)

read.table で表を読む

read.table は汎用的なテーブル読み込みで、read.csv はその変種である。header / sep / na.strings はよく使う引数である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# タブ区切り:
read.table('data.tsv', header = TRUE, sep = '\t')
# ヘッダーなし:
read.table('f.txt', sep = ',')
# カスタム NA 値:
read.table('f.csv', header = TRUE, sep = ',', na.strings = c('NA', ''))
# 行をスキップ:
read.table('f.csv', skip = 2)
# 列型を指定:
read.table('f.csv', colClasses = c('numeric', 'character'))
# 行名:
read.table('f.csv', row.names = 1)
# 件数制限:
read.table('f.csv', nrows = 100)

RDS への保存と読込

saveRDS は単一の R オブジェクトを保存し、readRDS で読み戻す。型と属性を保持するため CSV より完全である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
saveRDS(df, 'df.rds')
d2 <- readRDS('df.rds')
identical(df, d2) # TRUE
# 複数オブジェクトを保存:
save(df, x, file = 'all.RData')
load('all.RData') # df と x を復元
# 圧縮:
saveRDS(df, 'df.rds', compress = TRUE)
# 他ツールから読み取る:
# readRDS は以前のバージョンも読み込み可能
# 大規模データの推奨:
# data.table::fread / fwrite の方が効率的
# ファイルを確認:
# file.info('df.rds')

JSON の読み書き

jsonlite は主流の JSON パッケージである。fromJSON で解析、toJSON でシリアライズする。改行区切りの JSON は API レスポンスの処理に使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(jsonlite)
json <- '{"name":"R","ver":4.4}'
fromJSON(json) # 名前付きリスト
# データフレームを JSON に:
df <- data.frame(a = 1:2, b = c('x', 'y'))
toJSON(df)
# ネスト構造:
fromJSON('{"x":[1,2],"y":{"z":true}}')
# 行ごとの JSON(各行が 1 オブジェクト):
stream_in(file('rows.jsonl'))
# 配列:
fromJSON('[1,2,3]')
# ファイルに書き出す:
write(toJSON(df), 'out.json')
# 複雑な構造をデータフレームに:
# fromJSON('...', flatten = TRUE)

コネクションオブジェクト

コネクションはファイルやネットワークなどのデータソースを抽象化したものである。file で開き、readLines / writeLines で読み書きし、close で閉じる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
con <- file('f.txt', 'w')
writeLines('一行', con)
close(con)
# 読み取り用コネクション:
con <- file('f.txt', 'r')
content <- readLines(con)
close(con)
# 1 行ずつ処理:
con <- file('big.txt', 'r')
while (length(line <- readLines(con, n = 1)) > 0) {
# line を処理
}
close(con)
# テキスト/バイナリモード:
file('f.bin', 'rb')
# url コネクション:
url('https://example.com')
# 自動クローズ:
# on.exit(close(con))

バイナリの読み書き

readBin / writeBin でバイナリを読み書きする。raw 型はバイト列を保持する。大容量ファイルや画像データはバイナリを使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x <- as.raw(c(0x48, 0x49)) # "H" "I"
writeBin(x, 'f.bin')
back <- readBin('f.bin', 'raw', n = 2)
# 整数を読む:
writeBin(1:3, 'ints.bin')
readBin('ints.bin', 'integer', n = 3)
# バイト長:
length(x)
# ファイルサイズ:
file.info('f.bin')$size
# 画像:
# png::readPNG / jpeg::readJPEG
# 大規模データの読み書き:
# readBin で分割読み取り
# raw ビューを確認:
charToRaw('A') # 41

その他のフォーマット

readxl は Excel を読み、haven は SPSS/Stata を読む。feather / parquet は列指向フォーマットである。必要に応じて読み込む。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Excel:
# library(readxl)
# read_excel('book.xlsx', sheet = 1)
# readxl::excel_sheets('book.xlsx')
# SPSS / Stata:
# library(haven)
# read_sav('data.sav'); read_dta('data.dta')
# 列指向ストレージ:
# library(arrow)
# write_parquet(df, 'df.parquet')
# read_parquet('df.parquet')
# feather:
# arrow::write_feather(df, 'df.feather')
# データベース:
# library(DBI); dbConnect(RSQLite::SQLite())
# dbReadTable(con, 'tbl')

13.よくある落とし穴

R で日常的によく踏む落とし穴と正しい書き方。

条件の長さが 1 ではない

if は長さ 1 の条件を要求し、ベクトル条件は先頭要素のみを判定して警告が出る。any / all で集約するか ifelse を使う。

1
2
3
4
5
6
7
8
x <- c(1, 2, 3)
// BAD if は最初の要素のみ判定し、警告あり
if (x == 2) print('找到')
// GOOD any / all でベクトル全体を判定
if (any(x == 2)) print('找到')
if (all(x > 0)) print('全正')
// GOOD 要素ごとの条件は ifelse
ifelse(x == 2, '命中', '未命中')

因子を数値へ変換

因子を直接 as.numeric で変換すると内部コードが返る。一度文字に変換してから数値にすると正しい値になる。

1
2
3
4
5
6
7
8
9
f <- factor(c('10', '20', '30'))
// BAD 内部コード 1 2 3 になる
as.numeric(f)
// GOOD 文字に戻してから数値へ
as.numeric(as.character(f))
// GOOD または levels でインデックス
as.numeric(levels(f))[f]
# CSV 読み込み時は stringsAsFactors = FALSE で予防
read.csv('f.csv', stringsAsFactors = FALSE)

次元の喪失

行列から 1 行 / 1 列を取り出すと既定ではベクトルに次元が落ちる。drop = FALSE で次元を維持できる。

1
2
3
4
5
6
7
8
9
10
11
12
m <- matrix(1:9, nrow = 3)
m[1, ] # ベクトル 1 4 7
// BAD 行を結合したいとき次元が落ちる
rbind(m[1, ], m[2, ])
// GOOD 行の次元を保持
rbind(m[1, , drop = FALSE], m[2, , drop = FALSE])
# 部分集合のあと行列を保ちたい:
m[1:2, , drop = FALSE]
# データフレームから単一列でベクトルが返る:
df <- data.frame(a = 1:3, b = 4:6)
df[1] # 依然 data.frame
df[[1]] # ベクトル

ループでのベクトル伸長

ループ内で c() で連結するたびにベクトル全体がコピーされ極めて遅い。先に確保するかベクトル化する。

1
2
3
4
5
6
7
8
9
10
11
n <- 5000
// BAD 毎回 c() がコピー、O(n^2)
res <- numeric()
for (i in 1:n) res <- c(res, i)
// GOOD 長さを事前確保
res <- numeric(n)
for (i in 1:n) res[i] <- i
// GOOD 直接ベクトル化(最速)
res <- 1:n
# リストに集めてから結合:
# do.call(c, lapply(1:n, function(i) i))

NA と比較

NA を比較に含めると NA になり、FALSE にはならない。NA を除外するには is.na で判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x <- c(1, NA, 3)
x == NA # NA NA NA(こうしない)
// BAD NA を残したまま比較
x[x == 1] # NA が混入する
// GOOD 先に NA を除外
x[!is.na(x) & x == 1]
// GOOD which なら NA を無視できる
which(x == 1)
# NA の存在を判定:
any(is.na(x))
# NA と NaN は異なる:
is.nan(NaN); is.na(NaN) # TRUE TRUE
# 統計時にスキップ:
sum(x, na.rm = TRUE)

部分一致

$ と [[ はユニークな接頭辞で一致する。列名の略記が静かに動作するためバグが潜みがちで、完全に書くか dplyr を使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
df <- data.frame(age_group = 1:3, score = 4:6)
// BAD age_group に黙ってマッチ
df$age_g
// GOOD 完全な列名を使う
df$age_group
// GOOD [[ で完全一致
df[['age_group']]
# 部分一致をオフに:
df[['age_g', exact = FALSE]] # それでも曖昧マッチ
# dplyr は tidyselect を使い、曖昧でない:
# dplyr::select(df, age_g) # エラーで指摘される
# 列名を確認:
names(df)

ベクトルのリサイクル

短いベクトルは演算時に長い側の長さに合わせて循環補完される。長さの比が整数倍でない場合は警告のみで、潜んだバグになりやすい。

1
2
3
4
5
6
7
8
9
10
11
x <- c(1, 2, 3, 4)
y <- c(10, 20)
// BAD リサイクル:y は c(10, 20, 10, 20) に補われる
x + y # 11 22 13 24
// 倍数でないときだけ警告:
x + c(1, 2) # 長さ 4 vs 2、警告なし
x + c(1, 2, 3) # 警告:4 は 3 で割り切れない
// GOOD 明示的に長さが一致することを確認
stopifnot(length(x) == length(y))
# 比較でも循環リサイクルは同様に紛らわしい:
# x > c(1, 100)

リストのフラット化

c() はリストに対してトップ階層を平坦化しようとする。リストの要素をリストとして入れるには list() を使う。unlist の再帰的な平坦化には落とし穴がある。

1
2
3
4
5
6
7
8
9
10
11
12
13
l <- list(a = list(x = 1), b = list(y = 2))
// BAD c() がトップレベルのリストを平らにする
c(l$a, l$b)
// GOOD ネストを保持
list(l$a, l$b)
// GOOD 平らにしたいときは intent を明示
unlist(l, recursive = FALSE)
# 2 つのリストを結合:
c(list(a = 1), list(b = 2))
# 構造を確認:
str(c(l$a, l$b))
# ベクトル化された要素収集:
# do.call(list, l)

パッケージのシャドウィング

library で読み込んだパッケージは同名の関数を覆い隠す。search 順で後ろにあるものが先に隠れる。:: で曖昧さを解消できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 2 つのパッケージが filter をエクスポート:
# library(stats)
# library(dplyr) # dplyr が stats::filter を遮蔽
// BAD 遮蔽後は filter の意味が読み込み順で変わる
filter(x, rep(1, 3)) # dplyr::filter かもしれない
// GOOD 明示的に名前空間を指定して呼ぶ
stats::filter(x, rep(1, 3))
dplyr::filter(df, a > 1)
# 遮蔽を確認:
# find('filter')
# パッケージをアンロード:
# detach('package:dplyr')
# 特定の関数だけ取り出す:
# dplyr::select
# conflicted パッケージで検出:
# library(conflicted); conflict_prefer('filter', 'dplyr')

14.並列と並行処理

parallel、mclapply、foreach、future による R のマルチプロセス並列。

parallel の概要

R の並列はマルチプロセス (fork) またはソケットに基づく。parallel パッケージには mclapply とクラスタが同梱されている。並列には起動コストがかかるため、タスクは十分に大きい必要がある。

1
2
3
4
5
6
7
8
9
10
library(parallel)
detectCores() # CPU コア数
detectCores(logical = FALSE) # 物理コア
# 直列と並列の時間を比較:
f <- function(i) sqrt(i)
system.time(lapply(1:1e5, f))
# 並列版:
# system.time(mclapply(1:1e5, f))
# 小さいタスクは並列の方が遅い(プロセス起動オーバーヘッド)
# 適した場面:時間のかかる関数、大規模データの一括処理

mclapply で並列化

mclapply は parallel の並列版 lapply で、fork で実装されている。Unix 系のみで利用可能 (Windows ではクラスタが必要)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
library(parallel)
# Unix/macOS:
f <- function(i) {
Sys.sleep(0.01); i^2
}
# res <- mclapply(1:8, f, mc.cores = 4)
# Windows は mc.cores = 1 かクラスタを使用
res <- lapply(1:8, f) # 直列のフォールバック
# mc.preschedule:一括スケジューリング
# mclapply(x, f, mc.cores = 2, mc.preschedule = TRUE)
# 戻り型は lapply と同じ:
unlist(res)
# エラーの確認:
# is.atomic(res)

クラスタ並列

makeCluster でプロセスクラスタを作り、parLapply で並列に適用する。Windows でも対応している。使用後は stopCluster で解放する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
library(parallel)
cl <- makeCluster(2) # worker を 2 つ
# 各 worker は独立した環境で、データを渡す必要がある:
clusterExport(cl, 'shared_data')
res <- parLapply(cl, 1:4, function(i) i * 2)
stopCluster(cl)
unlist(res)
# 各 worker でパッケージをロード:
# clusterEvalQ(cl, library(dplyr))
# 変数を事前設定:
# clusterExport(cl, varlist = c('x', 'y'))
# 乱数シード:
# clusterSetRNGStream(cl)
# Windows では並列にクラスタが必須

foreach で並列化

foreach はループで結果を集め、%dopar% で並列実行する (doParallel が必要)。%do% は逐次版である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(foreach)
# library(doParallel)
# registerDoParallel(cores = 2)
res <- foreach(i = 1:4) %do% {
i * 2 # 直列
}
# 並列版:
# res <- foreach(i = 1:4, .combine = c) %dopar% i * 2
# 結果の結合:
# .combine = c / rbind / list
# パッケージと依存を渡す:
# .packages = 'dplyr', .export = 'fun'
# 停止:
# stopImplicitCluster()
# 結果の結合:
unlist(res)

future で非同期

future パッケージは並列処理を「未来の値」として抽象化する。future() で非同期タスクを開始し、value() で結果を取得する。plan で戦略を選択する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# library(future)
plan(multisession) # マルチセッション戦略
f <- future({
Sys.sleep(0.1)
42
}) # すぐに future を返す
value(f) # 42、ブロックして結果取得
# 一括:
# library(furrr)
# plan(multisession)
# future_map(1:4, ~ .x * 2)
# 戦略の切り替え:
# plan(sequential) # 直列に戻す
# エラーの伝播:
# try(value(future(stop('x'))))
# グローバル変数は自動で渡る:
# future({ x + 1 }) の x は自動で引き継がれる

共有状態

並列 worker はメモリを共有せず、それぞれが環境をコピーする。ファイル書き込みは並列競合を防ぎ、結果は return で集める。

1
2
3
4
5
6
7
8
9
10
11
12
# データは worker に明示的に渡す:
shared <- 1:10
# mclapply(shared, function(x) x * 2)
# clusterExport か future の自動捕捉を使う
# 同じファイルへの並行書き込みは衝突する:
# 各 worker が独立したファイルに書く:
# file <- paste0('out_', i, '.csv')
# 結果をまとめる:
res <- parLapply(cl, 1:4, function(i) i^2)
final <- Reduce(`+`, res)
# グローバル変数は worker から見えない:
# 明示的にエクスポートしないと「オブジェクトが見つからない」

乱数シード

並列時は乱数を worker ごとに独立したシードで管理する必要がある。set.seed をグローバルで設定し、clusterSetRNGStream や future.seed で再現性を担保する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
set.seed(42)
rnorm(3) # 再現可能な直列
# クラスタ並列のシード:
# clusterSetRNGStream(cl)
# または:
# clusterSetRNGStream(cl, 123)
# foreach 並列のシード:
# registerDoParallel(); set.seed(1)
# future は自動管理:
# future.seed = TRUE
# 再現性の検証:
set.seed(7)
a <- rnorm(5)
set.seed(7)
b <- rnorm(5)
identical(a, b) # TRUE

並列性能

並列の効果はタスク粒度、コア数、通信オーバーヘッドに左右される。先に逐次のボトルネックを profile し、最も大きなブロックを並列化する。

1
2
3
4
5
6
7
8
9
10
11
12
# 並列のオーバーヘッドがタスクより大きいときは並列にしない
f <- function(i) sqrt(i)
system.time(lapply(1:1e5, f))
# system.time(mclapply(1:1e5, f, mc.cores = 4))
# 適切:タスク 1 件あたり数十 ms 以上
# 線形スケーリングの観察:
# time(1 core) vs time(4 cores)
# 通信ボトルネック:大きなデータの往復コピーは遅い
# 推奨:結果を小さなオブジェクトにまとめて返す
# 大きな共有オブジェクトを頻繁に渡さない
# 速度計測ツール:
# microbenchmark::microbenchmark(...)

15.ネットワークリクエスト

ダウンロード、httr2 / httr でのリクエスト、JSON API、URL 処理、Web スクレイピング。

ファイルダウンロード

download.file でファイルをダウンロードする。mode でバイナリを指定する。R.utils はレジュームをサポートする。低速回線では timeout を設定できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
download.file(
'https://example.com/data.csv',
'data.csv',
mode = 'wb'
)
# タイムアウト指定:
# download.file(url, dest, timeout = 60)
# 結果の確認:
file.info('data.csv')$size
# ダウンロードしたデータを読み込む:
read.csv('data.csv')
# バイナリデータ:
# download.file(url, 'img.png', mode = 'wb')
# ミラー関連:
# setInternet2(TRUE) # 旧 Windows

httr2 でリクエスト

httr2 は新世代の HTTP クライアントである。req_perform で実行し、resp_body_json で解析する。パイプ形式でリクエストを構築する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(httr2)
req <- request('https://api.example.com')
req <- req %>%
req_url_path_append('v1') |>
req_url_query(q = 'rlang') |>
req_headers('Authorization' = 'Bearer xx')
# resp <- req_perform(req)
# resp_body_json(resp)
# エラー処理:
# tryCatch(req_perform(req), error = function(e) ...)
# レート制限:
# req_throttle(req, rate = 10)
# 認証:
# req_auth_bearer_token(req, 'token')

GETリクエスト

GETでリソースを取得する。baseのreadLinesでテキストを取得できる;httrのGETとcontentで解析する。queryパラメータはURLに含める。

1
2
3
4
5
6
7
8
9
10
11
12
13
# base でシンプルな GET:
txt <- readLines('https://example.com')
# library(httr)
resp <- httr::GET('https://api.example.com/items')
httr::status_code(resp) # 200
httr::content(resp, 'text')
# クエリパラメータ付き:
httr::GET('https://api.example.com/search',
query = list(q = 'rlang', page = 2))
# レスポンスヘッダー:
httr::headers(resp)
# 接続タイムアウト:
httr::GET(url, httr::timeout(30))

POSTリクエスト

POSTでデータを送信する。bodyにはJSONまたはフォームを渡す。httrのPOSTとhttr2のreq_body_jsonの2つのスタイルがある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# library(httr)
httr::POST(
'https://api.example.com/login',
body = list(user = 'a', pass = 'b'),
encode = 'form'
)
# JSON ボディ:
httr::POST(
'https://api.example.com/submit',
body = '{"x":1}',
content_type_json()
)
# httr2 スタイル:
# req_body_json(req, list(x = 1))
# レスポンス解析:
# resp <- httr::POST(...)
# httr::content(resp, 'parsed')
# ファイルのアップロード:
# httr::POST(url, body = list(f = upload_file('f.csv')))

JSON API

JSON APIを呼び出す:リクエストとjsonliteで解析する。結果は入れ子リストになることが多く、flattenでデータフレームに変換する。ページネーションとエラー処理も対応する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(httr)
# library(jsonlite)
url <- 'https://api.github.com/repos/tidyverse/dplyr'
resp <- httr::GET(url)
stopifnot(httr::status_code(resp) == 200)
js <- httr::content(resp, 'text')
info <- jsonlite::fromJSON(js)
info$full_name
info$stargazers_count
# リストをデータフレームに:
# fromJSON(js, flatten = TRUE)
# ページング:
# query パラメータ page / per_page でループ
# 統一エラー処理:
# tryCatch(..., error = ...)

URL処理

parse_urlでURLの構成要素を分解し、URLencodeでエンコードする。URLdecodeで復号する。baseに組み込まれた処理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(httr)
u <- parse_url('https://user:[email protected]/a?b=1&c=2')
u$scheme; u$hostname; u$path
u$query # b=1 c=2
# 変更して再構築:
u$query$d <- 3
build_url(u)
# URL エンコーディング:
URLencode('a b/c') # a%20b%2Fc
URLdecode('%E4%BD%A0%E5%A5%BD')
# 中国語をエンコード:
URLencode('你好')
# 相対パスの解決:
# url_absolute('/a', 'https://x.com')
# クエリ文字列の解析:
# parse_url('https://x.com/?a=1')$query

ウェブスクレイピング

rvestでHTMLを解析する。html_elementsでノードを選択し、html_textでテキストを取得する。robotsと頻度制限を遵守する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(rvest)
url <- 'https://example.com'
page <- read_html(url)
page %>% html_elements('h1') %>% html_text()
page %>% html_elements('a') %>%
html_attr('href')
# テーブル:
# page %>% html_table()
# CSS セレクタ:
page %>% html_elements('#main p')
page %>% html_elements('.item')
# XPath:
page %>% html_elements(xpath = '//div[@class="x"]')
# サイトのルールを尊重:
# 頻度制限、User-Agent の明示
# 合法的な用途:公開データの分析

TCPソケット

socketConnectionでTCP接続を確立し、文字列を読み書きする。シンプルなプロトコルや社内サービスに適している。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
con <- socketConnection(
host = 'localhost',
port = 9999,
server = FALSE, # クライアント
open = 'r+b'
)
# 書き込み:
writeLines('hello', con)
# 読み取り:
line <- readLines(con, n = 1)
close(con)
# サーバーを立てる:
# srv <- socketConnection(host = 'localhost',
# port = 9999, server = TRUE)
# 注意:ブロッキングで待機
# 複雑なプロトコルは httpuv / plumber を推奨
# 本番 API は素の socket よりフレームワークを推奨

16.日付と時刻

Sys.Date / POSIXct、フォーマット、lubridateとタイムゾーン。

現在時刻

Sys.Dateは今日の日付、Sys.timeは現在の日付時刻を返す。date()は現在の時刻文字列を返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
Sys.Date() # 2026-08-02
Sys.time() # POSIXct の日時
format(Sys.time())
date() # "Sat Aug ... 2026"
# コンポーネントを取り出す:
as.POSIXlt(Sys.time())
unclass(as.POSIXlt(Sys.time()))
# タイムスタンプ:
unclass(Sys.time()) # 秒
# タイムゾーンを確認:
Sys.timezone()
# 手動設定:
# Sys.setenv(TZ = 'Asia/Shanghai')

日付の基本

Dateクラスは日付型である。as.Dateで文字列を変換し、日数を加算・減算する。unclassで日数の数値を確認できる。

1
2
3
4
5
6
7
8
9
10
11
12
13
d <- as.Date('2026-08-02')
class(d) # "Date"
d + 1 # 明日
Sys.Date() - d # 差の日数
difftime(Sys.Date(), d, units = 'days')
# 他の形式を解析:
as.Date('2026/08/02')
as.Date('02-08-2026', format = '%d-%m-%Y')
# 系列:
seq(as.Date('2026-01-01'), by = 'day', length.out = 3)
# コンポーネント:
format(d, '%Y-%m-%d')
format(d, '%A') # 曜日

POSIXct

POSIXctは秒単位のタイムスタンプを保持し、POSIXltはコンポーネントに分解する。as.POSIXctは文字列を解析し、タイムゾーンを処理する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
t <- as.POSIXct('2026-08-02 10:30:00')
class(t) # POSIXct POSIXt
unclass(t) # 1970 年からの秒数
# タイムゾーン指定:
as.POSIXct('2026-08-02 10:30:00', tz = 'UTC')
# コンポーネントに変換:
lt <- as.POSIXlt(t)
lt$year + 1900 # 2026
lt$mon + 1 # 8 月
lt$mday; lt$hour; lt$min
# 文字列にフォーマット:
format(t, '%Y-%m-%d %H:%M:%S')
# 加算/減算:
t + 3600 # +1 時間
# Date と相互変換:
as.Date(t)

フォーマット

strftime / formatはプレースホルダで出力する。%Yは年、%mは月、%dは日、%Hは時、%Mは分、%Sは秒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
d <- Sys.Date()
t <- Sys.time()
format(d, '%Y-%m-%d')
format(d, '%d/%m/%Y')
format(d, '%A') # 曜日名
format(t, '%H:%M:%S')
format(t, '%Y 年第 %j 天')
# 解析:
as.Date('02/08/2026', format = '%d/%m/%Y')
# strptime:
strptime('2026-08-02 10:00', format = '%Y-%m-%d %H:%M')
# プレースホルダ早見:
# %Y 4 桁の年 %y 2 桁の年 %m 月 %d 日
# %H %M %S 時/分/秒 %A 曜日名
# %j 年の何日目

lubridateの概要

lubridateは使いやすい日付関数を提供する:ymd / ymd_hmsで解析し、year / monthでコンポーネントを取得し、インターバルの加算・減算を行う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# library(lubridate)
ymd('20260802')
ymd('2026-08-02')
ymd_hms('2026-08-02 10:30:00')
# コンポーネントを取得:
year(t); month(t); day(t)
hour(t); minute(t); second(t)
wday(t) # 曜日(数値)
# 加算/減算:
t + days(1); t - weeks(2)
months(3) # 3 か月
# 間隔:
interval(d, Sys.Date()) |> as.duration()
# タイムゾーン設定:
with_tz(t, 'UTC')
# 丸め:
round_date(t, 'hour')
floor_date(t, 'day')

日付のシーケンス

seqで日付シーケンスを生成する:byは日 / 月 / 年を指定する。任意のインターバルの営業日にはseq.Dateを使う。

1
2
3
4
5
6
7
8
9
10
11
seq(as.Date('2026-01-01'), as.Date('2026-01-10'), by = 'day')
seq(as.Date('2026-01-01'), by = 'month', length.out = 3)
seq(as.Date('2026-01-01'), by = 'year', length.out = 2)
# 件数を指定:
seq(as.Date('2026-01-01'), as.Date('2026-12-31'), length.out = 5)
# seq.Date の方が明確:
seq.Date(as.Date('2026-01-01'), by = '2 days', length.out = 3)
# 平日との関係:
# 週末を除外:
x <- seq(as.Date('2026-01-01'), by = 'day', length.out = 30)
x[!weekdays(x) %in% c('Saturday', 'Sunday')]

時間差

日付同士を減算するとdifftimeが得られる。unitsで単位を指定する。as.numericで数値を取得する。所要時間の統計に使える。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
t0 <- Sys.time()
Sys.sleep(0.5)
Sys.time() - t0 # Time difference
# 単位を指定:
difftime(Sys.time(), t0, units = 'secs')
difftime(Sys.time(), t0, units = 'mins')
# 数値を取り出す:
as.numeric(Sys.time() - t0)
# 日付の差:
Sys.Date() - as.Date('2026-01-01')
# 平均時間:
# mean は difftime に有効
# 単位のカスタム:
# difftime(t2, t1, units = 'hours')
# 計測ツール:
# system.time(expr)
# tictoc::tic() / toc()

タイムゾーン

tz引数でタイムゾーンを指定する。Sys.timezoneで現在のタイムゾーンを取得する。OlsonNamesは利用可能なタイムゾーンを列挙する。タイムゾーンをまたいだ時刻を比較する。

1
2
3
4
5
6
7
8
9
10
11
12
Sys.timezone() # 現在のタイムゾーン
OlsonNames() # 全有効なタイムゾーン名
# タイムゾーンを指定して解析:
as.POSIXct('2026-08-02 10:00', tz = 'Asia/Shanghai')
# 同じ時刻を異なるタイムゾーンで表示:
t <- as.POSIXct('2026-08-02 10:00', tz = 'UTC')
format(t, tz = 'Asia/Shanghai')
format(t, tz = 'America/New_York')
# タイムゾーン換算:
with_tz(t, 'Asia/Shanghai') # lubridate
# UTC タイムスタンプ:
# as.integer(as.POSIXct('2026-08-02', tz = 'UTC'))

17.プロセスと環境

システムコマンドの実行、環境変数、コマンドライン引数、プラットフォーム情報、パスについて。

コマンドの実行

systemはシステムコマンドを実行し終了コードを返す。system2は引数渡しがより安全である。出力をキャプチャするにはcapture.outputまたはinternを使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
system('echo hello')
system2('echo', 'hello')
# 出力をキャプチャ:
system('date', intern = TRUE)
system2('date', stdout = TRUE)
# 終了コード:
code <- system('ls')
code # 0 成功
# 引数渡しでシェルインジェクション回避:
# system2('cp', c('a.txt', 'b.txt'))
# 出力を無視:
system('echo x', ignore.stdout = TRUE)
# タイムアウト:
# system2('cmd', timeout = 10)

環境変数

Sys.getenvで読み込み、Sys.setenvで書き込む。Sys.unsetenvで削除する。PATHやR関連の変数がよく使われる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sys.getenv('PATH')
Sys.setenv(MY_VAR = 'hello')
Sys.getenv('MY_VAR') # "hello"
Sys.unsetenv('MY_VAR')
# すべて列挙:
Sys.getenv()
# デフォルト値付き:
Sys.getenv('NOPE', unset = 'default')
# よく使う変数:
Sys.getenv('HOME')
Sys.getenv('R_VERSION') # または:
R.version.string
# 条件判定:
if (nzchar(Sys.getenv('CI'))) print('在 CI 环境')

コマンドライン引数

commandArgsはスクリプトの引数を取得する。trailingOnlyでR自身の引数を除外する。解析にはoptparseまたはbaseで手書きする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 実行:Rscript app.R --name Rex
args <- commandArgs(trailingOnly = TRUE)
args # "--name" "Rex"
# 簡易パース:
get_arg <- function(name) {
i <- which(args == name)
if (length(i)) args[i + 1] else NULL
}
get_arg('--name')
# 本格的なパース:
# library(optparse)
# parser <- OptionParser(option_list = list(
# make_option('--name', type = 'character')))
# opts <- parse_args(parser)
# デフォルト値:
# 引数の個数検証:
stopifnot(length(args) >= 1)

終了ステータス

quitでRを終了する。q('no')はワークスペースを保存しない。エラー終了にはquit(status = 1)を使い、スクリプトで判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
quit(save = 'no') # 保存せずに終了
# ステータスコード指定:
# quit(status = 1) # 失敗で終了
# 条件付き終了:
if (!file.exists('data.csv')) {
message('缺文件')
quit(status = 1)
}
# 正常終了:
# Rscript はスクリプト末尾で自然終了
# 終了コードを取得(shell):
# Rscript app.R; echo $?
# 終了を止めて browse:
# browser() # デバッグで一時停止

プラットフォーム情報

R.versionでバージョン、Sys.infoでシステム情報、.Platformでプラットフォームの詳細を取得する。パスの区切りなどクロスプラットフォーム処理もある。

1
2
3
4
5
6
7
8
9
10
11
12
13
R.version.string # R 4.4.x
version # 完全なバージョン
Sys.info() # システム情報リスト
Sys.info()['sysname'] # Windows/Linux/Darwin
.Platform$file.sep # / または \
.Platform$OS.type # "windows" / "unix"
# 条件判定:
if (.Platform$OS.type == 'windows') 'Win' else 'Unix'
# アーキテクチャ:
R.version$arch
# パス区切り:
# file.path が自動処理:
file.path('a', 'b', 'c.txt') # プラットフォーム対応の区切り

パス管理

file.pathでパスを連結し、dirname / basenameで分解し、normalizePathで絶対パスに正規化する。file.existsで存在を判定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
file.path('data', 'sub', 'f.csv')
basename('a/b/f.csv') # "f.csv"
dirname('a/b/f.csv') # "a/b"
file.exists('f.csv')
dir.exists('data')
# 作業ディレクトリ:
getwd()
setwd('data') # 変更は慎重に
# 正規化パス:
normalizePath('..')
# ~ を展開:
path.expand('~/R')
# ディレクトリを列挙:
list.files('.')
list.files('.', pattern = '\\.csv$')
# ディレクトリを作成:
dir.create('out', showWarnings = FALSE)

一時停止

Sys.sleepで指定した秒数だけ一時停止する。レート制限や外部リソースの準備待ちに使える。単位は秒である。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Sys.sleep(1) # 1 秒停止
t0 <- Sys.time()
Sys.sleep(0.5)
Sys.time() - t0
# ループのレート制限:
for (i in 1:3) {
cat(i, '\n')
Sys.sleep(0.2) # 0.2 秒ごと
}
# ファイルの出現を待つ:
# while (!file.exists('done.txt')) Sys.sleep(1)
# ネットワークリソースを待つ:
# httr の timeout と組み合わせる
# 注意:現在のプロセスをブロック

Rscriptスクリプト

Rscriptは非インタラクティブな実行入口であり、スケジュールタスクやバッチ処理に適している。スクリプトの先頭にはshebangを書ける。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# スクリプト冒頭(Unix):
# #!/usr/bin/env Rscript
# 実行:
# $ Rscript script.R arg1
# 使用:
args <- commandArgs(trailingOnly = TRUE)
message('开始处理: ', args[1])
# 構造化された結果を出力:
cat('RESULT:', mean(1:100), '\n')
# 終了コード:
# エラー時は quit(status = 1)
# バッチパイプライン:
# stdin を読む:
input <- readLines(file('stdin'), n = 1)
# stdout に書けばパイプで次へ渡せる

18.正規表現

grep / grepl、gsubでの置換、regexprでの位置特定、stringrとよく使われるパターン。

grepとgrepl

grepは一致したインデックスを返し、greplは論理ベクトルを返す。ignore.caseで大文字小文字を区別しない。valueで一致した値を返す。

1
2
3
4
5
6
7
8
9
10
11
12
13
x <- c('apple', 'banana', 'apricot')
grepl('^ap', x) # TRUE FALSE TRUE
grep('^ap', x) # 1 3
grep('^ap', x, value = TRUE) # "apple" "apricot"
grep('^AP', x, ignore.case = TRUE)
# 固定マッチ:
grepl('a.', x, fixed = TRUE) # リテラルの .
# カウント:
sum(grepl('a', x))
# 反転選択:
x[!grepl('^ap', x)]
# 複数パターン:
grepl('a|b', c('x', 'a'))

gsubでの置換

gsubは一致した箇所をすべて置換し、subは最初の1つだけ置換する。\\1はキャプチャグループを参照する。perl=TRUEで拡張構文を有効化する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
gsub('a', 'o', 'banana') # "bonono"
sub('a', 'o', 'banana') # "bonana"
# マッチを削除:
gsub('[0-9]', '', 'a1b2')
# キャプチャグループ参照:
gsub('(\\d{4})-(\\d{2})', '\\2/\\1', '2026-08')
# 複数の置換:
gsub('a|b', 'x', 'abacus')
# 大文字小文字を無視:
gsub('a', 'o', 'ABC', ignore.case = TRUE)
# 固定リテラル:
gsub('a.b', 'x', 'a.b', fixed = TRUE)
# ベクトル化:
gsub('s', 'S', c('sun', 'sea'))

regexprでの位置特定

regexprは最初の一致位置と長さを返し、gregexprはすべて返す。regmatchesで一致したテキストを抽出する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s <- 'price is 99 dollars'
m <- regexpr('[0-9]+', s)
m # 位置と長さ
regmatches(s, m) # "99"
# 全マッチ:
ms <- gregexpr('[a-z]+', 'a1bc2def')
regmatches('a1bc2def', ms) # リスト
# マッチとキャプチャを抽出:
# 正規表現マッチ後に unlist:
unlist(regmatches('x12y34', gregexpr('[0-9]+', 'x12y34')))
# マッチしないとき:
regexpr('z', 'abc') # -1
# マッチ位置をインデックスとして:
# substring と組み合わせて使う

構文の基礎

基本的な正規表現のメタ文字:^は先頭、$は末尾、.は任意の1文字、文字クラスは[]、グループは()、量指定子は* + ? {}、エスケープは\\。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'^abc' # abc で始まる
'abc$' # abc で終わる
'^[a-z]+$' # すべて小文字
'[0-9]{2,4}' # 2 〜 4 桁の数字
'colou?r' # colour/color
'a.c' # a?c の任意の文字
'\\.' # リテラルのピリオド
'[^0-9]' # 数字以外
'(ab|cd)' # グループの OR
'\\d' '\\w' '\\s' # 数字/英数字/空白
# R ではバックスラッシュを二重書き:
# 正規表現 \d は R の文字列では \\d
# 検証:
grepl('^[0-9]+$', '123') # TRUE

よく使われるパターン

よく使われるパターンの早引き:メール、電話番号、日付、空白の除去、数字の抽出。要件に応じて微調整する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 数字を抽出:
regmatches('v1.2.3', regexpr('[0-9]+', 'v1.2.3'))
# 空白を除去:
gsub('[[:space:]]', '', 'a b c')
# 簡易メール:
pat <- '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$'
grepl(pat, '[email protected]') # TRUE
# 日付 YYYY-MM-DD:
pat <- '^\\d{4}-\\d{2}-\\d{2}$'
grepl(pat, '2026-08-02')
# 中国語:
grepl('[\\u4e00-\\u9fa5]', '你好')
# 括弧の中身を抽出:
sub('.*\\((.+)\\).*', '\\1', 'name(value)')
# 区切り:
strsplit('a,b;c', '[,;]')

stringrの一致

stringrは正規表現の構文は同じだがインターフェースが統一されている。str_detect / str_extract / str_matchおよび_allバージョンを提供する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# library(stringr)
str_detect(c('a1', 'b2'), '\\d')
str_extract('price 99', '\\d+')
str_extract_all('a1 b22', '\\d+')
str_match('a=1;b=2', 'a=(\\d+)')
str_match_all('a1b2', '([ab])(\\d)')
# 位置:
str_locate('abcabc', 'b')
str_locate_all('abcabc', 'b')
# 置換:
str_replace('a-b', '-', '_')
str_replace_all('a-b-c', '-', '+')
# 正規表現境界の組み立て:
str_detect('apple', regex('^ap'))

stringrでの置換

str_replace / str_replace_allで置換する。regexpはfixed / perlを指定できる。str_removeで一致部分を削除する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# library(stringr)
str_replace('one-two', '-', '_')
str_replace_all('a-b-c', '-', '')
# キャプチャ参照:
str_replace('2026-08', '(\\d{4})-(\\d{2})', '\\2/\\1')
# 削除:
str_remove('xx-abc', 'xx-')
str_remove_all('a1b2', '\\d')
# 固定リテラル:
str_replace_all('a.b', fixed('.'), 'X')
# 大文字小文字を無視:
str_replace('ABC', 'a', 'x', regex(ignore_case = TRUE))
# ベクトル化は自動:
str_replace_all(c('a1', 'b2'), '\\d', '#')
# 境界マッチ:
str_replace('abc', '^a', 'X')

フラグと拡張

perl=TRUEでPCRE拡張を有効化する:先読み、名前付きグループなど。fixed=TRUEはリテラル一致。ignore.caseも大文字小文字を無視する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 先読み:
grepl('a(?=b)', 'ab', perl = TRUE) # TRUE
# 否定先読み:
grepl('a(?!b)', 'ac', perl = TRUE) # TRUE
# 名前付きグループ:
# gsub('(?<y>\\d+)', '\\k<y>', s, perl = TRUE)
# 非欲張り:
regmatches('a<b>c<b>', regexpr('<.+?>', 'a<b>c<b>', perl = TRUE))
# 複数行 / ドットが改行にマッチ:
# grepl('a.b', 'a\nb', perl = TRUE)
# fixed リテラル:
grepl('a.b', 'a.b', fixed = TRUE)
# フラグの組み合わせ:
# grepl(pat, x, ignore.case = TRUE, perl = TRUE)

19.パッケージ管理とビルド

CRANからのインストール、renvによる依存関係管理、パッケージ構造、R CMD、testthat、roxygenについて。

パッケージのインストール

install.packagesはCRANからインストールする。update.packagesは更新する。libraryは読み込む。開発版にはdevtools / remotesを使う。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
install.packages('dplyr')
# ミラーからインストール:
install.packages('ggplot2', repos = 'https://cloud.r-project.org')
# 更新:
# update.packages()
# 開発版:
# remotes::install_github('tidyverse/dplyr')
# ローカル:
# install.packages('path/pkg_0.1.0.tar.gz', repos = NULL)
# インストール済みを確認:
rownames(installed.packages())
# ロード:
library(dplyr)
# 依存のインストール:
# install.packages(c('dplyr', 'tidyr'))

CRANとリポジトリ

CRANは公式のパッケージリポジトリである。reposでミラーを指定する。available.packagesはインストール可能なパッケージを列挙する。CRANのポリシーが公開を規定する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 現在のリポジトリ:
getOption('repos')
# 利用可能な全パッケージ:
ap <- available.packages()
ap[1:5, 'Package']
# パッケージ検索:
# available.packages() をフィールドで grep
# パッケージ情報:
# packageDescription('dplyr')
# 依存:
# packageDescription('dplyr')$Depends
# CRAN チェック:
# R CMD check を通過してから公開可能
# ミラー一覧:
# https://cran.r-project.org/mirrors.html
# 中国のミラー:
# https://mirrors.tuna.tsinghua.edu.cn/CRAN/

renvによる依存関係管理

renvはプロジェクトの依存バージョンを固定し、Pythonのvenvに類似する。renv::initで初期化し、snapshot / restoreでロックファイルを同期する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 初期化:
# renv::init()
# 依存のインストール:
# renv::install('dplyr')
# 依存の記録:
# renv::snapshot() # renv.lock を書く
# 別マシンで復元:
# renv::restore()
# 状態の確認:
# renv::status()
# プロジェクトライブラリの分離:
# renv::activate()
# アップグレード:
# renv::update()
# 使用上の注意:
# .Rprofile が renv を自動ロード
# renv.lock をバージョン管理にコミット

パッケージの構造

Rパッケージの標準構造:DESCRIPTION、R/、man/、tests/、NAMESPACE。R/にソースコード、man/にドキュメントを配置する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mypkg/
# ├── DESCRIPTION メタデータ:パッケージ名/バージョン/依存
# ├── NAMESPACE エクスポートする関数
# ├── R/ ソースファイル *.R
# ├── man/ ドキュメント *.Rd
# ├── tests/ テスト
# └── data/ 組み込みデータ
# DESCRIPTION の主要フィールド:
# Package: mypkg
# Version: 0.1.0
# Imports: dplyr
# usethis で雛形を生成:
# usethis::create_package('mypkg')
# ドキュメント:
# usethis::use_roxygen_md()

R CMDコマンド

R CMD系コマンドでパッケージをビルド/検査する:buildはパッケージ化、checkは検査、INSTALLはインストール。公開前には必ずcheckを通す。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# tar.gz をビルド:
# R CMD build mypkg
# チェック(重要なステップ):
# R CMD check mypkg_0.1.0.tar.gz
# インストール:
# R CMD INSTALL mypkg
# よく使うチェック項目:
# --as-cran で CRAN 厳格チェックをシミュレート
# 実行時相当:
# system('R CMD INSTALL --help')
# R セッション内で:
# system2('Rscript', c('-e', '1+1'))
# devtools のラッパー:
# devtools::check()

testthatによるテスト

testthatは主流のテストフレームワークである。expect_equalなどのアサーション、test_thatでテストケースをまとめる。usethisはテストファイルを生成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# library(testthat)
f <- function(x) x * 2
test_that('f 正确加倍', {
expect_equal(f(2), 4)
expect_identical(f(0), 0)
expect_true(f(-1) < 0)
expect_error(f('a'))
})
# 実行:
# testthat::test_dir('tests')
# または開発時:
# devtools::test()
# よく使うアサーション:
# expect_equal / expect_identical
# expect_true / expect_false
# expect_warning / expect_message
# CI と組み合わせて自動実行

フォーマットと検査

stylerはコードスタイルを統一し、lintrは静的検査を行う。RStudio Addinsやpre-commitと組み合わせて整然と保つ。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# library(styler)
# ファイルをフォーマット:
# styler::style_file('R/foo.R')
# パッケージ全体をフォーマット:
# styler::style_pkg()
# スタイル差分の確認:
# styler::style_file('R/foo.R', dry = 'only')
# lintr チェック:
# lintr::lint('R/foo.R')
# よく使う設定:
# .lintr ファイルで linters を指定
# 自動実行:
# pre-commit hooks
# CI 統合:
# lintr を check ステップとして

roxygenドキュメント

roxygen2はコメントから.Rdドキュメントを生成する。#'で始まり、@param / @return / @exportタグを使う。関数のドキュメントはソースコードと同じ場所に置かれる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#' 二つの数値を加算
#'
#' @param a 1 番目の数値
#' @param b 2 番目の数値
#' @return
#' @export
add2 <- function(a, b) a + b
# ドキュメント生成:
# devtools::document()
# または:
# roxygen2::roxygenise()
# NAMESPACE のエクスポート生成:
# @export が自動で NAMESPACE に書き込まれる
# パッケージレベルのドキュメント:
# @keywords internal
# ドキュメントの確認:
# R CMD check

セッションの再現

sessionInfoはバージョンと依存関係を記録し、再現性を保証する。renv.lockで依存関係を固定する。Rのバージョンも記録する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
sessionInfo() # R バージョン + ロード済みパッケージ
R.version.string
# 出力例:
# R version 4.4.x
# Platform: ...
# 添付パッケージとそのバージョン
# 再現の要点:
# 1. sessionInfo() の出力を記録
# 2. renv::snapshot() で依存をロック
# 3. R バージョンを固定(Docker イメージ)
# 欠落の確認:
# 依存一覧:
# renv::dependencies()
# 問題報告時は添付:
# sessionInfo() + 最小の再現コード

公式リンク

公式ドキュメントとリソースへの直接リンク。

このチートシートについて

このページはR 4.4の自己完結型のクイックリファレンスマニュアルであり、実際のデータ分析で最もよく使われるbase Rとtidyverseの約80%のシーンをカバーする。内容はモダンな慣用法に偏っている:ネイティブパイプライン `|>`、匿名関数 `\(x)` の短縮形、`stringsAsFactors = FALSE` を指定した`data.frame`、ベクトル化された`ifelse`、型付きの`purrr::map_*`による集合操作、そしてR独特のコピーオンライト(copy-on-modify)と環境の参照セマンティクス。権威あるリファレンスは公式のRマニュアルとR for Data Scienceを参照のこと。 19の章はそれぞれ一つのテーマに焦点を当てている——最初のプログラム、変数と型から、applyファミリー、S3/R6オブジェクト指向、並列処理、ネットワークまで。各節は8〜9個のサンプル付きサブセクション(各5〜20行)に分かれ、合計約160のトピックがある。コードスニペットは意図的に短く自己説明的であり、コピーしてそのままRやRStudioに貼り付けて実行できる。 すべての処理はブラウザ内で行われる——アップロードも追跡もない。このページはGuruToolkitの無料開発者ツールセットの一部である;コードスニペットは自由に利用でき、いかなる保証もない。

バージョン 2.1.0