5.3 함수 (function)
- 함수: 특정한 목적을 위한 연산을 수행하기 위해 명명된 일련의 문장(추상화)
- 예:
sum(x)
\(\rightarrow\) 벡터x
의 값을 모두 합산하는 함수로 “sum” 이라고 명명된 내장 함수 - R 콘솔에서 함수 명칭(예:
sum
)을 입력 후 실행하면 함수 내부 확인 가능
- 예:
sum
function (..., na.rm = FALSE) .Primitive("sum")
- 함수의 명칭(위의 예에서
sum
)으로 특정 합수를 호출(call)
R의 스크립트는 내장된 혹은 사용자가 정의한 함수들을 호출함으로써 작성됨
함수를 사용해야만 하는 이유
- 매우 큰 프로그램 작업을 해야할 경우 함수를 통해 작업 단위 별로 분할 가능
- 한 번 작성한 함수는 재사용 가능
- 프로그램의 체계적 관리가 가능하기 때문에 유지 및 보수가 용이
- 프로그램 코드의 간결화
5.3.1 함수의 정의
function
이라는 R의 예약어를 통해 사용자 함수 정의- 함수 정의 시 함수의 명칭을 반드시 부여해야 함
함수 이름 <- function()
- 함수는 일반적으로 인수(argument)로 입력값을 전달 받으면 그 결과값을 반환(return)
- 함수의 인수와 반환에 따라 다음과 같이 4 가지 유형의 함수 정의 가능
- 인수를 갖는 함수
- 인수를 갖지 않는 함수
- 값을 반환하는 함수
- 값을 반환하지 않는 함수
# (1) 인수를 갖는 함수
## (모)분산을 계산하는 함수
<- function(x) {
var_pop <- length(x)
n if (n < 2) {
stop("적어도 두 개 이상의 관찰값이 존재해야 합니다")
}<- mean(x)
mx <- sum((x - mx)^2)/n
v return(v) # 결과를 반환하는 함수: v를 함수의 출력값으로 설정
}
## test
set.seed(1) # 동일한 난수 생성을 위해 seed 번호 부여
<- rnorm(1)
x var_pop(x)
Error in var_pop(x): 적어도 두 개 이상의 관찰값이 존재해야 합니다
set.seed(1000)
<- rnorm(1000, 2, 4) # 평균이 2이고 표준편차가 4인 정규분포로부터 1000개 난수 추출
x var_pop(x)
[1] 15.40581
# (2) 인수를 갖지 않는 함수
<- function() {
print_lyrics_let_it_be print("When I find myself in times of trouble, ")
print("Mother Mary comes to me.")
print("Speaking words of wisdom 'let it be'.")
}
print_lyrics_let_it_be()
[1] "When I find myself in times of trouble, "
[1] "Mother Mary comes to me."
[1] "Speaking words of wisdom 'let it be'."
print_lyrics_let_it_be(beatles)
Error in print_lyrics_let_it_be(beatles): 사용되지 않은 인자 (beatles)
## 주사위를 돌리는 함수
<- function() {
rolling_dice sample(1:6, 1, replace = TRUE)
}
rolling_dice(); rolling_dice(); rolling_dice();
[1] 4
[1] 4
[1] 4
# (3) 값을 반환하는 함수
<- function(x) {
manual_mean <- length(x)
n <- 0
sumi for (i in 1:n) {
<- sumi + x[i]
sumi
}return(sumi/n)
}
set.seed(20)
<- sample(1:200, 20, replace = FALSE) # 1 ~ 200 중 랜덤하게 20개 추출(비복원)
x manual_mean(x)
[1] 107
# 미리 정의하지 않은 인수를 입력한 경우
set.seed(4)
<- sample(1:length(x), 4)
na_idx <- NA
x[na_idx] manual_mean(x, na.rm = TRUE)
Error in manual_mean(x, na.rm = TRUE): 사용되지 않은 인자 (na.rm = TRUE)
# (4) 값을 반환하지 않는 함수(void function)
<- function(x, ...) {
summary_mean <- sum(!is.na(x))
n <- sum(x, ...)/n
mx cat("Data: ", sprintf("%.2f", x), "\n") # 소숫점 2째 짜리 까지 출력
cat("전체 관찰값 개수(결측 제외) = ", n, "\n")
cat("산술평균 = ", mx, "\n")
}
set.seed(20)
<- rnorm(20)
x summary_mean(x)
Data: 1.16 -0.59 1.79 -1.33 -0.45 0.57 -2.89 -0.87 -0.46 -0.56 -0.02 -0.15 -0.63 1.32 -1.52 -0.44 0.97 0.03 -0.09 0.39
전체 관찰값 개수(결측 제외) = 20
산술평균 = -0.1877639
<- summary_mean(x) result
Data: 1.16 -0.59 1.79 -1.33 -0.45 0.57 -2.89 -0.87 -0.46 -0.56 -0.02 -0.15 -0.63 1.32 -1.52 -0.44 0.97 0.03 -0.09 0.39
전체 관찰값 개수(결측 제외) = 20
산술평균 = -0.1877639
result
NULL
<- NA
x[na_idx] # ...를 통해 미리 정하지 않은 인수를
# 함수 내부에서 호출한 다른 함수로 전달 가능
summary_mean(x, na.rm = TRUE)
Data: 1.16 -0.59 NA -1.33 -0.45 0.57 NA -0.87 -0.46 -0.56 NA -0.15 -0.63 1.32 -1.52 -0.44 0.97 0.03 NA 0.39
전체 관찰값 개수(결측 제외) = 16
산술평균 = -0.1590692
<- summary_mean(x, na.rm = TRUE) x
Data: 1.16 -0.59 NA -1.33 -0.45 0.57 NA -0.87 -0.46 -0.56 NA -0.15 -0.63 1.32 -1.52 -0.44 0.97 0.03 NA 0.39
전체 관찰값 개수(결측 제외) = 16
산술평균 = -0.1590692
5.3.2 함수의 인수 전달 방법
- 함수는 입력값(input) 을 가지며, 이러한 입력값은 함수의 인수(argument)에 해당 값을 할당함으로써 입력값이 함수로 전달됨
- 함수의 인수 정의는 내 마음대로 가능(개수 무관)
- R에서 함수 호출 시 인수 전달은 “값”을 호출 하는 방식(call by value)
call by value와 상반되는 개념으로 참조에 의한 호출(call by reference)로 값이 아니라 값이 저장되어 있는 메모리의 주소 값을 전달하는 방식(대표적인 예: C 언어
의 포인터)임. 계산 효율은 참조에 의한 호출이 월등히 뛰어나지만, 프로그램의 구조가 복잡하다는 단점을 가짐. R은 데이터 분석에 특화된 프로그램이기 때문에 직관적인 call by value 방식을 택함.
- 예시
# 두 변수의 값을 바꾸는 함수: swap
<- function(x, y) {
swap <- x
temp <- y
x <- temp
y cat("두 값이 바뀌었습니다.", sprintf("x = %d, y = %d", x, y) ,"\n")
}
<- 3; y <- 10
x swap(x, y)
두 값이 바뀌었습니다. x = 10, y = 3
# x, y 두 값이 바뀌지 않음 x; y
[1] 3
[1] 10
- 인수를 전달하는 방법은 다음 두 가지임
- 인수의 위치 순서에 의한 전달: 정의한 인수의 순서대로 각 인수에 대응하는 값을 전달
- 인수의 이름에 의한 전달: 위치와 관계 없이 정의한 인수의 이름을 지정하여 값을 전달
# 표준편차 계산 함수: stdev
<- function(x, na.rm = TRUE) {
stdev if (is.matrix(x)) apply(x, 2, sd, na.rm = na.rm)
else if (is.vector(x)) sqrt(var(x, na.rm = na.rm))
else if (is.data.frame(x)) sapply(x, sd, na.rm = na.rm)
else sqrt(var(as.vector(x), na.rm = na.rm))
}
set.seed(1000)
<- matrix(rnorm(1000), 100, 10)
X <- rpois(50, lambda = 10) # 포아송 분포(lambda = 10)에서 50개 난수 추출
x <- mtcars # R 내장 데이터를 dat 에 저장
dat
# (1) 순서에 의한 전달
stdev(X, T); stdev(X) # 동일한 결과
[1] 1.0065940 0.9033927 0.9727257 0.9905631 0.8202803 1.0114516 0.9855547
[8] 1.0211373 1.0716219 1.0426811
[1] 1.0065940 0.9033927 0.9727257 0.9905631 0.8202803 1.0114516 0.9855547
[8] 1.0211373 1.0716219 1.0426811
stdev(x)
[1] 3.41569
stdev(dat)
mpg cyl disp hp drat wt
6.0269481 1.7859216 123.9386938 68.5628685 0.5346787 0.9784574
qsec vs am gear carb
1.7869432 0.5040161 0.4989909 0.7378041 1.6152000
stdev(TRUE, dat) # 오류 why??
Error in if (na.rm) "na.or.complete" else "everything": the condition has length > 1
# (2) 이름에 의한 전달
set.seed(5)
<- sample(1:50, 5)
na_idx <- NA
x[na_idx]
stdev(na.rm = T, x = x)
[1] 3.411211
stdev(dat = dat, na.rm = TRUE) # 오류 why???
Error in stdev(dat = dat, na.rm = TRUE): 사용되지 않은 인자 (dat = dat)
5.3.3 함수의 기본 구성 요소
function()
에서()
안의 부분(일반적으로 첫 번째 줄)을 머리(header) 부분- 함수의 초기 형태(매개변수 또는 인수의 형태)를 지정
- 연산 또는 명령이 수행되는 부분은 함수의 몸통(body) 부분(
{}
로 표시)- 함수 내부에서 실행되는 연산 명령어들의 집합으로 구성
- 인수(argument): 함수의 기능을 선택적으로 조정하는 parameter로 함수 안에서 작동하는 매개변수들을 통칭
- 인수는
argument
또는argument = default value
로 설정 - 복수의 인수는 콤마(
,
)로 구분 \(\rightarrow\)fun_name <- function(arg1, arg2, arg3)
- 특수 인수
...
: 어떠한 개수의 인수를 함수로 전달할 수 있음- 일반적으로 인수의 개수가 불특정하거나 함수 안에서 다른 함수를 호출할 때 특정 인수를 다른 함수로 전달시킬 때 유용(위 예시 참고)
- 인수는
# (1) 인수에 default 값을 주지 않은 함수
<- function(x, y) {
fun_without_arg_default *y
x
}
set.seed(10)
<- sample(1:20, 10, replace = TRUE) # 복원 추출
a 7] <- NA
a[<- 5
b
fun_without_arg_default(a, b)
[1] 55 45 50 80 60 40 NA 95 75 75
# (2) 인수에 default 값을 부여한 함수
<- function(x = 5, y = 8) {
fun_with_arg_default *y
x
}
fun_with_arg_default()
[1] 40
<- function(x, trim = 0, na.rm = F) {
trim_mean mean(x, trim = trim, na.rm = na.rm)
}trim_mean(a)
[1] NA
trim_mean(x = a, trim = 0.2, na.rm = TRUE) # 인수 이름으로 값 전달
[1] 12.57143
trim_mean(a, 0.3, TRUE) # 인수 순서대로 값 전달
[1] 12.6
# (3) ... 인수 사용 예제
# list() 함수를 이용해 `...`에 해당하는 인수들을 리스트 객체로 만든 후
# 이를 함수에서 사용
<- function(x, ...) {
dot_example # browser()
<- 0
trim <- FALSE
na.rm
<- list(...) # ...에 해당하는 인수 추출
dots for (arg in names(dots)) {
if (arg == "trim") trim <- as.numeric(dots[arg])
if (arg == "na.rm") na.rm <- as.logical(dots[arg])
}
mean(x, trim = trim, na.rm = na.rm)
}
dot_example(a)
[1] NA
set.seed(30)
<- sample(1:30, 15, replace = TRUE) # 복원 추출
a dot_example(a)
[1] 17.06667
9] <- NA
a[dot_example(a)
[1] NA
dot_example(a, trim = 0.1, na.rm = TRUE)
[1] 17.75
# (4) `...` 인수가 함수 내 사용(호출)된
# 다른 함수의 인수로 전달하는 경우
# summary_mean() 함수 예제와 유사
<- function(x, ...) {
mean_manual mean(x, ...)
}
set.seed(30)
<- rnorm(30, mean = 10, sd = 5)
x <- sample(1:30, 3, replace = TRUE)
na_idx <- x; xna[na_idx] <- NA
xna
mean_manual(x)
[1] 8.347683
mean_manual(xna)
[1] NA
mean_manual(xna, na.rm = TRUE)
[1] 8.127862
mean_manual(x = xna, trim = 0.2, na.rm = TRUE)
[1] 7.534424
함수 몸체 안에
browser()
을 입력하면, browser()
전 까지 함수 몸체 안 명령들이 수행되고, 이후 명령들이 어떻게 실행되는지 확인할 수 있음. 함수 작성 시 함수 로직을 세우고 디버깅 할 때 매우 유용하게 사용
인수 관련 몇 가지 유용한 함수들
args()
: 특정 함수에서 사용되는 인수 확인
args(fun_without_arg_default)
function (x, y)
NULL
args(rnorm)
function (n, mean = 0, sd = 1)
NULL
body()
: 함수의 몸체 조회
body(var_pop)
{
n <- length(x)
if (n < 2) {
stop("적어도 두 개 이상의 관찰값이 존재해야 합니다")
}
mx <- mean(x)
v <- sum((x - mx)^2)/n
return(v)
}
body(dot_example)
{
trim <- 0
na.rm <- FALSE
dots <- list(...)
for (arg in names(dots)) {
if (arg == "trim")
trim <- as.numeric(dots[arg])
if (arg == "na.rm")
na.rm <- as.logical(dots[arg])
}
mean(x, trim = trim, na.rm = na.rm)
}
match.arg()
: 인수를 매치하는 함수로 매치할 대상의 인수를 지정- arg: 매치할 대상 인수 지정
- choice: 매치될 인수값 목록
- several.ok: 복수 선택 여부(
TRUE/FALSE
)
# 인수의 매치
match.arg(arg = c("med", "max"),
choices = c("mean", "median", "iqr", "minimum", "maximum", "range"),
several.ok = TRUE)
[1] "median" "maximum"
match.arg(arg = c("median", "maximuum"),
choices = c("mean", "med", "iqr", "minimum", "max", "range"),
several.ok = TRUE) # 오류 why??
Error in match.arg(arg = c("median", "maximuum"), choices = c("mean", : 'arg' should be one of "mean", "med", "iqr", "minimum", "max", "range"
match.arg(arg = c("med", "max"),
choices = c("mean", "median", "iqr", "minimum", "maximum", "range"),
several.ok = FALSE)
Error in match.arg(arg = c("med", "max"), choices = c("mean", "median", : 'arg'는 반드시 길이가 1이어야 합니다
# match.arg() 함수 응용
# 중심값 관련 통계량 계산 함수
# 평균(mean), 절삭평균(trimmed mean), 중앙값(median), 최빈수(mode) 계산
# pkg_list <- rownames(installed.packages()) # 설치된 패키지 목록
# if (!("DescTools" %in% pkg_list))
# install.packages("DescTools") # 최빈수를 구하기 위한 패키지 설치
<- function(x,
center type = c("mean", "trimmed", "median", "mode"),
...
)
{# browser()
= 0; na.rm = FALSE # dot 인수 초기값
trim <- match.arg(type)
type <- list(...)
dots for (arg in names(dots)) {
if (arg == "trim") trim <- as.numeric(dots[arg])
if (arg == "na.rm") na.rm <- as.logical(dots[arg])
}
switch(type,
mean = mean(x, na.rm = na.rm),
trimmed = mean(x, trim = trim, na.rm = na.rm),
median = median(x, na.rm = na.rm),
mode = DescTools::Mode(round(x, 1), na.rm = na.rm)
# DescTools 패키지 내 Mode 함수를
# workspace에 불러오지 않고 사용
)
}
set.seed(100)
<- rchisq(100, df = 3) # 자유도가 3인 카이제곱분포에서 난수 추출
x <- x; xna[na_idx] <- NA
xna
center(x, "mean"); center(x, "me")
[1] 2.929673
Error in match.arg(type): 'arg' should be one of "mean", "trimmed", "median", "mode"
[1] 2.929673
center(x, "trimmed", trim = 0.1)
[1] 2.565866
center(x, "median")
[1] 2.45614
center(x, "mode")
Error in loadNamespace(x): 'DescTools'이라고 불리는 패키지가 없습니다
center(xna, "median")
[1] NA
center(xna, "median", na.rm = TRUE)
[1] 2.423723
switch()
함수는 ifelse()
함수의 확장 버전으로 \(n\) 개의 조건에 대한 분기 가능
함수 제어 관련 주요 함수
return()
: 계산된 결과를 반환하는 함수로 함수의 흐름에서return()
이 나타나면 결과값을 반환하고 함수 종료- 강제 종료가 필요한 경우 응용 가능
# (1) 객체 반환
set.seed(100)
<- rnorm(100, mean = 24, sd = 2.2)
x <- function(x) {
value_return1 <- sum(x)
tot <- length(x)
n <- list(size = n, total = tot, average = mean(x), stdev = sd(x))
result return(result)
}value_return1(x)
$size
[1] 100
$total
[1] 2400.641
$average
[1] 24.00641
$stdev
[1] 2.245563
<- value_return1(x)
desc $stdev desc
[1] 2.245563
<- function(x) {
value_return2 return(sum(x)/length(x))
}value_return2(x)
[1] 24.00641
# (2) 강제 종료 시 활용
<- function(x) {
value_return3 if (anyNA(x)) return
return(sum(x)/length(x))
}
<- x; xna[na_idx] <- NA
xna value_return3(xna)
[1] NA
value_return3(x)
[1] 24.00641
stop()
: 예외처리 함수의 일종으로 특정 조건일 경우 (오류) 메세지를 출력하고 함수 종료- 인수로 문자열을 가짐
# (1) stop() 함수 사용
# 복소수 값을 실수와 허수로 분할
<- function(z) {
split_complex if(!is.complex(z))
stop("입력값이 복소수가 아닙니다")
<- Re(z)
re <- Im(z)
im return(list(real = re, imaginary = im))
}
split_complex(pi)
Error in split_complex(pi): 입력값이 복소수가 아닙니다
split_complex(23 + 7i)
$real
[1] 23
$imaginary
[1] 7
5.3.4 함수의 적용 범위(scoping rule)
Scoping rule: 변수 또는 객체가 어디에서 사용 가능한지를 결정하는 규칙
- 매개변수(parameter): 함수를 적용할 때 사용되는 변수로 인수로부터 발생함
- 함수의 인수 리스트에서 인수값이 매개변수로 할당
- 지역변수(local variable): 함수의 몸체 부분에서 정의된 변수들을 지칭하며 함수의 종료와 동시에 재사용 불가
- 전역변수(global variable): 함수의 외부(workspace)에서 정의된 변수로 함수 내부에서 값을 할당하지 않더라도 사용 가능
# (1) 매개변수, 지역변수, 전역변수 구분
<- 10 # 전역변수
x <- 5 # 전역변수
y
<- function(x) {
scope1 <- x^2
y print(x) # 매개변수
print(y) # 지역변수
}
# 전역변수가 출력 x; y
[1] 10
[1] 5
scope1(x = 10)
[1] 10
[1] 100
- 작업공간에서
x
와y
는 각각 10, 5 값이 할당- 작업공간 상에서
x
y
값은 변하지 않음- 지역변수
y
의 사용 범위는 함수 몸체이기 때문에 함수 밖에 있는 y는 값이 변하지 않음
<- 10 # 전역변수
x <- 5 # 전역변수
y rm(z)
<- function(x) {
scope2 <- x^2
y print(x) # 매개변수
print(y) # 지역변수
print(z)
}scope2(x = 5)
[1] 5
[1] 25
Error in scope2(x = 5): 객체 'z'를 찾을 수 없습니다
<- 13 # 전역변수로 z 할당
z scope2(x = 5)
[1] 5
[1] 25
[1] 13
- 함수 외부와 내부 모두에서
z
가 정의되지 않았기 때문에 에러 출력- 작업공간 상에
z
를 정의한 경우 함수 내부에서 workspace에서 정의한z
를 그대로 사용 \(\rightarrow\) 함수 외부와 내부 자유로이 사용 가능한 변수를 자유변수(free variable)이라고 지칭함.
지역변수의 사용 범위는 함수 몸체 안이지만 그 범위를 밖으로 확장할 수 있음 \(\rightarrow\) <<-
또는 ->>
사용
# 지경변수의 확장 예제
<- 1; y <- 2; z <- 3; k <- 10
x <- function(x) {
scope3 <<- x + 10
y * 3 ->> z
y
print(x) # 매개변수
print(y) # 지역변수
print(z) # 지역변수
print(k) # 자유변수
}
x;y;z;k
[1] 1
[1] 2
[1] 3
[1] 10
scope3(x = 2)
[1] 2
[1] 12
[1] 36
[1] 10
x;y;z;k
[1] 1
[1] 12
[1] 36
[1] 10
하나의 함수 내부에 또 다른 함수 생성 가능
<- function(x) {
mean_manual2 <- sum(x)
tot <- function(x) {
size return(length(x))
}return(tot/size(x))
}
mean_manual2(1:10)
[1] 5.5