3  R - 조건문, 반복문, 함수를 이용한 프로그래밍

Acknowledgement: 본 절의 구성에는 다음 교재를 발췌하였다.

1 < 0                               # FALSE
[1] FALSE
1 > 0                               # TRUE
[1] TRUE
a = 3 
a > 2                               # TRUE
[1] TRUE
a < 2                               # FALSE
[1] FALSE
a <= 2                              # FALSE
[1] FALSE
a == 3                  # a는 3과 같다      # TRUE
[1] TRUE
a != 3                  # a는 3과 같지 않다   # FALSE
[1] FALSE

3.1 조건문(if/else)

  • if/else문의 기본 문법은 다음과 같다.
if ( condition )
{
    expression
    ....
}

if ( condition )
{
    expression 1
    ....
} else      # }과 else 사이에 엔터키가 있        # 으면 안됨
{
    expression 2
    ....
}
  • 만약 condition이 참이면 expression을 실행하라. (거짓이면 실행하지 않음)

  • 만약 condition이 참이면 expression 1을 실행하고, 그렇지 않으면 expression 2를 실행하라.

  • condition에는 참/거짓을 판별할 수 있는 문장을 적는다.

    • e.g.) a == 3, 4 > b, a >= b, a < b, a < 0, …
  • expression에는 실행하고 싶은 명령어들을 자유롭게 입력한다. 아무거나 해도 된다. 몇 줄이 되어도 상관 없다. 새 변수를 정의해서 더하고 빼고 벡터를 새로 만들어서 그걸 또… 등등. if구문 안에 또 if를 집어넣는 것 또한 당연히 된다.

  • 조건에 쓰이는 괄호와 실행문에 쓰이는 중괄호는 (일단은) 반드시 필요하다(고 하자).

  • ( condition )과 중괄호 { 사이에 절대로 세미콜론을 쓰면 안 된다.

3.1.1 예제 1. 두 수의 일치/불일치 여부 판별

a = 3
b = 3 

if ( a == b )
{
    c = 1           
} else
{
    c = 0
}

c 
[1] 1
  • a와 b의 값을 바꾸어 가며 c의 값이 어떻게 달라지는지 실험해 보라.

3.2 반복문(for)

  • 다음 예제를 이용하여 설명한다.

3.2.1 예제 2. 성분 10개짜리 벡터에 차례대로 홀수 저장하기>

  • 물론 다음 코드는 rep(1, 19, 2) 를 입력한 결과와 완벽하게 똑같지만, for를 익히기 위해 잊자.
a = rep(0, 10)              # 변수 초기화

for (i in 1:10)
{
    a[i] = 2*i - 1          # a[i] : 벡터 a의 i번째 성분값
}

a 
 [1]  1  3  5  7  9 11 13 15 17 19
  • i에 1, 2, 3, … , 10이 차례대로 대입되면서 중괄호 안의 명령을 반복적으로 수행한다.

  • 꼭 대입될 변수의 이름이 i일 필요는 없다. 아무 변수나, 이름짓는 규칙만 만족한다면 OK. 보통 수학에서의 관습을 따라 i, j, k, … 등을 흔히 쓴다.

  • 1:10은 아까 벡터 소개 부분에서도 언급했듯 1부터 10까지 차례대로 나열된 벡터 c(1, 2, …, 10)를 뜻한다. 그 말은, 위의 for 구문을 입력할 때 (i in 2:19), (i in c(5, 1, 3)), … 같은 형태가 모두 가능하다는 뜻이다.

  • if구문의 {} 내부와 마찬가지로, for 구문의 {} 내부에도 아무거나 자유롭게 입력할 수 있다.

  • 문법을 정리하면 다음과 같다.

for ( 변수 in 변수가 차례대로 취할 수들을 나열한 벡터 )
{
    expression
}
  • 중괄호 안에 반드시 i(반복하여 대입되는 문자)가 쓰여야 하는 건 아니다. 예를 들면,

3.2.2 예제 3. for 구문을 이용하여 (2k + 1) 수열의 10번째 항 구하기

a = 1               # 변수 초기화. 지금은 수열의 0번째 항 저장

for (i in 1:10)
{
    a = a + 2
}

a
[1] 21
  • for 구문을 잘 들여다 보면, 한 번 반복 수를 정하면(위에서는 i=1부터 i=10까지 10번) 그 횟수만큼은 꼭 루프를 돌아야 할 것 같다. 때에 따라서는 “평상시에는 정상적인 반복문을 수행하되 만약 어떤 일이 생기면 반복을 그만 두어라” 라고 명령하고 싶어질 수도 있다. 그럴 때는 break를 이용한다.

3.2.3 예제 4. (2^k - 1) 수열에서 10^10보다 작은 마지막 항과 그 항의 값 알아내기

maxiter = 10000

for (i in 1:maxiter)
{
    a = 2^i - 1 
    if (a > 10^10)
    {
        res.index = i - 1 
        res.value = 2^(res.index) - 1 
        break 
    }
}

res.index 
[1] 33
res.value 
[1] 8589934591

3.3 함수(function)

  • 자주 쓰이는 알고리즘을 코드에서 반복적으로 입력하는 것보다는, 함수의 형태로 짜 놓았다가 호출하는 것이 더 효율적이다. 가령, (3 + 3 - 1)^2, (-6 + 10 - 1)^2, … 등의 값이 자꾸 필요한 상황이라면, 이것들을 일일이 입력하는 것보다는 F(x1, x2) = (x1 + x2 - 1)^2 라고 R에게 알려주고 F(3, 3), F(-6, 10)… 등을 이용하는 것이 편하다.

  • 다음을 입력하고 실행해 보라.

F = function(x1, x2)
{
    temp = x1 + x2 - 1 ;
    return ( temp^2 ) ;
}
  • 아마 R은 아무런 반응을 하지 않았을 것이다. 그러나 R은 이제 F의 규칙을 기억하였다. 다음을 시도하여 보라.
F(3, 1)
[1] 9
result = F(3, 1)
result
[1] 9
a = 3 ; b = 1 ; result = F(a, b) 
result
[1] 9
  • 함수 내부에서 계산된 temp에 대해 temp^2값이 최종적으로 반환(return)됨을 알 수 있다.

  • 문법을 정리하면 다음과 같다.

함수 이름 = function(input variables)   # input variable는 여러 개여도 되고, 아예 없어도 된다.
{
    expression
    return(value)           # return시키고 싶은 값이 없으면 안 써도 됨
}
  • for나 if에서처럼 중괄호 안의 expression 안에서는 여러 명령을 중첩하여 입력할 수 있다.

3.3.1 예제 5. 벡터 x의 성분 중 홀수의 개수를 세는 함수

oddcount = function(x)
{
    count = 0 ;
    n = length(x) ;         # length(벡터) : 벡터의 길이를 리턴하는 함수
    for (i in 1:n)
    {
        temp = x[i] %% 2 ;  # a %% b : a를 b로 나눈 나머지(remainder)
        if (temp == 1) { k = k + 1 ;}
    }
    return(k) ;
}
  • oddcount(c(1,5,2)) ;…. 등을 자유롭게 입력하여 보라.

  • 위 함수 안에는 count, n, temp 등 다양한 변수가 쓰였다. 그러면 함수 바깥에서 저 변수들이 여전히 자기 값을 갖고 있을까? 다음을 입력해 보라. 아마 변수를 못 찾겠다는 메시지가 뜰 것이다.

count
n
temp
  • 함수 안에서 정의된 변수(위 예제에서는 count, n, i, temp, k)는 지역 변수이다. 즉, 함수 내부 계산을 마치고 밖으로 빠져나오면 사라진다. 바깥에서 같은 이름의 변수를 써도, 함수 바깥에 있는 변수는 변하지 않는다.
rm(list=ls())       # 작업공간(메모리) 상의 모든 개체 삭제
myftn = function(a, b=2) {temp = 4 ; return( a + b + temp ) }
temp ;          # 함수 내에서 사용된 temp는 함수가 끝나고 사라진다.
에러:개체 'temp'이 없습니다
temp = 0
myftn(0)
[1] 6
temp            # 함수 내에서 사용된 temp는 함수 바깥의 temp와는 아예 다른              # 세상(?)에서 살다가 사라진다.
[1]
  • 함수를 만들 때는 input variable들을 무엇으로 할 것인가, 무엇을 return할 것인가(하지 않을 것인가)를 생각하고 만드는 것이 좋다.

  • 함수를 만들 때 등호로써 기본값을 미리 지정할 수 있다. MAXITER나 initial value같은 귀찮은 input을 다룰 때 유용하다.

myftn = function(a, b=2) return( a + b ) 
myftn(a=4) 
[1] 6
myftn(a=4, b=3) 
[1] 7

3.4 예제들

3.4.1 예제 6

벡터의 평균을 계산하는 함수 mymean()을 만들되 다음 규칙을 따라서 만들라.

  • input : 벡터 x, output : 평균값
  • mean()이나 sum()함수를 쓰지 말 것
  • for 문을 이용하여 벡터 x의 성분들에 직접 접근할 것
  • 함수를 테스트할 수 있는 코드도 같이 작성할 것
mymean = function(x)
{
    n = length(x)           # n에 x의 길이 입력
    sum.temp = 0 ;          # sum.temp : 벡터의 성분별 누적합을 저장할 공간
    for (i in 1:n) sum.temp = sum.temp + x[i] ;
         # 한 루프마다 실행할 명령이 하나뿐이므로 굳이 중괄호 {}를 입력하지 않음
    return(sum.temp / n) ;      # (벡터 성분 총합) / 벡터 길이를 리턴
}

a = c(1, 2, 3, 4)
b = c(0, 1, 0, 1)
mymean(a)
[1] 2.5
mymean(b)
[1] 0.5

3.4.2 예제 7

\(a_{n+1} = f(a_n)\)으로 정의된 수열 \(\{a_n\}\)의 수렴 여부를 판정하는 함수 convtest()를 다음 규칙에 따라 만들라. - input : 함수 F, 수열의 초항(\(a_0\)) a, \(n\)에 대입할 최대 index M - for문 이용 - for문의 매 루프마다 loop index와 \(|a_n - a_{n-1}|\)을 화면에 출력할 것 - n = M까지 가기 전에 \(|a_n - a_{n-1}| < 10^{-6}\)이 달성되면 수렴된 것으로 판정하고 그 때의 \(a_n\)값을 수렴값으로서 반환, 만약 n = M이 되어도 \(|a_n - a_{n-1}| < 10^{-6}\)이 달성되지 않으면 수렴하지 않은 것으로 판정하고 NULL을 반환 - \(f(t) = \sqrt{t+1}\), a = 1, M = 100 으로 테스트 코드 작성

convtest = function(F, a, M)
{
    a.new = a               # a.new의 초기화
    for (i in 1:M)
    {
        a.old = a.new 
        a.new = F(a.old) 
        err = abs(a.new - a.old)    # err에  대입
        cat("Step ", i, ", current error : ", err, "\n", sep="") 
        if ( err < 10^(-6) )
        {
            cat("Convergence occured!!\n") 
            return(a.new) 
        }
    }
    cat("Convergence not occured\n") 
    return(NULL) 
}

f = function(t) return( sqrt(t + 1) ) ; a = 1 ; M = 100 
convtest(f, a, M) 
Step 1, current error : 0.4142136
Step 2, current error : 0.1395604
Step 3, current error : 0.04427921
Step 4, current error : 0.01379457
Step 5, current error : 0.004273452
Step 6, current error : 0.001321592
Step 7, current error : 0.0004084921
Step 8, current error : 0.0001262403
Step 9, current error : 3.90113e-05
Step 10, current error : 1.205524e-05
Step 11, current error : 3.725282e-06
Step 12, current error : 1.151176e-06
Step 13, current error : 3.557331e-07
Convergence occured!!
[1] 1.618034