Skip to content

R チートシート

統計計算とグラフィックスの言語。

01

ベクトルと基礎

変数、型と代入

Rは<-を推奨代入演算子として使用します(=も機能しますが関数引数で曖昧になる可能性があります)。Rのすべてはベクトルです — 単一の数値も長さ1のベクトルです。コアの型はcharacter、numeric(double)、integer、logical、complexです。NAは欠損データを表し、演算を通じて伝播します(スキップするにはna.rm=TRUEを使用)。NULLは値の不在(空のオブジェクト)で、NAとは異なります。分析前に常にNAをチェックしてください。

r
# assignment operators (all equivalent for simple values)
name <- "Alice"      # preferred
age = 30L            # = also works (avoid in functions)
30L -> age2          # rightward assignment

# basic types (called "modes" in R)
class("text")        # "character"
class(42)            # "numeric" (double)
class(42L)           # "integer"
class(3.14)          # "numeric"
class(TRUE)          # "logical"
class(2 + 3i)        # "complex"

# check and convert types
is.numeric(age)      # TRUE
is.character(name)   # TRUE
as.integer(3.9)      # 3 (truncates, not rounds)
as.character(42)     # "42"
as.numeric("3.14")   # 3.14

# special values
NA                   # missing value (Not Available)
NULL                 # null object (no value)
NaN                  # Not a Number (0/0)
Inf                  # infinity (1/0)
is.na(NA)            # TRUE
is.null(NULL)        # TRUE

ベクトルの作成とインデックス付け

c()が値をベクトルに結合します — Rの最も基本的な関数。Rは1始まりです(最初の要素は[0]ではなく[1])。負のインデックスは要素を除外します:nums[-1]は最初を削除。論理インデックス(nums[nums > 3])が条件でフィルタします — 非常に強力。ベクトルは名前を持て、ラベルでアクセス可能です。ベクトルのすべての要素は同じ型でなければなりません。型を混ぜるとRは強制変換します(例:c(1, 'a')はc('1', 'a')になります)。

r
# create vectors with c() (combine)
nums <- c(1, 2, 3, 4, 5)
chars <- c("a", "b", "c")
logs <- c(TRUE, FALSE, TRUE)

# sequences
1:5                  # 1 2 3 4 5
seq(1, 10, by = 2)  # 1 3 5 7 9
seq(0, 1, length.out = 5)  # 0 0.25 0.5 0.75 1
rep(1:3, times = 2) # 1 2 3 1 2 3
rep(1:3, each = 2)  # 1 1 2 2 3 3

# indexing (1-indexed!)
nums[1]              # 1 (first element)
nums[length(nums)]   # 5 (last element)
nums[c(1, 3, 5)]     # 1 3 5 (multiple indices)
nums[-1]             # 2 3 4 5 (all EXCEPT first)
nums[-c(1, 2)]       # 3 4 5 (exclude first two)
nums[2:4]            # 2 3 4 (range)

# logical indexing
nums[nums > 2]       # 3 4 5
nums[nums %% 2 == 0] # 2 4 (even numbers)
nums[nums > 2] <- 0  # modify in place

# named vectors
ages <- c(alice = 30, bob = 25, carol = 28)
ages["alice"]        # 30
ages[c("alice", "bob")]  # 30 25

ベクトル演算と関数

Rのベクトル化は特徴的な機能です — 演算は自動的に要素ごとに適用され、ループは不要です。リサイクルは短い方のベクトルを再利用して長い方に一致させます(c(1,2,3,4) + c(10,20)は11,22,13,24になります)。order()はベクトルをソートするインデックスを返します — あるベクトルを別のベクトルでソートするのに不可欠。unique()が重複を除去します。これらはすべて最適化されたCコードで、Rのベクトル演算を高速にします。

r
# arithmetic is element-wise (vectorized)
a <- c(1, 2, 3)
b <- c(4, 5, 6)
a + b                # 5 7 9
a * b                # 4 10 18
a ^ 2                # 1 4 9
a / b                # 0.25 0.4 0.5

# recycling: shorter vector repeats
c(1, 2, 3, 4) + c(10, 20)  # 11 22 13 24

# summary functions
sum(a)               # 6
mean(a)              # 2
median(a)            # 2
sd(a)                # 1
var(a)               # 1
min(a); max(a)       # 1; 3
range(a)             # 1 3
cumsum(a)            # 1 3 6
cumprod(a)           # 1 2 6

# sorting and ordering
sort(c(3, 1, 2))              # 1 2 3
sort(c(3, 1, 2), decreasing = TRUE)  # 3 2 1
order(c(3, 1, 2))             # 2 3 1 (indices that would sort)
v[order(v)]                   # sort using order

# useful functions
length(a)            # 3
unique(c(1, 1, 2, 2, 3))  # 1 2 3
rev(a)                # 3 2 1
head(a, 2)            # 1 2 (first n)
tail(a, 2)            # 2 3 (last n)

文字と文字列操作

paste/paste0はRの文字列結合関数です — paste0は区切り文字なし(Pythonの+のように)。substrが部分文字列を抽出します(1始まり)。gsubがすべての一致を置換し、subは最初のみ。grepが一致する要素のインデックスを返し、greplは論理ベクトルを返します(フィルタリングに便利)。RはデフォルトでPOSIX拡張正規表現を使用します。sprintfがCスタイルのフォーマットを提供します。stringrパッケージ(tidyverse)がよりクリーンで一貫したAPIを提供します。

r
# paste and paste0 (concatenation)
paste("Hello", "World")           # "Hello World"
paste("Hello", "World", sep = "_") # "Hello_World"
paste0("a", "b", "c")             # "abc" (no separator)
paste(c("a", "b", "c"), collapse = "-")  # "a-b-c"
paste("file", 1:3, ".csv", sep = "")     # "file1.csv" "file2.csv" "file3.csv"

# case conversion
toupper("hello")    # "HELLO"
tolower("WORLD")    # "world"

# substring
substr("Hello World", 1, 5)  # "Hello"
nchar("Hello")               # 5 (character count)

# split and replace
strsplit("a,b,c", ",")[[1]]  # "a" "b" "c"
gsub("o", "0", "Hello World") # "Hell0 W0rld" (all matches)
sub("o", "0", "Hello World")  # "Hell0 World" (first match only)
gsub("[0-9]+", "N", "a1b22c333") # "aNbNcN" (regex)

# grep and grepl (pattern matching)
grep("^A", c("Alice", "Bob", "Anna"))  # 1 3 (indices)
grepl("^A", c("Alice", "Bob"))         # TRUE FALSE

# sprintf (C-style formatting)
sprintf("Pi = %.2f", pi)       # "Pi = 3.14"
sprintf("%s is %d", "Alice", 30) # "Alice is 30"

欠損値と型変換

NA(Not Available)は欠損データを表し、ほとんどの演算を通じて伝播します — 常にna.rm=TRUEを使用するかフィルタで除去してください。NULLは異なります:値の不在でベクトルから削除されます。Rは結合時に最も一般的な型に強制変換します(logical < integer < numeric < character)。非数値文字列のas.numericは警告付きでNAを生成します。ifelseはベクトル化三項演算子です — 連続変数からカテゴリ変数を作成するのに非常に便利です。

r
# NA propagation and handling
x <- c(1, 2, NA, 4, 5)
mean(x)              # NA (NA propagates!)
mean(x, na.rm = TRUE) # 3 (skip NA)
sum(x, na.rm = TRUE)  # 12
is.na(x)             # FALSE FALSE TRUE FALSE FALSE
sum(is.na(x))        # 1 (count of NAs)
x[!is.na(x)]         # 1 2 4 5 (remove NAs)

# NULL vs NA
length(c(1, NA, 3))  # 3 (NA is an element)
length(c(1, NULL, 3)) # 2 (NULL is dropped)

# type coercion hierarchy: logical < integer < double < character
c(TRUE, 1)           # 1 1 (logical -> numeric)
c(1L, 2.5)           # 1.0 2.5 (integer -> double)
c(1, "a")            # "1" "a" (numeric -> character)

# explicit coercion
as.numeric(c("1", "2", "abc"))  # 1 2 NA (with warning)
as.logical(c(0, 1, 2))          # FALSE TRUE TRUE
factor(c("low", "high", "low")) # factor with 2 levels

# ifelse vectorized conditional
ifelse(x > 2, "big", "small")  # "small" "small" NA "big" "big"
02

データ構造

リスト(異種コンテナ)

リストはRの最も柔軟なデータ構造です — 任意の型とサイズの要素を保持できます(PythonのdictやJavaScriptのオブジェクトのように)。$演算子は名前付きアクセスの便利なショートカットです。重要な区別:[ ]はサブリストを返し(まだリスト)、[[ ]]は実際の要素を抽出します。これがR初心者の最大の混乱源です。値自体が必要な場合は[[ ]]、サブセットが必要な場合は[ ]を使ってください。lapply/sapplyがリスト要素にわたって関数を適用します。

r
# lists can hold different types and sizes
user <- list(
    name = "Alice",
    age = 30,
    scores = c(90, 85, 88),
    active = TRUE
)

# access by name ($ or [[]])
user$name             # "Alice"
user[["age"]]         # 30
user[["scores"]][2]   # 85

# [] returns a sublist (list); [[]] returns the element
user["name"]          # list with one element
user[["name"]]        # "Alice" (the string itself)
user[1:2]             # sublist with first 2 elements

# modify and add
user$age <- 31
user$email <- "[email protected]"  # add new element
user[["scores"]] <- NULL           # remove element

# iterate over a list
for (key in names(user)) {
    cat(key, ":", user[[key]], "\n")
}

# lapply and sapply on lists
lapply(user$scores, sqrt)   # list of square roots
sapply(user$scores, sqrt)   # vector of square roots

データフレーム

データフレームはRの主要な表形式データ構造です — スプレッドシートやSQLテーブルのように各列が異なる型にできます。列には$または[[ ]]でアクセスし、論理インデックスで行をフィルタします(df[df$age > 25, ])。subset()がよりクリーンな代替です。cbindが列を追加し、rbindが行を追加します。str()が構造(型とプレビュー)を表示します。文字列を因子ではなく文字として保持するため、常にstringsAsFactors=FALSEを設定してください(R 4.0+ではデフォルトです)。

r
# create a data frame (like a table/spreadsheet)
df <- data.frame(
    name = c("Alice", "Bob", "Carol"),
    age = c(30, 25, 28),
    score = c(90, 85, 88),
    stringsAsFactors = FALSE
)

# dimensions
nrow(df)              # 3
ncol(df)              # 3
dim(df)               # 3 3
names(df)             # "name" "age" "score"
str(df)               # structure summary
head(df, 2)           # first 2 rows

# access columns
df$name               # vector (by name)
df[["age"]]           # vector (by name, alternative)
df[, "age"]           # vector (column)
df[, 2]               # vector (by index)
df[2]                 # data frame with one column

# access rows
df[1, ]               # first row (as data frame)
df[1:2, ]             # first 2 rows
df[c(1, 3), ]         # rows 1 and 3

# filter rows (logical indexing)
df[df$age > 26, ]              # rows where age > 26
df[df$score >= 88, c("name", "score")]
subset(df, age > 26, select = c(name, score))

# add and modify columns
df$grade <- c("A", "B", "A")   # add column
df$age <- df$age + 1           # modify column
df <- cbind(df, pass = df$score > 60)  # column bind

# add rows
new_row <- data.frame(name = "Dan", age = 22, score = 75, grade = "C")
df <- rbind(df, new_row)

行列と配列

行列はすべての要素が同じ型でなければならない2D配列です(データフレームとは異なります)。%*%が行列乗算、*が要素ごと。solve()が行列の逆行列、det()が行列式を計算します。rowSums/colSums/rowMeans/colMeansが高速な組み込みショートカットです。apply(m, MARGIN, FUN)が行(MARGIN=1)または列(MARGIN=2)にわたって関数を適用する一般的な方法です。配列は行列をn次元に拡張します。データ分析にはデータフレームを優先し、線形代数には行列を使ってください。

r
# create a matrix (2D, all same type)
m <- matrix(1:12, nrow = 3, ncol = 4)
#      [,1] [,2] [,3] [,4]
# [1,]    1    4    7   10
# [2,]    2    5    8   11
# [3,]    3    6    9   12

# fill by row instead of column
m2 <- matrix(1:6, nrow = 2, byrow = TRUE)

# dimensions and attributes
dim(m)                # 3 4
nrow(m)               # 3
ncol(m)               # 4
rownames(m) <- c("r1", "r2", "r3")
colnames(m) <- c("c1", "c2", "c3", "c4")

# indexing
m[2, 3]               # 8 (single element)
m[1, ]                # 1 4 7 10 (first row)
m[, 2]                # 4 5 6 (second column)
m[1:2, 2:3]           # 2x2 submatrix
m["r1", "c2"]         # 4 (by name)

# matrix operations
t(m)                  # transpose
m * m                 # element-wise multiply
m %*% t(m)            # matrix multiplication
solve(m[, 1:3])       # inverse (square matrix)
det(matrix(1:4, 2))   # determinant
rowSums(m)            # sum of each row
colMeans(m)           # mean of each column
apply(m, 1, sum)      # row sums (general)
apply(m, 2, max)      # column maxima

# arrays (n-dimensional)
arr <- array(1:24, dim = c(2, 3, 4))  # 2x3x4 array
arr[1, 2, 3]          # single element

因子とカテゴリカルデータ

因子はカテゴリカルデータをラベルマッピング付きの整数コードとして効率的に格納します — 統計モデリングに不可欠(lm、glmはグループ化に因子を使用)。よくある落とし穴:as.numeric(factor)は元の値ではなく整数コードを与えます — 常にas.character経由で変換してください。cut()が連続データを因子レベルにビン分割します。ordered=TRUEが比較演算子をサポートする順序因子を作成します。relevelが参照カテゴリを変更します(回帰解釈に重要)。table()が頻度カウントを生成します。

r
# factors represent categorical data (stored as integers with labels)
gender <- factor(c("male", "female", "male", "female"))
print(gender)
# [1] male   female male   female
# Levels: female male

levels(gender)        # "female" "male" (sorted alphabetically)
table(gender)         # frequency table
nlevels(gender)       # 2

# ordered factor (for ordinal data)
size <- factor(c("M", "S", "L", "XL"),
               levels = c("S", "M", "L", "XL"),
               ordered = TRUE)
size[1] > size[2]     # TRUE (M > S)

# convert to factor and back
nums <- factor(c(1, 2, 3, 2, 1))
as.numeric(nums)      # 1 2 3 2 1 (level codes, NOT original values!)
as.numeric(as.character(nums))  # 1 2 3 2 1 (correct way)

# relevel (change reference level)
gender <- relevel(gender, ref = "male")

# cut: convert numeric to factor (binning)
ages <- c(15, 25, 35, 45, 55, 65)
age_groups <- cut(ages, breaks = c(0, 18, 35, 50, 100),
                  labels = c("child", "young", "adult", "senior"))
table(age_groups)     # frequency per group

# interaction of factors
f1 <- factor(c("A", "A", "B", "B"))
f2 <- factor(c("X", "Y", "X", "Y"))
interaction(f1, f2)   # A.X A.Y B.X B.Y

リストからデータフレームと再形成

wideとlong形式間の再形成は一般的なデータラングリングタスクです。pivot_longer/pivot_wider(tidyr、tidyverse)がモダンで直感的な関数です。wide形式はサブジェクトごとに1行で各時点の列を持ち、long形式は観測ごとに1行です。long形式はggplot2とほとんどの分析に好まれます。split()が因子でデータフレームをリストに分割し、do.call(rbind, ...)が再結合します。base Rのreshape()関数は強力ですがインターフェースが分かりにくいです — tidyrを優先してください。

r
# convert list to data frame
my_list <- list(
    a = 1:3,
    b = c("x", "y", "z")
)
df <- as.data.frame(my_list)

# stack multiple vectors
do.call(rbind, list(
    data.frame(name = "A", val = 1),
    data.frame(name = "B", val = 2)
))

# reshape: wide to long
library(tidyr)
wide_df <- data.frame(
    id = 1:2,
    q1 = c(10, 20),
    q2 = c(15, 25)
)
long_df <- pivot_longer(wide_df, cols = starts_with("q"),
                        names_to = "quarter", values_to = "value")

# long to wide
pivot_wider(long_df, names_from = quarter, values_from = value)

# base R reshape (without tidyr)
reshape(wide_df, direction = "long",
        varying = c("q1", "q2"), v.names = "value",
        timevar = "quarter", idvar = "id")

# split and combine
split_results <- split(df, df$group)  # list of data frames by group
combined <- do.call(rbind, split_results)  # recombine

# melt and cast (reshape2 package, legacy)
# library(reshape2)
# melt(wide_df, id.vars = "id")
# dcast(long_df, id ~ quarter, value.var = "value")
03

制御フローと関数

If / Else と Switch

Rのif/elseは複数行の本体にブレースが必要で、elseは閉じブレースと同じ行になければなりません(そうでないとRはifが完了したとみなします)。ifelse(test, yes, no)はベクトル化されています — ベクトル全体に一度に適用され、結果のベクトルを返します。複数条件にはdplyrのcase_whenがネストしたifelseよりはるかにクリーンです。switchは文字列(または数値位置)でディスパッチします — 長いif-elseチェーンのクリーンな代替です。

r
# if-else if-else
score <- 85
if (score >= 90) {
    grade <- "A"
} else if (score >= 80) {
    grade <- "B"
} else {
    grade <- "C"
}

# ifelse (vectorized ternary)
ages <- c(15, 25, 35, 45)
ifelse(ages >= 18, "adult", "minor")
# "minor" "adult" "adult" "adult"

# nested ifelse (avoid deep nesting!)
ifelse(ages < 18, "minor",
       ifelse(ages < 65, "adult", "senior"))

# dplyr::case_when is cleaner for multiple conditions
# case_when(
#     ages < 18 ~ "minor",
#     ages < 65 ~ "adult",
#     TRUE      ~ "senior"
# )

# switch (dispatch on a value)
day_type <- function(day) {
    switch(day,
        "Mon" = "weekday",
        "Tue" = "weekday",
        "Wed" = "weekday",
        "Thu" = "weekday",
        "Fri" = "weekday",
        "Sat" = "weekend",
        "Sun" = "weekend",
        "unknown"
    )
}

ループ:For、While、Repeat

forループはRで要素(またはシーケンス)を反復します。xが空の場合、seq_along(x)は1:length(x)より安全です(c(1,0)ではなくinteger(0)を返します)。nextが次の反復にスキップし(continueのように)、breakが抜けます。repeatは明示的にbreakしなければならない無限ループです。結果ベクトルを常に事前割り当てしてください(result <- numeric(N)) — c()でループ内でベクトルを成長させるとO(n²)で非常に遅いです。ただし、可能な場合はループよりベクトル化演算やapplyファミリーを優先してください。

r
# for loop
for (i in 1:5) {
    print(i)
}

# iterate over a vector
fruits <- c("apple", "banana", "cherry")
for (fruit in fruits) {
    print(paste("Fruit:", fruit))
}

# iterate with index
for (i in seq_along(fruits)) {
    print(paste(i, fruits[i]))
}

# while loop
count <- 0
while (count < 5) {
    count <- count + 1
    if (count == 3) next    # skip (like continue)
    print(count)
}

# repeat loop (infinite, must break)
i <- 0
repeat {
    i <- i + 1
    if (i >= 3) break       # exit loop
    print(i)
}

# preallocate for speed (IMPORTANT!)
result <- numeric(1000)
for (i in 1:1000) {
    result[i] <- i^2
}

# nested loops
mat <- matrix(0, 3, 3)
for (i in 1:3) {
    for (j in 1:3) {
        mat[i, j] <- i * j
    }
}

関数定義と引数

R関数は最後に評価された式を自動的に返します(明示的なreturnは不要、ただし早期終了にはreturn()が明確)。デフォルト引数で関数を柔軟にします。...(省略記号)が渡す余分な引数をキャプチャします — ラッパー関数に不可欠。名前付き引数は任意の順序で可能です。Rは遅延評価を使用します:引数は最初に使用された時にのみ評価されるため、未使用の引数はエラーを起こしません。複数の値を返すにはリストにパッケージ化します。

r
# basic function (last expression is returned)
add <- function(a, b) {
    a + b
}
add(3, 4)            # 7

# explicit return
is_positive <- function(x) {
    if (x > 0) return(TRUE)
    FALSE
}

# default arguments
greet <- function(name, greeting = "Hello", punctuation = "!") {
    paste0(greeting, ", ", name, punctuation)
}
greet("Alice")                    # "Hello, Alice!"
greet("Bob", greeting = "Hi")     # "Hi, Bob!"
greet(name = "Carol", punctuation = "?")  # named args

# ... (ellipsis: pass arguments through)
my_plot <- function(x, y, ...) {
    plot(x, y, col = "blue", ...)
}
my_plot(1:10, 1:10, main = "My Plot", type = "l")

# return multiple values via list
stats <- function(x) {
    list(mean = mean(x), sd = sd(x), n = length(x))
}
result <- stats(1:10)
result$mean         # 5.5

# lazy evaluation (args evaluated only when used)
f <- function(a, b) {
    a * 2    # b is never used, so not evaluated
}
f(5)                # 10 (no error despite missing b)

Applyファミリー

applyファミリーはループを関数型反復で置き換えます — より慣用的でしばしば高速。lapplyは常にリストを返し、sapplyはベクトル/行列に簡略化を試みます(便利だが予測不能)、vapplyが保証された戻り値型の安全なバージョンです。applyは行列で動作します(MARGIN=1が行、2が列)。tapplyが因子でデータをグループ化して関数を適用します — ミニGROUP BYのように。replicateがランダムシミュレーションを繰り返します。データフレームにはpurrrパッケージ(tidyverse)がよりクリーンで一貫したmap()ファミリーを提供します。

r
# lapply: apply function to each element of a list, returns list
my_list <- list(a = 1:3, b = 4:6, c = 7:9)
lapply(my_list, mean)    # list: 2, 5, 8
lapply(my_list, sum)     # list: 6, 15, 24

# sapply: like lapply but simplifies to vector/matrix
sapply(my_list, mean)    # 2 5 8 (named vector)
sapply(my_list, range)   # matrix with 2 rows

# vapply: like sapply but with guaranteed output type (safer)
vapply(my_list, mean, numeric(1))  # always numeric vector

# apply: apply function over matrix/array margins
m <- matrix(1:12, nrow = 3)
apply(m, 1, sum)         # row sums: 22 26 30
apply(m, 2, mean)        # column means: 2 5 8 11
apply(m, c(1, 2), sqrt)  # element-wise sqrt

# mapply: multivariate apply (vectorized over multiple args)
mapply(rep, 1:3, 3:1)    # rep(1,3), rep(2,2), rep(3,1)

# tapply: apply function by groups
df <- data.frame(
    group = c("A", "A", "B", "B", "B"),
    value = c(10, 20, 30, 40, 50)
)
tapply(df$value, df$group, mean)  # A: 15, B: 40

# replicate: repeat an expression n times
replicate(3, mean(rnorm(10)))  # 3 random means

スコープと環境

Rはレキシカルスコープを使用します:関数は呼び出された場所ではなく定義された環境で自由変数をルックアップします。これによりクロージャが可能になります — 囲む環境をキャプチャする関数です。<<-演算子は親環境の変数に代入し(スーパー代入)、これがクロージャが状態を維持する方法です(カウンターの例のように)。各関数呼び出しが新しい環境を作成します。検索パス(search())がRがオブジェクトを探す場所を決定します — globalenvがワークスペースで、続いてアタッチされたパッケージです。

r
# lexical scoping: functions look up variables in defining environment
y <- 10
f <- function(x) {
    x + y    # y found in global env
}
f(5)                 # 15

# local variables shadow globals
g <- function(x) {
    y <- 100         # local y
    x + y
}
g(5)                 # 105
y                    # 10 (global unchanged)

# <<- assigns to parent environment (super-assignment)
counter <- function() {
    count <- 0
    function() {
        count <<- count + 1   # modifies count in enclosing scope
        count
    }
}
c1 <- counter()
c1()                 # 1
c1()                 # 2

# search path for variables
search()             # shows environments: globalenv, package namespaces
ls()                 # list objects in current environment
ls(envir = .GlobalEnv)  # explicitly global
exists("y")          # TRUE if variable exists

# new environment (isolated scope)
e <- new.env()
e$x <- 42
e$x                  # 42 (separate from global x)
04

データ操作(dplyrとtidyr)

dplyr:Filter、Select、Arrange

dplyr(tidyverseの一部)はSQL操作を反映する直感的なデータ操作動詞を提供します。filterが条件で行を選択し、selectが列を選び、arrangeがソートします。%in%演算子がメンバーシップをテストします。starts_with、ends_with、containsのようなヘルパー関数が列選択を柔軟にします。rename()がコピーなしで列名を変更します。これらの動詞はパイプ(%>%)で合成し読みやすいデータパイプラインを作ります。dplyrは内部でC++を使用するため大きなデータでbase Rよりはるかに高速です。

r
library(dplyr)

df <- data.frame(
    name = c("Alice", "Bob", "Carol", "Dan"),
    age = c(30, 25, 28, 35),
    dept = c("Eng", "Sales", "Eng", "Sales"),
    salary = c(80000, 50000, 75000, 90000)
)

# filter rows (like WHERE in SQL)
filter(df, age > 26)
filter(df, dept == "Eng" & salary > 70000)
filter(df, dept %in% c("Eng", "Sales"))
filter(df, age > 26 | salary > 85000)

