[R] 사용자 정의 함수

DongGyu Jung·2021년 10월 4일
0

어떤 언어이든 가장 중요한 것은
사용자 정의 함수 아닌가 싶다.

필자는 Python3를 먼저 배운 후,
R을 접하게 되었는데 사실 과거 R을 맛만 보았을 때는
dplyr패키지를 통해 데이터 전처리만 해보았지 사용자 정의 함수까지는 써보지 않았기에
새삼 놀랍기도하고 특이해보이기도 했다.

Python의 경우에는 def를 통해 함수를 선언하는 반면
R의 함수 선언은 함수명 을 작성하고 function()이라는 명령을 대입하는 형태이다.

어쨋든 이번 글에서는 간단한 함수정의와 쓸만한 기능들을 간략하게 적어보겠다.



📕 사용자 정의 함수


💡 선언

Func_Name <- function([Argument]){
		(조건문 / 반복문 / 수식 / 처리구문 등)
		}

선언 방식은 간단하게 위와 같은 모습이라고 보면 된다.
Func_Name <- function
"선언하는 부분"과 {}를 통해 "실행코드 작성란 생성하는 부분"만 독특하다.
나머지 수식 입력 등은 보통의 함수 선언과 비슷하다.

R은 Python과 다르게 한 줄씩 선택적으로 코드 실행이 가능하기 때문에
위 아래로 왔다갔다하면서 실행하면 물론 불편하겠지만
단일 Script 내에서 위치/순서 상관없이 여러 개의 변수와 함수를 선언할 수있고
사전에 A함수를 선언했다면
이 후, B함수 내부에서 A함수를 중첩 사용할 수 있다.

# Local 변수와 Global 변수

흔히 지역변수와 전역변수라고 불리는데
이 또한, 다른 언어와 다를 바 없이 밖에서 선언한 변수는 Global 변수가 되어
모든 함수 생성시에 사용할 수 있고

함수 선언식 내부에서 선언한 변수는 Local 변수가 되어 해당 함수 내부에서만 임시적으로 생성되는 변수가 된다.

zz <- 30

func1 <- function() {
  xx <- 10   # 지역변수
  yy <- 20
  return(xx*yy)
}
func2 <- function() {
  xx <- 10
  return(xx*zz)
}

func1()
> 200

func2()
> 300

xx # 오류 발생 (출력 불가 _ Local 변수)
yy # 오류 발생 (출력 불가_ Local 변수)
zz
> 30

result <- func1() #변수에 함수 자체 대입
result
> 200

💡 인수

1. 인수 (argument)

일반적인 인수이다.
해당 인수는 함수의 실행에 있어 필수적인 역할을 가지고
각 인수의 위치순서, 의미가 고유하다.
인수의 이름으로 올바른 위치와 순서로 입력하면 된다.

func1 <- function(x,y){
  return(y/x)
}

func2 <- function(y,x) {
  return(x/(y*x))
}

func3 <- function(x,y) {
  return(x/y)
}

> func1(2,3)
[1] 1.5
> func2(2,3)
[1] 0.5
> func3(2,3)
[1] 0.6666667

위와 같이 각 인수의 쓰임이 정해져있다고 생각하면 된다.


2. 키워드 가변 인수 (keyword arguments)

해당 인수는 기본값을 갖고 있는 인수라고 보면 된다.

함수의 실행에 있어 필수적인 인수이지만
기본값이 있기 때문에 필수적인 입력이 필요한 인수는 아니다.

단, 각자의 위치와 쓰임은 고유하기 때문에
값을 입력할 땐 위치를 고려 하여 입력해야한다.
(꼭 (인수명)=을 입력해줄 필요는 없다!! 위치만 주의하자)

가변인수와는 달리
꼭 인수들 중 가장 마지막 위치에 올 필요는 없지만
만약 키워드 가변인수가 중간에 위치한다면 구분해주는 ","의 입력을 신경써서 해주어야 한다.

f<- function (p1="ㅋㅋㅋ",p2) for(i in 1:p2) print(p1)

> f(p1="abc", p2=3)
[1] "abc"
[1] "abc"
[1] "abc"

> f("abc", 3) 
[1] "abc"
[1] "abc"
[1] "abc"

> f(p2=5) 
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"

> f(,3)
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"
[1] "ㅋㅋㅋ"

> f(3)
'Error in f(3) : argument "p2" is missing, with no default'

3. 가변 인수 (arguments)

R에서는 가변인수를 주는 방법이 독특한데
Python에서는 **를 통해서 분류하는데
R은 (...)이다.

가변인수는 별도의 고유한 위치와 쓰임을 가지지 않고
보통 입력되는 다수의 값들의 반복적인 수행이 필요할 때 많이 쓰인다.
함수에 따라 다르겠지만
앞서 설명했던 인수와는 달리 입력하지 않더라도 오류가 발생하진 않는다.

만약 "입력되는 값들 각각의 값을 처리하는 함수"라면 c()list()벡터리스트형으로 변환해주어야 한다.

