iBetter Books
수정

조건문과 반복문

R의 조건문과 반복문은 다른 언어와 크게 다르지 않습니다. 그런데 데이터 분석을 하다 보면 반복문을 의외로 많이 쓰지 않게 됩니다. 벡터화 연산과 ifelse() 같은 함수가 반복문의 역할을 대신하기 때문입니다. 구조를 익히고, 언제 반복문 대신 벡터화를 써야 하는지도 함께 살펴봅니다.

if / else 조건문

기본 형태는 익숙합니다.

# 파일: 03_control_flow.R
score <- 85

if (score >= 90) {
  grade <- "A"
} else if (score >= 80) {
  grade <- "B"
} else if (score >= 70) {
  grade <- "C"
} else {
  grade <- "F"
}

cat("학점:", grade, "\n")   # 학점: B

조건이 하나이고 짧다면 중괄호 없이 한 줄로 쓸 수 있습니다.

if (score >= 60) cat("합격\n") else cat("불합격\n")

R에서 if는 값을 반환합니다. 조건에 따라 다른 값을 변수에 담을 수 있습니다.

status <- if (score >= 60) "합격" else "불합격"
status  # "합격"

주의 — 벡터에 if를 쓸 때

if는 길이 1짜리 조건만 받습니다. 벡터를 조건으로 넣으면 첫 번째 원소만 평가하고 경고가 나옵니다.

scores <- c(85, 45, 92, 60, 75)

# 이렇게 하면 안 됩니다
if (scores >= 60) {
  cat("합격")
}
# Warning: 조건의 길이가 1보다 큽니다 — 첫 번째 원소만 사용됩니다

벡터의 각 원소에 조건을 적용하려면 ifelse()를 씁니다.

ifelse() — 벡터화 조건문

ifelse(조건, 참일 때 값, 거짓일 때 값) 형태로 씁니다. 벡터 전체에 한 번에 조건을 적용합니다.

scores <- c(85, 45, 92, 60, 75)

result <- ifelse(scores >= 60, "합격", "불합격")
result
# "합격" "불합격" "합격" "합격" "합격"

ifelse()는 중첩할 수 있습니다.

scores <- c(95, 85, 75, 65, 55)

grade <- ifelse(scores >= 90, "A",
          ifelse(scores >= 80, "B",
           ifelse(scores >= 70, "C",
            ifelse(scores >= 60, "D", "F"))))

grade  # "A" "B" "C" "D" "F"

중첩이 깊어지면 읽기 어려워집니다. 이런 경우 dplyr::case_when()이 더 명확합니다. 이는 PART 03에서 자세히 다룹니다.

for 반복문

fruits <- c("사과", "바나나", "딸기")

for (fruit in fruits) {
  cat(fruit, "맛있다\n")
}
# 사과 맛있다
# 바나나 맛있다
# 딸기 맛있다

인덱스가 필요하면 seq_along()을 씁니다.

scores <- c(85, 92, 78)

for (i in seq_along(scores)) {
  cat(i, "번째 점수:", scores[i], "\n")
}
# 1 번째 점수: 85
# 2 번째 점수: 92
# 3 번째 점수: 78

1:length(scores) 대신 seq_along()을 쓰는 이유는 안전 때문입니다. 빈 벡터가 들어왔을 때 1:0c(1, 0)을 반환하지만, seq_along(c())는 빈 정수 벡터를 반환합니다.

결과를 누적할 때

반복문으로 결과를 쌓을 때는 미리 결과 벡터를 만들어두는 것이 빠릅니다.

n <- 1000
result <- numeric(n)   # 길이 1000짜리 0 벡터 미리 할당

for (i in 1:n) {
  result[i] <- i ^ 2
}

매 반복마다 c(result, 새값) 형태로 이어 붙이면 데이터가 커질수록 느려집니다. 미리 크기를 정해두는 것이 좋습니다.

while 반복문

조건이 TRUE인 동안 반복합니다.

count <- 1

while (count <= 5) {
  cat(count, " ")
  count <- count + 1
}
# 1  2  3  4  5

종료 조건을 잊으면 무한 루프가 됩니다. RStudio에서는 콘솔 상단의 정지 버튼으로 멈출 수 있습니다.

repeat와 break

repeat는 무조건 반복하고, 내부에서 break로 탈출합니다.

x <- 1

repeat {
  cat(x, " ")
  x <- x + 1
  if (x > 5) break
}
# 1  2  3  4  5

next — 다음 반복으로 건너뛰기

Python의 continue에 해당합니다.

for (i in 1:10) {
  if (i %% 2 == 0) next   # 짝수는 건너뜀
  cat(i, " ")
}
# 1  3  5  7  9

반복문 vs 벡터화 — 언제 무엇을 쓸까

R에서 반복문은 느립니다. 가능하면 벡터화 연산을 씁니다.

scores <- c(85, 45, 92, 60, 75)

# 반복문으로 합격 여부 판단 — 느리고 장황함
result_loop <- character(length(scores))
for (i in seq_along(scores)) {
  result_loop[i] <- if (scores[i] >= 60) "합격" else "불합격"
}

# 벡터화로 — 빠르고 간결함
result_vec <- ifelse(scores >= 60, "합격", "불합격")

다음 표를 참고하면 선택이 쉬워집니다.

상황 권장 방식
벡터의 각 원소에 같은 연산 벡터화 연산
조건 분기 (원소별) ifelse()
여러 그룹에 함수 적용 sapply(), lapply()
복잡한 순차 처리 for / while

apply 계열 함수

반복문 대신 apply() 계열 함수를 쓰면 더 R다운 코드가 됩니다.

scores_list <- list(
  alice   = c(85, 92, 78),
  bob     = c(70, 65, 80),
  charlie = c(95, 88, 91)
)

# lapply — 리스트를 받아 리스트 반환
avg_list <- lapply(scores_list, mean)

# sapply — 리스트를 받아 벡터/행렬 반환
avg_vec <- sapply(scores_list, mean)
avg_vec
# alice   bob charlie
# 85.000 71.667 91.333

sapply()lapply()의 결과를 자동으로 단순화해줍니다. 반환 타입이 일정하면 벡터로, 불일정하면 리스트로 반환합니다.

# 각 점수에 함수 적용
sapply(1:5, function(x) x ^ 2)
# 1  4  9 16 25

# 여러 값 반환
sapply(scores_list, function(x) c(평균 = mean(x), 최고 = max(x)))
#       alice  bob charlie
# 평균 85.000 71.667 91.333
# 최고 92.000 80.000 95.000

apply() 계열은 PART 03에서 tidyverse의 purrr 패키지와 함께 더 자세히 다룹니다.