# select columns (like SELECT in SQL)
select(df, name, age)
select(df, name:dept)           # range of columns
select(df, -salary)             # exclude column
select(df, starts_with("s"))    # columns starting with 's'
select(df, ends_with("e"))      # columns ending with 'e'
select(df, contains("am"))      # columns containing 'am'
select(df, everything())        # all columns (useful for reordering)

# arrange (sort, like ORDER BY)
arrange(df, age)                # ascending
arrange(df, desc(age))          # descending
arrange(df, dept, desc(salary)) # sort by dept, then salary desc

# rename columns
rename(df, years = age, department = dept)

# distinct rows
distinct(df, dept)              # unique departments
distinct(df, dept, .keep_all = TRUE)  # keep all columns

dplyr:Mutate、Summarize、Group By

mutateが列を追加または変更します(ベクトル化)。summarizeが各グループを単一のサマリー行に削減します。group_by + summarizeの組み合わせが強力です — SQLのGROUP BYに相当。n()が行をカウントし、across()が複数列にわたって関数を適用します(dplyr 1.0の新機能)。ウィンドウ関数(rank、cumsum、lag、lead)がグループ内で動作し、「部門内ランク」のような計算を可能にします。パイプ%>%が操作を左から右にチェーンし、複雑なパイプラインを読みやすくします。

r
library(dplyr)

# mutate: add/modify columns
df %>%
    mutate(
        bonus = salary * 0.1,
        total = salary + bonus,
        category = ifelse(age > 30, "senior", "junior")
    )

# transmute: like mutate but only keeps new columns
transmute(df, name, annual = salary, monthly = salary / 12)

# summarize (reduces to single row per group)
summarize(df,
    avg_salary = mean(salary),
    max_age = max(age),
    n = n()
)

# group_by + summarize (like GROUP BY in SQL)
df %>%
    group_by(dept) %>%
    summarize(
        count = n(),
        avg_salary = mean(salary),
        avg_age = mean(age)
    ) %>%
    arrange(desc(avg_salary))

# multiple summaries
df %>%
    group_by(dept) %>%
    summarize(across(everything(), list(mean, sd)))

# count and tally
count(df, dept)                  # count per dept
df %>% group_by(dept) %>% tally()

# window functions (within groups)
df %>%
    group_by(dept) %>%
    mutate(
        rank = rank(desc(salary)),
        cumsum = cumsum(salary)
    ) %>%
    arrange(dept, rank)

パイプ演算子(%>%)

パイプ(magrittr/dplyrの%>%)は左側を右側の最初の引数として渡し、ネストした関数呼び出しを読みやすい左から右へのパイプラインに変換します。これがtidyverseスタイルの決定的な機能です。ドット(.)は明示的に必要な場合にパイプされたデータを表します。%$%が列名を公開し、%<>%が戻して代入し、%T>%が副作用(プロットなど)の後にパイプを継続します。R 4.1+はネイティブパイプ|>を持ちますが、柔軟性に劣ります(ドットプレースホルダーなし)。パイプはデータラングリングコードを劇的に読みやすくします。

r
library(magrittr)  # or library(dplyr)

# without pipe: nested, hard to read
result <- arrange(
    filter(
        select(df, name, age, salary),
        age > 25
    ),
    desc(salary)
)

# with pipe: linear, readable
result <- df %>%
    select(name, age, salary) %>%
    filter(age > 25) %>%
    arrange(desc(salary))

# the dot (.) refers to the piped data
df %>% plot(.$age, .$salary)     # . = df

# %$% exposes column names (magrittr)
df %$% cor(age, salary)          # correlation

# %<>% assigns result back (compound assignment)
df %<>% filter(age > 20)

# T pipe: returns first argument (for side effects)
rnorm(100) %T>%
    hist() %>%                    # plot histogram (returns input)
    mean()                        # then compute mean

# native pipe (R 4.1+): |>
df |>
    subset(age > 25) |>
    colMeans()

データフレームの結合

dplyrのjoin関数はSQLのjoinを反映します:inner_join(積集合)、left_join(左のすべての行)、right_join、full_join(和集合)。semi_joinが一致する行にフィルタし(列を追加せず)、anti_joinが一致しない行を見つけます — 両方ともデータ検証に便利です。by引数が結合キーを指定し、列名が異なる場合は名前付きベクトル(c('id' = 'emp_id'))を使用します。joinはbase Rのmerge()よりはるかに高速です。予期しない重複をキャッチするため、結合前後に常に行数をチェックしてください。

r
library(dplyr)

employees <- data.frame(
    id = c(1, 2, 3, 4),
    name = c("Alice", "Bob", "Carol", "Dan")
)

salaries <- data.frame(
    id = c(1, 2, 3, 5),
    salary = c(80000, 50000, 75000, 60000)
)

# inner join: only matching rows
inner_join(employees, salaries, by = "id")
#   id  name salary
#   1  Alice 80000
#   2  Bob   50000
#   3  Carol 75000

# left join: all rows from left, NA for non-matches
left_join(employees, salaries, by = "id")
# Dan has NA salary

# right join: all rows from right
right_join(employees, salaries, by = "id")
# id 5 has NA name

# full join: all rows from both
full_join(employees, salaries, by = "id")

# different column names
left_join(employees, salaries, by = c("id" = "id"))

# semi join: rows in left that have a match (no right columns)
semi_join(employees, salaries, by = "id")

# anti join: rows in left with NO match
anti_join(employees, salaries, by = "id")
# Dan (id 4 has no salary)

# multiple keys
left_join(df1, df2, by = c("dept" = "department", "year" = "yr"))

tidyr:データの再形成

tidyr(tidyverse)がデータの再形成を処理します。pivot_longer/widerがレガシーのgather/spreadを置換します — より直感的で柔軟です。tidyデータは観測ごとに1行、変数ごとに1列を持ちます。pivot_longerがwideデータをこの形式に変換します(ggplot2に必要)。separate/uniteが列を分割・結合します。separate_rowsが区切り値を複数行に展開します。drop_na/replace_na/fillが欠損データをクリーンに処理します。これらの動詞はパイプ経由でdplyrと合成し強力なデータパイプラインを作ります。

r
library(tidyr)

# wide to long
wide_df <- data.frame(
    id = 1:2,
    q1_sales = c(100, 200),
    q2_sales = c(150, 250),
    q3_sales = c(120, 220)
)

long_df <- wide_df %>%
    pivot_longer(
        cols = starts_with("q"),
        names_to = "quarter",
        values_to = "sales"
    )

# long to wide
long_df %>%
    pivot_wider(
        names_from = quarter,
        values_from = sales
    )

# separate one column into multiple
df <- data.frame(id = 1, name = "Alice_Smith")
df %>% separate(name, into = c("first", "last"), sep = "_")

# unite multiple columns into one
df %>% unite("full_name", first, last, sep = " ")

# separate_rows: split delimited values into rows
df <- data.frame(id = 1:2, tags = c("a,b,c", "x,y"))
df %>% separate_rows(tags, sep = ",")

# drop NA values
df %>% drop_na()                    # drop rows with any NA
df %>% drop_na(salary)              # drop rows where salary is NA

# replace NA with a value
df %>% replace_na(list(salary = 0, name = "Unknown"))

# fill missing values (carry forward)
df %>% fill(salary, .direction = "down")
05

統計とモデリング

記述統計

summary()は任意のデータの概要を得る最速の方法です — min、四分位数、中央値、平均、maxを表示します。sd()とvar()がサンプル(n-1)統計を計算します。cor()が線形関連(Pearson)またはランク関連(Spearman)を測定します。NAを含む実データには常にna.rm=TRUEを使用してください。データフレームの場合、summary(df)が列ごとの統計を与えます。psychとHmiscパッケージが拡張記述統計を提供します。

r
data <- c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

# central tendency
mean(data)           # 5.5
median(data)         # 5.5
# R has no built-in mode; use:
as.numeric(names(sort(table(data), decreasing = TRUE)[1]))

# spread
sd(data)             # 3.028 (sample standard deviation)
var(data)            # 9.167 (sample variance)
IQR(data)            # 5 (interquartile range)
range(data)          # 1 10
diff(range(data))    # 9

# quantiles
quantile(data)              # 0% 25% 50% 75% 100%
quantile(data, c(0.1, 0.9)) # 10th and 90th percentiles

# summary (all at once)
summary(data)
# Min. 1st Qu. Median  Mean  3rd Qu.  Max.
# 1.00   3.25    5.50   5.50   7.75    10.00

# correlation and covariance
x <- c(1, 2, 3, 4, 5)
y <- c(2, 4, 5, 4, 5)
cor(x, y)            # 0.77 (Pearson correlation)
cor(x, y, method = "spearman")  # rank correlation
cov(x, y)            # covariance

# handling NA
data_na <- c(1, 2, NA, 4, 5)
mean(data_na, na.rm = TRUE)  # 3
sd(data_na, na.rm = TRUE)    # 1.826

確率分布

Rは一貫した命名規則を持つすべての一般的な分布を持ちます:d/p/q/r接頭辞 + 分布名。dは密度(連続)または確率質量(離散)、pは累積確率、qは分位数(パーセンタイル)、rがランダムサンプルを生成します。一般的な分布:norm(正規)、binom(二項)、pois(ポアソン)、unif(一様)、exp(指数)、t、chisq、f。再現可能な結果のためにランダム操作の前に常にset.seed()を呼んでください。sample()がベクトルからランダム要素を抽出します。

r
# R uses d/p/q/r prefix for distributions:
#   d = density (PDF/PMF)
#   p = cumulative (CDF: P(X <= x))
#   q = quantile (inverse CDF)
#   r = random samples

# Normal distribution
dnorm(0)                    # 0.399 (density at 0)
pnorm(1.96)                 # 0.975 (P(Z <= 1.96))
qnorm(0.975)                # 1.96 (97.5th percentile)
rnorm(5, mean = 100, sd = 15)  # 5 random samples

# Binomial distribution
dbinom(3, size = 10, prob = 0.5)  # P(X=3)
pbinom(3, size = 10, prob = 0.5)  # P(X<=3)
rbinom(10, size = 1, prob = 0.5)  # 10 coin flips

# Poisson distribution
dpois(2, lambda = 3)        # P(X=2) with rate 3
rpois(100, lambda = 5)      # 100 random samples

# Uniform distribution
runif(5, min = 0, max = 1)  # 5 uniform random numbers

# Exponential
rexp(10, rate = 0.5)        # 10 exponential samples

# set random seed for reproducibility
set.seed(42)
rnorm(3)                    # same results every time with same seed

# sample from a vector
sample(1:10, 5)             # 5 random numbers without replacement
sample(1:10, 5, replace = TRUE)  # with replacement
sample(c("A", "B", "C"), 2) # sample from categories

仮説検定

Rは仮説検定を簡単にします。t.testが平均を比較します(1サンプル、2サンプル、または対応)。p値 < 0.05が典型的に統計的有意性を示します。var.equal=TRUEは等分散を仮定(Student's t)、デフォルトはWelch's(より堅牢)です。chisq.testがカテゴリカル変数の独立性をチェックします。wilcox.testがノンパラメトリックな代替です(正規性の仮定なし)。aovが3+グループを比較するANOVAを実行します。すべてのテスト関数は$ p.value、$statistic、$conf.intを持つリストを返し、プログラム的に抽出できます。

r
# one-sample t-test (is mean different from hypothesized?)
data <- c(5.1, 4.9, 5.0, 5.2, 4.8, 5.1, 5.0)
t.test(data, mu = 5.0)
#  t = 0.527, df = 6, p-value = 0.616
#  (fail to reject H0: mean = 5)

# two-sample t-test (are two means different?)
group1 <- c(5.1, 4.9, 5.0, 5.2)
group2 <- c(4.5, 4.7, 4.6, 4.4)
t.test(group1, group2)
t.test(group1, group2, var.equal = TRUE)  # assume equal variance

# paired t-test (before/after)
before <- c(70, 80, 65, 90, 75)
after  <- c(75, 85, 70, 92, 80)
t.test(before, after, paired = TRUE)

# chi-squared test (independence of categorical vars)
tbl <- table(c("A","A","B","B"), c("X","Y","X","Y"))
chisq.test(tbl)

# Wilcoxon (non-parametric alternative to t-test)
wilcox.test(group1, group2)

# ANOVA (compare means across multiple groups)
df <- data.frame(
    value = c(5, 6, 7, 8, 9, 10),
    group = factor(c("A","A","B","B","C","C"))
)
result <- aov(value ~ group, data = df)
summary(result)         # F-test p-value

# extract p-value from any test
test_result <- t.test(data, mu = 5)
test_result$p.value     # 0.616

線形回帰(lm)

lm()は式構文で線形モデルをフィットします:y ~ x(単純)、y ~ x1 + x2(多重)、y ~ .(すべての列)、y ~ x1*x2(交互作用あり)、y ~ I(x^2)(変換)。summary()が係数、標準誤差、t値、p値、R²、F検定を表示します。因子は自動的にダミー変数に変換されます。式ミニ言語は強力です:- は項を削除、: は交互作用、* は主効果 + 交互作用です。モデルの仮定をチェックするために常に診断プロット(残差、Q-Qプロット)を調べてください。

r
# simple linear regression: y ~ x
x <- c(1, 2, 3, 4, 5)
y <- c(2, 4, 5, 4, 5)
model <- lm(y ~ x)

# view results
print(model)          # coefficients
summary(model)        # full summary with R², t-tests, F-test
coef(model)           # (Intercept) 2.2, x 0.6
fitted(model)         # predicted values
residuals(model)      # y - predicted
confint(model)        # 95% confidence intervals

# make predictions
predict(model, newdata = data.frame(x = c(6, 7)))

# multiple regression
df <- data.frame(
    y = c(10, 12, 15, 18, 20),
    x1 = c(1, 2, 3, 4, 5),
    x2 = c(5, 4, 3, 2, 1)
)
model2 <- lm(y ~ x1 + x2, data = df)
model3 <- lm(y ~ ., data = df)          # all predictors
model4 <- lm(y ~ x1 + I(x1^2), data = df)  # polynomial

# interactions
model5 <- lm(y ~ x1 * x2, data = df)    # x1 + x2 + x1:x2

# diagnostic plots
par(mfrow = c(2, 2))
plot(model)            # 4 diagnostic plots

# with factors (automatic dummy variables)
model6 <- lm(y ~ x1 + factor(group), data = df)

GLMとその他のモデル

glm()はfamily引数経由でlm()を非正規結果に一般化します:binomial(二値のロジスティック回帰)、poisson(カウントデータ)、gaussian(lmと同じ)。ロジスティックモデルのpredict()にtype='response'を指定すると(log-oddsではなく)確率を与えます。step()が自動変数選択を実行(AICベース)。anova(model1, model2)が大きなモデルが有意に優れているかテストします。高度なメソッドには、Rにすべてのパッケージがあります:lme4(混合モデル)、survival(Kaplan-Meier、Cox)、randomForest、caret(MLパイプライン)、glmnet(正則化)。

r
# logistic regression (binary outcome)
df <- data.frame(
    admit = c(0, 1, 0, 1, 1, 0),
    gre = c(380, 660, 800, 640, 520, 760),
    gpa = c(3.6, 3.7, 3.8, 3.9, 3.5, 3.6)
)
logit <- glm(admit ~ gre + gpa, data = df, family = binomial)
summary(logit)

# predict probabilities (type = "response")
predict(logit, newdata = df, type = "response")

# Poisson regression (count data)
counts <- c(2, 3, 1, 4, 2, 5)
pois <- glm(counts ~ x, family = poisson)

# stepwise model selection
full_model <- lm(y ~ x1 + x2 + x3, data = df)
step_model <- step(full_model)    # AIC-based selection

# anova to compare models
model1 <- lm(y ~ x1, data = df)
model2 <- lm(y ~ x1 + x2, data = df)
anova(model1, model2)             # is x2 significant?

# other models
# nls()  - nonlinear least squares
# glm.nb() - negative binomial (MASS package)
# lme()/lmer() - mixed effects (nlme/lme4)
# rpart()/randomForest() - tree-based methods
# coxph() - survival analysis (survival package)

# cross-validation
library(boot)
cv_result <- cv.glm(df, logit, K = 10)  # 10-fold CV
cv_result$delta    # cross-validation error
06

プロット(Base Rとggplot2)

Base R:plot、hist、boxplot

Base Rグラフィックスは探索的分析に十分な速さです。plot()は汎用です — 入力型に基づいてディスパッチします(2ベクトルの散布図、式のボックスプロット)。typeが点/線スタイルを制御し、pchが点記号、ltyが線の型を設定します。par(mfrow=c(r,c))が複数プロットをグリッドに配置します。freq=FALSEのhist()は密度を表示します(密度曲線を重ねられます)。出版品質のグラフィックスにはggplot2を使ってください。Base Rプロットは命令型です — ステップバイステップで構築します。

r
# scatter plot
x <- 1:10
y <- x^2
plot(x, y, type = "p",        # p=points, l=lines, b=both, o=overplotted
     main = "Quadratic",       # title
     xlab = "x", ylab = "y",   # axis labels
     col = "blue", pch = 16,   # color and point character
     xlim = c(0, 10), ylim = c(0, 100))

# add lines and points to existing plot
lines(x, x*10, col = "red", lty = 2)  # dashed line
points(x, y + 5, col = "green")
legend("topleft", legend = c("x^2", "10x"),
       col = c("blue", "red"), lty = c(NA, 2), pch = c(16, NA))

# histogram
data <- rnorm(1000)
hist(data, breaks = 30, col = "lightblue",
     main = "Normal Distribution", xlab = "Value", freq = FALSE)
lines(density(data), col = "red", lwd = 2)  # add density curve

# boxplot (compare groups)
boxplot(count ~ spray, data = InsectSprays,
        col = "lightgreen", main = "Insect Count by Spray")

# barplot
counts <- table(mtcars$cyl)
barplot(counts, col = c("red","green","blue"),
        main = "Cars by Cylinders", xlab = "Cylinders")

# multiple plots in one window
par(mfrow = c(2, 2))   # 2x2 grid
plot(x, y); hist(data); boxplot(data); plot(density(data))
par(mfrow = c(1, 1))   # reset

ggplot2:グラフィックスの文法

ggplot2(tidyverse)はグラフィックスの文法を実装します:プロットは+で組み合わされるレイヤー(データ、美学、幾何、スケール、ファセット、テーマ)から構築されます。aes()がデータ列を視覚的プロパティ(x、y、color、size)にマッピングします。各geom_*がレイヤーを追加します:geom_point(散布図)、geom_line、geom_bar、geom_histogram、geom_boxplot、geom_smooth(トレンドライン)。このレイヤー化アプローチにより複雑なプロットを段階的に構築できます。base Rと異なり、ggplot2は宣言型です — 描き方ではなく何を欲しいかを記述します。

r
library(ggplot2)

# basic structure: data + aesthetic mapping + geometry
ggplot(data = mtcars, aes(x = wt, y = mpg)) +
    geom_point()

# add aesthetics (color, size, shape mapped to data)
ggplot(mtcars, aes(x = wt, y = mpg, color = cyl, size = hp)) +
    geom_point()

# add layers
ggplot(mtcars, aes(x = wt, y = mpg)) +
    geom_point(color = "blue", size = 3) +
    geom_smooth(method = "lm", se = TRUE) +  # regression line
    labs(title = "MPG vs Weight",
         x = "Weight (1000 lbs)", y = "MPG",
         color = "Cylinders") +
    theme_minimal()

# the + operator adds layers (like %>% but for ggplot)
p <- ggplot(mtcars, aes(x = wt, y = mpg)) +
    geom_point()
p + geom_smooth()           # add to saved plot
p + facet_wrap(~ cyl)       # facet by cylinders

# key components:
#   data       - the data frame
#   aes()      - aesthetic mappings (x, y, color, size, shape)
#   geom_*()   - geometry (point, line, bar, histogram, etc.)
#   scale_*()  - control axes, colors, legends
#   facet_*()  - small multiples (subplots)
#   theme_*()  - overall appearance
#   labs()     - titles and labels

ggplot2:Geomsと美学

ggplot2にはすべてのチャートタイプの数十のgeomがあります。geom_bar/geom_colが棒、geom_histogram/geom_densityが分布、geom_boxplotが比較、geom_line/geom_areaが時系列。stat_summaryがサマリー(平均、エラーバー)を計算・プロットします。alphaが透明度(0-1)を制御します — 重なる点に不可欠。coord_flipがプロットを回転します。geom_jitterがオーバープロットを防ぐためノイズを追加します。各geomは理解する特定の美学を持ちます(例:geom_pointはxとyが必要、geom_barはxのみが必要)。

r
library(ggplot2)

# bar chart (counts)
ggplot(mpg, aes(class)) +
    geom_bar(fill = "steelblue") +
    coord_flip()              # horizontal bars

# histogram
ggplot(mpg, aes(hwy)) +
    geom_histogram(binwidth = 2, fill = "orange", color = "black")

# density plot
ggplot(mpg, aes(hwy, fill = class)) +
    geom_density(alpha = 0.5)   # semi-transparent

# boxplot by group
ggplot(mpg, aes(class, hwy)) +
    geom_boxplot() +
    geom_jitter(width = 0.2, alpha = 0.5)  # overlay points

# line plot (time series)
ggplot(economics, aes(date, unemploy)) +
    geom_line(color = "red") +
    geom_area(fill = "pink", alpha = 0.3)

# scatter with smoothing
ggplot(mpg, aes(displ, hwy, color = drv)) +
    geom_point(size = 2) +
    geom_smooth(method = "loess", se = FALSE)

# error bars
ggplot(df, aes(group, value)) +
    stat_summary(fun = mean, geom = "bar") +
    stat_summary(fun.data = mean_se, geom = "errorbar")

# text labels
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
    geom_text(size = 3)

ggplot2:ファセット、スケール、テーマ

ファセットはスモールマルチプルを作成します — カテゴリごとに1つのサブプロット — グループを比較する最良の方法です。facet_wrap(~var)が1Dリボンを作成し、facet_grid(row~col)が2Dグリッドを作成します。スケールがデータから視覚的プロパティへのマッピングを制御します:scale_x_log10が対数軸、scale_color_brewerが色覚対応パレット、scale_fill_manualがカスタム色。テーマが非データ要素(フォント、グリッド線、凡例)を制御します。theme_minimal/bw/classicがプリセットで、theme()が個別要素をカスタマイズします。ggsaveがPNG/PDF/SVGにサイズとDPIの制御付きでエクスポートします。

r
library(ggplot2)

# facets: small multiples (subplots by a variable)
ggplot(mpg, aes(displ, hwy)) +
    geom_point() +
    facet_wrap(~ class)            # one panel per class

# facet_grid: 2D grid of panels
ggplot(mpg, aes(displ, hwy)) +
    geom_point() +
    facet_grid(drv ~ cyl)          # rows ~ cols

# scales: control axes and colors
ggplot(mpg, aes(displ, hwy, color = class)) +
    geom_point() +
    scale_x_log10() +              # log scale
    scale_color_brewer(palette = "Set1") +  # color palette
    scale_size_continuous(range = c(2, 8))

# manual colors
ggplot(mpg, aes(class, fill = drv)) +
    geom_bar() +
    scale_fill_manual(values = c("red", "green", "blue"))

# themes: overall appearance
ggplot(mpg, aes(displ, hwy)) +
    geom_point() +
    theme_minimal() +              # or theme_classic, theme_bw
    theme(
        text = element_text(size = 14, family = "serif"),
        plot.title = element_text(face = "bold", hjust = 0.5),
        axis.text.x = element_text(angle = 45, hjust = 1),
        legend.position = "bottom",
        panel.grid.major = element_line(color = "gray90")
    )

# save plot
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = "pdf")

データの保存とエクスポート

RDSは単一のRオブジェクトを保存するのに最適です(型を保持、高速、コンパクト)。RDataは複数オブジェクトを保存します。CSVは最もポータブル(Excel、Pythonなどで読める)ですが型情報を失います — 常にstringsAsFactors=FALSEを設定してください。readrパッケージ(tidyverse)がより高速で一貫したCSV I/Oを提供し、tibble(改良されたデータフレーム)を返します。readxl/writexlがExcelを処理します。大きなデータセットにはdata.table::fread(非常に高速)やparquet(arrowパッケージ)の列指向ストレージを検討してください。CSV書き込み時には常にrow.names=FALSEを使用してください。

r
# save single object to RDS (binary, preserves type)
saveRDS(my_data, "data.rds")
restored <- readRDS("data.rds")

# save multiple objects to RData
save(df1, df2, model, file = "workspace.RData")
load("workspace.RData")    # loads all objects into workspace

# CSV (most portable)
write.csv(df, "data.csv", row.names = FALSE)
df <- read.csv("data.csv")
df <- read.csv("data.csv", stringsAsFactors = FALSE)
df <- read.csv("data.csv", na.strings = c("", "NA", "N/A"))

# readr (tidyverse): faster, smarter CSV I/O
library(readr)
write_csv(df, "data.csv")
df <- read_csv("data.csv")           # returns a tibble
df <- read_csv("data.csv", col_types = cols(
    age = col_integer(),
    name = col_character()
))

# Excel (requires readxl/writexl packages)
library(readxl)
df <- read_excel("data.xlsx", sheet = 1, range = "A1:D100")
library(writexl)
write_xlsx(df, "output.xlsx")

# RDS vs RData vs CSV
# RDS:   one object, binary, fast, preserves types
# RData: multiple objects, binary, fast
# CSV:   text, portable to other tools, loses type info

# save and load workspace
save.image("project.RData")  # save everything
load("project.RData")        # restore everything