# f1 함수처럼 한 줄로 선언하고 싶다면 각 실행문을 ";(세미콜론)"으로 구분을 해주어야한다.
f1<- function(...) { print("TEST"); data <- c(...); print(length(data))}

> f1(10, 20, 30)
[1] "TEST"
[1] 3
> f1("abc", T, 10, 20)
[1] "TEST"
[1] 4
> f1()
[1] "TEST"
[1] 0


f2<- function(...) {
  print("수행시작")
  # for문을 돌리기 위해 벡터형으로 묶어줌
  data <- c(...)
  for(item in data) {
    print(item)
  }
  return(length(data))
}

> f2()
[1] "수행시작"
[1] 0
> f2(10)
[1] "수행시작"
[1] 10
[1] 1
> f2(10,20)
[1] "수행시작"
[1] 10
[1] 20
[1] 2
> f2(10,20,30)
[1] "수행시작"
[1] 10
[1] 20
[1] 30
[1] 3
> f2(10,'abc', T, F)
[1] "수행시작"
[1] "10"
[1] "abc"
[1] "TRUE"
[1] "FALSE"
[1] 4


f3<- function(...) {
  # 입력값 중 하나라도 문자열(String)이 있으면 
  #모두 Numeric -> String 강제 변환 _ Vector의 특징
  data <- c(...)
  sum <- 0;
  for(item in data) {
    if(is.numeric(item))
      sum <- sum + item
    else
      print(item)
  }
  return(sum)
}

> f3(10,20,30)
[1] 60
#'test'때문에 모든 데이터들 문자열화
> f3(10,20,'test', 30,40)
[1] "10"
[1] "20"
[1] "test"
[1] "30"
[1] "40"
[1] 0


f4<- function(...) {
  #하지만 List형으로 바꾸면 각 값의 데이터 Type 고유성이 유지된다.
  data <- list(...)
  sum <- 0;
  for(item in data) {
    if(is.numeric(item))
      sum <- sum + item
    else
      print(item)
  }
  return(sum)
}

> f4(10,20,30)
[1] 60
> f4(10,20,"test", 30,40)
[1] "test"
[1] 100


💡 try() 와 tryCatch()

try() 함수와 tryCatch()함수를 설명할 때 사용할 함수를 선언할 겸,
유용한 함수 두 가지를 알아보자.

💥 stop() 과 warning()

함수 선언 중,
("잘못된 계산이 될 경우" ) 경고메세지를 전달하거나 멈추게 하는 설계할 때 필요한 함수들이다.

stop()

인수로 입력한 메세지를 출력하고
하던 계산을 즉시 중단한다.

Error_stop <- function(x){
  if(x<=0)
    stop("양수만 전달하시오. (실행 중단)")
  return(cat("양수가 입력되었다! \n"))
}

> Error_stop(5)
'양수가 입력되었다!'

> Error_stop(0)
'Error in Error_stop(0) : 양수만 전달하시오. (실행 중단)'

warning()

인수로 입력한 메세지만 출력하고
하던 계산을 계속 처리한다.

Error_Warn <- function(x){
  if(x<=0)
    stop("양수만 전달하시오. (실행 중단)")
  if(x>5){
    x <- 5
    waring("입력된 인수가 5보다 크면 안됩니다. (해당 값을 5로 수정)")
  }
  return(cat("입력값 : ", x," _ 양수가 입력되었다! \n"))
}

> Error_Warn(0)
'Error in Error_Warn(0) : 양수만 전달하시오. (실행 중단)'

> Error_Warn(3)
입력값 :  3  _ 양수가 입력되었다!

> Error_Warn(10)
입력값 :  5  _ 양수가 입력되었다! 
'Warning message:'
'In Error_Warn(10) : 입력된 인수가 5보다 크면 안됩니다. (해당 값을 5로 수정)'

1. try()

try() 함수는 성격으로 따지자면 정말 무심한 함수이다.

R 특성상 "여러 코드를 한 번에 Drag해서 실행을 할 경우,
중간에서 에러가 발생하면 즉시 수행 중단과 함께 에러 메세지가 출력된다.

특히, "엄청난 양의 웹 페이지를 크롤링할 때"
초반에는 잘 돌아가다가
내가 준비한 수집 데이터 처리 함수에 작동되지 않는
조그마한 티끌 데이터(?) 하나 때문에 중단되버리는 경우가 생각보다 많다.

그런 경우 쓰면 아주 효자스러운 편리한 함수이다.
에러가 발생하여 중단되는 코드를 try()함수의 인수로 입력해주면

메세지는 출력하지만 무시하고 꿋꿋이 남은 코드를 실행한다.

test1 <-function(p){
  cat("수행함\n")
  Error_stop(-1)
  cat("이거 보여주려고 어그로 끌었다. \n")
}

> test1()
 수행함
 'Error in Error_stop(-1) : 양수만 전달하시오. (실행 중단) '


