벡터와 리스트
R을 다른 언어와 구별 짓는 가장 큰 특징을 하나만 꼽으라면 벡터화 연산입니다. Python이나 Java에서 배열의 각 원소에 연산을 적용하려면 반복문을 써야 합니다. R에서는 그냥 연산자를 쓰면 됩니다. 벡터를 이해하는 순간 R의 사고방식이 보입니다.
벡터 만들기
c()는 combine의 약자로, 값들을 묶어 벡터를 만듭니다.
# 파일: 02_vector_list.R
scores <- c(85, 92, 78, 95, 88)
names <- c("Alice", "Bob", "Charlie", "Dana", "Eve")
flags <- c(TRUE, FALSE, TRUE, TRUE, FALSE)
length(scores) # 5 — 원소 개수
class(scores) # "numeric"
R에서는 단일 값도 사실 길이 1짜리 벡터입니다. x <- 42는 숫자 하나를 담은 벡터를 만드는 것과 같습니다.
수열을 만들 때는 : 연산자나 seq()를 씁니다.
1:10 # 1 2 3 4 5 6 7 8 9 10
seq(0, 1, by = 0.2) # 0.0 0.2 0.4 0.6 0.8 1.0
seq(1, 10, length.out = 5) # 1.00 3.25 5.50 7.75 10.00
rep(0, times = 5) # 0 0 0 0 0
rep(c(1, 2), times = 3) # 1 2 1 2 1 2
rep(c(1, 2), each = 3) # 1 1 1 2 2 2
벡터화 연산 — R의 핵심
벡터화 연산은 R의 가장 강력한 특징입니다. 벡터에 연산자를 적용하면 각 원소에 개별적으로 적용됩니다. 반복문 없이 말입니다.
scores <- c(85, 92, 78, 95, 88)
scores + 5 # 90 97 83 100 93 — 모든 점수에 5 더하기
scores * 2 # 170 184 156 190 176
scores > 90 # FALSE TRUE FALSE TRUE FALSE
scores >= 85 # TRUE TRUE FALSE TRUE TRUE
두 벡터끼리 연산도 원소별로 이루어집니다.
math <- c(80, 90, 70)
english <- c(85, 75, 95)
math + english # 165 165 165 — 각 과목 합산
math - english # -5 15 -25
(math + english) / 2 # 82.5 82.5 82.5 — 평균
이것이 Python에서 NumPy 배열이 하는 일과 같습니다. 하지만 R은 NumPy 없이 기본적으로 이 동작을 합니다.
재활용 규칙 (Recycling)
길이가 다른 벡터끼리 연산할 때, 짧은 쪽이 반복됩니다.
c(1, 2, 3, 4, 5, 6) + c(10, 20)
# 내부적으로: c(1, 2, 3, 4, 5, 6) + c(10, 20, 10, 20, 10, 20)
# 결과: 11 22 13 24 15 26
길이가 배수 관계가 아니면 경고가 출력됩니다. 의도한 것인지 항상 확인하는 것이 좋습니다.
벡터화 함수
R의 내장 함수도 대부분 벡터화되어 있습니다.
scores <- c(85, 92, 78, 95, 88)
sum(scores) # 438
mean(scores) # 87.6
max(scores) # 95
min(scores) # 78
sd(scores) # 표준편차
var(scores) # 분산
median(scores) # 중앙값: 88
cumsum(scores) # 누적합: 85 177 255 350 438
인덱싱 — 원소 꺼내기
R의 인덱스는 1부터 시작합니다. 대부분의 언어가 0부터 시작하는 것과 다릅니다.
scores <- c(85, 92, 78, 95, 88)
scores[1] # 85 — 첫 번째 원소
scores[3] # 78 — 세 번째 원소
scores[c(1, 3)] # 85 78 — 1번과 3번
scores[2:4] # 92 78 95 — 2번부터 4번까지
scores[-1] # 92 78 95 88 — 1번 제외 (음수 인덱스)
scores[-c(1,2)] # 78 95 88 — 1번과 2번 제외
논리 벡터로 인덱싱하면 조건에 맞는 원소만 추출합니다.
scores <- c(85, 92, 78, 95, 88)
scores[scores >= 90] # 92 95 — 90점 이상
scores[scores > 80 & scores < 93] # 85 92 88
# which() — 조건을 만족하는 인덱스 반환
which(scores >= 90) # 2 4
which.max(scores) # 4 — 최댓값의 인덱스
which.min(scores) # 3 — 최솟값의 인덱스
네임드 벡터
원소에 이름을 붙이면 이름으로 접근할 수 있습니다.
scores <- c(math = 85, english = 92, science = 78)
scores["math"] # math: 85
scores[c("math", "science")] # math: 85, science: 78
names(scores) # "math" "english" "science"
# 이름 변경
names(scores)[1] <- "수학"
scores # 수학: 85, english: 92, science: 78
벡터 수정
인덱싱으로 원소를 수정하거나 추가합니다.
scores <- c(85, 92, 78)
scores[2] <- 95 # 2번 수정
scores # 85 95 78
scores[4] <- 88 # 4번 추가 (길이 자동 확장)
scores # 85 95 78 88
scores[c(1, 3)] <- 0 # 여러 원소 동시 수정
scores # 0 95 0 88
문자 벡터와 집합 연산
fruits <- c("사과", "바나나", "딸기", "사과", "포도")
unique(fruits) # "사과" "바나나" "딸기" "포도"
duplicated(fruits) # FALSE FALSE FALSE TRUE FALSE
table(fruits) # 빈도표
# 집합 연산
a <- c(1, 2, 3, 4, 5)
b <- c(3, 4, 5, 6, 7)
union(a, b) # 합집합: 1 2 3 4 5 6 7
intersect(a, b) # 교집합: 3 4 5
setdiff(a, b) # 차집합: 1 2
4 %in% a # TRUE — 포함 여부
리스트 — 다양한 타입을 담는 그릇
벡터는 같은 타입의 값만 담을 수 있습니다. 다른 타입을 섞으면 하나의 타입으로 강제 변환됩니다. 여러 타입을 함께 담으려면 리스트를 씁니다.
person <- list(
name = "Alice",
age = 30,
scores = c(85, 92, 78),
is_student = FALSE
)
class(person) # "list"
length(person) # 4
리스트 접근
리스트에 접근하는 방법은 세 가지입니다.
person <- list(name = "Alice", age = 30, scores = c(85, 92, 78))
# 1. [[ ]] — 원소 하나를 꺼냄 (타입 유지)
person[["name"]] # "Alice"
person[[1]] # "Alice"
# 2. $ — 이름으로 접근 (가장 많이 씀)
person$name # "Alice"
person$scores # 85 92 78
person$scores[2] # 92 — 리스트 안의 벡터 접근
# 3. [ ] — 부분 리스트 반환 (리스트로 반환)
person["name"] # 리스트 형태로 반환
class(person["name"]) # "list"
class(person[["name"]]) # "character"
[[]]와 []의 차이가 처음에는 혼란스럽습니다. [[]]는 원소 자체를, []는 원소를 담은 리스트를 반환합니다. 실제 값을 쓰려면 [[]]나 $를 씁니다.
리스트 수정과 추가
person <- list(name = "Alice", age = 30)
# 수정
person$age <- 31
person[["name"]] <- "Alicia"
# 추가
person$city <- "서울"
person[["email"]] <- "[email protected]"
# 제거
person$city <- NULL
중첩 리스트
리스트 안에 리스트를 담을 수 있습니다. JSON과 비슷한 구조입니다.
team <- list(
leader = list(name = "Alice", age = 30),
members = list(
list(name = "Bob", age = 25),
list(name = "Charlie", age = 28)
)
)
team$leader$name # "Alice"
team$members[[1]]$name # "Bob"
team$members[[2]]$age # 28
벡터와 리스트 변환
v <- c(1, 2, 3)
l <- as.list(v) # 리스트로 변환
l <- list(1, 2, 3)
v <- unlist(l) # 벡터로 변환 (타입이 맞아야 함)
unlist()는 중첩 리스트도 납작하게 펼쳐줍니다. 데이터 정제 과정에서 자주 활용됩니다.