흐름제어 02: 함수
함수
함수(Functions)
- 스크립트와 같이 반복해서 활용하게 될 코드를 모아 놓은 것.
- 매번 동일하게 실행되는 부분과 달라지는 부분으로 구분해 볼 수 있다.
함수 만들기의 예
- 다음은 1에서 5까지의 합, 1에서 10까지의 합, 1에서 20까지의 합을 구하고 있다.
s=0
for (i in 1:5) {
  s = s + i
}
print(s)
## [1] 15
s=0
for (i in 1:10) {
  s = s + i
}
print(s)
## [1] 55
s=0
for (i in 1:20) {
  s = s + i
}
print(s)
## [1] 210
- 여기서 공통되는 부분을 함수로 나타내는 방법은 다음과 같이 진행할 수 있다.
- 공통되는 부분을 확인한 후, 달라지는 부분을 변수로 치환한다.
- 전체를 { }로 감싼다.
- 반환값을 명시한다: return( )
- 함수의 이름과 인자를 명시한다: = function(a, b, ...)
 
- 이를 따라 해보자.
달라지는 부분을 변수로 나타낸다.
s=0
for (i in 1:n) {
  s = s + i
}
print(s)
전체를 { }로 감싼다.
{
  s=0
  for (i in 1:n) {
    s = s + i
  }
  print(s)
}
반환값을 명시한다.
{
  s=0
  for (i in 1:n) {
    s = s + i
  }
  print(s)
  return(s)
}
함수의 이름과 인자를 명시한다.
sumToN = function(n) {
  s=0
  for (i in 1:n) {
    s = s + i
  }
  print(s)
  return(s) # 여기서 return은 생략할 수 있다.
}
- 이제 새롭게 정의된 함수 sumToN을 활용하면 앞의 긴 스크립트는 다음과 같이 간단해 진다.
sumToN(5)
## [1] 15
## [1] 15
sumToN(10)
## [1] 55
## [1] 55
sumToN(20)
## [1] 210
## [1] 210
함수의 인자
- 인자가 여럿일 때 구분하는 방법은 1) 이름으로, 2) 위치로, 3) 부분 매칭(partial matching)이 있다. 다음의 함수를 예로 설명해보자.
# startNum부터 endNum까지 더하는 함수
sumAToB = function(startNum , endNum) {
  s=0
  for (i in startNum:endNum) {
    s = s + i
  }
  print(s)
  return(s)
  8 }