## try() 사용
test2 <- function(p){
  cat("수행함\n")
  try(Error_stop(-1))
  cat("이거 보여주려고 어그로 끌었다. \n")
}

> test2()
수행함
'Error in Error_stop(-1) : 양수만 전달하시오. (실행 중단)'
이거 보여주려고 어그로 끌었다. 

2. tryCatch()

try()를 이해했다면 tryCatch()도 이해 가능하다.
우선
코드를 살펴보자.

testAll <-function(p){
  tryCatch({
      #실행문
      if(p=="오류테스트"){
        Error_stop(-1)
      }else if (p =="경고테스트"){
        Error_Warn(6)
      }else{
        cat("정상 수행..\n")
      }
    },
    # warning 값
    warning = function(w){
      print(w)
      cat("Warning! Warning! \n")
    },
    # error 값
    error = function(e){
      print(e)
      cat("Booooommm!! \n")
    },
    # 에러고 뭐고 무조건 실행되어야하는 부분 ( Except문의 Finally와 동일)
    finally ={
      cat("오류, 경고 발생 여부에 관계없이 반드시 수행되는 부분!! \n")
    }
  )
}


> testAll("오류테스트")

<simpleError in Error_stop(-1): 양수만 전달하시오. (실행 중단)>
Booooommm!! 
오류, 경고 발생 여부에 관계없이 반드시 수행되는 부분!! 


> testAll("경고테스트")

<simpleWarning in Error_Warn(6): 입력된 인수가 5보다 크면 안됩니다. (해당 값을 5로 수정)>
Warning! Warning! 
오류, 경고 발생 여부에 관계없이 반드시 수행되는 부분!! 


> testAll("아무거나")

정상 수행..
오류, 경고 발생 여부에 관계없이 반드시 수행되는 부분!! 

tryCatch( {본 실행 코드} , [warning 경우 실행 코드], [error 경우 실행 코드], [강제 실행 코드])

* warning= 이나 error= 등은 "키워드 가변 인수"이기 때문에 생략 가능하다.

이 함수는 finallytry() 기능 (무시하고 강제 실행) 을 보여주고
추가적으로 "에러(Error)의 경우"와 "경고(Warning)의 경우"에 실행될 코드를 한번에 작성할 수 있다.

💡 Invisible

함수의 꽃(?)이라고 할 수 있는 수식 처리 결과 반환 함수, return() !!
return()함수는 말 그대로 "돌려주는 역할"을 가지고 있는데
본 게시물에서 코드들을 보면 계산 코드를 줄줄이 쓴 다음, "결과값을 가진 변수""마무리 짓는 함수/수식" 을 인수로 입력한다.

얼마 전에 알고리즘 풀이를 게시했었는데
매우 깔끔했던 함수들의 대부분이 return() 함수의 인수로 인라인(In-Line) 코드을 입력한 형태이다.
단, 알고리즘 풀이에선 Python 언어를 사용했기 때문에
R에서는 Console창으로 주로 확인을 하는데 return()을 하면 바로바로 콘솔창에 출력되지만
Python에서는 print()함수가 없다면 결과값을 확인하기 힘들다

그래서 이번에 설명하려하는 R에서의 invisible()함수의 출력은 "Python에서의 return()과 같이 출력된다.

말 그대로 "invisible", "보이지 않는" 이라는 뜻을 가진다.
결과가 사라지는 것이 아니다.
결과가 보이지 않는 것이다.

invisible이 쓰인 함수에 인수를 입력한 채로 변수에 대입한 후,
해당 변수를 실행하면 그 결과를 확인할 수 있다.
(필자는 잘 쓰지 않지만 R 특성상 Console창을 통해 대부분을 확인하는 환경에서 "자잘한 결과가 보기 싫을 경우" 사용하기 좋을 것 같다.)

ft.1 <- function(x) return()
ft.2 <- function(x) return(x+10)
ft.3 <- function(x) invisible(x+10)

> ft.1(100)
NULL
> ft.2(100)
[1] 110
# Only 실행 
> ft.3(100)

r1 <- ft.1(1000);r1
r2 <- ft.2(1000);r2
r3 <- ft.3(1000);r3

> r1
NULL
> r2
[1] 1010
> r3
[1] 1010	# 계산은 된 것을 볼 수 있다.

🎈 별첨


❓ Sys.sleep()

아마 향후 R을 통한 웹크롤링에 대한 글에서 많이 등장할 함수인데
말 그대로 코드 실행을 지정한 초(Second)만큼 작동을 잠깐 일시정지 시켜주는 함수이다.
루프(Loop)의 경우나 실행할 코드가 컴퓨터 사양에 비해 처리하기 버거울 정도로 많은 경우에 사용한다.
웹크롤링의 경우, 지정해주지 않으면 페이지 로딩보다 빠르게 되어
실행 도중 과부하로 인한 멈춤이 발생할 수 있기때문에 많이 쓰인다.

Sys.sleep( [TimeValue(Seconds)] )

Sys.sleep(3)
Sys.sleep(0.5)

0개의 댓글