# serialize to JSON (jsonlite package)
library(jsonlite)
write_json(df, "data.json")
df <- fromJSON("data.json")
07

OOPと関数型プログラミング

S3クラスとメソッド(シンプルOOP)

S3はRの最も一般的なOOPシステムです — 軽量で非公式です。「クラス」はクラス属性を持つただのリストで、メソッドはgeneric.classnameという名前の関数です。ジェネリック内のUseMethod()がオブジェクトのクラスに基づいて適切なメソッドにディスパッチします。ほとんどのRオブジェクト(data.frame、lm、ggplot)はS3です。print()、summary()、plot()は拡張可能なジェネリックです。S3は非公式(検証なし)で柔軟ですがエラーを起こしやすいです。methods(generic)で全メソッド、methods(class='x')でクラスのメソッドを確認できます。

r
# S3 is R's simplest OOP system: a class is just an attribute
# create an object: list + class attribute
account <- list(owner = "Alice", balance = 1000)
class(account) <- "BankAccount"

# generic function dispatches by class
print.BankAccount <- function(x, ...) {
    cat("Account owner:", x$owner, "\n")
    cat("Balance: $", x$balance, "\n", sep = "")
}
print(account)
# Account owner: Alice
# Balance: $1000

# define a custom method for an existing generic
deposit <- function(obj, amount) {
    UseMethod("deposit")   # dispatch based on class of obj
}
deposit.BankAccount <- function(obj, amount) {
    obj$balance <- obj$balance + amount
    obj
}
account <- deposit(account, 500)
account$balance   # 1500

# check available methods for a generic
methods(print)         # all print methods
methods(class = "lm")  # methods for lm objects

# default method (fallback)
deposit.default <- function(obj, amount) {
    stop("No deposit method for this class")
}

S4クラス(公式OOP)

S4はRの公式OOPシステムです:クラスは型、検証、継承を持つ定義済みスロット(フィールド)を持ちます。setClass()で定義し、new()でインスタンス化します。スロットには@($ではなく)でアクセスします。setMethod()がジェネリックのメソッドを定義します。setValidity()が制約を強制します。S4はBioconductorと厳密な契約を必要とするパッケージ(例:Matrix、sp)で使用されます。S3より堅牢ですが冗長です。日常的なRはほとんどS3を使用し、型安全性と公式継承が必要な場合にS4を使ってください。

r
# S4 is formal: classes are defined with setClass and validated
setClass("Person",
    slots = list(
        name = "character",
        age  = "numeric"
    ),
    prototype = list(name = NA_character_, age = NA_real_)
)

# constructor: new(ClassName, ...)
alice <- new("Person", name = "Alice", age = 30)

# access slots with @ (not $)
alice@name       # "Alice"
slot(alice, "age")  # 30

# define a method
setMethod("show", "Person", function(object) {
    cat("Person:", object@name, "(", object@age, "y)\n")
})
show(alice)   # Person: Alice ( 30 y)

# validity check
setValidity("Person", function(object) {
    if (object@age < 0) return("age must be non-negative")
    TRUE
})
# new("Person", name="Bob", age=-5)  # error

# inheritance
setClass("Student", contains = "Person",
    slots = list(gpa = "numeric"))
stu <- new("Student", name="Bob", age=20, gpa=3.8)

クロージャと関数ファクトリ

クロージャは定義環境の変数へのアクセスを保持する関数です — Rのステートフル関数の作成とプライベートデータのカプセル化の主要な方法です。<<-演算子はローカルではなく囲む(親)環境に代入します。関数ファクトリ(関数を返す関数)は特殊化された関数の作成に強力です。一般的な用途:カウンター、メモ化(結果のキャッシュ)、モジュールパターン(プライベート状態を共有する関数のリストを返す)。これがRのプライベートフィールドを持つクラスに最も近い類似物です。

r
# a closure is a function that remembers its enclosing environment
# function factory: creates functions with private state
make_counter <- function() {
    count <- 0
    function() {
        count <<- count + 1   # <<- modifies in enclosing env
        count
    }
}

c1 <- make_counter()
c1()   # 1
c1()   # 2
c1()   # 3
c2 <- make_counter()
c2()   # 1 (independent counter)

# memoization (cache expensive results)
memoize <- function(f) {
    cache <- new.env(parent = emptyenv())
    function(x) {
        key <- as.character(x)
        if (!exists(key, envir = cache)) {
            assign(key, f(x), envir = cache)
        }
        get(key, envir = cache)
    }
}
slow_sqrt <- function(x) { Sys.sleep(0.1); sqrt(x) }
fast_sqrt <- memoize(slow_sqrt)
fast_sqrt(16)  # slow first time
fast_sqrt(16)  # instant second time

# capturing state in a list (module pattern)
bank_account <- function(initial) {
    balance <- initial
    list(
        deposit = function(amt) { balance <<- balance + amt },
        withdraw = function(amt) { balance <<- balance - amt },
        get_balance = function() balance
    )
}
acc <- bank_account(100)
acc$deposit(50)
acc$get_balance()   # 150

関数型プログラミング:Map/Reduce/Filter

Rには組み込みの関数型プログラミングプリミティブがあります:Map(各要素に適用)、Reduce(fold/蓄積)、Filter(一致する要素を保持)、Find/Position(検索)、Negate(述語の反転)。これらはリストやベクトルを返し、明示的なループを回避します。purrrパッケージ(tidyverse)がより一貫したAPIを提供します:map_dbl/map_chrが型付きベクトルを返し、keep/discardがフィルタし、reduceが蓄積します。~ .x式構文が無名関数を簡潔に作成します。関数型プログラミングはコードをより宣言的にし推論しやすくします、特にデータ変換パイプラインで。

r
# Map: apply a function to each element (returns list)
Map(function(x) x^2, 1:5)
# [[1]] 1 [[2]] 4 [[3]] 9 [[4]] 16 [[5]] 25

# Reduce: combine elements with a binary function
Reduce("+", 1:5)            # 15 (1+2+3+4+5)
Reduce("*", 1:5)            # 120 (factorial 5!)
Reduce(c, list(1:2, 3:4, 5:6))  # 1 2 3 4 5 6

# Filter: keep elements satisfying a predicate
Filter(function(x) x > 2, 1:5)   # 3 4 5
Filter(is.numeric, list(1, "a", 2, "b"))  # 1 2

# Negate: invert a predicate
Negate(is.null)
is_not_null <- Negate(is.null)

# Position: find first index satisfying a predicate
Position(function(x) x > 3, 1:10)  # 4

# Find: first element satisfying a predicate
Find(function(x) x > 3, 1:10)      # 4

# purrr (tidyverse): cleaner functional programming
library(purrr)
map_dbl(1:5, ~ .x^2)               # 1 4 9 16 25 (vector output)
map_chr(c("a","b"), toupper)       # "A" "B"
keep(1:5, ~ .x > 2)                # 3 4 5
reduce(1:5, `+`)                   # 15
walk(1:3, print)                   # side effects only

デバッグとエラー処理

browser()はRのインタラクティブデバッガーです — コードに挿入して一時停止し変数を調べます(n=次、c=継続、Q=終了)。debug(fn)が関数を1行ずつステップ実行します。traceback()がクラッシュ後にコールスタックを表示します。tryCatch()はRのtry/catchです:ハンドラー関数経由でエラーと警告をキャッチし、フォールバック値を返します。withCallingHandlers()が実行を中断せずに警告を処理します。options(warn=2)の設定が警告をエラーに変換します(発生源の特定に便利)。options(error=browser)が未捕捉エラーで自動的にデバッガに入ります。これらのツールの習得は複雑なRコードの問題診断に不可欠です。

r
# browser(): pause execution and inspect
f <- function(x) {
    browser()         # pauses here; use n, c, Q, ls, print
    y <- x * 2
    if (y > 10) stop("y too big")
    y
}
# f(6)  # enters browser at the browser() line

# debug() / undebug(): debug a function
debug(lm)
# lm(y ~ x)  # steps through lm line by line
undebug(lm)

# traceback(): see call stack after an error
# log("abc")   # error
# traceback()  # shows the call stack

# tryCatch(): handle errors gracefully
safe_log <- function(x) {
    tryCatch(
        log(x),
        error = function(e) {
            message("Error: ", conditionMessage(e))
            NA_real_
        },
        warning = function(w) {
            message("Warning: ", conditionMessage(w))
            log(x)   # retry or handle
        }
    )
}
safe_log("abc")   # NA with message
safe_log(-1)      # NA with warning (NaN)

# withCallingHandlers(): handle warnings without stopping
withCallingHandlers(
    { warn("oops"); 42 },
    warning = function(w) { message("caught: ", w$message); invokeRestart("muffleWarning") }
)

# options for debugging
options(warn = 2)   # warnings become errors (for debugging)
options(error = browser)  # enter browser on error
options(warn = 0)   # reset
08

時系列と予測

時系列オブジェクトの作成(ts)

ts()はbase Rの時系列クラスです — 時間属性(start、frequency)を持つベクトルです。frequencyが周期をエンコードします:月次は12、四半期は4、週次は52。window()が時間範囲でサブセットします。diff()が差分を計算します(系列を定常化するのに便利)。lag()が値をシフトします。aggregate()が低頻度に変換します(例:月次から四半期)。tsは規則的で固定頻度のデータに適しています。不規則なタイムスタンプ(例:隙間のある株価)にはxts/zooパッケージを使用してください。

r
# ts(): create a regular time series
# frequency: 12=monthly, 4=quarterly, 52=weekly, 365.25=daily
monthly_sales <- ts(c(30, 35, 40, 38, 42, 45, 50, 48, 52, 55, 60, 58),
                    start = c(2020, 1), frequency = 12)
print(monthly_sales)
#      Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
# 2020  30  35  40  38  42  45  50  48  52  55  60  58

# attributes
start(monthly_sales)    # 2020 1
end(monthly_sales)      # 2020 12
frequency(monthly_sales)  # 12
time(monthly_sales)     # time points

# window(): subset a time range
q1 <- window(monthly_sales, start = c(2020, 3), end = c(2020, 5))

# multiple series (multivariate)
ts1 <- ts(rnorm(12), start = 2020, frequency = 12)
ts2 <- ts(rnorm(12), start = 2020, frequency = 12)
mts <- cbind(ts1, ts2)   # multivariate ts

# lag and diff
lag(monthly_sales, k = 1)   # shift by 1 period
diff(monthly_sales)         # first differences (month-over-month change)
diff(monthly_sales, lag = 12)  # year-over-year change

# aggregate to lower frequency
aggregate(monthly_sales, nfrequency = 4, FUN = mean)  # monthly -> quarterly

分解とスムージング

時系列分解は系列をトレンド、季節、残差成分に分離します。decompose()が古典的移動平均分解(加法または乗法)を使用します。stl()はLOESSスムージングを使用し、外れ値に対してより堅牢で変化する季節性を処理します。HoltWinters()が指数スムージング(レベル + トレンド + 季節)をフィットします。forecastパッケージ(現在はtidyverseのfable)が信頼区間付きの予測のためにforecast()を提供します。モデリング前に構造を理解するために常に分解をプロットしてください。季節振幅がトレンドと共に成長する場合、乗法分解が適切です。

r
# decompose(): split a series into trend/seasonal/random
decomp <- decompose(monthly_sales, type = "additive")
# type = "additive" (T+S+R) or "multiplicative" (T*S*R)
plot(decomp)   # shows observed, trend, seasonal, random panels
decomp$trend      # trend component
decomp$seasonal   # seasonal component
decomp$random     # remainder

# stl(): Seasonal-Trend-Loess decomposition (more flexible)
stl_fit <- stl(monthly_sales, s.window = "periodic")
plot(stl_fit)
stl_fit$time.series[, "trend"]     # extract trend

# moving average smoothing
ma3 <- filter(monthly_sales, filter = rep(1/3, 3), sides = 2)
ma12 <- filter(monthly_sales, filter = rep(1/12, 12), sides = 1)

# HoltWinters(): exponential smoothing
hw <- HoltWinters(monthly_sales, seasonal = "additive")
plot(hw)
hw$fitted       # fitted values
hw$coefficients # alpha, beta, gamma

# forecast with HoltWinters
library(forecast)
fc <- forecast(hw, h = 12)   # 12-step ahead forecast
plot(fc)                      # with prediction intervals
autoplot(fc)                  # ggplot2 version

ACF、PACFとARIMAモデリング

ACF/PACFプロットが自己相関構造を診断します:ACFは各ラグの全相関を示し、PACFは直接(偏)相関を示します。パターンがARIMA次数を示唆します:ACFの打ち切りはMA、PACFの打ち切りはARを示唆します。adf.test()が定常性をチェックします(p<0.05は定常を意味)。auto.arima()(forecastパッケージ)がAICcで最適なARIMA(p,d,q)(P,D,Q)モデルを自動選択します。dは差分次数(定常化のため)、(P,D,Q)は季節成分です。checkresiduals()がモデルの適合を検証します(残差は白色ノイズであるべき)。ARIMAは時系列予測の主力です。

r
# ACF (autocorrelation) and PACF (partial autocorrelation)
acf(monthly_sales, main = "ACF")     # correlation with lagged self
pacf(monthly_sales, main = "PACF")   # direct correlation at each lag

# interpret:
#   ACF tails off slowly  -> need differencing (non-stationary)
#   ACF cuts off at lag p -> AR(p) process
#   PACF cuts off at lag q -> MA(q) process

# stationarity test
library(tseries)
adf.test(monthly_sales)   # Augmented Dickey-Fuller
# p < 0.05 -> stationary

# auto.arima(): automatically select ARIMA order
library(forecast)
fit <- auto.arima(monthly_sales)
summary(fit)              # shows ARIMA(p,d,q)(P,D,Q)[m]
# ARIMA(0,1,1)(1,0,0)[12]  example output

# forecast
fc <- forecast(fit, h = 12)
plot(fc)
accuracy(fc)              # ME, RMSE, MAE, MAPE

# manual ARIMA
fit2 <- arima(monthly_sales, order = c(1, 1, 1),
              seasonal = list(order = c(1, 0, 0), period = 12))

# residuals should look like white noise
checkresiduals(fit)       # Ljung-Box test
tsdisplay(residuals(fit)) # ACF + PACF of residuals

xtsとzoo(不規則時系列)

xts/zooは不規則時系列(例:週末/祝日の欠落がある金融データ)のためにtsを拡張します。xtsオブジェクトは実際の日付/時刻でインデックス付けされ、1月全体にprices['2024-01']のような直感的なサブセットを可能にします。merge()が複数系列を日付で整列し、隙間をNAで埋めます(carry forwardにはfill=na.locfを使用)。rollmean/rollapplyがローリング統計を計算します。endpoints/period.applyが粗い期間(週、月)に集計します。xtsはほとんどのR金融パッケージ(quantmod、TTR、PerformanceAnalytics)の基盤です。tidy時系列にはtsibble/fableパッケージがモダンな代替を提供します。

r
library(xts)
library(zoo)

# create xts from a matrix and time index
dates <- as.Date(c("2024-01-01", "2024-01-03", "2024-01-10"))
prices <- xts(c(100, 102, 98), order.by = dates)
colnames(prices) <- "AAPL"

# subset by date range (very intuitive)
prices["2024-01-03"]              # specific date
prices["2024-01-01/2024-01-05"]   # date range
prices["2024-01"]                 # entire January
prices["/2024-01-05"]             # up to a date

# lag and diff (returns xts)
lag(prices, k = 1)                # previous day's price
diff(prices)                      # daily change
daily_returns <- diff(prices) / lag(prices, 1)

# rolling operations (zoo)
rollmean(prices, k = 3)           # 3-day rolling mean
rollapply(prices, 3, sd)          # 3-day rolling std dev
rollmax(prices, 3)                # 3-day rolling max

# period.apply: aggregate by period
ep <- endpoints(prices, on = "weeks")  # week endpoints
period.apply(prices, ep, mean)         # weekly averages

# merge multiple series (aligns by date)
aapl <- xts(c(100, 102, 98), as.Date(c("2024-01-01","2024-01-02","2024-01-03")))
msft <- xts(c(200, 201), as.Date(c("2024-01-01","2024-01-03")))
merged <- merge(aapl, msft)       # NA fills gaps
merged <- merge(aapl, msft, fill = na.locf)  # carry forward

# to.ts <- as.ts(prices)  # convert to base ts (loses dates)

予測評価と可視化

予測は常にホールドアウトテストセットで評価し、サンプル内では評価しないでください。accuracy(fit, test)がエラーメトリクスを計算します:MAEとRMSE(絶対スケール)、MAPE(パーセンテージ、スケールフリーだがゼロ付近で不安定)、MASE(ナイーブ予測誤差でスケール、<1はナイーブより良いことを意味)。tsCV()が時系列交差検証(ローリングオリジン)を実行します。複数モデル(ナイーブベースライン、ETS、ARIMA)を比較し、最低エラーのものを選びます。autoplot() + autolayer()が予測区間付きで予測を可視化します。ナイーブ予測(最終値)は重要なベースラインです — モデルは有用であるためにそれを上回る必要があります。時系列にランダムtrain/test分割を使用しないでください(未来情報が漏洩します)。常に時系列で分割してください。

r
library(forecast)
library(ggplot2)

# split into train/test
train <- window(monthly_sales, end = c(2020, 9))
test  <- window(monthly_sales, start = c(2020, 10))

# fit multiple models
fit_naive  <- naive(train, h = 3)        # naive: last value
fit_snaive <- snaive(train, h = 3)       # seasonal naive
fit_ets    <- ets(train) %>% forecast(h = 3)  # exponential smoothing
fit_arima  <- auto.arima(train) %>% forecast(h = 3)

# compare accuracy on test set
accuracy(fit_naive, test)
accuracy(fit_arima, test)
# metrics: ME, RMSE, MAE, MPE, MAPE, MASE

# time series cross-validation
ts_cv <- tsCV(train, forecastfunction = naive, h = 3)
sqrt(mean(ts_cv^2, na.rm = TRUE))   # CV RMSE

# visualize forecasts with ggplot
autoplot(monthly_sales, series = "Actual") +
    autolayer(fit_arima, series = "ARIMA", PI = FALSE) +
    autolayer(fit_ets, series = "ETS", PI = FALSE) +
    labs(title = "Forecast Comparison", x = "Time", y = "Sales") +
    theme_minimal()

# prediction intervals
fc <- forecast(auto.arima(train), h = 3, level = c(80, 95))
autoplot(fc) +
    autolayer(test, series = "Actual")

# accuracy measures explained:
#   MAE  = Mean Absolute Error (same units as data)
#   RMSE = Root Mean Squared Error (penalizes large errors)
#   MAPE = Mean Absolute Percentage Error (scale-free, %)
#   MASE = Mean Absolute Scaled Error (< 1 beats naive)
09

dplyrの深掘り

コア動詞:filter、select、mutate、summarize

dplyrの5つのコア動詞がほとんどのデータ操作をカバーします:filter(条件で行)、select(名前で列)、mutate(列の追加/変換)、summarize(サマリー統計に集約)、arrange(ソート)。パイプ%>%(またはネイティブ|>)が操作を左から右にチェーンし、コードを読みやすくします。starts_with、ends_with、contains、everything()のようなヘルパー関数が列選択を簡潔にします。グループごとの統計のためにsummarizeの前に常にgroup_byしてください。n()がグループごとの行をカウントします。

r
library(dplyr)
df <- tibble(
  name = c("Alice","Bob","Carol","Dave"),
  dept = c("Eng","Sales","Eng","Sales"),
  salary = c(90000, 70000, 95000, 72000),
  years = c(5, 3, 8, 4)
)

# filter rows
eng <- df %>% filter(dept == "Eng", salary > 85000)

# select columns (with helpers)
df %>% select(name, salary)
df %>% select(starts_with("s"))
df %>% select(-years)
df %>% select(name:dept)         # range
df %>% select(dept, everything())# reorder

# mutate: add/modify columns
df %>% mutate(
  bonus = salary * 0.1,
  total = salary + bonus,
  level = if_else(years >= 5, "Senior", "Junior")
)

# transmute: keep only new columns
df %>% transmute(name, annual_k = salary / 1000)

# summarize (with group_by)
df %>%
  group_by(dept) %>%
  summarize(
    avg_salary = mean(salary),
    max_years = max(years),
    n = n()
  )

結合と集合演算

dplyrのjoinはSQLを反映します:結合用のinner、left、right、full。別のテーブルでフィルタするためのsemiとanti。意図しない列でのサイレントな一致を避けるため常にbyを指定してください。異なるキー名にはby = c('left_col' = 'right_col')を使用します。集合演算(union、intersect、setdiff)は同一の列セットが必要です。bind_rowsがスタックし(欠損列をNAで埋め)、bind_colsはキーをチェックせずに横に貼り付けます — 通常はjoinが必要です。結合は微妙なデータバグの最も一般的な源なので、前後で行数を検証してください。

r
employees <- tibble(id = 1:4, name = c("Al","Bo","Cy","Di"))
salaries  <- tibble(id = c(1,2,4,5), salary = c(50,60,80,90))

# mutating joins (combine columns)
inner_join(employees, salaries, by = "id")    # only matching ids
left_join(employees, salaries,  by = "id")    # all from left
right_join(employees, salaries, by = "id")    # all from right
full_join(employees, salaries,  by = "id")    # all rows

# filtering joins (filter rows, no new columns)
semi_join(employees, salaries, by = "id")     # rows in left with match
anti_join(employees, salaries, by = "id")     # rows in left WITHOUT match

# different column names
left_join(x, y, by = c("emp_id" = "id"))

# set operations (require identical columns)
union(a, b)          # unique rows in either
union_all(a, b)      # all rows (with dups)
intersect(a, b)      # rows in both
setdiff(a, b)        # rows in a but not b

# bind rows/columns
bind_rows(list(df1, df2))   # stack vertically
bind_cols(df1, df2)         # side by side (no key check!)

ウィンドウ関数とグループ化mutate

ウィンドウ関数はgroup_byで定義されたグループ内で動作します。row_number、min_rank、dense_rankは同点処理が異なります。lead/lagが隣接行にアクセスします — 時系列と変化検出に不可欠。累積関数(cumsum、cummax、cummean)が累計集計を計算します。slice_max/min/head/sampleがグループごとに特定行を抽出します。rowwise() + c_across()が列間で行ごとの操作を可能にします(ベクトル化より遅いですが、時には必要)。これらの関数によりdplyrはSQLウィンドウ関数と同等に強力になります。

r
df <- tibble(
  dept = c("A","A","A","B","B","B"),
  name = c("x","y","z","p","q","r"),
  salary = c(50, 70, 60, 80, 90, 75)
)

# ranking within groups
df %>% group_by(dept) %>%
  mutate(
    rank = row_number(),            # 1, 2, 3 (ties get distinct)
    min_rank = min_rank(salary),    # ties get same rank, gaps
    dense_rank = dense_rank(salary),
    pct_rank = percent_rank(salary)
  )

# offsets (lead / lag)
df %>% group_by(dept) %>%
  mutate(
    prev = lag(salary),
    next = lead(salary),
    change = salary - lag(salary)
  )

# cumulative aggregates
df %>% group_by(dept) %>%
  mutate(
    cum_total = cumsum(salary),
    cum_max = cummax(salary),
    running_avg = cummean(salary)
  )

# top N per group
df %>% group_by(dept) %>%
  slice_max(salary, n = 2)        # or slice_min, slice_head, slice_sample

# row-wise operations
df %>% rowwise() %>%
  mutate(total = sum(c_across(where(is.numeric))))

across、whereと複数列操作

across()(dplyr 1.0+)は複数列にわたって関数を適用するモダンな方法です — 古い_at、_if、_all接尾辞を置換します。where(is.numeric)が述語で列を選択します。.names引数が{col}と{fn}テンプレートで出力名を制御します。if_all/if_anyが選択した列のすべて/いずれかが条件を満たす行をフィルタします。.data代名詞がプログラム的な列アクセスを可能にします(関数やShinyアプリで便利)。これらのツールによりdplyrは多数の列のバッチ操作に非常に表現力豊かになります。

r
df <- tibble(id = 1:3, a = c(1,2,3), b = c(4,5,6), c = c("x","y","z"))

# apply function to multiple columns
df %>% mutate(across(a:b, ~ .x * 10))
df %>% mutate(across(where(is.numeric), log))
df %>% mutate(across(everything(), as.character))

# summarize multiple columns
df %>% summarize(across(where(is.numeric), list(
  mean = ~mean(.x),
  sd   = ~sd(.x)
), .names = "{.col}_{.fn}"))

# rename multiple columns
df %>% rename_with(toupper, starts_with("a"))

# conditional transformation
df %>% mutate(across(where(is.numeric), ~ if_else(.x > 3, "high", "low")))

# use .data pronoun for programmatic access
col <- "a"
df %>% mutate(new = .data[[col]] * 2)

# pick columns by type in any verb
df %>% filter(if_all(a:b, ~ .x > 1))   # all must satisfy
df %>% filter(if_any(a:b, ~ .x > 4))   # any must satisfy

データベースバックエンドとdbplyr

dbplyrがdplyr動詞をSQLに変換し、ローカルデータフレームと同じ構文でデータベーステーブルを操作できます。これはビッグデータに巨大です:重い計算はデータベース(しばしば列指向/並列)で行われ、結果のみがcollect()経由でRに引き込まれます。show_query()がデバッグ用に生成されたSQLを表示します。ほとんどのdplyr動詞は直接変換されます。ウィンドウ関数と一部の文字列操作はSQL固有の関数が必要な場合があります。完了時に常にdbDisconnectしてください。本番では、SQLインジェクションを防ぐために接続プールとパラメータ化クエリを使用してください。