- 이름으로 : sumAToB(startNum=1, endNum=5),sumAToB(endNum=5,startNum=1)
- 위치로 : sumAToB(1, 5)
- 부분 매칭으로 : sumAToB(startNum = 1, end=5)
인자
- 인자는 기본값(default value)을 설정해 줄 수 있고, 기본값이 주어진 인자는 함수를 부를 때 생략할 수 있다.
sumAToB = function(startNum=1, endNum=10) {
  s=0
  for (i in startNum:endNum) {
    s = s + i
  }
  print(s)
  return(s)
}
- 함수는 ...(dot-dot-dot)을 활용하여 불특정 다수의 인자를 받아들일 수 있다. 이때에는 부분 매칭이 불가능하다. 보통 함mean에서처럼 인자의 갯수가 가변적이거나 인자들을 다른 함수로 넘길 때 사용된다.
sqPlot = function(x, y, ...) {
  plot(x, y^2, ...)
}
sqPlot((-3):3, (-3):3)
sqPlot((-3):3, (-3):3, col='red', pch=3)
- 만약 함수가 제대로 작동하기 위해 인자가 특별한 조건을 만족해야 하는 경우가 있다. 이때에는 if문과stop("Message")을 활용할 수 있다. 논리적 오류에 강건한 함수를 만드는데 도움이 된다. 다음의boxcox함수는x와lambda두 인자를 받는데lambda는 길이가 1이어야 한다. 만약 그렇지 않다면boxcox함수는 에러를 발생시킨다.
boxcox = function(x, lambda=1)
{
  if(length(lamdba)!=1) stop("the length of lambda must be one.")
  if (lambda != 0) {
    return((x^lambda -1)/lambda)
  } else {
    log(x)
  }
}
boxcox(c(1,3,2))
## Error in boxcox(c(1, 3, 2)): object 'lamdba' not found
boxcox(c(1,3,2), 2)
## Error in boxcox(c(1, 3, 2), 2): object 'lamdba' not found
boxcox(c(1,3,2), c(1,2))
## Error in boxcox(c(1, 3, 2), c(1, 2)): object 'lamdba' not found
- stopifnot(cond)으로 대체할 수도 있다. 에러 메세지는 조건문이 그대로 출력된다.
boxcox = function(x, lambda=1)
{
  stopifnot(length(lambda)==1)
  if (lambda != 0) {
    return((x^lambda -1)/lambda)
  } else {
    log(x)
  }
}
boxcox(c(1,3,2))
## [1] 0 2 1
boxcox(c(1,3,2), 2)
## [1] 0.0 4.0 1.5
boxcox(c(1,3,2), c(1,2))
## Error in boxcox(c(1, 3, 2), c(1, 2)): length(lambda) == 1 is not TRUE
함수와 인자의 클래스
- 범용함수(Generic function): 인자의 클래스(class)에 따라 함수의 행동이 달라지는 함수이다. methods( )를 통해 클래스에 따른 함수를 확인할 수 있다.
options(max.print=10)
methods(print) # or methods('print')
##  [1] print.acf*      print.AES*      print.all_vars* print.anova*   
##  [5] print.any_vars* print.aov*      print.aovlist*  print.ar*      
##  [9] print.Arima*    print.arima0*  
##  [ reached getOption("max.print") -- omitted 249 entries ]
## see '?methods' for accessing help and source code
methods(summary)
##  [1] summary.aov                    summary.aovlist*              
##  [3] summary.aspell*                summary.check_packages_in_dir*
##  [5] summary.connection             summary.data.frame            
##  [7] summary.Date                   summary.default               
##  [9] summary.ecdf*                  summary.factor                
##  [ reached getOption("max.print") -- omitted 27 entries ]
## see '?methods' for accessing help and source code
- print라는 함수는 입력되는 인자의 클래스에 따라 서로 다른 함수를 부르는데 예를 들어 만약 인자의 클라스가- factor라면- print.factor라는 함수를 부른다. 만약 인자의 클라스가- xyz인데,- print.xyz라는 함수가 존재하지 않는다면,- print.default함수를 부른다.
그 밖의 몇 가지
- R은 기본적으로 Call by value이다. 함수의 인자는 인자의 주소가 아니라 값이 함수로 전달된다.
- 함수 내에서 사용된 변수는 함수가 끝나는 순간 사라진다.
- 함수 밖에서 할당된 변수는 참조할 수 있지만 =또는<-로 수정할 수 없다. 따라서 R의 함수 내 변수는 흔히 말하는 지역 변수(local variables)라고 할 수 있다.
- 만약 함수 밖에서 할당된 변수에 새로운 값을 할당하려면 연산자 <<-를 사용한다.
- R의 어떤 함수에 대해 도움말을 보고 싶다면 ?또는help( )를 사용한다. 예를 들어 함수read.table에 대한 도움말을 보고 싶다면help(read.table)을 콘솔에 입력한다.- 도움말의 첫 부분 Usage를 보면 함수와 인자가 소개되고 인자의 기본값을 확인할 수 있다. 여기서numerals=c("allow.loss"", "warn.loss", "no.loss")로 나타난 부분은 인자numeral를"allow.loss"또는"warn.loss"또는"no.loss"로 설정할 수 있음을 나타낸다.
 
- 도움말의 첫 부분 
- 만약 함수 plot에 대한 예시를 보고 싶다면example(plot)을 해본다. 다른 함수에 대해서도example( )를 해보자.
디버깅
- 만약 script의 부분이 이상행동을 보인다면 스크립트를 한 줄씩 실행하면서 디버깅을 할 수 있다.
- 하지만 어떤 함수 안에서 이상한 결과가 나온다면 함수를 해체해서 한 줄씩 실행하거나 R studio의 디버깅을 사용할 수 있다.
- R studio에는 함수 내부를 디버깅할 수 있는 도구가 마련되어 있다. 특히 break-point를 지정하는 방법을 숙지하자. 에러가 발생되었을 때, Show Traceback, Rerun with Debug 등을 사용할 수 있다.
Leave a comment