ベクトルと基礎
変数、型と代入
Rは<-を推奨代入演算子として使用します(=も機能しますが関数引数で曖昧になる可能性があります)。Rのすべてはベクトルです — 単一の数値も長さ1のベクトルです。コアの型はcharacter、numeric(double)、integer、logical、complexです。NAは欠損データを表し、演算を通じて伝播します(スキップするにはna.rm=TRUEを使用)。NULLは値の不在(空のオブジェクト)で、NAとは異なります。分析前 に常にNAをチェックしてください。
# 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')になります)。
# 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のベクトル演算を高速にします。
# 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を提供します。
# 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はベクトル化三項演算子です — 連続変数からカテゴリ変数を作成するのに非常に便利です。
# 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"データ構造
リスト(異種コンテナ)
リストはRの最も柔軟なデータ構造です — 任意の型とサイズの要素を保持できます(PythonのdictやJavaScriptのオブジェクトのように)。$演算子は名前付きアクセスの便利なショートカットです。重要な区別:[ ]はサブリストを返し(まだリスト)、[[ ]]は実際の要素を抽出します。これがR初心者の最大の混乱源です。値自体が必要な場合は[[ ]]、サブセットが必要な場合は[ ]を使ってください。lapply/sapplyがリスト要素にわたって関数を適用します。
# 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+ではデフォルトです)。
# 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次元に拡張します。データ分析にはデータフレームを優先し、線形代数には行列を使ってください。
# 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()が頻度カウントを生成します。
# 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を優先してください。
# 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")制御フローと関数
If / Else と Switch
Rのif/elseは複数行の本体にブレースが必要で、elseは閉じブレースと同じ行になければなりません(そうでないとRはifが完了したとみなします)。ifelse(test, yes, no)はベクトル化されています — ベクトル全体に一度に適用され、結果のベクトルを返します。複数条件にはdplyrのcase_whenがネストしたifelseよりはるかにクリーンです。switchは文字列(または数値位置)でディスパッチします — 長いif-elseチェーンのクリーンな代替です。
# 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ファミリーを優先してください。
# 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は遅延評価を使用します:引数は最初に使用された時にのみ評価されるため、未使用の引数はエラーを起こしません。複数の値を返すにはリストにパッケージ化します。
# 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()ファミリーを提供します。
# 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がワークスペースで、続いてアタッチされたパッケージです。
# 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)データ操作(dplyrとtidyr)
dplyr:Filter、Select、Arrange
dplyr(tidyverseの一部)はSQL操作を反映する直感的なデータ操作動詞を提供します。filterが条件で行を選択し、selectが列を選び、arrangeがソートします。%in%演算子がメンバーシップをテストします。starts_with、ends_with、containsのようなヘルパー関数が列選択を柔軟にします。rename()がコピーなしで列名を変更します。これらの動詞はパイプ(%>%)で合成し読みやすいデータパイプラインを作ります。dplyrは内部でC++を使用するため大きなデータでbase 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 columnsdplyr:Mutate、Summarize、Group By
mutateが列を追加または変更します(ベクトル化)。summarizeが各グループを単一のサマリー行に削減します。group_by + summarizeの組み合わせが強力です — SQLのGROUP BYに相当。n()が行をカウントし、across()が複数列にわたって関数を適用します(dplyr 1.0の新機能)。ウィンドウ関数(rank、cumsum、lag、lead)がグループ内で動作し、「部門内ランク」のような計算を可能にします。パイプ%>%が操作を左から右にチェーンし、複雑なパイプラインを読みやすくします。
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+はネイティブパイプ|>を持ちますが、柔軟性に劣ります(ドットプレースホルダーなし)。パイプはデータラングリングコードを劇的に読みやすくします。
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()よりはるかに高速です。予期しない重複をキャッチするため、結合前後に常に行数をチェックしてください。
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と合成し強力なデータパイプラインを作ります。
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")統計とモデリング
記述統計
summary()は任意のデータの概要を得る最速の方法です — min、四分位数、中央値、平均、maxを表示します。sd()とvar()がサンプル(n-1)統計を計算します。cor()が線形関連(Pearson)またはランク関連(Spearman)を測定します。NAを含む実データには常にna.rm=TRUEを使用してください。データフレームの場合、summary(df)が列ごとの統計を与えます。psychとHmiscパッケージが拡張記述統計を提供します。
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 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を持つリストを返し、プログラム的に抽出できます。
# 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プロット)を調べてください。
# 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(正則化)。
# 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プロット(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プロットは命令型です — ステップバイステップで構築します。
# 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)) # resetggplot2:グラフィックスの文法
ggplot2(tidyverse)はグラフィックスの文法を実装します:プロットは+で組み合わされるレイヤー(データ、美学、幾何、スケール、ファセット、テーマ)から構築されます。aes()がデータ列を視覚的プロパティ(x、y、color、size)にマッピングします。各geom_*がレイヤーを追加します:geom_point(散布図)、geom_line、geom_bar、geom_histogram、geom_boxplot、geom_smooth(トレンドライン)。このレイヤー化アプローチにより複雑なプロットを段階的に構築できます。base Rと異なり、ggplot2は宣言型です — 描き方ではなく何を欲しいかを記述します。