r
library(DBI)
library(dbplyr)

# connect to a database
con <- dbConnect(RSQLite::SQLite(), "my.db")
# or: con <- dbConnect(odbc::odbc(), "PostgreSQL")

# copy data to database
copy_to(con, mtcars, "mtcars_db", temporary = FALSE)

# reference a remote table
mtcars_remote <- tbl(con, "mtcars_db")

# dplyr verbs translate to SQL!
mtcars_remote %>%
  group_by(cyl) %>%
  summarize(avg_mpg = mean(mpg), n = n()) %>%
  arrange(desc(avg_mpg))

# see the generated SQL
mtcars_remote %>%
  filter(mpg > 20) %>%
  select(mpg, cyl, hp) %>%
  show_query()

# collect: pull results into local tibble
result <- mtcars_remote %>%
  filter(cyl == 4) %>%
  collect()

# write back to database
copy_to(con, result, "efficient_cars")

dbDisconnect(con)
10

ggplot2の深掘り

グラフィックスの文法とレイヤープロット

ggplot2はグラフィックスの文法に基づいて構築されています:すべてのプロットはデータ、美学マッピング(aes)、幾何オブジェクト(geom_*)、統計(stat_*)、スケール、座標系、ファセットの組み合わせです。レイヤーは+で追加されます。美学がデータ列を視覚的プロパティ(x、y、color、size、shape)にマッピングし、固定値はaes()の外に出します。facet_wrapとfacet_gridがスモールマルチプルを作成します — 最も強力な探索ツールの1つ。ほとんどのgeomはデフォルトのstatを持ちます(例:geom_barはstat_countを使用)が、カスタム集計のためにstat_summaryで上書きできます。

r
library(ggplot2)
df <- data.frame(x = 1:10, y = (1:10)^2 + rnorm(10))

# basic structure: data + aesthetic + geom
ggplot(df, aes(x = x, y = y)) +
  geom_point()

# multiple layers
ggplot(df, aes(x, y)) +
  geom_point(color = "blue", size = 3) +
  geom_smooth(method = "lm", se = TRUE)

# aesthetics map data to visual properties
ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point() +
  geom_smooth(method = "loess", se = FALSE)

# facets: small multiples
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(~ class, ncol = 4)

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_grid(drv ~ cyl)   # rows ~ cols

# statistics (compute then plot)
ggplot(diamonds, aes(price)) +
  geom_histogram(bins = 50)              # stat_bin
ggplot(diamonds, aes(cut, price)) +
  stat_summary(fun = "mean", geom = "bar")

スケール、テーマと注釈

スケールがデータから視覚的プロパティへのマッピングを制御します — すべての美学に対応するスケールがあります(scale_x_*、scale_color_*など)。scale_*_log10、scale_*_sqrtが軸を変換し、scale_*_continuous/discrete/manualが値をカスタマイズします。Viridisパレットは色覚対応でグレースケールでもよく印刷されます。labs()が1回の呼び出しですべてのラベルを設定します。theme()が非データ要素(フォント、グリッド線、凡例位置)を制御し、theme_minimalまたはtheme_classicから始めて微調整します。annotate()がデータから独立した固定要素(テキスト、長方形、線分)を追加します。

r
p <- ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point(size = 3)

# scales control how data maps to visuals
p + scale_x_log10() +
    scale_y_continuous(limits = c(0, 50), breaks = seq(0, 50, 10))

# manual colors
p + scale_color_manual(values = c("red","blue","green"))

# color brewer / viridis (colorblind-safe)
p + scale_color_brewer(palette = "Set1")
p + scale_color_viridis_d()

# labels
p + labs(
  title = "Fuel Efficiency",
  subtitle = "By vehicle class",
  x = "Engine Displacement (L)",
  y = "Highway MPG",
  color = "Class",
  caption = "Source: EPA"
)

# themes
p + theme_minimal()
p + theme_classic()
p + theme(
  plot.title = element_text(face = "bold", size = 16),
  panel.grid.minor = element_blank(),
  legend.position = "bottom"
)

# annotations
p + annotate("text", x = 5, y = 40, label = "Outlier", color = "red") +
    annotate("rect", xmin = 4, xmax = 6, ymin = 30, ymax = 45,
             alpha = 0.2, fill = "blue")

統計geomと分布

統計geomが分布とサマリーを可視化します。ボックスプロットは四分位数と外れ値を示し、バイオリンは密度形状を追加します。geom_densityがヒストグラムをスムージングし、geom_bin2d/hexが大規模データセットの2D分布を示します。geom_qqが正規性をチェックします(点が線に沿うべき)。geom_errorbar/geom_pointrangeが不確実性を表示します。ペア比較にはggsignifが有意性ブラケットを追加します。ggridgesパッケージがリッジラインプロットを作成します — 多数のグループ間の分布比較に優れています。常に基礎データを正直に表現するgeomを選択してください。

r
# boxplot by group
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot() +
  coord_flip()

# violin + boxplot overlay
ggplot(mpg, aes(class, hwy, fill = class)) +
  geom_violin() +
  geom_boxplot(width = 0.1)

# density plot
ggplot(mpg, aes(hwy, fill = drv)) +
  geom_density(alpha = 0.5)

# 2D density / heatmap
ggplot(diamonds, aes(carat, price)) +
  geom_bin2d(bins = 50)            # or geom_hex()

# quantile-quantile plot
ggplot(mtcars, aes(sample = mpg)) +
  geom_qq() + geom_qq_line()

# error bars
df <- data.frame(group = c("A","B","C"),
                 mean = c(5, 7, 4), se = c(0.5, 0.8, 0.3))
ggplot(df, aes(group, mean)) +
  geom_col() +
  geom_errorbar(aes(ymin = mean - se, ymax = mean + se), width = 0.2)

# ridgeline plot (ggridges)
# library(ggridges)
# ggplot(diamonds, aes(price, cut, fill = cut)) + geom_density_ridges()

ファセット、座標系と拡張

scales = 'free'のファセットは各パネルが独自の軸範囲を持てます — グループが非常に異なるスケールを持つ場合に便利です。coord_polarが棒グラフをパイ/レーダーチャートに変え、coord_flipが軸を入れ替えます(水平棒グラフに便利)。sfパッケージがgeom_sfで空間データを統合し、マップを作成します。patchworkが+、/、|演算子で複数プロットを組み合わせます — gridExtraよりはるかに柔軟です。ggsaveがPNG/PDF/SVGにエクスポートし、cairo_pdfがカスタムフォントを処理します。ggplotlyがggplotをインタラクティブなHTMLウィジェットに変換しWebデプロイに使用します。

r
# free scales per facet
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(~ class, scales = "free")

# polar coordinates (pie chart from bar)
ggplot(mtcars, aes(x = factor(1), fill = factor(cyl))) +
  geom_bar(width = 1) +
  coord_polar(theta = "y")

# flipped coordinates
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot() +
  coord_flip()

# map coordinates (sf)
# library(sf)
# ggplot(nc_sf) + geom_sf(aes(fill = AREA))

# patchwork: combine multiple plots
# library(patchwork)
# p1 <- ggplot(...)
# p2 <- ggplot(...)
# p1 + p2
# p1 / (p2 + p3)

# save plots
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = cairo_pdf)  # vector, supports fonts

# interactive (plotly)
# library(plotly)
# ggplotly(p)

再現可能なプロットと関数

ggplot呼び出しを関数でラップすると再利用可能になります。{{ }}演算子(tidy評価)でクォートされていない列名を渡せます。.data[[string]]がプログラム的アクセスを処理します。保存されたプロットオブジェクト(.rds)は再ロードして変更できます — わずかなバリエーションが必要なレポートに便利です。カスタムテーマは一度定義してどこでも適用でき、プロジェクト全体で視覚的一貫性を保証します。バッチ生成には、.data[[]]とggsaveで列名をループします。この関数型アプローチはShinyアプリと自動レポートパイプラインに不可欠です。

r
# wrap plots in functions for reuse
make_scatter <- function(data, x, y, color = NULL) {
  ggplot(data, aes({{ x }}, {{ y }}, color = {{ color }})) +
    geom_point() +
    theme_minimal()
}
make_scatter(mpg, displ, hwy, class)

# using .data pronoun (string column names)
plot_col <- function(data, col) {
  ggplot(data, aes(.data[[col]])) +
    geom_bar() +
    theme_minimal()
}
plot_col(mpg, "class")

# save and load plot objects
p <- ggplot(mpg, aes(displ, hwy)) + geom_point()
saveRDS(p, "plot.rds")
p_loaded <- readRDS("plot.rds")

# modify saved plot
p_loaded + geom_smooth()

# themes as reusable objects
my_theme <- theme_minimal() +
  theme(text = element_text(family = "Helvetica"),
        plot.title = element_text(face = "bold"))
ggplot(mpg, aes(displ, hwy)) + geom_point() + my_theme

# programmatic plot generation
for (col in c("hwy","cty","cyl")) {
  p <- ggplot(mpg, aes(.data[[col]])) + geom_histogram()
  ggsave(paste0("hist_", col, ".png"), p)
}
11

tidyrデータタイディング

Pivot LongerとWider

tidyデータは観測ごとに1行、変数ごとに1列を持ちます — ほとんどの分析関数がこの形式を期待します。pivot_longerがwideをlongに変換し(列をキーと値のペアに集約)、pivot_widerが逆を行います。names_toの.valueセンチネルが列名の一部を別々の列として保持します(例:'a_1'がa=1、b=1になります)。プロットやモデリングの前に常に再形成してください:ggplot2はグループ化美学にlong形式を必要とし、一部のモデリング関数はwide形式を必要とします。names_pattern引数が正規表現でより複雑な列名構造を処理します。

r
library(tidyr)

# wide format (one row per observation, columns for each variable)
wide <- tibble(
  country = c("A","B","C"),
  `2020` = c(100, 150, 200),
  `2021` = c(110, 160, 210),
  `2022` = c(120, 170, 220)
)

# pivot_longer: wide -> long
long <- wide %>%
  pivot_longer(
    cols = -country,            # all columns except country
    names_to = "year",
    values_to = "gdp"
  )
# A tibble: 9 x 3

# pivot_wider: long -> wide
wide2 <- long %>%
  pivot_wider(names_from = year, values_from = gdp)

# multiple value columns
df <- tibble(id = 1:2, a_1 = c(1,2), a_2 = c(3,4), b_1 = c(5,6), b_2 = c(7,8))
df %>%
  pivot_longer(
    -id,
    names_to = c(".value", "time"),   # .value keeps a, b as columns
    names_sep = "_"
  )
# id  time   a     b
# 1   1      1     5
# 1   2      3     7

Separate、UniteとExtract

separateが区切り文字で列を複数列に分割し、uniteが複数列を1つに結合します。extractが正規表現キャプチャグループでより柔軟な分割を行います。separate_rowsが区切り文字列を複数行に展開します — セルがリスト(例:タグ、カテゴリ)を含む場合に不可欠。convert = TRUEオプションが型(数値、日付)を自動変換します。これらの関数は乱雑な実データをクリーンにします:フルネームの分割、日付の解析、区切りフィールドの正規化。pivot_*と組み合わせてほぼすべての再形成タスクを処理します。

r
df <- tibble(
  name = c("John_Smith", "Jane_Doe", "Bob_Jones"),
  date_range = c("2020-01-01_2020-12-31", "2021-01-01_2021-12-31", "2022-01-01_2022-12-31")
)

# separate one column into many
df %>% separate(name, into = c("first", "last"), sep = "_")

# separate with conversion
df %>% separate(name, into = c("first","last"), sep = "_", convert = TRUE)

# extract with regex groups
df %>%
  extract(date_range,
          into = c("start", "end"),
          regex = "(.+)_(.+)")

# unite multiple columns into one
df2 <- tibble(first = c("John","Jane"), last = c("Smith","Doe"))
df2 %>% unite("full_name", first:last, sep = " ")

# separate_rows: split delimited values into rows
df3 <- tibble(id = 1:2, tags = c("a,b,c", "x,y"))
df3 %>% separate_rows(tags, sep = ",")
# id  tags
# 1   a
# 1   b
# 1   c
# 2   x
# 2   y

欠損値の処理

実データには欠損値が至る所にあります。drop_naがNAを行ごとに削除し、replace_naが定数で埋めます。fillが最後の観測を前方に伝播します(LOCF) — 時系列で一般的。coalesceが列間で最初の非NAを選びます(重複するソースのマージに便利)。na_ifがセンチネル値(-99や'N/A'など)を適切なNAに変換します。値が欠損している理由を代入前に常に調査してください。MCAR(完全にランダムな欠損)、MAR、MNARは異なる意味を持ちます。高度な代入にはmiceやAmeliaパッケージを使用してください。

r
df <- tibble(
  x = c(1, 2, NA, 4, 5),
  y = c(NA, 2, 3, NA, 5),
  z = c("a", NA, "c", "d", NA)
)

# drop rows with any NA
df %>% drop_na()
df %>% drop_na(x)              # only column x

# replace NA with a value
df %>% replace_na(list(x = 0, y = -1, z = "unknown"))

# fill NA with previous/next value
df %>% fill(x, y, .direction = "down")   # carry forward
df %>% fill(x, y, .direction = "up")     # carry backward

# coalesce: first non-missing value
df %>% mutate(x_filled = coalesce(x, y, 0))

# na_if: replace specific value with NA
df %>% mutate(z = na_if(z, "a"))

# detect missing values
is.na(df$x)
sum(is.na(df))                 # total NAs
df %>% map_df(~ sum(is.na(.))) # NAs per column

# imputation (simple)
df %>% mutate(x = if_else(is.na(x), mean(x, na.rm = TRUE), x))

ネストとリスト列

リスト列は行ごとに複数の値(または完全なtibble、モデル、プロットさえ)を格納します — split-apply-combineワークフローに強力です。nest()が行をネストされたtibbleにグループ化し、map()が各々に適用し、unnest()が結果を展開します。このパターン(nest → map → unnest)は多くのforループを置き換え、tidyverseでグループごとの分析を行う慣用的な方法です。broom::tidy/glance/augmentがモデルオブジェクトをtibbleに変換し、ネストフレンドリーにします。リスト列はRのpurrrベースの関数型プログラミングの基盤でもあります。

r
# nest: group rows into list-columns
nested <- mtcars %>%
  group_by(cyl) %>%
  nest()
# cyl  data
# 4    <tibble 11x10>
# 6    <tibble 7x10>
# 8    <tibble 14x10>

# access nested data
nested$data[[1]]               # first group's tibble

# fit models to each group
models <- nested %>%
  mutate(
    model = map(data, ~ lm(mpg ~ wt, data = .x)),
    glance = map(model, broom::glance),
    tidy = map(model, broom::tidy)
  )

# unnest results
models %>% unnest(glance)
models %>% unnest(tidy)

# unnest a list-column back to rows
df <- tibble(id = 1:2, vals = list(c(1,2,3), c(4,5)))
df %>% unnest(vals)
# id  vals
# 1   1
# 1   2
# 1   3
# 2   4
# 2   5

# chop/unchop (similar but keeps other columns as lists)
df %>% chop(vals)
df %>% unchop(vals)

レクタングリングとJSON

レクタングリングはネストされた/階層データ(JSON、APIレスポンス)をtidy tibbleに変換します。hoist()がリスト列から特定の要素を引き出し、unnest_wider()がリストを列に展開し、unnest_longer()が各要素を行に展開します。深くネストされた構造には、purrrのmap関数とtibble構築を組み合わせます。simplifyDataFrame = TRUEのjsonlite::fromJSONがシンプルなJSONを自動フラット化します。このワークフローはREST API、NoSQLデータベース、設定ファイルの操作に不可欠です — モダンデータエンジニアの日常です。

r
library(jsonlite)
library(tidyr)

# parse JSON
json <- '[
  {"name":"Alice","age":30,"skills":["R","Python"]},
  {"name":"Bob","age":25,"skills":["SQL"]}
]'
parsed <- fromJSON(json, simplifyDataFrame = FALSE)

# convert list to tibble
tibble(person = parsed) %>%
  hoist(person,
    name = "name",
    age = "age",
    skills = "skills"
  )
# name   age  skills
# Alice  30   <chr [2]>
# Bob    25   <chr [1]>

# unnest longer for nested lists
tibble(person = parsed) %>%
  unnest_wider(person)              # spread list to columns

tibble(skills = list(c("R","Python"), c("SQL"))) %>%
  unnest_longer(skills)

# deeply nested: use purrr + tibble
flatten_df <- function(lst) {
  tibble(
    name = lst$name,
    age = lst$age,
    n_skills = length(lst$skills)
  )
}
map_dfr(parsed, flatten_df)

# rectangularize arrays
jsonlite::fromJSON(json, simplifyDataFrame = TRUE)  # auto-flatten
12

purrr関数型プログラミング

mapファミリーと型安全バリアント

purrrのmapファミリーはlapply/sapplyを一貫した型安全バリアントで置き換えます。map_dbl/chr/int/lglが型付きベクトルを返します(不一致でエラー) — サイレントに強制変換するsapplyよりはるかに安全。map2とpmapが複数ベクトルを並行して反復します。imapが値とインデックスの両方を提供します。walkは戻り値が不要な副作用(印刷、ファイル書き込み)用です。~ .xショートハンドが無名関数を作成し、.xが第1引数、.yが第2引数です。型不安定性を避けるため本番コードでは常にsapplyよりmap_*を優先してください。

r
library(purrr)

# map: apply function to each element, return list
map(1:3, ~ .x ^ 2)             # list(1, 4, 9)

# type-specific variants (safer, faster)
map_dbl(1:3, ~ .x ^ 2)         # numeric vector: 1 4 9
map_chr(c(1,2,3), ~ paste("n=", .x))
map_int(c(1.5, 2.7), floor)
map_lgl(c(1, NA, 3), ~ !is.na(.x))

# map over two vectors in parallel
map2_dbl(c(1,2,3), c(10,20,30), ~ .x + .y)   # 11 22 33

# map over multiple vectors (pmap)
pmap_dbl(list(a = 1:3, b = 4:6, c = 7:9), sum)  # 12 15 18

# map over indices (imap)
imap_chr(c("a","b","c"), ~ paste0(.y, ":", .x))
# "1:a" "2:b" "3:c"

# walk: side effects (no return value)
walk(c("file1.csv","file2.csv"), ~ print(read.csv(.x)))

# map over columns of a data frame
map_dbl(mtcars, mean)          # mean of each column
map(mtcars, class)             # class of each column

無名関数と式構文

