iBetter Books
수정

벡터와 리스트

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()는 중첩 리스트도 납작하게 펼쳐줍니다. 데이터 정제 과정에서 자주 활용됩니다.