재현가능한 과학적 분석을 위한 R

제어 흐름

학습 목표

  • ifelse 를 갖는 조건문을 작성한다.
  • for 루프를 이해하고 작성한다.

종종 코딩할 때, 동작 흐름을 제어하고 싶을 때가 있다. 한 조건 혹은 조건 집합이 만족될 때만 동작이 일어나게 설정함으로써 이런 작업을 수행한다. 또 다른 방식으로, 특정 횟수만큼 동작이 일어나도록 설정할 수도 있다.

R에서 흐름을 제어하는 방식이 몇가지 있다. 조건문에 대해서, 가장 흔히 사용되는 접근법이 루프 구성체(loop construct)다:

# if
if (condition is true) {
  perform action
}

# if ... else
if (condition is true) {
  perform action
} else {  # that is, if the condition is false,
  perform alternative action
}

예를 들어, 변수 x가 특정값을 갖게 되면, 메시지를 출력하게 R에게 지시할 수도 있다:

# sample a random number from a Poisson distribution
# with a mean (lambda) of 8

x <- rpois(1, lambda=8)

if (x >= 10) {
  print("x is greater than or equal to 10")
}

x
[1] 8

옆사람과 동일하지 않는 출력결과를 같지 않음에 주목한다. 왜냐하면, 동일한 분포에서 다른 난수가 표집되어서 그렇다.

초기 숫자를 설정해서 모두 동일한 ’의사 난수’를 생성하고 나서, 더 많은 정보를 출력하자:

x <- rpois(1, lambda=8)

if (x >= 10) {
  print("x is greater than or equal to 10")
} else if (x > 5) {
  print("x is greater than 5")
} else {
  print("x is less than 5")
}
[1] "x is greater than 5"

중요: R이 if 문 내부 조건을 평가할 때, 논리적 요소를 찾는다, 즉 TRUE 혹은 FALSE. 이것이 초보자에게 두통을 유발할 수 있다. 예를 들어:

x  <-  4 == 3
if (x) {
  "4 equals 3"
}

실행하면, 벡터 xFALSE 라서 메시지가 출력되지 않는다.

x <- 4 == 3
x
[1] FALSE

도전과제 1

if 문을 사용해서 gapminder 데이터셋에서 2002 년부터 어떤 레코드가 있는지 보고하도록 메시지를 출력하게 한다. 2012 년에 대해서도 동일한 작업을 수행한다.

다음과 같은 경고 메시지가 나오는 사람이 있나요?

Warning in if (gapminder$year == 2012) {: length > 1 이라는 조건이 있고, 첫
번째 요소만이 사용될 것입니다

작성한 조건문이 하나 이상 논리 요소를 갖는 벡터를 평가하게 되면, if 함수는 쭉 실행되지만, 첫번째 요소에 대한 조건만 평가한다. 따라서, 조건문이 길이 1 이 되도록 확실히 할 필요가 있다.

반복 연산자

값 집합에 반복 작업을 수행하고, 반복 순서가 중요하고, 각각에 대해 동일한 연산을 수행하는 경우, for 루프가 해당 작업을 실행한다. 앞선 쉘 수업에서 for 루프를 살펴봤다. for 루프는 가장 유연한 루프를 도는 연산이지만, 유연성으로 인해 올바르게 사용하기 가장 어렵기도 하다. 반복 순서가 중요하지 않는 경우, for 루프 사용을 회피한다: 즉, 반복할 때마다 연산작업은 이전 반복작업 결과에 의존하게 된다.

for 루프에 대한 기본 구조는 다음과 같다:

for(iterator in set of values){
  do a thing
}

예를 들어:

for(i in 1:10){
  print(i)
}
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10

1:10 은 즉석에서 벡터를 생성한다; 또한, 다른 벡터도 반복할 수 있다.

두개 이상을 반복하는데 또다른 for 루프를 중첩할 수도 있다.

for (i in 1:5){
  for(j in c('a', 'b', 'c', 'd', 'e')){
    print(paste(i,j))
  }
}
[1] "1 a"
[1] "1 b"
[1] "1 c"
[1] "1 d"
[1] "1 e"
[1] "2 a"
[1] "2 b"
[1] "2 c"
[1] "2 d"
[1] "2 e"
[1] "3 a"
[1] "3 b"
[1] "3 c"
[1] "3 d"
[1] "3 e"
[1] "4 a"
[1] "4 b"
[1] "4 c"
[1] "4 d"
[1] "4 e"
[1] "5 a"
[1] "5 b"
[1] "5 c"
[1] "5 d"
[1] "5 e"

결과를 바로 출력하는 대신에, 루프 출력결과를 새로운 객체에 대입할 수도 있다.

output_vector <- c()
for (i in 1:5){
  for(j in c('a', 'b', 'c', 'd', 'e')){
    temp_output <- paste(i, j)
    output_vector <- c(output_vector, temp_output)
  }
}
output_vector
 [1] "1 a" "1 b" "1 c" "1 d" "1 e" "2 a" "2 b" "2 c" "2 d" "2 e" "3 a"
[12] "3 b" "3 c" "3 d" "3 e" "4 a" "4 b" "4 c" "4 d" "4 e" "5 a" "5 b"
[23] "5 c" "5 d" "5 e"

이러한 접근법은 유용할 수도 있지만, ‘실행결과를 키워나감’ (실행결과 객체를 점진적으로 키워나감) 이런 전략은 컴퓨터 계산 측면으로 보면 비효율적이다. 그래서, 많은 값을 반복할 때는 회피한다.

더 좋은 방식은 값을 채워넣기 전에 (빈) 출력결과를 저장할 객체를 정의하는 것이다. 이번 예제의 경우, 더 많은 관련성이 보이지만, 여전히 더 효율적이다.

output_matrix <- matrix(nrow=5, ncol=5)
j_vector <- c('a', 'b', 'c', 'd', 'e')
for (i in 1:5){
  for(j in 1:5){
    temp_j_value <- j_vector[j]
    temp_output <- paste(i, temp_j_value)
    output_matrix[i, j] <- temp_output
  }
}
output_vector2 <- as.vector(output_matrix)
output_vector2
 [1] "1 a" "2 a" "3 a" "4 a" "5 a" "1 b" "2 b" "3 b" "4 b" "5 b" "1 c"
[12] "2 c" "3 c" "4 c" "5 c" "1 d" "2 d" "3 d" "4 d" "5 d" "1 e" "2 e"
[23] "3 e" "4 e" "5 e"

도전과제 2

output_vectoroutput_vector2 객체를 비교하라. 두 객체는 동일한가? 만약 동일하지 않나면, 왜 동일하지 않을까? 코드 마지막 블록을 변경해서 어떻게 output_vector2output_vector 와 같도록 만들 수 있을까?

도전과제 3

gapminder 데이터 루프를 대륙별로 돌리는 스크립트를 작성한다. 그리고 나서 평균 기대수명이 50년 보다 길거나 짧은지 결과를 출력한다.

도전과제 4

도전과제 3 에서 나온 스크립트를 변경해서 각 국각별로 루프를 돌린다. 이번에는 예상수명이 50 년보다 짧은지, 50 년에서 70 년 사이, 70 년 이상인지 결과를 출력한다.

도전과제 5 - Advanced

gapminder 데이터셋에서 각 국가별로 루프를 돌리는 스크립트를 작성한다. 국가명이 ‘B’ 로 시작하는지 테스트하고, 만약 평균 기대수명이 50 년보다 작은 경우 선그래프로 시간에 대해 기대수명을 그래프를 그린다.