purrrは関数を指定する複数の方法を提供します:シンプルなケースの式ショートハンド(~ .x + 1)、複雑な本体の完全なfunction(x)構文、再利用のための名前付き関数。map関数は名前/位置で抽出するために文字列/数値も受け付けます — ラッパー関数不要。pluck()が.defaultフォールバックで深くネストされた構造を安全にナビゲートし、chuck()が欠落パスでエラーになる厳密版です。これによりpurrrはJSON、APIレスポンス、base Rの[[抽出が扱いにくくなる他の階層データの処理に理想的です。

r
# formula syntax (~ creates a function)
map(1:3, ~ .x + 10)              # .x is the argument
map2(1:3, 10:12, ~ .x + .y)      # .x, .y for two args
pmap(list(1:3, 4:6, 7:9), ~ ..1 + ..2 + ..3)  # ..1, ..2 for many

# full function syntax (for complex bodies)
map(1:3, function(x) {
  if (x > 2) return(x * 10)
  x
})

# named function
square <- function(x) x^2
map(1:3, square)

# extract by name/position (no function needed)
map(list(a = list(x = 1), b = list(x = 2)), "x")
map(list(list(1,2), list(3,4)), 1)   # first element of each

# pluck: safely extract deeply nested
deep <- list(a = list(b = list(c = 42)))
pluck(deep, "a", "b", "c")           # 42
pluck(deep, "a", "x", "y", .default = NA)  # NA (no error)

# chuck: same but errors if missing
# chuck(deep, "a", "x")  # error

Reduce、Accumulateと述語関数

reduce()が要素をペアごとに結合します(左fold) — 多くのデータフレームをマージしたり積を計算するのに完璧です。accumulate()が中間結果を保持し、累計に便利です。keep/discardが述語で要素をフィルタします(dplyr::filterのようにベクトル/リスト用)。some/everyがいずれか/すべての要素が条件を満たすかテストします。detectが最初の一致を見つけます。これらの高階関数は多くのループを簡潔で宣言的なコードに置き換えます。negate()が述語関数を反転します — 条件の合成に便利。これらによりpurrrは完全な関数型プログラミングツールキットになります。

r
# reduce: combine elements pairwise
reduce(c(1,2,3,4), `+`)            # 10 (sum)
reduce(c(1,2,3,4), `*`)            # 24 (product)
reduce(list(df1, df2, df3), full_join, by = "id")  # merge many

# accumulate: keep intermediate results
accumulate(c(1,2,3,4), `+`)        # 1 3 6 10 (running sum)
accumulate(c(2,3,4), `*`)          # 2 6 24 (running product)

# keep/discard: filter by predicate
keep(1:10, ~ .x %% 2 == 0)        # 2 4 6 8 10
discard(1:10, ~ .x %% 2 == 0)     # 1 3 5 7 9
keep(mtcars, is.numeric)          # keep numeric columns

# some/every/detect: test predicates
some(1:10, ~ .x > 5)              # TRUE
every(1:10, ~ is.numeric(.x))     # TRUE
detect(1:10, ~ .x > 5)            # 6 (first match)
detect_index(1:10, ~ .x > 5)      # 6 (position)

# head_while/tail_while
head_while(1:10, ~ .x < 5)        # 1 2 3 4
tail_while(10:1, ~ .x > 5)        # 10 9 8 7 6

# negate a predicate
negate(is.na)(5)                  # TRUE
keep(c(1, NA, 3), negate(is.na))  # 1 3

Safely、Possiblyとエラー処理

safely()が関数をラップして常に'result'と'error'を持つリストを返します — 決してスローしません。これは1つの失敗が全体を止めるべきでないバッチ操作に不可欠です。possibly()がエラー時にデフォルト値を返します(エラー詳細が不要な場合によりクリーン)。quietly()が警告とメッセージをキャプチャします。transpose()が{result, error}ペアのリストを別々のリストに変換します — 成功と失敗の分離に便利。個別に失敗する可能性のある多くのアイテムを処理する場合にこれらを使用してください(API呼び出し、ファイル読み取り、モデルフィット)。

r
# safely: capture errors without stopping
safe_log <- safely(log)
safe_log(10)         # list(result = 2.3, error = NULL)
safe_log(-1)         # list(result = NULL,  error = <error>)

# process many, capturing failures
results <- map(c(10, -1, 0, "x"), safely(log))
successes <- map(results, "result") %>% discard(is.null)
failures  <- map(results, "error")  %>% discard(is.null)

# possibly: return default on error
safe_log2 <- possibly(log, otherwise = NA_real_)
map_dbl(c(10, -1, 0, "x"), safe_log2)   # 2.3 NA NA NA

# quietly: capture warnings/messages
quiet_log <- quietly(log)
quiet_log(10)   # list(result, warnings, messages)

# transpose: restructure list-of-lists
transposed <- transpose(results)
transposed$result   # all results
transposed$error    # all errors

# rate-limited / retried operations
# library(purrr)
# safely_slow <- slowly(safely(f), rate = rate_backoff())

# walk + safely for batch file processing
walk(files, ~ safely(read.csv)(.x))

ベクトル化と並列purrr

modify()はmap()に似ていますが入力型を保持します — データフレーム列をその場で変換するのに完璧です。modify_ifとmodify_atが特定列をターゲットにします。list_modify/list_mergeがリストを非破壊的に更新します。並列性にはfurrrがfutureバックエンドを使用するfuture_map(mapのドロップイン置換)を提供します — plan()の変更で逐次から並列に切り替えられます。.progress = TRUEオプションがプログレスバーを表示し、長時間実行のmapに貴重です。これらのツールによりpurrrはインタラクティブな探索と本番パイプラインの両方に適しています。

r
# vectorized map (faster for simple operations)
# map_vec returns a vector, auto-detecting type
map_vec(1:3, ~ .x * 2)            # numeric vector
map_vec(c("a","b"), ~ toupper(.x)) # character vector

# modify: like map but preserves type
modify(mtcars, ~ .x * 2)         # still a data frame
modify_if(mtcars, is.numeric, ~ .x * 2)
modify_at(mtcars, c("mpg","cyl"), ~ .x * 2)

# list_modify / list_merge
l1 <- list(a = 1, b = 2)
list_modify(l1, b = 20, c = 30)  # a=1, b=20, c=30
list_merge(l1, list(b = 20, c = 30))

# parallel mapping with future + furrr
# library(furrr)
# plan(multisession)              # parallel backend
# future_map(1:100, slow_function, .options = furrr_options(seed = TRUE))

# progress bar
# walk(1:100, ~ Sys.sleep(0.1), .progress = TRUE)

# conditional execution in map
map(1:5, ~ if (.x > 3) .x * 10 else .x)
# 1 2 3 40 50
13

stringrとlubridate

stringrパターンマッチングと抽出

stringrはICU正規表現エンジンをラップする一貫したパイプフレンドリーなAPIを提供します。すべての関数はstr_で始まり簡単なオートコンプリートができます。str_detect/which/subsetがパターンでフィルタします。str_extract/matchが一致を引き出します(matchはグループをキャプチャ)。str_replace/removeがテキストを変更します。str_splitが文字列をピースに分割します。基盤の正規表現構文は標準的(PCRE風)で、\\w、\\d、\\sのようなヘルパーがあります。固定文字列(正規表現なし)には、str_detect(text, fixed('a.b'))で文字通りマッチします。stringrはbase Rのgrep/subファミリーよりはるかに一貫しています。

r
library(stringr)

text <- c("apple pie", "banana bread", "cherry tart")

# detect pattern
str_detect(text, "pie")           # TRUE FALSE FALSE
str_which(text, "pie")            # 1
str_count(text, "a")              # 1 3 1

# subset strings
str_subset(text, "pie")           # "apple pie"
str_extract(text, "[aeiou]")      # first vowel
str_extract_all(text, "[aeiou]")  # list of all vowels

# match groups
str_match(text, "(\\w+) (\\w+)")  # matrix with groups
str_match_all(text, "(\\w+)")

# locate pattern positions
str_locate(text, "a")             # start/end matrix
str_locate_all(text, "a")

# replace
str_replace(text, "a", "A")       # first match
str_replace_all(text, "a", "A")   # all matches
str_remove(text, "a")             # str_replace(text, "a", "")

# split
str_split("a,b,c", ",")           # list of vectors
str_split_fixed("a,b,c", ",", 3)  # matrix

文字列操作と変換

stringrは一貫したインターフェースで一般的な文字列操作をカバーします。大文字小文字変換(str_to_upper/lower/title/sentence)はロケールを尊重します。str_trimが空白を除去し、str_squishは内部空白も圧縮します。str_padが固定幅に文字列を揃えます(テーブルフォーマットに便利)。str_subがRの1ベースインデックスで部分文字列を抽出または置換します(負のインデックスは末尾からカウント)。str_cはpaste0のパイプフレンドリーな同等物です。これらの関数により文字列操作がbase Rの散在する文字列関数に比べて予測可能で読みやすくなります。

r
library(stringr)

# case conversion
str_to_upper("hello")             # "HELLO"
str_to_lower("WORLD")             # "world"
str_to_title("the wind in the willows")  # "The Wind In The Willows"
str_to_sentence("hello world")    # "Hello world"

# trimming and padding
str_trim("  hello  ")             # "hello" (both sides)
str_trim("  hello  ", side = "left")
str_squish("  hello   world  ")   # "hello world" (collapse spaces)
str_pad("5", width = 3, pad = "0") # "005"
str_pad("5", width = 3, side = "left", pad = "0")  # "005"

# subsetting
str_sub("hello", 1, 3)            # "hel"
str_sub("hello", -3, -1)          # "llo" (negative from end)
str_sub("hello", 2)               # "ello" (to end)
str_sub("hello", 1, 1) <- "H"     # assignment: "Hello"

# length
str_length("hello")               # 5
str_length(c("a","bb","ccc"))     # 1 2 3

# combine and duplicate
str_c("a", "b", "c", sep = "-")   # "a-b-c"
str_c(c("x","y"), collapse = ",") # "x,y"
str_dup("ab", 3)                  # "ababab"

# truncate
str_trunc("Hello World", 8)       # "Hello..."

正規表現の詳細

stringrは標準構文のICU正規表現エンジンを使用します。^と$が開始/終了にアンカーします。文字クラス[a-z]が範囲をマッチし、\w、\d、\sがショートハンドです。量指定子{n,m}、?、+、*が繰り返しを制御します。括弧がキャプチャグループを作成し、|が代替です。先読み(?=)と後読み(?<=)が消費せずに表明します。名前付きグループ(?<name>...)が抽出を自己文書化します。正規表現メタ文字を含むリテラル文字列をマッチする場合は常にfixed()を使用してください — またより高速です。複雑な解析には、正規表現を読みやすく構築するrebusパッケージを検討してください。

r
library(stringr)

# anchors
str_detect(c("cat","scat","catch"), "^cat")   # starts with: T F T
str_detect(c("cat","scat","catch"), "cat$")   # ends with:   T F F

# character classes
str_extract_all("a1 b2 c3", "[a-z]")          # letters
str_extract_all("a1 b2 c3", "[0-9]")          # digits
str_extract_all("a1 b2 c3", "[^0-9]")         # non-digits
str_extract_all("a1 b2 c3", "\\w")          # word chars
str_extract_all("a1 b2 c3", "\\s")          # whitespace

# quantifiers
str_detect("aaa", "a{2,3}")      # 2-3 a's
str_detect("color", "colou?r")   # u optional
str_detect("abbbbc", "ab+c")     # one or more
str_detect("ac", "ab*c")         # zero or more

# groups and alternation
str_match("2024-01-15", "(\\d{4})-(\\d{2})-(\\d{2})")
str_detect("cat", "cat|dog|bird")  # alternation

# lookahead/lookbehind
str_extract("abc123", "(?<=abc)\\d+")  # 123 (lookbehind)
str_extract("abc123", "\\d+(?=end)")   # nothing (no 'end')

# named capture (stringr 1.5+)
str_match("John 30", "(?<name>[A-Za-z]+) (?<age>\\d+)")

# use fixed() for literal matching
str_detect("a.b.c", fixed("."))  # TRUE (no regex)

lubridate日付と時間の解析

lubridateの解析関数(ymd、mdy、dmy)がセパレータとフォーマットを自動検出します — base Rのstrptimeよりはるかに寛容です。関数名が順序を示します:ymd = 年-月-日。make_date/assembleがコンポーネントから構築します。today()とnow()が現在の日付/時刻を返します。コンポーネント関数(year、month、day、wday、yday)は値の取得と設定の両方を行い、wday(label=TRUE)が曜日名を返します。update()が複数コンポーネントを一度に変更します。datetimeには常にtz(タイムゾーン)を明示的に指定し、サイレントなUTC仮定を避けてください。

r
library(lubridate)

# parse dates (auto-detect format)
ymd("2024-01-15")               # 2024-01-15
ymd("2024/01/15")
ymd("24/1/15")
mdy("01-15-2024")               # month-day-year
dmy("15/01/2024")               # day-month-year
ymd_hms("2024-01-15 14:30:00")
ymd_hm("2024-01-15 14:30")
hms("14:30:45")

# from components
make_date(2024, 1, 15)
make_datetime(2024, 1, 15, 14, 30, 0, tz = "UTC")

# current date/time
today()                          # 2024-01-15
now()                            # 2024-01-15 14:30:00 UTC

# extract components
d <- ymd("2024-01-15")
year(d)                          # 2024
month(d)                         # 1
month(d, label = TRUE)           # "Jan"
day(d)                           # 15
wday(d, label = TRUE)            # "Mon"
yday(d)                          # 15 (day of year)
quarter(d)                       # 1
semester(d)                      # 1

# set components
year(d) <- 2025
month(d) <- 6
d <- update(d, year = 2025, month = 6, day = 1)

タイムゾーン、期間と間隔

タイムゾーンはdatetime処理の最も厄介な部分です。with_tzが同じ瞬間を別のゾーンで表示し、force_tzが時計を変更せずにゾーンを変更します(誤ラベル付けされたデータの修正に便利)。期間(dseconds、dhours)は正確な秒数です — 物理に適しています。ピリオド(minutes、hours、days)はカレンダー対応です:months(1)をJan 31に追加するとFeb 28になり、days(1)がDST遷移を処理します。間隔(start %--% end)が固定端点を持つスパンを表します。人間スケールの算術(スケジューリング)にはピリオドを、経過時間測定には期間を使用してください。

r
library(lubridate)

# timezones
t <- ymd_hms("2024-01-15 14:30:00", tz = "UTC")
with_tz(t, "America/New_York")   # 09:30 EST
with_tz(t, "Asia/Shanghai")      # 22:30 CST
force_tz(t, "America/New_York")  # keeps clock, changes zone

# list timezones
OlsonNames()[1:5]

# durations (exact seconds)
d <- dseconds(60) + dminutes(2)  # 180 seconds
as.numeric(d, "seconds")
dhours(1) + ddays(1)

# periods (calendar-aware, variable length)
p <- minutes(5) + hours(2)
ymd("2024-01-15") + p            # 2024-01-15 02:05:00
ymd("2024-03-10") + days(1)      # handles DST
months(1)                        # variable length!

# intervals (start to end)
int <- interval(ymd("2024-01-01"), ymd("2024-02-01"))
int_length(int)                  # seconds
int %within% int2                # test overlap
as.period(int)                   # "1M 0S"

# arithmetic
ymd("2024-01-15") %--% ymd("2024-02-15")  # interval
ymd("2024-01-15") + weeks(2)               # period
ymd("2024-01-15") + dweeks(2)              # duration (exact)

# rounding dates
floor_date(t, "hour")            # round down
ceiling_date(t, "day")
round_date(t, "hour")
14

R Markdownとレポート

R Markdownの基礎とチャンク

R Markdownは散文(Markdown)、コード(R/Python/SQL)、出力(テーブル、プロット)を再現可能なレポートに結合します。YAMLヘッダーがメタデータと出力形式を設定します。三重バッククォートで区切られたコードチャンクはknit時に実行され、チャンクオプションが挙動を制御します(echo=FALSEがコードを非表示、include=FALSEが実行するが何も表示しない、fig.widthがプロットサイズを設定)。単一バッククォートのインラインコードがテキストに値を挿入します。kable()がテーブルをフォーマットし、より高度なテーブルにはkableExtraやgtを使用します。knitボタンでHTML/PDF/Wordにレンダリングします。

r
---
title: "Analysis Report"
author: "Data Team"
date: "`r format(Sys.Date(), '%B %d, %Y')`"
output: html_document
---

## Introduction

This is **Markdown** with embedded R.

Inline code: the mean is `r mean(mtcars$mpg)`.

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
library(dplyr)
library(ggplot2)
```

```{r load-data}
df <- mtcars
head(df)
```

```{r plot, fig.width=8, fig.height=6, fig.cap="MPG by weight"}
ggplot(df, aes(wt, mpg)) + geom_point() + theme_minimal()
```

```{r table}
library(knitr)
kable(summary(df), caption = "Summary statistics")
```

出力形式とパラメータ

1つのR Markdownファイルから同じソースで複数の出力形式(HTML、PDF、Word、スライド)を生成できます — output以下にリストするだけです。HTML固有のオプション(toc_float、code_folding、theme)がインタラクティブなドキュメントを作成します。パラメータ(params)を使用すると同じレポートを異なる入力でレンダリングできます — バッチレポート作成に不可欠です(地域、顧客、期間ごとに1つ)。rmarkdown::render()でプログラム的にレンダリングし、cronジョブやShinyアプリでレポート生成を自動化します。paramsオブジェクトはチャンク内でデータのフィルタリングに使用できます。

r
---
title: "Quarterly Report"
output:
  html_document:
    toc: true
    toc_float: true
    code_folding: hide
    theme: flatly
    df_print: paged
  pdf_document:
    toc: true
    number_sections: true
  word_document: default
params:
  quarter: "Q1"
  region: "North"
---

## Report for `r params$quarter` - `r params$region`

```{r}
data <- filter(all_data, quarter == params$quarter, region == params$region)
```

# Render with parameters:
# rmarkdown::render("report.Rmd", params = list(quarter = "Q2"))

# parameterized batch rendering
quarters <- c("Q1","Q2","Q3","Q4")
for (q in quarters) {
  rmarkdown::render("report.Rmd",
    params = list(quarter = q),
    output_file = paste0("report_", q, ".html")
  )
}

kable、gt、DTによるテーブル

kable + kableExtraはスタイリング、グループ化、条件付き書式付きの出版品質のテーブルを生成します。gtはより表現力のある文法を持つ現代的な代替です(テーブル版のggplotのようなもの)。DTは検索、ソート、ページネーション付きのインタラクティブなHTMLテーブルを作成します — 探索的レポートに最適です。reactableはさらに高いインタラクティビティを提供します。出力に基づいて選択してください:PDF/Wordにはkable/gt、HTMLにはDT/reactable。数値は一貫してフォーマットし(fmt_number、formatRound)、コンテキストのためにキャプションを追加してください。良いテーブルは結果を伝える上で良いプロットと同じくらい重要です。

r
library(knitr); library(kableExtra)

# basic kable
kable(head(mtcars), caption = "First 6 rows")

# styled kable
kable(head(mtcars), "html") %>%
  kable_styling(bootstrap_options = c("striped","hover","condensed"),
                full_width = FALSE) %>%
  row_spec(0, bold = TRUE, background = "lightblue") %>%
  column_spec(1, bold = TRUE)

# gt package (modern, flexible)
library(gt)
mtcars %>%
  head() %>%
  gt() %>%
  tab_header(title = "Car Specifications", subtitle = "Top 6") %>%
  fmt_number(columns = mpg, decimals = 1) %>%
  data_color(columns = mpg, colors = scales::col_numeric("Reds", NULL))

# DT: interactive tables
library(DT)
datatable(mtcars,
  filter = "top",
  options = list(pageLength = 10),
  caption = "Searchable table"
) %>%
  formatRound(columns = c("mpg","disp"), digits = 2)

# reactable for even more interactivity
# library(reactable)

Quartoと高度な機能

QuartoはR Markdownの後継で、1つのドキュメントでR、Python、Julia、Observableをサポートします。クロスリファレンス(@fig-label、@tbl-label)が図とテーブルを自動採番します。チャンクオプションの#|構文は古いknitr optsよりクリーンです。コードフォールディングはインタラクティブHTML用の折りたたみ可能なコードブロックを作成します。Quartoの多言語サポートはRとPythonの両方を使用するチームに最適です。既存の.Rmdファイルは変換可能で、構文は似ていますがより一貫性があります。Quartoは同じソースからプレゼンテーション(revealjs)、Webサイト、書籍も生成します。

r
---
title: "Modern Report"
format:
  html:
    toc: true
    code-fold: true
  pdf:
    documentclass: article
execute:
  echo: false
  warning: false
filters:
  - lightbox
---

## Cross-references

See Figure @fig-scatter and Table @tbl-summary.

```{r}
#| label: fig-scatter
#| fig-cap: "MPG vs Weight"
ggplot(mtcars, aes(wt, mpg)) + geom_point()
```

```{r}
#| label: tbl-summary
#| tbl-cap: "Summary by cylinder"
mtcars %>%
  group_by(cyl) %>%
  summarize(mean_mpg = mean(mpg)) %>%
  gt()
```

## Multiple languages

```{python}
import pandas as pd
df = pd.DataFrame({'a': [1,2,3]})
print(df)
```

```{sql}
SELECT * FROM mtcars WHERE mpg > 20
```

# Quarto supports Python, Julia, Observable, and more
# in the same document, sharing data via ojs.

自動レポート作成とCron

自動レポート作成は一度きりの分析を定期的な成果物に変えます。rmarkdown::render()がプログラム的にレポートを生成し、paramsと組み合わせてカスタマイズします。blastulaやemayiliで結果をメール送信します。cronR(Linux/Mac)やtaskscheduleR(Windows)で日次/週次実行をスケジュールします。バッチ生成にはpurrr::walkでパラメータをループします。チャンクキャッシュ(cache=TRUE)を有効にして高コスト計算の反復を高速化します — 変更されたチャンクのみ再実行されます。このパイプラインはRにおけるビジネスインテリジェンスと自動データプロダクトの基幹です。

r
# render a report programmatically
rmarkdown::render("daily_report.Rmd",
  output_dir = "reports",
  output_file = paste0("report_", Sys.Date(), ".html"),
  params = list(date = Sys.Date() - 1),
  quiet = TRUE
)

# email the report
# library(blastula)
# render_email("email_template.Rmd") %>%
#   smtp_send(
#     to = "[email protected]",
#     from = "[email protected]",
#     subject = paste("Daily Report -", Sys.Date())
#   )

# schedule with cron (Linux/Mac) or Task Scheduler (Windows)
# crontab -e:
# 0 8 * * * cd /path/to/project && Rscript -e 'rmarkdown::render("daily.Rmd")'

# or use the cronR package
# library(cronR)
# cmd <- cron_rscript("render_report.R")
# cron_add(cmd, frequency = "daily", at = "08:00")

# batch render multiple reports
purrr::walk(regions, function(r) {
  rmarkdown::render("regional.Rmd",
    params = list(region = r),
    output_file = paste0("report_", r, ".html")
  )
})

# version control: cache expensive computations
# knitr::opts_chunk$set(cache = TRUE)
15

Shiny Webアプリ

アプリ構造:UIとサーバー

すべてのShinyアプリはui(HTMLレイアウト)とserver(Rロジック)の2つの部分で構成されます。UIはfluidPageとレイアウト関数(sidebarLayout、tabsetPanel、navbarPage)を使用します。入力(sliderInput、selectInputなど)がユーザーデータを収集し、出力(plotOutput、textOutput)が結果を表示します。server関数がrender*関数でそれらを接続します。リアクティブ式(reactive({...}))が計算をキャッシュし、入力が変更された時のみ再実行します。app.Rとして保存し、runApp()で実行するか、shinyapps.io / RStudio Connect / Shiny Serverでホストします。

r
library(shiny)

ui <- fluidPage(
  titlePanel("My First Shiny App"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("n", "Number of points:", min = 1, max = 100, value = 50),
      selectInput("color", "Color:", choices = c("red","blue","green"))
    ),
    mainPanel(
      plotOutput("scatter"),
      verbatimTextOutput("summary")
    )
  )
)

server <- function(input, output, session) {
  data <- reactive({
    data.frame(x = rnorm(input$n), y = rnorm(input$n))
  })

  output$scatter <- renderPlot({
    plot(data()$x, data()$y, col = input$color, pch = 19,
         xlab = "X", ylab = "Y", main = paste("n =", input$n))
  })

  output$summary <- renderPrint({
    summary(data())
  })
}

shinyApp(ui, server)

# save as app.R in a folder, then runApp("folder")
# or run with shiny::runApp()

リアクティブプログラミング

リアクティビティはShinyの中核概念です。reactive()は依存関係が変更された時のみ再実行される遅延キャッシュ式を作成します。observe()は副作用(入力の更新、ログ記録)のために即座に実行されます。observeEvent/eventReactiveは特定のイベント(ボタンクリック)でトリガーされます。reactiveValとreactiveValuesはミュータブルな状態を保持します。isolate()は依存関係を作らずに値を読み取ります。重要な洞察:出力はリアクティブ依存関係が変更されると自動的に再レンダリングされます。リアクティビティの誤解はShinyのバグの第1の原因です — reactiveLogViewer()で依存グラフをデバッグします。

r
server <- function(input, output, session) {
  # reactive expression: lazy, cached
  filtered <- reactive({
    mtcars %>% filter(cyl == input$cyl)
  })

  # observe: eager, for side effects
  observe({
    updateSelectInput(session, "model",
      choices = unique(filtered()$model))
  })

  # observeEvent: triggered by specific input
  observeEvent(input$reset, {
    updateSliderInput(session, "cyl", value = 4)
  })

  # eventReactive: lazy, triggered by event
  result <- eventReactive(input$go, {
    run_analysis(input$param)
  })

  # reactiveVal: mutable value
  counter <- reactiveVal(0)
  observeEvent(input$add, counter(counter() + 1))

  # reactiveValues: multiple mutable values
  rv <- reactiveValues(data = NULL, status = "ready")
  observeEvent(input$load, {
    rv$data <- read.csv(input$file$datapath)
    rv$status <- "loaded"
  })

  # isolate: read without dependency
  output$plot <- renderPlot({
    plot(isolate(rv$data))
  })
}

動的UIとモジュール

動的UI(renderUI + uiOutput)はデータやユーザーの選択に基づいてコントロールを生成します。insertUI/removeUIはページ全体を再レンダリングせずに要素を追加/削除します。モジュール(NS + moduleServer)はUI+サーバーロジックを再利用のためにカプセル化します — 繰り返しコンポーネントを持つ複雑なアプリに不可欠です。各モジュールインスタンスは一意の名前空間(ns)を取得し、input IDの衝突を防ぎます。モジュールは保守可能なShinyアプリ構築の鍵です:アプリを小さくテスト可能なモジュール(チャートモジュール、フィルターモジュール、データアップロードモジュール)に分割し、それらを組み合わせます。

r
# dynamic UI
ui <- fluidPage(
  uiOutput("dynamic_controls"),
  plotOutput("plot")
)

server <- function(input, output, session) {
  output$dynamic_controls <- renderUI({
    cols <- names(input$data)
    selectInput("xvar", "X variable:", choices = cols)
  })

  # insert/remove UI
  observeEvent(input$add, {
    insertUI("#placeholder", "beforeEnd",
      sliderInput(paste0("s", input$add), "Slider", 0, 100, 50))
  })

  # modules: reusable UI+server
  histogramUI <- function(id) {
    ns <- NS(id)
    tagList(
      selectInput(ns("var"), "Variable:", names(mtcars)),
      plotOutput(ns("hist"))
    )
  }
  histogramServer <- function(id) {
    moduleServer(id, function(input, output, session) {
      output$hist <- renderPlot(hist(mtcars[[input$var]]))
    })
  }

  # use module
  ui <- fluidPage(histogramUI("hist1"), histogramUI("hist2"))
  server <- function(input, output, session) {
    histogramServer("hist1")
    histogramServer("hist2")
  }
}

入力、出力、レンダリング

Shinyは多くの入力タイプ(file、date、slider、selectize、checkbox)と出力タイプ(plot、table、text、image、UI)をサポートします。DT::renderDataTableは検索/ソート付きのインタラクティブテーブルを作成します。downloadHandlerでユーザーがデータをエクスポートできます。.data[[]]でggplotの動的列選択を有効にします。大規模データにはDTのサーバーサイド処理やplotlyのインタラクティブプロットを使用します。renderImageは事前生成ファイルを表示します(複雑なビジュアルにはrenderPlotより高速)。入力をリアクティブ式と組み合わせて洗練されたレスポンシブダッシュボードを構築します。

r
# file upload
fileInput("data", "Upload CSV:", accept = ".csv")
# in server: input$data$datapath (temp file path)

# date range
dateRangeInput("dates", "Period:", start = Sys.Date() - 30, end = Sys.Date())

# tabset with conditional panels
tabsetPanel(
  tabPanel("Plot", plotOutput("p")),
  tabPanel("Table", DT::dataTableOutput("t")),
  tabPanel("Summary", verbatimTextOutput("s"))
)

# render DataTable (interactive)
output$t <- DT::renderDataTable({
  datatable(filtered(), filter = "top", options = list(pageLength = 25))
})

# render UI from ggplot
output$p <- renderPlot({
  ggplot(filtered(), aes(.data[[input$x]], .data[[input$y]])) +
    geom_point() + theme_minimal()
})

# download handlers
output$download <- downloadHandler(
  filename = function() paste0("data_", Sys.Date(), ".csv"),
  content = function(file) write.csv(filtered(), file)
)

# render image (pre-generated)
output$img <- renderImage({
  list(src = "plot.png", contentType = "image/png", width = 400)
}, deleteFile = FALSE)

パフォーマンス、デプロイ、スケーリング

Shinyパフォーマンス:急速な入力(検索ボックス)をdebounce/throttleして過剰な再計算を回避します。future + promisesで非同期操作を使用します(イベントループをブロックしない)。bindCacheはキーごとにレンダリング結果をキャッシュします — 多くのユーザーがアクセスする高コストプロットに大きな効果。shinyapps.io(マネージドクラウド)、RStudio Connect(商用)、またはDocker背後のShiny Server(オープンソース)にデプロイします。高トラフィックには複数のワーカープロセスを実行しロードバランスします。shinylogsで使用状況を監視し、ユーザー行動を理解しエラーを捕捉します。遅いアプリはprofvis::profvis()でプロファイリングしてボトルネックを特定します。

r
# performance: debounce/throttle rapid inputs
input_debounced <- debounce(reactive({ input$text }), 500)

# async with future
library(future)
plan(multisession)
result <- reactive({
  future_promise({ expensive_computation(input$params) })
})

# caching expensive computations
# in UI: add resourcePath or use bindCache
output$plot <- renderPlot({ ... }) %>% bindCache(input$dataset)

# deploy to shinyapps.io
# library(rsconnect)
# deployApp("myapp/")

# run multiple Shiny apps behind a load balancer
# (shinyapps.io / RStudio Connect handle this automatically)

# dockerize for self-hosting
# Dockerfile:
# FROM rocker/shiny
# COPY app.R /srv/shiny-server/
# EXPOSE 3838

# shiny server config (/etc/shiny-server/shiny-server.conf)
# run_as shiny;
# server { listen 3838; location / { site_dir /srv/shiny-server; } }

# monitor with shinylogs or shiny.telemetry
16

統計的検定と推測

仮説検定のフレームワーク

仮説検定は観測データが帰無仮説と一致するかを評価します。t検定は平均を比較します(パラメトリック、正規性を仮定);Wilcoxonはノンパラメトリックな代替です。p値だけでなく効果量と信頼区間を常に報告してください — pはサンプルサイズに依存し、CIは実用的な有意性を示します。解釈前に仮定をチェック:正規性(Shapiro-Wilk)、等分散(Levene)。検定力分析(pwrパッケージ)はデータ収集前に必要なサンプルサイズを決定します。0.05の閾値は慣習的であり魔法ではありません — 効果量とコンテキストを考慮してください。

r
# one-sample t-test: is mean different from hypothesized value?
t.test(mtcars$mpg, mu = 20)
#  t = 0.085, df = 31, p-value = 0.933
#  95% CI: [17.92, 22.26]
#  mean of x: 20.09

# two-sample t-test
t.test(mpg ~ cyl, data = subset(mtcars, cyl %in% c(4, 6)))

# paired t-test
t.test(pre, post, paired = TRUE)

# Wilcoxon (non-parametric alternative)
wilcox.test(mpg ~ cyl, data = subset(mtcars, cyl %in% c(4,6)))

# interpret results:
#   p < 0.05: reject null hypothesis (significant)
#   p > 0.05: fail to reject (not enough evidence)
#   CI shows plausible range of effect
#   check assumptions: normality (shapiro.test), equal variance

# power analysis
library(pwr)
pwr.t.test(d = 0.5, sig.level = 0.05, power = 0.8)
# n = 64 per group needed to detect medium effect

ANOVAと多重比較

ANOVAは3+グループ間で平均が異なるかを検定します。F検定は何らかの差が存在するかを示し、事後検定(Tukey HSD、補正付きpairwise.t.test)はどのグループが異なるかを特定します。常に仮定(正規性、等分散性)をチェックし、違反時はノンパラメトリック代替(Kruskal-Wallis)を使用します。反復測定や階層データには混合効果モデル(lme4::lmer)を使用し、被験者内相関を適切に処理します。多重比較補正(Bonferroni、FDR)は多くの検定を実行する際の偽陽性を防ぎます。afexパッケージが反復測定ANOVAを簡素化します。

r
# one-way ANOVA: compare means across 3+ groups
fit <- aov(mpg ~ factor(cyl), data = mtcars)
summary(fit)
#              Df Sum Sq Mean Sq F value  Pr(>F)
# factor(cyl)   2  824.8   412.4    39.7 4.98e-09

# check assumptions
plot(fit)                       # residuals vs fitted, QQ
shapiro.test(resid(fit))        # normality of residuals
library(car); leveneTest(fit)  # equal variance

# post-hoc tests (which groups differ?)
TukeyHSD(fit)                   # Tukey HSD
pairwise.t.test(mtcars$mpg, mtcars$cyl, p.adjust = "bonferroni")

# two-way ANOVA with interaction
fit2 <- aov(mpg ~ cyl * am, data = mtcars)
summary(fit2)

# Kruskal-Wallis (non-parametric ANOVA)
kruskal.test(mpg ~ factor(cyl), data = mtcars)

# repeated measures ANOVA
# library(afex)
# aov_ez(id = "subject", dv = "score", data = df, within = "condition")

# mixed-effects models (lme4)
library(lme4)
lmer(score ~ treatment + (1|subject), data = df)

相関と回帰診断

相関は線形関連を測定します(-1から1);cor.testが推測を追加します。線形回帰(lm)はy = β0 + β1*x + εに適合します。4つの診断プロットが仮定違反を明らかにします:非線形性、非正規残差、不均一分散、影響点。VIF > 5-10は多重共線性(予測変数の過剰な相関)を示します。Cook's distanceが影響力のある観測値を特定します。信頼区間(平均応答)と予測区間(新規観測)を適切に使い分けます。モデルをanova(ネスト)またはAIC/BIC(非ネスト、低いほど良い)で比較します。p値を信頼する前に常に可視化してください。

r
# correlation
cor(mtcars$mpg, mtcars$wt)                    # -0.867
cor.test(mtcars$mpg, mtcars$wt)               # with p-value
cor(mtcars[, c("mpg","wt","hp","disp")])      # correlation matrix
library(corrplot); corrplot(cor(mtcars[,1:4]))# visualize

# linear regression
fit <- lm(mpg ~ wt + hp, data = mtcars)
summary(fit)
confint(fit)                                   # CIs for coefficients

# diagnostics
par(mfrow = c(2,2)); plot(fit)
# 1. Residuals vs Fitted: check linearity
# 2. Normal Q-Q: check normality
# 3. Scale-Location: check homoscedasticity
# 4. Residuals vs Leverage: check influential points

# check for multicollinearity
library(car); vif(fit)                         # variance inflation factor

# influential observations
cooks.distance(fit) |> plot()
influence.measures(fit)

# predictions with intervals
predict(fit, newdata, interval = "confidence") # CI for mean
predict(fit, newdata, interval = "prediction") # PI for new obs

# compare nested models
anova(fit1, fit2)                              # F-test
AIC(fit1, fit2); BIC(fit1, fit2)              # information criteria

カテゴリカルデータ:カイ二乗とFisher

カイ二乗検定はカテゴリ変数間の独立性を検定します;Fisherの正確検定は小サンプル(期待度数 < 5)でより正確です。カイ二乗結果を信頼する前に期待度数をチェックします。適合度検定は観測値を理論的比率と比較します。McNemarはペアの名義データ(前/後)を検定します。効果量(Cramer's V、phi)はp値を超えて関連の強さを定量化します。モザイクプロットは分割表を直感的に可視化します。順序データにはSpearman相関や傾向検定を検討します。vcd(Visualizing Categorical Data)パッケージが包括的なツールを提供します。

r
# contingency table
tbl <- table(mtcars$cyl, mtcars$am)
#    0  1
# 4  3  8
# 6  4  3
# 8 12  2

# chi-square test of independence
chisq.test(tbl)
# X-squared = 8.74, df = 2, p-value = 0.0126

# expected counts (should be > 5 for valid chi-square)
chisq.test(tbl)$expected

# Fisher's exact test (small samples)
fisher.test(tbl)

# goodness of fit (one variable vs expected proportions)
chisq.test(c(10, 20, 30), p = c(1/3, 1/3, 1/3))

# McNemar's test (paired nominal data)
mcnemar.test(table(pre, post))

# Cochran-Mantel-Haenszel (stratified)
mantelhaen.test(tbl3d)

# visualize
library(ggplot2); library(ggmosaic)
ggplot(as.data.frame(tbl)) +
  geom_mosaic(aes(weight = Freq, x = product(Var1), fill = Var2))

# effect size
library(vcd); assocstats(tbl)  # Cramer's V, phi

brmsによるベイズ推測

ベイズ推測(Stanをラップするbrms経由)は点推定の代わりに完全な事後分布を提供します。事前分布を指定してドメイン知識をエンコード;弱情報事前分布(normal(0, 10))はバイアスなしで正則化します。事後要約は信用区間を与えます(頻度論的CIと異なり直接的な確率ステートメント)。pp_checkはシミュレーションデータと観測データを比較してモデル適合を検証します。LOO-CVとWAICは交差検証予測精度でモデルを比較します。階層モデルはグループデータを自然に処理します。ベイズ手法は小サンプル、複雑モデル、不確実性の定量化が必要な場合に優れています。

r
library(brms)

# Bayesian linear regression
fit <- brm(
  mpg ~ wt + hp,
  data = mtcars,
  family = gaussian(),
  prior = c(
    prior(normal(0, 10), class = "b"),       # weakly informative
    prior(cauchy(0, 2), class = "sigma")
  ),
  chains = 4, cores = 4, iter = 2000
)

# summary
summary(fit)
plot(fit)                       # posterior distributions
pp_check(fit)                   # posterior predictive check

# extract posterior samples
posterior <- as_draws_df(fit)
mean(posterior$b_wt > 0)        # P(coefficient > 0)

# predictions
posterior_predict(fit, newdata) |> as.data.frame() -> preds

# model comparison
fit_null <- brm(mpg ~ 1, data = mtcars, ...)
loo(fit, fit_null)              # leave-one-out CV
waic(fit, fit_null)

# hierarchical model
fit_h <- brm(
  score ~ treatment + (1 + treatment|subject),
  data = df, family = gaussian()
)

# Stan code behind the model
stancode(fit)
17

caretによる機械学習

データ分割と前処理

適切なデータ分割と前処理はML成功の80%です。createDataPartitionは層化サンプリングを行います(クラスバランスを保持)。trainControlはリサンプリング(CV、ブートストラップ、反復CV)を設定します。preProcessは標準化、変換、PCA、インピュテーションを処理します — 訓練データのみに適合させ、テストに適用してリークを回避します。nzvは情報のない列を削除します。時系列にはランダムCVの代わりにcreateTimeSlicesを使用します。caretの統一インターフェースにより同じ前処理がすべてのモデルタイプで機能します。

r
library(caret)

# split data
set.seed(42)
idx <- createDataPartition(iris$Species, p = 0.8, list = FALSE)
train <- iris[idx, ]; test <- iris[-idx, ]

# stratified split for classification
# createDataPartition preserves class proportions

# k-fold cross-validation
ctrl <- trainControl(
  method = "cv", number = 10,
  savePredictions = "final",
  classProbs = TRUE,           # for AUC
  summaryFunction = multiClassSummary
)

# preprocessing pipeline
preproc <- preProcess(train[, -5],
  method = c("center","scale","YeoJohnson","nzv"))
train_processed <- predict(preproc, train[, -5])
test_processed  <- predict(preproc, test[, -5])

# common preprocessing methods:
#   center, scale: standardize
#   BoxCox, YeoJohnson: transform to normality
#   pca: principal components
#   nzv: remove near-zero variance
#   knnImpute, bagImpute: impute missing values

# dummy variables for categorical
dummies <- dummyVars(Species ~ ., data = train)
predict(dummies, train)

モデル訓練とチューニング

caretのtrain()関数は200+モデルへの統一インターフェースを提供します — method文字列を変更するだけです。tuneLengthがチューニンググリッドを自動生成;tuneGridが完全制御を与えます。resamples()はリサンプリング性能で複数モデルを比較します(単一テストセット評価より誠実)。公平な比較のためすべてのモデルで同じtrControlを使用します。resamplesのドットプロットは性能の重なりを示します — CIが重なる場合、モデルは有意に異なりません。最良の1標準誤差以内の最もシンプルなモデルを選びます('one-SE rule')。

r
library(caret)

# train a random forest
ctrl <- trainControl(method = "cv", number = 5)
rf_fit <- train(
  Species ~ ., data = train,
  method = "rf",
  trControl = ctrl,
  tuneGrid = expand.grid(mtry = 1:4),
  tuneLength = 5              # auto-tune grid
)
print(rf_fit)                  # shows accuracy by tuning param
plot(rf_fit)                   # tuning curve

# try multiple models
models <- c("rf","gbm","svmRadial","knn","rpart")
fits <- lapply(models, function(m) {
  train(Species ~ ., data = train, method = m, trControl = ctrl)
})
names(fits) <- models

# compare models
resamps <- resamples(fits)
summary(resamps)
dotplot(resamps, metric = "Accuracy")

# custom tuning grid
grid <- expand.grid(
  mtry = c(2, 4, 8),
  splitrule = c("gini","extratrees"),
  min.node.size = c(1, 5, 10)
)
fit <- train(Species ~ ., data = train, method = "ranger",
             trControl = ctrl, tuneGrid = grid)

分類指標と混同行列

精度のみは不均衡データに誤解を招きます。confusionMatrixはクラスごとの感度(再現率)、特異度、適合率、F1を提供します。二値問題ではROC-AUCが判別力を測定;正クラスが稀な場合はPR曲線が優れています。多クラスにはマクロ/マイクロ平均指標やlog-lossを使用します。常にホールドアウトテストセットで評価します(またはネストCVで不偏推定)。caretではtrainControlのsummaryFunctionを設定して正しい指標を最適化します(例:確率的予測にはmnLogLoss)。点推定だけでなく性能の信頼区間を報告します。

r
# predictions
pred <- predict(rf_fit, test)
prob <- predict(rf_fit, test, type = "prob")

# confusion matrix
cm <- confusionMatrix(pred, test$Species)
print(cm)
#               Reference
# Prediction   setosa versicolor virginica
# setosa          10       0       0
# versicolor       0      10       1
# virginica        0       0       9
# Overall Accuracy: 0.9667
# byClass: Sensitivity, Specificity, etc.

# two-class metrics
cm$byClass[, c("Sensitivity","Specificity","Precision","Recall","F1")]

# ROC and AUC
library(pROC)
roc_curve <- roc(test$Species == "versicolor", prob$versicolor)
auc(roc_curve)
plot(roc_curve)

# multi-class AUC
library(pRoc)
multiclass.roc(test$Species, prob)

# precision-recall curve (better for imbalanced data)
library(PRROC)
pr <- pr.curve(scores.class0 = prob$versicolor,
               weights.class0 = test$Species == "versicolor",
               curve = TRUE)
plot(pr)

# custom metrics in trainControl
twoClassSummary  # built-in for 2-class
mnLogLoss         # multi-class log loss

特徴選択と解釈

特徴選択はモデル性能と解釈性を向上させます。RFE(再帰的特徴除去)はモデルをラップし最も重要でない特徴を反復的に削除します。varImpはcaret訓練モデル(ランダムフォレスト、gbmなど)から重要度を抽出します。フィルタ法(findCorrelation、findLinearCombos)は訓練前に冗長特徴を削除します。ブラックボックス解釈にはSHAP値(fastshap、shapviz)が予測を特徴に帰属させます。選択バイアスを回避するため交差検証内で特徴選択を実行します。より少ない特徴のシンプルなモデルは一般化しやすい傾向があります。

r
library(caret)

# recursive feature elimination (RFE)
ctrl <- rfeControl(functions = rfFuncs, method = "cv", number = 10)
rfe_fit <- rfe(train[,-5], train$Species,
  sizes = c(1:4), rfeControl = ctrl)
print(rfe_fit)              # optimal subset
predictors(rfe_fit)         # selected features
plot(rfe_fit)

# variable importance from trained model
varImp(rf_fit)              # caret extracts importance
plot(varImp(rf_fit))

# filter methods (before training)
# remove highly correlated features
cor_matrix <- cor(train[,-5])
high_cor <- findCorrelation(cor_matrix, cutoff = 0.9)
train_filtered <- train[,-high_cor]

# remove linear dependencies
findLinearCombos(train[,-5])

# genetic algorithm / simulated annealing selection
# gafsFit <- gafs(x, y, iters = 20)
# safsFit <- safs(x, y, iters = 20)

# SHAP values (model-agnostic interpretation)
# library(fastshap)
# explain(rf_fit, X = test, nsim = 100)

アンサンブルとスタッキング

アンサンブルは単一モデルより良い性能のために複数モデルを組み合わせます。caretEnsembleは同一リサンプリングでモデルを訓練します(公平なスタッキングに必要)。caretStackはベース予測上にメタモデルを訓練します — メタモデルは各ベースモデルをいつ信頼するかを学習します。類似精度モデルには単純平均が驚くほど効果的です。重み付きアンサンブルでより良いモデルを強調できます。バギング(treebag)はブートストラップモデルを平均して分散を削減します。ベースモデルの多様性が個別精度より重要です — 異なるエラーをするモデルを組み合わせます。

r
library(caretEnsemble)

# train multiple models with same resampling
ctrl <- trainControl(method = "cv", number = 5,
                     savePredictions = "final",
                     classProbs = TRUE)

models <- caretList(
  Species ~ ., data = train,
  trControl = ctrl,
  methodList = c("rf","gbm","svmRadial","knn")
)

# simple ensemble (average predictions)
ens <- caretEnsemble(models)
summary(ens)
plot(ens)

# stack: train a meta-model on base predictions
stack <- caretStack(models, method = "glm",
  metric = "Accuracy", trControl = ctrl)
print(stack)

# predict with ensemble
pred_ens <- predict(ens, test)
pred_stack <- predict(stack, test)

# weighted ensemble (custom weights)
weights <- c(0.4, 0.3, 0.2, 0.1)
probs <- lapply(models, function(m) predict(m, test, type = "prob"))
final_prob <- Reduce('+', Map(function(p, w) p * w, probs, weights))

# bagging (bootstrap aggregating)
bag_fit <- train(Species ~ ., data = train, method = "treebag",
                 trControl = ctrl)
18

Apply族とパフォーマンス

apply、lapply、sapply、vapply

apply族はbase Rの関数型プログラミングツールキットです。applyは配列で動作します(行はマージン1、列は2を使用)が、組み込みのrowSums/colSumsの方が高速です。lapplyは常にリストを返します;sapplyはベクトルに簡素化を試みます(便利だが型不安定)。vapplyは安全版 — 出力テンプレートを指定するため、サイレントに型変換せず不一致でエラーになります。本番コードではvapplyを、対話的にはsapplyを使用します。replicateはシミュレーションに便利です。mapply(またはMap)は複数引数を並列に反復します。現代コードでは一貫性のためにpurrrのmap族を推奨します。

r
# apply: over array margins (rows or columns)
m <- matrix(1:12, 3, 4)
apply(m, 1, sum)          # row sums: 22 26 30
apply(m, 2, mean)         # column means
apply(m, c(1,2), sqrt)    # element-wise (silly but valid)

# built-in row/col functions are faster
rowSums(m); rowMeans(m)
colSums(m); colMeans(m)

# lapply: list in, list out
lapply(list(a=1:3, b=4:6), mean)   # list(a=2, b=5)
lapply(mtcars, class)               # class of each column

# sapply: list in, vector out (simplifies)
sapply(mtcars, mean)                # named numeric vector
sapply(mtcars, is.numeric)          # logical vector

# vapply: type-safe sapply (specify output template)
vapply(mtcars, mean, numeric(1))    # always numeric(1)
vapply(mtcars, class, character(1)) # always character(1)

# replicate: repeat expression n times
replicate(5, rnorm(3))              # 3x5 matrix
replicate(100, mean(rexp(30)))      # 100 sample means

# mapply: multivariate map
mapply(rep, 1:3, 3:1)               # list(1,1,1, 2,2, 3)
mapply(function(a,b) a+b, 1:3, 4:6) # 5 7 9

ベクトル化と速度

ベクトル化はRの第1のパフォーマンス最適化です。Rの算術、比較、数学関数は最適化されたCコード経由でベクトル全体で動作します — Rのループはインタプリタ実行され遅いです。ifelseはベクトル化されていますがオーバーヘッドがあります;直接的な論理インデックス(x * (x > 5))が最速です。データフレームでapplyを使用しないでください(行列に型変換);ベクトル化列操作を使用します。結果ベクトルは常に事前割り当てしてください — c()で成長させるのはO(n^2)です。真にホットなループにはRcppでC++をインライン記述できます。改善を検証するにはmicrobenchmarkでマイクロベンチマーク;system.timeは粗すぎます。

r
# BAD: element-wise loop
x <- 1:1e6
y <- numeric(length(x))
for (i in seq_along(x)) y[i] <- x[i]^2

# GOOD: vectorized
y <- x^2   # ~100x faster

# ifelse vs vectorized
# BAD
y <- numeric(length(x))
for (i in seq_along(x)) {
  y[i] <- if (x[i] > 5) x[i] else 0
}
# GOOD
y <- ifelse(x > 5, x, 0)
# BEST (fastest)
y <- x * (x > 5)

# row-wise operations (avoid if possible)
df <- data.frame(a = 1:1000, b = 1001:2000)
# BAD
df$sum <- apply(df, 1, sum)
# GOOD
df$sum <- df$a + df$b

# preallocate!
n <- 1000
result <- numeric(n)       # not result <- c()
for (i in 1:n) result[i] <- i^2

# use Rcpp for hot loops
# library(Rcpp)
# cppFunction('double sumc(NumericVector x) { return sum(x); }')

# benchmark
library(microbenchmark)
microbenchmark(
  loop = {y <- numeric(length(x)); for(i in seq_along(x)) y[i] <- x[i]^2},
  vectorized = x^2,
  times = 10
)

メモリとdata.table

data.tableは大規模データ(100万行以上)でdata.frame/dplyrより劇的に高速でメモリ効率が良いです。dt[i, j, by]構文がフィルタリング、選択、グループ化を1つの式に結合します。参照セマンティクス(:=)はコピーなしでインプレース修正 — メモリに重要。setkeyがインデックスを作成し二分探索ルックアップと高速マージを可能にします。fread/fwriteはread.csv/write.csvより5-10倍高速です。メモリに収まるデータにはdata.tableがSparkをも上回ることがあります。トレードオフはより急な学習曲線とdplyrより読みにくい構文です。

r
# data.table: faster, memory-efficient alternative to data.frame
library(data.table)
dt <- data.table(mtcars)

# syntax: dt[i, j, by]
dt[mpg > 20, .(mean_hp = mean(hp)), by = .(cyl, gear)]
#   cyl gear mean_hp
# 1:   4    4    76.0
# 2:   4    5   102.0

# reference semantics (modify in place, no copy)
dt[, mpg_dbl := mpg * 2]              # add column in place
dt[1:5, mpg := 0]                     # modify subset in place
dt[, c("a","b") := .(mpg*2, hp/10)]   # multiple columns

# setkey for fast lookups and joins
setkey(dt, cyl)
dt[.(4)]                               # all rows where cyl == 4 (binary search)
dt[.(4), mean(mpg)]                    # fast aggregation

# joins
dt1 <- data.table(id = 1:3, x = letters[1:3])
dt2 <- data.table(id = 2:4, y = LETTERS[2:4])
setkey(dt1, id); setkey(dt2, id)
dt1[dt2]                               # right join
dt2[dt1]                               # left join
merge(dt1, dt2, by = "id")             # inner join

# fread/fwrite: fast I/O
big <- fread("huge.csv")               # ~10x faster than read.csv
fwrite(big, "out.csv")

並列コンピューティング

Rはデフォルトでシングルスレッドですが、並列化は簡単です。parallelパッケージ(組み込み)はmclapply(フォーク、Linux/Macのみ)とparLapply(クラスター、全プラットフォーム)を提供します。foreach + doParallelは人気のある代替です。futureエコシステム(furrr付き)は現代的で統一されています — plan()の変更でバックエンドを切り替えます。caretではallowParallel = TRUEを設定しバックエンドを登録します。必要な変数をエクスポートし、ワーカーでパッケージをロードしてください。並列化にはオーバーヘッドがあります — 各タスクが実質的(>100ms)な場合のみ助けになります。速度向上を検証するためベンチマーク;アムダールの法則が向上を制限します。

r
# parallel package (built-in)
library(parallel)
ncores <- detectCores() - 1

# parallel lapply
cl <- makeCluster(ncores)
results <- parLapply(cl, 1:100, function(i) slow_function(i))
stopCluster(cl)

# foreach with doParallel
library(foreach); library(doParallel)
registerDoParallel(ncores)
results <- foreach(i = 1:100, .combine = "c") %dopar% {
  slow_function(i)
}
stopImplicitCluster()

# future ecosystem (modern, flexible)
library(future); library(furrr)
plan(multisession, workers = ncores)   # or multicore (Linux/Mac)
results <- future_map(1:100, slow_function)

# parallel caret
library(caret)
ctrl <- trainControl(method = "cv", number = 10, allowParallel = TRUE)
fit <- train(y ~ ., data = train, method = "rf", trControl = ctrl)

# benchmark
library(microbenchmark)
microbenchmark(
  serial = lapply(1:100, slow_function),
  parallel = parLapply(makeCluster(4), 1:100, slow_function),
  times = 5
)

# export variables to workers
clusterExport(cl, c("my_data", "my_function"))
clusterEvalQ(cl, library(dplyr))

プロファイリングと最適化ワークフロー

最適化前にプロファイリング — ボトルネックの直感は通常間違っています。profvisは行ごと、呼び出しごとの時間を示すインタラクティブなフレームグラフを提供します。Rprofがbase Rの同等品。Rprofmemが割り当てを追跡します。object.sizeがメモリを測定;gc()がガベージコレクションを強制し使用量を報告します。最適化の階層:(1)ベクトル化、(2)事前割り当て、(3)data.tableに切り替え、(4)還元不能ループにRcpp、(5)並列化。memoiseは関数結果をキャッシュ — 同じ引数で繰り返し呼ばれる高コスト純粋関数に最適。改善前後で常に測定して確認します。

r
# profile to find bottlenecks
library(profvis)
profvis({
  result <- my_analysis(big_data)
})
# opens interactive flame graph

# Rprof (base R)
Rprof("profile.out")
my_analysis(big_data)
Rprof(NULL)
summaryRprof("profile.out")

# memory profiling
Rprofmem("mem.out")
my_analysis(big_data)
Rprofmem(NULL)

# object sizes
object.size(my_data)              # bytes
format(object.size(my_data), "MB")

# find memory hogs
sort(sapply(ls(), function(x) object.size(get(x))))

# garbage collection
gc()                              # force collection, show memory
gcinfo(TRUE)                      # report on auto GC

# common optimizations:
# 1. Vectorize (avoid loops)
# 2. Preallocate
# 3. Use data.table instead of data.frame
# 4. Use Rcpp for hot loops
# 5. Avoid growing objects (c(), rbind())
# 6. Use appropriate data types (integer vs double)
# 7. Cache expensive computations (memoise)
library(memoise)
slow_cached <- memoise(slow_function)
19

dplyrディープダイブ

コア動詞

dplyrの5つのコア動詞:filter(条件で行)、select(列)、mutate(新規列)、arrange(ソート)、summarize(集約)。%>%でチェーンします。group_by + summarizeが集約の主力です。na.rm = TRUEが不可欠 — そうでないとデータ内のNAが要約をNAにします。

r
library(dplyr)

starwars %>%
  filter(species == "Human", height > 170) %>%
  select(name, height, mass, homeworld) %>%
  mutate(bmi = mass / (height/100)^2) %>%
  arrange(desc(height)) %>%
  slice_head(n = 5)

# group_by + summarize
starwars %>%
  group_by(species) %>%
  summarize(
    n = n(),
    avg_height = mean(height, na.rm = TRUE),
    avg_mass = mean(mass, na.rm = TRUE)
  ) %>%
  arrange(desc(n)) %>%
  filter(n >= 2)

結合

変更結合は列を結合;フィルタ結合は行をサブセット。left_joinが最も一般的 — xのすべての行を保持し、非マッチにはNAを埋めます。右テーブルの重複キーを常にチェックします(行の増殖を引き起こす)。semi_join/anti_joinは列を持ってこずに別テーブルに基づいてフィルタするのに優れています。

r
library(dplyr)

# mutating joins (add columns from y to x)
left_join(x, y, by = "id")        # all rows in x
right_join(x, y, by = "id")       # all rows in y
inner_join(x, y, by = "id")       # only matching rows
full_join(x, y, by = "id")        # all rows from both

# different column names
left_join(x, y, by = c("id" = "user_id"))

# multiple keys
left_join(x, y, by = c("first", "last"))

# filtering joins (filter x, no columns added)
semi_join(x, y, by = "id")        # rows in x that match y
anti_join(x, y, by = "id")        # rows in x that DON'T match y

# check for duplicates
x %>% count(id) %>% filter(n > 1)

ウィンドウ関数

ウィンドウ関数は現在の行に関連する行全体で値を計算します。lag/leadは前/次の行にアクセス — 時系列に不可欠。cumsum/cummeanは累積集計です。slice_max/slice_minはグループごとのtop-nの省略形です。グループ内で計算するには常にgroup_byを先に行います。

r
library(dplyr)

# row numbering and ranking
df %>% group_by(group) %>%
  mutate(
    row_num = row_number(),
    rank = rank(value),
    dense_rank = dense_rank(value),
    min_rank = min_rank(value)
  )

# offsets
df %>% group_by(group) %>%
  mutate(
    prev = lag(value),
    next = lead(value, 2),
    diff = value - lag(value)
  )

# cumulative
df %>% group_by(group) %>%
  mutate(
    cum_sum = cumsum(value),
    cum_mean = cummean(value),
    cum_max = cummax(value),
    cum_min = cummin(value)
  )

# top n per group
df %>% group_by(group) %>%
  slice_max(value, n = 3)         # top 3
df %>% group_by(group) %>%
  slice_min(value, n = 2)

acrossと複数列

across(dplyr 1.0+)は古い_at、_if、_allサフィックスを置き換えます。where(is.numeric)で述語により列を選択します。.x代名詞はラムダ(~)内で現在の列を参照します。rename_withは関数を使用して列名を変更します。acrossは複数列を操作する現代的で一貫した方法です。

r
library(dplyr)

# apply function to multiple columns
starwars %>%
  mutate(across(where(is.numeric), ~ replace_na(.x, 0)))

# summarize multiple columns
starwars %>%
  summarize(across(where(is.numeric), mean, na.rm = TRUE))

# rename multiple columns
starwars %>%
  rename_with(tolower, everything())

# transform specific columns
df %>%
  mutate(across(c(height, mass), ~ .x * 0.453592))  # lb to kg

# multiple functions
df %>%
  summarize(across(value, list(mean = mean, sd = sd, n = ~ n()), na.rm = TRUE))

# conditional transformation
df %>%
  mutate(across(where(is.character), tolower))

集約パターン

summarizeはグループを単一値に還元します。n()が行数;n_distinct()がユニーク値数。統計関数には常にna.rm = TRUEを渡すかNAが伝播します。across + listで複数統計を一度に計算できます。count()はgroup_by + summarize(n = n()) + ungroupの省略形です。

r
library(dplyr)

# basic
df %>%
  group_by(category) %>%
  summarize(
    count = n(),
    distinct = n_distinct(id),
    total = sum(value, na.rm = TRUE),
    avg = mean(value, na.rm = TRUE),
    median = median(value, na.rm = TRUE),
    sd = sd(value, na.rm = TRUE),
    min = min(value, na.rm = TRUE),
    max = max(value, na.rm = TRUE),
    first = first(value),
    last = last(value),
    q25 = quantile(value, 0.25, na.rm = TRUE)
  )

# multiple summary stats at once
df %>%
  group_by(category) %>%
  summarize(
    across(value, list(mean = mean, sd = sd, n = ~ n()), na.rm = TRUE)
  )

# count and proportions
df %>%
  count(category) %>%
  mutate(pct = n / sum(n))
20

ggplot2高度編

レイヤーと美学

ggplotは+で接続されたレイヤーでプロットを構築します。aes()がデータ列を視覚プロパティにマッピング。geom_*がジオメトリを定義;scale_*が軸/色を制御;labsがすべてにラベル;theme_*が非データ要素をスタイル。facet_wrapが1変数で分割;facet_gridが2変数の2Dグリッドを作成。

r
library(ggplot2)

ggplot(mpg, aes(displ, hwy, color = class)) +
  geom_point(size = 2, alpha = 0.7) +
  geom_smooth(method = "lm", se = TRUE) +
  scale_color_brewer(palette = "Set2") +
  labs(
    title = "Fuel efficiency by engine size",
    subtitle = "Source: EPA mpg dataset",
    x = "Engine displacement (L)",
    y = "Highway MPG",
    color = "Vehicle class"
  ) +
  theme_minimal(base_size = 12) +
  theme(legend.position = "bottom")

# facets
ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_wrap(~ class, ncol = 4)

ggplot(mpg, aes(displ, hwy)) +
  geom_point() +
  facet_grid(drv ~ cyl)

スケールと座標

scale_*関数がデータから視覚プロパティへのマッピングを制御します。scale_x_log10が対数変換;scale_color_viridis_cが知覚的に均一なカラーマップを与えます。coord_cartesianはデータをドロップせずにズーム(xlimと異なる)。coord_flipが軸を入れ替え。coord_polarがバーをパイスライスに変換。scale_x_dateが日付軸をフォーマット。

r
ggplot(mpg, aes(displ, hwy, color = cty)) +
  geom_point() +
  scale_x_log10() +
  scale_y_continuous(limits = c(10, 50), breaks = seq(10, 50, 5)) +
  scale_color_viridis_c(option = "plasma") +
  coord_cartesian(xlim = c(1, 7))   # zoom without removing data

# coord flip
ggplot(mpg, aes(class, hwy)) +
  geom_boxplot() +
  coord_flip()

# coord polar (pie chart from bar)
ggplot(mtcars, aes(factor(1), fill = factor(cyl))) +
  geom_bar(width = 1) +
  coord_polar(theta = "y")

# date axis
ggplot(economics, aes(date, unemploy)) +
  geom_line() +
  scale_x_date(date_labels = "%Y", date_breaks = "5 years")

テーマとカスタマイズ

theme()がすべての非データ要素を制御します。element_text/rect/line/blankが構成要素。一般的な調整:x軸ラベルの回転(angle、hjust)、太字タイトル、マイナーグリッドの非表示(panel.grid.minor = element_blank())。ggsaveでPNG/PDF/SVGにエクスポート — ラスター形式はインチとdpiで寸法を指定。

r
library(ggplot2)

p <- ggplot(mpg, aes(class, hwy)) + geom_boxplot()

# built-in themes
p + theme_minimal()
p + theme_classic()
p + theme_bw()

# custom theme
p + theme(
  panel.background = element_rect(fill = "white"),
  panel.grid.major = element_line(color = "gray90"),
  panel.grid.minor = element_blank(),
  axis.text.x = element_text(angle = 45, hjust = 1, size = 10),
  axis.title = element_text(face = "bold"),
  plot.title = element_text(size = 16, hjust = 0.5),
  plot.subtitle = element_text(color = "gray40"),
  legend.position = "right",
  legend.key = element_blank(),
  strip.background = element_rect(fill = "lightblue"),
  strip.text = element_text(face = "bold")
)

# save
ggsave("plot.png", width = 8, height = 6, dpi = 300)
ggsave("plot.pdf", device = cairo_pdf)

統計と平滑化

stat_summaryがカスタム要約(mean、median、mean_se、mean_cl_normal)を計算。geom_smoothがトレンドラインを追加 — method='lm'で線形、'loess'で局所回帰、'gam'で一般化加法。geom_density/geom_density_2dが分布を表示。geom_violinがボックスプロットと並んで完全な分布形状を表示。

r
library(ggplot2)

# stat_summary for custom summaries
ggplot(mtcars, aes(factor(cyl), mpg)) +
  stat_summary(fun = "mean", geom = "point", size = 3) +
  stat_summary(fun.data = "mean_se", geom = "errorbar")

# smooth
ggplot(mtcars, aes(wt, mpg)) +
  geom_point() +
  geom_smooth(method = "lm", formula = y ~ x, se = TRUE) +
  geom_smooth(method = "loess", se = FALSE, color = "red")

# density
ggplot(iris, aes(Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.5)

# 2d density
ggplot(diamonds, aes(carat, price)) +
  geom_density_2d() +
  scale_x_log10() + scale_y_log10()

# histograms with binning
ggplot(diamonds, aes(price)) +
  geom_histogram(bins = 50, fill = "steelblue") +
  scale_x_log10()

# boxplot and violin
ggplot(iris, aes(Species, Sepal.Length)) +
  geom_boxplot() +
  geom_violin(alpha = 0.3, fill = "orange")

拡張とpatchwork

patchworkが+(横並び)、/(積み上げ)、plot_layoutで複数プロットを組み合わせます。plot_annotationが全体タイトルとタグ(A、B、C...)を追加。ggplot2拡張エコシステムは巨大:ラベルにggrepel、リッジプロットにggridges、アニメーションにgganimate、インタラクティブにggiraph、地図にgeom_sf。

r
library(ggplot2)
library(patchwork)

p1 <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
p2 <- ggplot(mtcars, aes(factor(cyl))) + geom_bar()
p3 <- ggplot(mtcars, aes(mpg)) + geom_histogram(bins = 10)
p4 <- ggplot(mtcars, aes(factor(cyl), mpg)) + geom_boxplot()

# combine plots
p1 + p2                          # side by side
p1 / p2                          # stacked
(p1 + p2) / p3                   # grouped
p1 + p2 + p3 + plot_layout(nrow = 2, byrow = FALSE)

# annotations
p1 + p2 + plot_annotation(
  title = "MT cars analysis",
  tag_levels = "A"
)

# other extensions:
#   ggrepel: non-overlapping labels
#   ggridges: joy plots
#   gganimate: animated plots
#   ggiraph: interactive
#   ggplot2::geom_sf: maps

library(ggrepel)
ggplot(mtcars, aes(wt, mpg, label = rownames(mtcars))) +
  geom_point() +
  geom_text_repel()
21

tidyrデータラングリング

ピボットで長く/広く

pivot_longer/pivot_wider(gather/spreadの置換)がデータを再形成。長い形式はggplotとdplyr集約に最適;広い形式は人間の読解に適しています。names_patternと.valueで列名構造に基づいて複数列に分割できます。cols、names_to、values_toを常に明示的に指定します。

r
library(tidyr)

# wide to long
#   id  q1  q2  q3
#   A   10  20  30
wide_data %>%
  pivot_longer(
    cols = starts_with("q"),
    names_to = "quarter",
    values_to = "score"
  )
#   id  quarter  score
#   A   q1       10
#   A   q2       20
#   A   q3       30

# long to wide
long_data %>%
  pivot_wider(
    names_from = quarter,
    values_from = score
  )

# multiple value columns
df %>%
  pivot_longer(
    cols = c(starts_with("q"), starts_with("r")),
    names_to = c(".value", "quarter"),
    names_pattern = "([a-z])([0-9])"
  )

分割と統合

separateは1列を多数に分割;uniteは多数を1つに統合。separate_rowsは複数行に分割(タグリストに便利)。extractは正規表現キャプチャグループを使用。すべてsep引数を取ります(デフォルトは非英数字)。separateでconvert = TRUEで型を自動変換。

r
library(tidyr)

# split a column
df <- tibble(x = c("a_1", "b_2", "c_3"))
df %>% separate(x, c("letter", "number"), sep = "_")
#   letter number
#   a      1
#   b      2

# split into rows
df %>% separate_rows(x, sep = "_")
#   x
#   a
#   1
#   b
#   2

# unite columns
df %>%
  separate(x, c("letter", "number")) %>%
  unite("combined", letter, number, sep = "-")
#   combined
#   a-1
#   b-2

# extract with regex
df %>% extract(x, c("letter", "number"), "([a-z])_([0-9])")

欠損値

replace_naはNAを定数で埋める;fillは値を伝播(時系列に最適);drop_naは不完全な行を削除;coalesceは列間で最初の非NAを選択。completeは指定列のすべての組み合わせに展開(デカルト積のように) — 要約で欠損グループが現れることを保証するのに便利。

r
library(tidyr)

# replace NA
df %>% replace_na(list(value = 0, name = "unknown"))

# fill NA with previous/next value
df %>% fill(value, .direction = "down")    # forward fill
df %>% fill(value, .direction = "up")      # backward fill
df %>% fill(value, .direction = "downup")  # down then up

# drop rows with NA
df %>% drop_na()
df %>% drop_na(value)              # only specific columns

# detect NA
df %>% filter(!is.na(value))
sum(is.na(df$value))
df %>% mutate_all(~ sum(is.na(.))) # NA count per column

# coalesce: first non-NA
df %>% mutate(value = coalesce(value, fallback, 0))

# complete: expand combinations
df %>% complete(group, year)       # all group-year combos, NA filled
df %>% complete(group, year, fill = list(value = 0))

ネストとリスト列

nestはグループ化された行をリスト列にパック — purrrとのsplit-apply-combineの基盤。unnestが逆転。unnest_widerはリスト要素を列に展開;unnest_longerは行に展開。リスト列でtibble内に任意オブジェクト(モデル、データフレーム、ベクトル)を保持できます。

r
library(tidyr)
library(dplyr)

# nest: pack rows into list columns
nested <- mtcars %>%
  nest(data = -cyl)
#   cyl  data
#   4    <tibble 11x10>
#   6    <tibble 7x10>
#   8    <tibble 14x10>

# operate on each group
nested %>%
  mutate(model = map(data, ~ lm(mpg ~ wt, data = .x)))

# unnest
df %>%
  unnest(data)

# unnest specific column
df %>%
  unnest_wider(data)              # list-col of named lists -> cols
df %>%
  unnest_longer(data)             # list-col of vectors -> rows

# chop (similar to nest)
df %>% chop(value)

# pack (opposite of unnest)
df %>% pack(x = c(a, b))

ティブルとデータ読み込み

ティブルは改良されたデータフレーム:文字列から因子への変換なし、行名の操作なし、より良い印刷。read_csvはread.csvよりはるかに高速でティブルを返します。サプライズを避けるためcol_typesを指定(例:IDが数値として読まれる)。na引数で複数の文字列をNAとして扱えます。表形式データにはbaseより常にreadrを使用します。

r
library(tibble)
library(readr)

# create tibble
tibble(
  x = 1:5,
  y = c("a", "b", "c", "d", "e"),
  z = runif(5)
)

# tribble: row-wise construction
tribble(
  ~name, ~age, ~score,
  "Alice", 30, 90,
  "Bob",   25, 80
)

# read csv
df <- read_csv("data.csv", col_names = TRUE, col_types = "cdd")
df <- read_csv("data.csv", na = c("", "NA", "N/A"))
df <- read_tsv("data.tsv")
df <- read_delim("data.txt", delim = "|")

# write
write_csv(df, "out.csv")
write_tsv(df, "out.tsv")

# read from clipboard (Excel)
df <- read_clipboard()

# column types
#   c = character, d = double, i = integer, l = logical, f = factor, D = date
22

統計的検定

t検定

t検定は平均を比較します。1サンプル(値に対)、2サンプル(グループ間)、対応あり(被験者内)。デフォルトはWelch(不等分散) — 通常これが望むもの。常に仮定をチェック:正規性(Shapiro)と等分散(var.test)。p値だけでなく効果量(Cohen's d)を報告。

r
# one-sample t-test
t.test(x, mu = 5)
# H0: mean of x equals 5

# two-sample t-test
t.test(x, y)
t.test(x, y, var.equal = TRUE)    # Student's (assume equal var)
t.test(x, y, alternative = "greater")

# paired t-test
t.test(before, after, paired = TRUE)

# output interpretation
result <- t.test(x, y)
result$p.value                    # < 0.05 -> reject H0
result$conf.int                   # 95% CI of difference
result$statistic                  # t value

# check assumptions
shapiro.test(x)                   # normality
var.test(x, y)                    # equal variances

# effect size (effsize package)
library(effsize)
cohen.d(x, y)

ANOVA

ANOVAは3+グループ間の差を検定します。交互作用を持つ要因デザインには*を使用。有意なANOVA後には常に事後検定(TukeyHSD)を行いどのペアが異なるかを見つけます。分散の均一性(Levene)をチェック。非正規データにはKruskal-Wallisを使用。反復測定にはlme4のlmerを使用。

r
# one-way ANOVA
result <- aov(yield ~ fertilizer, data = df)
summary(result)

# two-way ANOVA with interaction
result <- aov(yield ~ fertilizer + density + fertilizer:density, data = df)
result <- aov(yield ~ fertilizer * density, data = df)  # shorthand

# Tukey post-hoc
TukeyHSD(result)

# check assumptions
plot(result)                      # diagnostic plots
library(car)
leveneTest(yield ~ fertilizer, data = df)  # equal variances

# Kruskal-Wallis (non-parametric alternative)
kruskal.test(yield ~ fertilizer, data = df)

# repeated measures
result <- aov(score ~ time + Error(subject/time), data = df)

# mixed effects (lme4)
library(lme4)
model <- lmer(score ~ time + (1 | subject), data = df)
anova(model)

カイ二乗とカテゴリカル

カイ二乗は2つのカテゴリ変数が独立かを検定。各セルの期待度数は >= 5であるべき;そうでない場合はFisherの正確検定を使用。McNemarはペアのバイナリデータ(前/後)用。Pearson残差がどのセルが期待から最も逸脱しているかを示す。assocstatsが効果量としてCramer's Vを与える。

r
# chi-square test of independence
tbl <- table(df$gender, df$vote)
chisq.test(tbl)

# goodness of fit
chisq.test(table(df$color), p = c(0.25, 0.25, 0.25, 0.25))

# Fisher's exact (small samples)
fisher.test(tbl)

# McNemar (paired binary)
mcnemar.test(tbl)

# expected frequencies
ct <- chisq.test(tbl)
ct$expected
ct$observed
ct$residuals                     # Pearson residuals

# association measures
library(vcd)
assocstats(tbl)

# visualize
library(ggplot2)
ggplot(df, aes(gender, fill = vote)) +
  geom_bar(position = "fill")

相関

Pearsonは線形相関を測定;Spearman/Kendallは単調(ランクベース)を測定 — 外れ値と非線形にロバスト。cor.testがp値とCIを与える。corrplotが行列を可視化;ggpairsが散布図と相関を表示。use='pairwise.complete.obs'で行全体をドロップせずに欠損データを処理。

r
# Pearson (linear, normal)
cor(x, y, method = "pearson")
cor.test(x, y, method = "pearson")

# Spearman (rank, non-parametric)
cor(x, y, method = "spearman")

# Kendall (rank, smaller samples)
cor(x, y, method = "kendall")

# correlation matrix
cor(mtcars[, c("mpg", "wt", "hp", "disp")])

# with p-values
library(psych)
corr.test(mtcars[, 1:6])

# visualize
library(corrplot)
M <- cor(mtcars)
corrplot(M, method = "circle", type = "lower", order = "hclust")

# GGally for scatterplot matrix
library(GGally)
ggpairs(mtcars[, c("mpg", "wt", "hp", "cyl")])

# handle missing
cor(df, use = "complete.obs")     # listwise deletion
cor(df, use = "pairwise.complete.obs")

ノンパラメトリック検定

ノンパラメトリック検定は正規性を仮定しません — サンプルが小さい、データが歪んでいる、順序データがある場合に使用。Mann-Whitney/Wilcoxonはt検定のランクベースの対応物。順列検定とブートストラップは計算集約的だが最小の仮定しか置かない。p値と並んで常に効果量を報告。

r
# Mann-Whitney U (alternative to two-sample t)
wilcox.test(x, y)
wilcox.test(x, y, paired = FALSE)

# Wilcoxon signed-rank (alternative to paired t)
wilcox.test(before, after, paired = TRUE)

# Kruskal-Wallis (alternative to one-way ANOVA)
kruskal.test(y ~ group, data = df)

# Friedman (alternative to repeated measures ANOVA)
friedman.test(y ~ group | subject, data = df)

# permutation test
library(coin)
oneway_test(y ~ group, data = df, distribution = approximate(nresample = 9999))

# bootstrap CI
library(boot)
boot_mean <- function(data, idx) mean(data[idx])
b <- boot(x, boot_mean, R = 10000)
boot.ci(b, type = "perc")

# sign test
library(BSDA)
SIGN.test(x, md = 5)              # median different from 5?
23

回帰分析

線形回帰

lmが線形モデルに適合。summaryが係数、標準誤差、t値、p値、R²、F統計量を表示。常に診断をチェック:線形性/等分散性の残差プロット、多重共線性のVIF(>5は懸念)。I()が数式の算術を保護;poly(x, 2)が直交多項式を与える。

r
# simple linear
model <- lm(mpg ~ wt, data = mtcars)
summary(model)
coef(model)
confint(model)
fitted(model)
residuals(model)
predict(model, newdata = new_df, interval = "confidence")

# multiple regression
model <- lm(mpg ~ wt + hp + cyl, data = mtcars)

# interactions
model <- lm(mpg ~ wt * hp, data = mtcars)
model <- lm(mpg ~ wt + hp + wt:hp, data = mtcars)

# transformations
model <- lm(log(mpg) ~ wt, data = mtcars)
model <- lm(mpg ~ poly(wt, 2), data = mtcars)  # quadratic
model <- lm(mpg ~ I(wt^2) + wt, data = mtcars)

# categorical predictors
model <- lm(mpg ~ factor(cyl), data = mtcars)

# diagnostics
plot(model)                       # 4 diagnostic plots
library(car)
vif(model)                        # variance inflation factor

一般化線形モデル

glmはlmを非正規応答に拡張。family=binomialでロジスティック(二値結果);family=poissonでカウント。ロジスティックではexp(coef)がオッズ比を与える。ネストモデルをanova(..., test='Chisq')で比較。過分散カウント(分散 > 平均)にはPoissonの代わりにglm.nb(負の二項)を使用。

r
# logistic regression (binary)
model <- glm(am ~ wt + hp, data = mtcars, family = binomial)
summary(model)

# predicted probabilities
predict(model, type = "response")

# odds ratios
exp(coef(model))

# Poisson regression (counts)
model <- glm(count ~ group, data = df, family = poisson)

# negative binomial (overdispersed counts)
library(MASS)
model <- glm.nb(count ~ group, data = df)

# compare models
model1 <- glm(y ~ x1, family = binomial, data = df)
model2 <- glm(y ~ x1 + x2, family = binomial, data = df)
anova(model1, model2, test = "Chisq")

# goodness of fit
1 - pchisq(model$deviance, model$df.residual)

# McFadden pseudo R²
1 - model$deviance / model$null.deviance

モデル選択

ステップワイズ選択は簡単だがバイアスあり — 全部分集合(regsubsets)または正則化(glmnet)を推奨。AIC/BICが適合と複雑さのバランス(低いほど良い)。交差検証が誠実なサンプル外推定を与える。glmnetでalpha=0はリッジ、alpha=1はラッソ(係数をゼロにでき — 特徴選択)。

r
# stepwise selection
full <- lm(mpg ~ ., data = mtcars)
step(full, direction = "both")
step(full, direction = "backward")
step(full, direction = "forward", scope = list(lower = ~1, upper = full))

# AIC and BIC
AIC(model1, model2)
BIC(model1, model2)

# all subsets
library(leaps)
leaps <- regsubsets(mpg ~ ., data = mtcars, nvmax = 10)
plot(leaps, scale = "adjr2")
plot(leaps, scale = "Cp")

# cross-validation
library(caret)
train(mpg ~ ., data = mtcars, method = "lm",
      trControl = trainControl(method = "cv", number = 10))

# regularized (glmnet)
library(glmnet)
X <- model.matrix(mpg ~ ., mtcars)[, -1]
y <- mtcars$mpg
cv_model <- cv.glmnet(X, y, alpha = 0)  # ridge
cv_model <- cv.glmnet(X, y, alpha = 1)  # lasso

混合効果モデル

混合効果モデルは相関データ(反復測定、クラスターサンプル)を処理。(1 | subject)は被験者ごとのランダム切片;(time | subject)はランダム傾きを追加。ネストグループは/を使用。交差グループは+を使用。連続にはlmer、非連続にはglmer。p値にはlmerTestを使用(lme4はデフォルトで計算しない)。

r
library(lme4)
library(lmerTest)

# random intercept
model <- lmer(score ~ treatment + (1 | subject), data = df)

# random intercept and slope
model <- lmer(score ~ time + (time | subject), data = df)

# nested random effects
model <- lmer(score ~ 1 + (1 | school/class), data = df)

# crossed random effects
model <- lmer(score ~ 1 + (1 | subject) + (1 | item), data = df)

# generalized mixed model
glmer(outcome ~ treatment + (1 | subject), data = df, family = binomial)

# summary and CIs
summary(model)
confint(model, method = "Wald")
confint(model, method = "boot")

# anova
anova(model)

# random effects
ranef(model)
VarCorr(model)

# predict
predict(model, newdata = df, allow.new.levels = TRUE)

診断と予測

常に診断をチェック:残差プロットが非線形性と不均一分散を明らかに;Cook's distance(>4/n)が影響点をフラグ;hatvalues(>2p/n)が高レバレッジ点をフラグ。predictでinterval='confidence'が平均、'prediction'が個別値(より広い)。ggeffectsが可視化用の限界効果を計算。

r
model <- lm(mpg ~ wt + hp, data = mtcars)

# diagnostic plots
par(mfrow = c(2, 2))
plot(model)
par(mfrow = c(1, 1))

# residuals vs leverage
plot(model, which = 5)

# influence measures
influence.measures(model)
hatvalues(model)                   # leverage
cooks.distance(model)              # Cook's distance
dfbeta(model)                      # change in coef per obs

# identify outliers
which(cooks.distance(model) > 4 / nrow(mtcars))

# predictions
new_data <- data.frame(wt = c(2, 3, 4), hp = c(100, 150, 200))
predict(model, newdata = new_data, interval = "confidence")
predict(model, newdata = new_data, interval = "prediction")

# marginal effects
library(ggeffects)
ggeffect(model, terms = "wt")
plot(ggeffect(model, terms = c("wt", "hp")))

# R squared variants
library(rsq)
rsq(model, type = "v")             # variance-based
rsq(model, type = "kl")            # Kullback-Leibler
24

R Markdownとレポート

R Markdownの基礎

R Markdownはナラティブ(Markdown)、コード(Rチャンク)、出力(テーブル/プロット)を結合します。YAMLヘッダーがメタデータと出力形式を設定。チャンクオプション:echo=FALSEがコードを非表示、include=FALSEが実行するがすべて非表示、fig.capがキャプションを追加。バッククォートのインラインrコードが散文に値を挿入。Knit(Ctrl+Shift+K)でレンダリング。

r
---
title: "Quarterly Report"
author: "Data Team"
date: "2024-12-31"
output: html_document
---

## Section

Inline code: the mean is `r mean(x)`.

```{r setup, include=FALSE}
knitr::opts_chunk$set(echo = FALSE, warning = FALSE, message = FALSE)
library(dplyr)
library(ggplot2)
data <- read_csv("data.csv")
```

```{r plot, fig.cap="Sales over time"}
ggplot(data, aes(date, sales)) + geom_line()
```

```{r table}
library(knitr)
kable(head(data), caption = "First 6 rows")
```

チャンクオプション

チャンクオプションがコード実行と出力を制御。cache=TRUEは再knitを高速化するが変更を隠す可能性 — キャッシュ無効化のためdependsonを設定。R Markdownはknitrエンジン経由で多くの言語をサポート:python、bash、sql、javascriptなど。Pythonにはreticulateパッケージを使用しRとPython間でオブジェクトを共有。

r
```{r chunk-name, options}
# code here
```

# Common options:
#   echo=FALSE       hide code, show output
#   eval=FALSE       show code, don't run
#   include=FALSE    run, hide code and output
#   results="hide"   show code, hide text output
#   warning=FALSE    hide warnings
#   message=FALSE    hide messages
#   fig.width=6      figure width (inches)
#   fig.height=4     figure height
#   fig.cap="..."    figure caption
#   fig.path="figs/" where to save figures
#   cache=TRUE       cache results (faster re-knits)
#   dependson="..."  cache dependencies
#   dev="svg"        output device

# global options
knitr::opts_chunk$set(
  echo = TRUE,
  fig.width = 6,
  fig.height = 4,
  fig.path = "figures/",
  cache = TRUE
)

# language engines
```{python}
# Python code
```
```{bash}
# Shell commands
```
```{sql, connection=db}
# SQL queries
```

出力形式

html_documentが最も柔軟(インタラクティブ、code_folding、ページテーブル)。pdf_documentはLaTeXが必要(tinytex::install_tinytex()でインストール)。word_documentはスタイリング用の参照docxを使用してWordファイルを生成。プレゼンテーションにはioslides(組み込み)またはrevealjs(より洗練)。複数の出力を一緒に指定可能。

r
---
output:
  html_document:
    toc: true
    toc_float: true
    toc_depth: 3
    number_sections: true
    theme: flatly
    highlight: tango
    code_folding: hide
    df_print: paged
---

---
output:
  pdf_document:
    toc: true
    number_sections: true
    fig_caption: true
    latex_engine: xelatex
---

---
output:
  word_document:
    toc: true
    reference_docx: template.docx
---

---
output:
  ioslides_presentation:
    widescreen: true
---

---
output:
  revealjs::revealjs_presentation:
    theme: solarized
    transition: slide
---

# Multiple formats
---
output:
  html_document: default
  pdf_document: default
---

kableとgtによるテーブル

kable + kableExtraはグループ化、条件付き書式、スタイリング付きの出版品質テーブルを生成。gtはよりグラフィックス文法スタイルの新しい代替。DTはソート、フィルタ、ページネーション付きのインタラクティブHTMLテーブルを作成 — HTMLレポートに最適。出力形式に基づいて選択(kableはどこでも動作;DTはHTMLのみ)。

r
library(knitr)
library(kableExtra)
library(gt)

# basic kable
kable(head(mtcars))

# styled kable
kable(head(mtcars), format = "html", caption = "Top cars") %>%
  kable_styling(bootstrap_options = c("striped", "hover", "condensed"),
                full_width = FALSE) %>%
  add_header_above(c(" " = 1, "Specs" = 3, "Performance" = 7))

# conditional formatting
kable(df) %>%
  cell_spec(value, color = ifelse(value > 0, "green", "red")) %>%
  row_spec(1, bold = TRUE, background = "yellow")

# gt (modern alternative)
mtcars %>%
  head() %>%
  gt() %>%
  tab_header(title = "MT Cars", subtitle = "First 6 rows") %>%
  cols_label(mpg = "MPG", cyl = "Cylinders") %>%
  tab_options(table.width = pct(80))

# DT for interactive tables
library(DT)
datatable(mtcars, filter = "top", options = list(pageLength = 5))

パラメータと自動化

paramsでレポートを再利用可能に — YAMLで定義、params$<name>でアクセス。rmarkdown::render()でカスタムパラメータでレンダリング。パラメータ値をループして複数レポートを生成(地域ごと、四半期ごとなど)。これが自動レポートパイプラインの基盤。定期レポートにはcron/スケジュール済みRと組み合わせ。

r
---
title: "Regional Sales Report"
output: html_document
params:
  region: "West"
  year: 2024
  data_file: "sales.csv"
---

## Report for `r params$region` in `r params$year`

```{r}
data <- read_csv(params$data_file) %>%
  filter(region == params$region, year == params$year)
```

# Render from R
rmarkdown::render("report.Rmd",
                  params = list(region = "East", year = 2024),
                  output_file = "east_2024.html")

# Render from command line
# Rscript -e 'rmarkdown::render("report.Rmd", params = list(region = "East"))'

# Loop over parameters
for (r in c("West", "East", "North")) {
  rmarkdown::render("report.Rmd",
                    params = list(region = r),
                    output_file = paste0(r, "_report.html"))
}
25

purrr関数型プログラミング

map族

mapはtidyverseのlapply — 常にリストを返す。map_dbl/chr/int/lglは型付きベクトルを返す(mapより安全)。map2とpmapは複数引数を並列に反復。walkは副作用(印刷、保存)用。.x代名詞は現在の要素を参照;pmapでは..1、..2などを使用。map_dfrはデータフレームを行でバインド。

r
library(purrr)

# map: list output
map(1:5, ~ .x^2)                  # list(1, 4, 9, 16, 25)
map_dbl(1:5, ~ .x^2)              # numeric vector
map_chr(1:5, ~ paste0("n", .x))   # character vector
map_int(1:5, ~ .x * 2L)           # integer vector
map_lgl(1:5, ~ .x > 3)            # logical vector
map_dfr(1:3, ~ data.frame(id = .x, val = .x^2))  # row-bound df
map_dfc(1:3, ~ data.frame(x = .x))               # col-bound df

# multiple arguments
map2(1:3, 4:6, ~ .x + .y)
pmap(list(1:3, 4:6, 7:9), ~ ..1 + ..2 + ..3)

# walk (for side effects)
walk(1:5, ~ print(.x))
walk(files, ~ process(.x))
walk2(plots, filenames, ~ ggsave(.y, .x))

# in pipelines
mtcars %>%
  split(.$cyl) %>%
  map(~ lm(mpg ~ wt, data = .x)) %>%
  map(summary) %>%
  map_dbl(~ .x$r.squared)

safelyとquietly

safelyは関数をラップしてスローする代わりに(result, error)を返す — 1つの失敗がすべてを止めないバッチ処理に不可欠。possiblyはデフォルト値を返す。quietlyはメッセージ/警告をキャプチャ。transposeはペアのリストをリストのペアに反転。insistentlyはバックオフで再試行 — 不安定なAPIに最適。

r
library(purrr)

# safely: capture errors without stopping
safe_log <- safely(log)
safe_log(10)                       # list(result = 2.3, error = NULL)
safe_log("a")                      # list(result = NULL, error = <error>)

# process many, keep going on error
results <- map(values, safely(my_function))
successes <- map(results, "result") %>% compact()
errors <- map(results, "error") %>% compact()

# quietly: capture messages/warnings
quiet_log <- quietly(log)
quiet_log(10)                      # list(result, output, warnings, messages)

# possibly: return default on error
safe_log <- possibly(log, otherwise = NA_real_)
map(values, safe_log)              # never errors

# transpose for cleaner output
results <- map(values, safely(fun)) %>% transpose()
results$result                     # all results
results$error                      # all errors

# insistently: retry on failure
slow_fn <- insistently(api_call, rate = rate_backoff())

reduceとaccumulate

reduceは要素をペアワイズで結合(foldlのように);accumulateは中間値を保持。多くのデータフレームを結合または累積計算を構築するのに便利。detect/findは最初のマッチを見つける;keep/discardがフィルタ。every/someが述語をテスト。これらはループを簡潔で構成可能な操作に置き換えます。

r
library(purrr)

# reduce: combine pairwise
reduce(c(1, 2, 3, 4), )        # 10
reduce(c(1, 2, 3, 4), ~ .x * .y)  # 24
reduce(list(df1, df2, df3), full_join, by = "id")

# accumulate: keep intermediate results
accumulate(c(1, 2, 3, 4), )    # 1 3 6 10
accumulate(c(2, 3, 4), ~ .x * .y) # 2 6 24

# right reduce
reduce(c(1, 2, 3, 4), , .dir = "backward")

# with init
reduce(c(1, 2, 3), ~ c(.x, .y), .init = numeric(0))

# detect
detect(1:10, ~ .x > 5)            # 6 (first match)
detect_index(1:10, ~ .x > 5)      # 6 (index)
detect(1:10, ~ .x > 5, .right = TRUE)  # 10 (last match)

# keep/discard
keep(1:10, ~ .x %% 2 == 0)        # 2 4 6 8 10
discard(1:10, ~ .x %% 2 == 0)     # 1 3 5 7 9
every(1:10, ~ .x > 0)             # TRUE
some(1:10, ~ .x > 5)              # TRUE

部分適用と合成

partialは引数を事前入力 — 一般的なものから専門的な関数を作成するのに便利。composeは関数をチェーン(右から左)。negateは述語を反転。liftは関数を引数のリストを取るように変換。imapはインデックスを第2引数とするmap2。これらのツールで関数合成をクリーンで読みやすく。

r
library(purrr)

# partial: pre-fill arguments
add_one <- partial(, 1)
add_one(5)                         # 6

round2 <- partial(round, digits = 2)
round2(3.14159)                    # 3.14

# compose: chain functions
clean_string <- compose(
  str_trim,
  str_to_lower,
  ~ str_replace_all(.x, "[^a-z]", " ")
)
clean_string("  Hello, World!  ")  # "hello  world "

# %>% (magrittr pipe) vs compose
# pipe: data %>% f %>% g %>% h
# compose: g %>% f creates a new function

# negate
is_missing <- negate(is.na)
is_missing(5)                      # TRUE

# lift (vectorize)
sum2 <- lift()                  # takes a list of 2 args
sum2(list(1, 2))                   # 3

# walk with index
imap(letters[1:3], ~ paste0(.y, ": ", .x))
# list("1: a", "2: b", "3: c")

リスト列とネストデータ

nest + map + unnestパターンはtidyverseのsplit-apply-combine。リスト列は任意オブジェクト(モデル、予測、サブデータ)を保持。データフレームのmapは列を反復。pluckはネスト要素を安全に抽出。modify_ifは述語にマッチする要素を変更し元の構造を保持。

r
library(purrr)
library(dplyr)
library(tidyr)

# nest data, fit models, predict
mtcars %>%
  group_by(cyl) %>%
  nest() %>%
  mutate(
    model = map(data, ~ lm(mpg ~ wt, data = .x)),
    rsq = map_dbl(model, ~ summary(.x)$r.squared),
    pred = map2(model, data, predict)
  )

# unnest predictions
mtcars %>%
  group_by(cyl) %>%
  nest() %>%
  mutate(model = map(data, ~ lm(mpg ~ wt, .x))) %>%
  mutate(pred = map2(model, data, predict)) %>%
  select(cyl, data, pred) %>%
  unnest(c(data, pred))

# apply function to each column
mtcars %>% map_dbl(mean)
mtcars %>% map_dbl(sd)

# apply to columns of specific type
iris %>%
  map_if(is.numeric, mean) %>%
  map_dbl(round, 2)

# pluck
list(a = list(b = list(c = 42))) %>% pluck("a", "b", "c")
# 42

# modify (returns same type)
modify_if(iris, is.numeric, ~ .x * 2)
modify_depth(nested_list, 2, ~ .x + 1)
26

Shinyアプリ

基本アプリ構造

Shinyアプリはui(レイアウト)とserver(ロジック)を持ちます。入力はinput$<id>から;出力はrender*関数経由でoutput$<id>へ。fluidPageが基本レイアウト;sidebarLayoutはサイドバー(コントロール)とメイン(出力)に分割。app.Rとして独自フォルダに保存;フォルダ名がアプリ名になります。

r
library(shiny)

ui <- fluidPage(
  titlePanel("Hello Shiny"),
  sidebarLayout(
    sidebarPanel(
      sliderInput("n", "Number of points", 1, 100, 50)
    ),
    mainPanel(
      plotOutput("scatter")
    )
  )
)

server <- function(input, output, session) {
  output$scatter <- renderPlot({
    plot(1:input$n, rnorm(input$n))
  })
}

shinyApp(ui, server)

# Save as app.R in a folder, run with:
#   shiny::runApp("path/to/folder")
# Or click "Run App" in RStudio

入力とリアクティビティ

Shinyには多くの入力コントロールがあります。observeEventは入力が変更された時コードを実行;eventReactiveはイベントからリアクティブ値を作成。reactive()は入力が変更されるまで結果をキャッシュ。reactiveVal/reactiveValuesはミュータブルな状態を保持。明示的トリガーにはactionButton + observeEventを使用(すべてのキーストロークに反応しない)。

r
library(shiny)

ui <- fluidPage(
  numericInput("n", "N", value = 10, min = 1, max = 100),
  textInput("label", "Label", value = "Data"),
  selectInput("color", "Color", choices = c("red", "blue", "green")),
  dateInput("date", "Date"),
  dateRangeInput("range", "Range"),
  checkboxInput("show", "Show?", value = TRUE),
  checkboxGroupInput("vars", "Variables", choices = names(mtcars)),
  radioButtons("dist", "Distribution",
               choices = c("Normal", "Uniform", "Exponential")),
  fileInput("file", "Upload CSV", accept = ".csv"),
  actionButton("go", "Go!")
)

server <- function(input, output, session) {
  # event-triggered
  observeEvent(input$go, {
    showNotification(paste("Clicked", input$go))
  })

  # reactive expression (cached)
  data <- reactive({
    rnorm(input$n)
  })

  # reactive value
  val <- reactiveVal(0)
  observeEvent(input$go, val(val() + 1))
}

出力とレンダリング

各出力タイプにマッチするrender*関数:プロットにrenderPlot、テーブルにrenderTable、コンソール出力にrenderPrint、文字列にrenderText、動的UIにrenderUI。出力はリアクティブ — 入力依存関係が変更されると再実行。DT::dataTableOutputがインタラクティブテーブルの標準。

r
library(shiny)
library(ggplot2)
library(DT)

ui <- fluidPage(
  plotOutput("plot", click = "plot_click"),
  tableOutput("table"),
  DT::dataTableOutput("dt"),
  verbatimTextOutput("summary"),
  textOutput("text"),
  uiOutput("dynamic")
)

server <- function(input, output, session) {
  output$plot <- renderPlot({
    ggplot(mtcars, aes(wt, mpg)) + geom_point()
  })

  output$table <- renderTable({
    head(mtcars)
  })

  output$dt <- DT::renderDataTable({
    datatable(mtcars, filter = "top")
  })

  output$summary <- renderPrint({
    summary(mtcars)
  })

  output$text <- renderText({
    paste("You clicked:", input$plot_click$x)
  })

  output$dynamic <- renderUI({
    if (input$n > 5) strong("Big!") else em("Small")
  })
}

# observe vs reactive
#   observe: side effects (output$ assignments, updates)
#   reactive: returns a value, used by other reactives

リアクティブプログラミング

reactive()が主力 — キャッシュされ遅延(読み取られる時のみ再計算)。observe()は即座(依存関係変更時に即実行) — 副作用用。eventReactiveはイベントを待機。reactiveValuesは複数のミュータブル値を保持(小さなリアクティブオブジェクトのように)。isolateは依存関係を作らずに値を読み取る。debounceが急速な入力変更をスロットル。

r
library(shiny)

server <- function(input, output, session) {
  # reactive: cached, lazy
  filtered <- reactive({
    mtcars %>% filter(cyl == input$cyl)
  })

  # observe: eager, side effects
  observe({
    updateSelectInput(session, "model",
                      choices = unique(filtered()$model))
  })

  # eventReactive: triggered by event
  result <- eventReactive(input$go, {
    run_analysis(input$params)
  })

  # reactiveVal: single mutable value
  counter <- reactiveVal(0)
  observeEvent(input$add, counter(counter() + 1))

  # reactiveValues: multiple values
  rv <- reactiveValues(data = NULL, status = "idle")
  observeEvent(input$load, {
    rv$data <- read.csv(input$file$datapath)
    rv$status <- "loaded"
  })

  # debounce (throttle rapid changes)
  search <- reactive({ input$search }) %>% debounce(500)

  # isolate (use value without dependency)
  output$plot <- renderPlot({
    plot(isolate(rv$data))  # not reactive on rv$data
  })
}

デプロイ

shinyapps.ioが最も簡単なホスティング(フリーティアあり)。自己ホスティングにはLinuxにShiny ServerをインストールまたはDocker(rocker/shinyイメージ)を使用。パフォーマンス:プロットをキャッシュ、長いタスクにasync(future/promises)を使用、render関数内でファイルを再読み込みしない。reactlog(Ctrl+F3)を有効にしてリアクティブグラフを可視化しデバッグ。

r
# Deploy to shinyapps.io
#   1. Create account at shinyapps.io
#   2. Install rsconnect
install.packages("rsconnect")
#   3. Authorize (paste token from shinyapps.io)
rsconnect::setAccountInfo(name = "<name>", token = "<token>", secret = "<secret>")
#   4. Deploy
rsconnect::deployApp("path/to/app")

# Run on local network
shiny::runApp("app.R", host = "0.0.0.0", port = 3838)

# Shiny Server (self-hosted on Linux)
# Install shiny-server, put apps in /srv/shiny-server/
# Config: /etc/shiny-server/shiny-server.conf
# Access at http://server:3838/appname

# Docker
#   FROM rocker/shiny
#   COPY app.R /srv/shiny-server/myapp/
#   EXPOSE 3838

# Performance tips:
#   - Use renderCachedPlot for expensive plots
#   - Use future + promises for async work
#   - Move heavy compute to reactive({...})
#   - Avoid re-reading files in render functions

# Debugging
options(shiny.fullstacktrace = TRUE)
options(shiny.reactlog = TRUE)  # press Ctrl+F3 in app
27

パフォーマンスとプロファイリング

profvisによるプロファイリング

profvisは現代のプロファイラ — 時間がどこで費やされたかを示すインタラクティブなフレームグラフを生成。広いバー(遅い操作)と高いスタック(深い呼び出しチェーン)を探す。現実的なデータサイズでプロファイリング;小さな入力はO(n²)問題を隠す。summaryRprofがbase Rの代替。最適化前に常にプロファイリング — 直感はしばしば間違っている。

r
library(profvis)

# profile a block of code
profvis({
  data <- read.csv("large.csv")
  result <- lapply(data, function(x) {
    x[x > 0]
  })
  plot(result)
})

# profile a function call
profvis(my_function(arg1, arg2))

# save profile
p <- profvis({ ... })
htmlwidgets::saveWidget(p, "profile.html")

# Rprof (base R)
Rprof("profile.out")
# ... code to profile ...
Rprof(NULL)
summaryRprof("profile.out")

# tips:
#   - profile realistic inputs (not too small)
#   - run multiple times for stable results
#   - look for the widest bars in the flame graph
#   - focus on hotspots, not micro-optimizations

ベクトル化

ベクトル化はRの最大のパフォーマンスレバー — ベクトル全体の操作はCにドロップするためループより10-100倍高速。事前割り当て(numeric(n))は第2の大きな改善 — ループ内でベクトルを成長させない。vapplyはsapplyより安全で高速(型付き出力)。rowMeans/colSumsは高度に最適化 — applyより使用。

r
# BAD: loop with growing result
slow <- function(n) {
  result <- numeric(0)
  for (i in 1:n) {
    result <- c(result, i^2)
  }
  result
}

# BETTER: preallocate
better <- function(n) {
  result <- numeric(n)
  for (i in 1:n) {
    result[i] <- i^2
  }
  result
}

# BEST: vectorize
fast <- function(n) {
  (1:n)^2
}

# benchmarks
microbenchmark::microbenchmark(
  slow = slow(1000),
  better = better(1000),
  fast = fast(1000),
  times = 10
)

# apply family
#   sapply/lapply: list apply
#   vapply: typed sapply (safer, faster)
#   map_dbl: tidyverse, typed
#   rowMeans/colSums: faster than apply(x, 1, mean)

ホットスポットにRcpp

RcppでRから呼び出し可能なC++関数を記述 — ベクトル化できないループに典型的に10-100倍高速。ワンライナーにcppFunction;ファイルにsourceCpp。Rcpp 'sugar'はベクトル化されたC++演算子を提供(x*2+1がベクトルで動作)。プロファイリングで特定されたホットスポットに使用、すべてにではない — Rのベクトル化操作はすでにC。

r
library(Rcpp)

# inline C++ in R
cppFunction('
  double sumC(NumericVector x) {
    double s = 0;
    for (int i = 0; i < x.size(); i++) {
      s += x[i];
    }
    return s;
  }
')
sumC(1:1e6)

# source from file
# file: code.cpp
# #include <Rcpp.h>
# using namespace Rcpp;
# // [[Rcpp::export]]
# double meanC(NumericVector x) {
#   return std::accumulate(x.begin(), x.end(), 0.0) / x.size();
# }

sourceCpp("code.cpp")

# use Rcpp sugar (vectorized C++)
cppFunction('
  NumericVector funC(NumericVector x) {
    return x * 2 + 1;  // vectorized via Rcpp sugar
  }
')

# data frames
cppFunction('
  DataFrame subsetC(DataFrame df, LogicalVector mask) {
    return df[mask];
  }
')

メモリとdata.table

data.tableは大規模データ(100万行以上)でdplyrより劇的に高速 — しばしば5-50倍。dt[i, j, by]構文は一度学べば簡潔。:=はインプレース修正(コピーなし) — 巨大なメモリ節約。setkeyが高速ルックアップと結合を有効化。fread/fwriteがRで最速のCSV読み書き。速度が重要な場合はdata.tableを使用;可読性にはdplyr。

r
library(data.table)

# data.table is much faster than data.frame for large data
dt <- fread("huge.csv")            # fast CSV reader
fwrite(dt, "out.csv")              # fast CSV writer

# syntax: dt[i, j, by]
dt[, mean(value), by = group]      # group by + summarize
dt[order(-value)]                  # sort
dt[value > 100, .N, by = group]    # filter + count by group
dt[, .(avg = mean(value), n = .N), by = group]

# reference semantics (no copy)
dt[, new_col := value * 2]         # add column in place
dt[value < 0, value := 0]          # modify in place

# keys for fast lookups
setkey(dt, id)
dt["abc"]                          # fast lookup
dt["abc", mult = "first"]

# joins
dt1[dt2, on = "id"]                # left join
dt1[dt2, on = .(id), nomatch = 0]  # inner join

# memory tips
#   - gc() to force garbage collection
#   - object.size(x) to check size
#   - rm() large objects when done
#   - read data in chunks for huge files

並列コンピューティング

parallel(base R)はポータブルだが冗長。future + furrrは現代的でtidyverseフレンドリーなアプローチ — plan()でバックエンドを切り替え。multicoreはフォークを使用(高速、Linux/Macのみ);multisessionは別Rセッションを使用(ポータブル、遅い)。並列化にはオーバーヘッドあり — 各タスクが>100msかかる場合のみ価値。前後で常にベンチマーク。

r
library(parallel)
library(future)
library(furrr)

# parallel lapply
cl <- makeCluster(detectCores() - 1)
result <- parLapply(cl, items, function(x) {
  # work on x
})
stopCluster(cl)

# future: modern, simpler
library(future)
plan(multisession)                 # parallel backend
result <- future_lapply(items, fun)
# or
plan(multicore)                    # fork (Linux/Mac, faster)
plan(cluster, workers = 4)

# furrr: parallel purrr
library(furrr)
plan(multisession, workers = 4)
result <- future_map(items, fun)
result <- future_map_dbl(items, ~ .x^2)

# foreach
library(foreach)
library(doParallel)
registerDoParallel(4)
result <- foreach(i = 1:10, .combine = c) %dopar% {
  i^2
}

# always benchmark — parallel overhead can outweigh gains
# for small tasks
microbenchmark::microbenchmark(
  serial = lapply(1:100, slow_fn),
  parallel = future_lapply(1:100, slow_fn),
  times = 5
)

Was this helpful?