함수 직접 만들기
함수 직접 만들기 (Writing your own functions)
R에서 직접 함수를 만들어 쓰는 법을 다룹니다. 여기저기서 자연스럽게 R이 function이라는 종류(mode)의 객체를 만들 수 있게 해준다는 걸 봐왔는데요, 그 함수들이야말로 R 언어에 엄청난 힘과 편의, 그리고 우아함을 더해주는 핵심입니다. 유용한 함수를 직접 써보는 것이 R을 편하고 생산적으로 쓰는 가장 중요한 방법 중 하나예요.
R 시스템에 기본으로 딸려오는 함수들, 가령 mean(), var(), postscript() 같은 것들도 대부분 R로 작성되어 있습니다. 즉 기본 함수와 사용자가 만든 함수 사이에 본질적인 차이는 없다는 뜻이에요.
출처: R 공식 매뉴얼
본문
함수는 다음과 같은 형태의 할당(assignment)으로 정의됩니다.
> name <- function(arg_1, arg_2, ...) expression
이때 expression은 인자들인 arg_i를 이용해 값을 계산하는 R 표현식(보통은 여러 표현식을 묶은 grouped expression)입니다. 이 표현식이 계산한 값이 곧 함수가 돌려주는 값이 돼요.
함수를 호출할 때는 보통 name(expr_1, expr_2, …) 같은 형태를 쓰며, 함수 호출이 허용되는 곳이라면 어디서든 쓸 수 있습니다.
간단한 예시 (Simple examples)
첫 예시로, 두 표본의 t 통계량을 "모든 과정을 하나씩 보여주면서" 계산하는 함수를 생각해 볼게요. 물론 같은 결과를 더 간단하게 얻는 방법이 이미 있으니 이건 일부러 인위적으로 만든 예시입니다.
함수는 이렇게 정의합니다.
> twosam <- function(y1, y2) {
n1 <- length(y1); n2 <- length(y2)
yb1 <- mean(y1); yb2 <- mean(y2)
s1 <- var(y1); s2 <- var(y2)
s <- ((n1-1)*s1 + (n2-1)*s2)/(n1+n2-2)
tst <- (yb1 - yb2)/sqrt(s*(1/n1 + 1/n2))
tst
}
함수가 정의되고 나면, 아래처럼 호출해서 두 표본 t-검정을 수행할 수 있어요.
> tstat <- twosam(data$male, data$female); tstat
두 번째 예시로, MATLAB의 백슬래시(backslash) 명령을 그대로 흉내 내는 함수를 만들어 볼게요. 백슬래시 명령은 벡터 y를 행렬 X의 열 공간(column space)에 직교 투영했을 때의 계수, 즉 흔히 회귀 계수의 최소제곱 추정량이라고 부르는 값을 돌려줍니다. 보통은 qr() 함수로 처리하는데, 이걸 직접 쓰려면 조금 까다롭기 때문에 아래처럼 간단한 함수 하나를 만들어두면 안전하게 쓸 수 있어요.
n by 1 벡터 y와 n by p 행렬 X가 있을 때, X \ y는 (X’X)^{-}X’y로 정의됩니다. 여기서 (X’X)^{-}는 X'X의 일반화 역행렬(generalized inverse)이에요.
> bslash <- function(X, y) {
X <- qr(X)
qr.coef(X, y)
}
이 객체를 만든 뒤에는 다음과 같은 문장에서 바로 사용할 수 있어요.
> regcoeff <- bslash(Xmat, yvar)
R의 전설적인 함수 lsfit()이 이 작업을 아주 잘 처리해 주고, 그 이상의 기능도 제공합니다. lsfit()은 위에서 본 다소 직관에 반하는 방식으로 qr()과 qr.coef()를 사용해서 이 계산을 수행해요. 그래서 자주 쓰는 사람이라면 이 부분만 떼어 간단한 함수로 만들어 두는 게 분명 가치가 있을 거예요. 그리고 더 편하게 쓰고 싶다면 이걸 행렬 이항 연산자로 만들어 버릴 수도 있고요.
새 이항 연산자 정의하기 (Defining new binary operators)
bslash() 함수에 아래 형태의 다른 이름을 주면, 함수 형태가 아니라 이항 연산자(binary operator)로 쓸 수 있게 됩니다.
%anything%
예를 들어 안쪽 문자로 !를 고른다고 해볼게요. 그럼 함수 정의는 이렇게 시작합니다.
> "%!%" <- function(X, y) { ... }
(따옴표를 쓰는 것에 주목하세요.) 이제 이 함수는 X %!% y 형태로 사용할 수 있어요. (백슬래시 기호 자체는 이 맥락에서 특별한 문제를 일으키기 때문에 편리한 선택이라고는 할 수 없습니다.)
행렬 곱셈 연산자인 %*%와 외적(outter product) 연산자인 %o%도 바로 이런 방식으로 정의된 이항 연산자의 예입니다.
이름 있는 인자와 기본값 (Named arguments and defaults)
앞서 정규 수열 생성에서 처음 언급했듯이, 호출할 함수의 인자를 "name=object" 형태로 주면 인자 순서를 마음대로 바꿔도 됩니다. 게다가 처음 몇 개의 인자는 이름 없이 위치 기준으로 주고, 그 뒤에 이름 있는 인자를 이어서 지정하는 것도 가능해요.
fun1이라는 함수가 아래처럼 정의되어 있다고 해볼게요.
> fun1 <- function(data, data.frame, graph, limit) {
[function body omitted]
}
그러면 이 함수는 여러 방식으로 호출할 수 있는데, 예를 들어
> ans <- fun1(d, df, TRUE, 20)
> ans <- fun1(d, df, graph=TRUE, limit=20)
> ans <- fun1(data=d, limit=20, graph=TRUE, data.frame=df)
세 호출은 모두 동일합니다.
많은 경우 인자에 공통으로 적절한 기본값(default value)을 줄 수 있는데, 그럴 때는 기본값이 맞다면 호출에서 아예 생략해도 됩니다. fun1이 아래처럼 정의되어 있다면,
> fun1 <- function(data, data.frame, graph=TRUE, limit=20) { ... }
이렇게 호출할 수 있어요.
> ans <- fun1(d, df)
이 호출은 위의 세 경우와 동일합니다. 또는
> ans <- fun1(d, df, limit=10)
처럼 호출하면 기본값 중 하나만 바꿔줄 수 있어요.
중요한 점은 기본값이 단순한 상수일 필요 없이, 같은 함수의 다른 인자를 포함한 임의의 표현식이 될 수 있다는 것입니다. 위의 간단한 예시에서처럼 상수에만 제한되지는 않아요.
'...' 인자 (The '...' argument)
자주 필요해지는 또 한 가지 요구사항은, 한 함수가 받은 인자 설정을 다른 함수로 넘겨주는 것입니다. 예를 들어 그래픽 함수들은 대부분 par()를 사용하고, plot() 같은 함수는 사용자가 그래픽 매개변수를 par()로 넘겨 그래픽 출력을 제어할 수 있게 해주죠. (par() 함수에 대한 자세한 내용은 "Permanent changes: The par() function"에서 다룹니다.) 이는 함수에 말 그대로 '...'라는 인자를 하나 더 넣고, 그 인자를 다음 함수로 넘겨주는 방식으로 처리합니다. 개요 예시는 아래와 같아요.
fun1 <- function(data, data.frame, graph=TRUE, limit=20, ...) {
[omitted statements]
if (graph)
par(pch="*", ...)
[more omissions]
}
조금 덜 자주 쓰이지만, 함수가 '...'의 구성 요소를 직접 참조해야 할 때도 있습니다. list(...)라는 표현식은 그런 인자를 모두 평가해서 이름 있는 리스트로 돌려주는 반면, ..1, ..2 등은 하나씩 차례로 평가하며, '..n'은 n번째 일치하지 않은 인자를 돌려줍니다.
함수 안에서의 할당 (Assignments within functions)
함수 안에서 이루어지는 일반적인 할당은 모두 지역적이고 임시적이며, 함수를 빠져나가면 사라집니다. 그래서 X <- qr(X)라는 할당은 호출한 프로그램 쪽의 인자 값에는 영향을 주지 않아요.
R 할당의 유효 범위(scope)를 지배하는 규칙을 완전히 이해하려면 평가 프레임(evaluation frame)이라는 개념을 알아야 합니다. 이 주제는 꽤 고급이긴 하지만 어렵다고 하긴 어렵고, 여기서는 더 다루지 않겠습니다.
함수 안에서 전역적이고 영구적인 할당을 의도한다면, '슈퍼할당(superassignment)' 연산자인 <<-나 assign() 함수를 사용하면 됩니다. 자세한 내용은 help 문서를 참고하세요.
조금 더 복잡한 예시 (More advanced examples)
조금 더 완성도 있으면서 다소 평범한 함수 예시로, 블록 설계(block design)의 효율 인자(efficiency factors)를 구하는 함수를 생각해 볼게요. (이 문제의 일부 측면은 이미 "Index matrices"에서 다뤘습니다.)
블록 설계는 두 요인, 즉 blocks(수준 수 b)와 varieties(수준 수 v)로 정의됩니다. R과 K를 각각 v by v와 b by b의 반복 수와 블록 크기 행렬이라고 하고, N을 b by v 출현 행렬(incidence matrix)이라고 하면, 효율 인자는 행렬 E = I_v - R^{-1/2}N’K^{-1}NR^{-1/2} = I_v - A’A의 고유값으로 정의됩니다. 여기서 A = K^{-1/2}NR^{-1/2}예요. 함수를 쓰는 한 가지 방법은 아래와 같습니다.
> bdeff <- function(blocks, varieties) {
blocks <- as.factor(blocks) # minor safety move
b <- length(levels(blocks))
varieties <- as.factor(varieties) # minor safety move
v <- length(levels(varieties))
K <- as.vector(table(blocks)) # remove dim attr
R <- as.vector(table(varieties)) # remove dim attr
N <- table(blocks, varieties)
A <- 1/sqrt(K) * N * rep(1/sqrt(R), rep(b, v))
sv <- svd(A)
list(eff=1 - sv$d^2, blockcv=sv$u, varietycv=sv$v)
}
이 경우에는 고유값 계산 루틴보다 특이값 분해(singular value decomposition)로 작업하는 편이 수치적으로 조금 더 안정적입니다.
이 함수가 돌려주는 결과는 리스트로, 첫 번째 구성요소로 효율 인자를 주고 블록과 품종의 표준 대비(canonical contrasts)도 함께 담습니다. 때로는 이 값들이 추가로 유용한 정성적 정보를 주기 때문이에요.
배열 출력에서 이름 지우기 (Dropping all names in a printed array)
큰 행렬이나 배열을 출력할 때는 배열 이름이나 숫자 없이 빽빽하게 이어붙인 형태로 출력하는 게 유용한 경우가 많습니다. 하지만 dimnames 속성을 없앤다고 이 효과가 나오는 게 아니라, 배열에 빈 문자열로만 이루어진 dimnames 속성을 줘야 해요. 예를 들어 행렬 X를 출력할 때,
> temp <- X
> dimnames(temp) <- list(rep("", nrow(X)), rep("", ncol(X)))
> temp; rm(temp)
이 작업은 아래의 no.dimnames() 함수를 "감싸는(wrap around)" 방식으로 쓰면 훨씬 편리하게 처리할 수 있습니다. 이 예시는 또 효과적이고 유용한 사용자 함수가 얼마나 짧을 수 있는지도 보여줘요.
no.dimnames <- function(a) {
## Remove all dimension names from an array for compact printing.
d <- list()
l <- 0
for(i in dim(a)) {
d[[l <- l + 1]] <- rep("", i)
}
dimnames(a) <- d
a
}
이 함수가 정의되면 배열을 빽빽한 형태로 출력할 때 이렇게 씁니다.
> no.dimnames(X)
이건 특히 큰 정수 배열에서 값 자체보다 패턴이 진짜 관심사일 때 아주 유용합니다.
재귀적 수치 적분 (Recursive numerical integration)
함수는 재귀적(recursive)일 수 있고, 자기 안에 함수를 다시 정의할 수도 있습니다. 다만 주의할 점은, 그런 함수나 변수는 검색 경로(search path)에 있었을 때처럼 더 높은 평가 프레임에 있는 호출된 함수들에게 상속되지는 않는다는 것입니다.
아래 예시는 1차원 수치 적분을 수행하는 순진한(naive) 방법을 보여줍니다. 피적분 함수(integrand)를 구간의 양 끝점과 중간점에서 평가합니다. 한 구간 짜리 사다리꼴 공식(trapezium rule) 결과가 두 구간 결과와 충분히 가깝다면, 두 구간 결과를 값으로 돌려줍니다. 그렇지 않으면 각 구간에 대해 같은 과정을 재귀적으로 반복해요. 그 결과는 적분 함수가 선형에서 가장 멀리 벗어난 영역에 함수 평가를 집중시키는 적응형(adaptive) 적분 과정이 됩니다. 다만 오버헤드가 크기 때문에 다른 알고리즘과 경쟁할 수 있는 경우는 피적분 함수가 부드럽고 그리고 평가가 매우 어려울 때뿐입니다.
이 예시는 R 프로그래밍의 작은 퍼즐로도 주어지는 경우가 많아요.
area <- function(f, a, b, eps = 1.0e-06, lim = 10) {
fun1 <- function(f, a, b, fa, fb, a0, eps, lim, fun) {
## function ‘fun1’ is only visible inside ‘area’
d <- (a + b)/2
h <- (b - a)/4
fd <- f(d)
a1 <- h * (fa + fd)
a2 <- h * (fd + fb)
if(abs(a0 - a1 - a2) < eps || lim == 0)
return(a1 + a2)
else {
return(fun(f, a, d, fa, fd, a1, eps, lim - 1, fun) +
fun(f, d, b, fd, fb, a2, eps, lim - 1, fun))
}
}
fa <- f(a)
fb <- f(b)
a0 <- ((fa + fb) * (b - a))/2
fun1(f, a, b, fa, fb, a0, eps, lim, fun1)
}
스코프 (Scope)
이 절의 논의는 이 문서의 다른 부분보다 조금 더 기술적입니다. 하지만 그만큼 S-PLUS와 R 사이의 주요 차이 중 하나를 자세히 설명해 줍니다.
함수 본문에 나타나는 기호(symbol)는 세 부류로 나눌 수 있습니다. 형식 매개변수(formal parameters), 지역 변수(local variables), 자유 변수(free variables)가 그것이에요. 함수의 형식 매개변수는 함수의 인자 목록에 나타나는 것들로, 실제 인자(formal parameters)를 형식 매개변수에 *결합(binding)*하는 과정을 통해 값이 정해집니다. 지역 변수는 함수 본문의 표현식을 평가하면서 값이 결정되는 변수들이고, 형식 매개변수도 지역 변수도 아닌 변수는 자유 변수라고 부릅니다. 자유 변수는 할당을 받게 되면 지역 변수가 됩니다. 다음 함수 정의를 살펴볼게요.
f <- function(x) {
y <- 2*x
print(x)
print(y)
print(z)
}
이 함수에서 x는 형식 매개변수, y는 지역 변수, z는 자유 변수입니다.
R에서 자유 변수의 결합은 먼저 함수가 만들어진 환경(environment)을 살펴보면서 해결됩니다. 이를 렉시컬 스코프(lexical scope)라고 해요. 먼저 cube라는 함수를 정의해 볼게요.
cube <- function(n) {
sq <- function() n*n
n*sq()
}
sq 함수 안의 변수 n은 그 함수의 인자가 아닙니다. 따라서 자유 변수이고, 스코프 규칙을 이용해 그 n에 결합될 값을 알아내야 해요. 정적 스코프(static scope, S-PLUS) 아래에서는 n이라는 전역 변수에 결합된 값이 됩니다. 반면 렉시컬 스코프(lexical scope, R)에서는 sq 함수가 정의될 당시 n에 대해 활성화된 결합이 바로 cube 함수의 매개변수이므로, 그 매개변수가 값이 됩니다. R에서의 평가와 S-PLUS에서의 평가 차이는, S-PLUS는 n이라는 전역 변수를 찾는 반면 R은 cube가 호출될 때 만들어진 환경 안에서 n이라는 변수를 먼저 찾는다는 점에 있습니다.
## first evaluation in S
S> cube(2)
Error in sq(): Object "n" not found
Dumped
S> n <- 3
S> cube(2)
[1] 18
## then the same function evaluated in R
R> cube(2)
[1] 8
렉시컬 스코프는 함수에 가변 상태(mutable state)를 부여하는 데도 쓰일 수 있습니다. 아래 예시에서 R로 은행 계좌를 흉내 내는 방법을 보여줄게요. 제대로 작동하는 은행 계좌에는 잔고(총액), 출금 함수, 입금 함수, 현재 잔고를 알려주는 함수가 필요하죠. account 안에서 세 함수를 만든 다음, 그 함수들을 담은 리스트를 돌려줌으로써 이를 구현합니다. account가 호출되면 숫자 인자 total을 받아 세 함수를 담은 리스트를 돌려줍니다. 이 함수들이 total을 담고 있는 환경 안에서 정의되기 때문에, total의 값에 접근할 수 있게 되는 거예요.
특별한 할당 연산자인 <<-는 total에 결합된 값을 바꾸는 데 사용됩니다. 이 연산자는 둘러싸고 있는(감싸는) 환경들을 거슬러 올라가며 total이라는 기호를 담고 있는 환경을 찾고, 찾으면 그 환경 안의 값을 우변(right hand side)의 값으로 교체합니다. 전역 또는 최상위 환경까지 올라갔는데도 total이라는 기호를 찾지 못하면 그 변수를 만들어서 거기에 할당합니다. 대부분의 사용자에게 <<-는 전역 변수를 하나 만들고 우변의 값을 그 변수에 할당하는 셈이에요. 여기서 설명한 특별한 동작이 나타나는 경우는 <<-가 다른 함수의 값으로 돌려진 함수 안에서 사용될 때뿐입니다.
open.account <- function(total) {
list(
deposit = function(amount) {
if(amount <= 0)
stop("Deposits must be positive!\n")
total <<- total + amount
cat(amount, "deposited. Your balance is", total, "\n\n")
},
withdraw = function(amount) {
if(amount > total)
stop("You don't have that much money!\n")
total <<- total - amount
cat(amount, "withdrawn. Your balance is", total, "\n\n")
},
balance = function() {
cat("Your balance is", total, "\n\n")
}
)
}
ross <- open.account(100)
robert <- open.account(200)
ross$withdraw(30)
ross$balance()
robert$balance()
ross$deposit(50)
ross$balance()
ross$withdraw(500)
환경 맞춤화하기 (Customizing the environment)
사용자는 여러 가지 방식으로 자신의 환경을 맞춤화할 수 있습니다. 사이트(site) 초기화 파일이 있고, 모든 디렉터리는 각자의 특별한 초기화 파일을 가질 수 있어요. 마지막으로 특별한 함수인 .First와 .Last를 쓸 수도 있습니다.
사이트 초기화 파일의 위치는 R_PROFILE 환경 변수의 값에서 가져옵니다. 그 변수가 설정되어 있지 않으면, R 홈 디렉터리의 etc 하위 디렉터리에 있는 Rprofile.site 파일을 사용합니다. 이 파일에는 시스템에서 R을 시작할 때마다 실행하고 싶은 명령들을 담아야 해요. 그리고 .Rprofile이라는 두 번째 개인 프로필 파일은 아무 디렉터리에나 둘 수 있습니다. R이 그 디렉터리에서 호출되면 그 파일이 소싱(source)됩니다. 이 파일은 개별 사용자가 자신의 워크스페이스를 제어할 수 있게 해주고, 작업 디렉터리에 따라 서로 다른 시작 절차를 허용해 줘요. 시작 디렉터리에서 .Rprofile 파일을 찾지 못하면 R은 사용자 홈 디렉터리에서 .Rprofile 파일을 찾아 (존재한다면) 그 파일을 사용합니다. R_PROFILE_USER 환경 변수가 설정되어 있으면, 그 변수가 가리키는 파일이 .Rprofile 파일들 대신 사용됩니다.
두 프로필 파일 중 하나나 .RData 이미지 안에 있는 .First()라는 이름의 함수는 특별한 지위를 가집니다. 이 함수는 R 세션이 시작될 때 자동으로 실행되며 환경을 초기화하는 데 사용할 수 있어요. 예를 들어 아래 예시의 정의는 프롬프트를 $로 바꾸고, 나머지 세션에서 당연하게 쓸 수 있는 여러 유용한 것들을 설정해 줍니다.
따라서 파일이 실행되는 순서는 Rprofile.site, 사용자 프로필, .RData, 그리고 마지막으로 .First()입니다. 나중에 나오는 파일의 정의가 앞서 나오는 파일의 정의를 가립니다(mask).
> .First <- function() {
options(prompt="$ ", continue="+\t") # $ is the prompt
options(digits=5, length=999) # custom numbers and printout
x11() # for graphics
par(pch = "+") # plotting character
source(file.path(Sys.getenv("HOME"), "R", "mystuff.R"))
# my personal functions
library(MASS) # attach a package
}
마찬가지로 .Last() 함수가 정의되어 있으면 (보통) 세션이 맨 마지막에 실행됩니다. 예시는 아래와 같아요.
> .Last <- function() {
graphics.off() # a small safety measure.
cat(paste(date(),"\nAdios\n")) # Is it time for lunch?
}
클래스, 제네릭 함수와 객체 지향 (Classes, generic functions and object orientation)
객체의 클래스(class)는 그 객체가 제네릭(generic) 함수라고 불리는 것들에 의해 어떻게 처리될지를 결정합니다. 반대로 말하면, 제네릭 함수는 인자 자신의 클래스에 특화된 작업이나 동작을 인자에 대해 수행합니다. 인자에 class 속성이 전혀 없거나, 해당 제네릭 함수가 특별히 다루지 않는 클래스라면, 항상 기본 동작(default action)이 제공됩니다.
예시를 보면 훨씬 명확해질 거예요. 클래스 메커니즘은 사용자에게 특별한 목적의 제네릭 함수를 설계하고 작성할 수 있게 해줍니다. 다른 제네릭 함수들로는 객체를 그래픽으로 보여주는 plot(), 여러 종류의 분석을 요약하는 summary(), 통계 모형을 비교하는 anova() 등이 있어요.
한 클래스를 특정한 방식으로 처리할 수 있는 제네릭 함수의 수는 꽤 많을 수 있습니다. 예를 들어 클래스가 "data.frame"인 객체를 어떤 식으로든 수용할 수 있는 함수에는 다음이 포함됩니다.
[ [[<- any as.matrix
[<- mean plot summary
현재의 완전한 목록은 methods() 함수로 얻을 수 있습니다.
> methods(class="data.frame")
반대로 한 제네릭 함수가 처리할 수 있는 클래스의 수도 역시 꽤 많을 수 있어요. 예를 들어 plot() 함수에는 기본 메서드(default method)가 있고, 클래스가 "data.frame", "density", "factor" 등인 객체를 위한 변형들이 있습니다. 완전한 목록은 역시 methods() 함수로 얻을 수 있어요.
> methods(plot)
많은 제네릭 함수에서 함수 본문은 상당히 짧습니다. 예를 들어
> coef
function (object, ...)
UseMethod("coef")
UseMethod가 있는 건 이 함수가 제네릭 함수라는 뜻입니다. 어떤 메서드가 사용 가능한지 보려면 methods()를 쓰면 돼요.
> methods(coef)
[1] coef.aov* coef.Arima* coef.default* coef.listof* coef.maov*
[6] coef.nls*
see '?methods' for accessing help and source code
이 예시에는 여섯 개의 메서드가 있는데, 별표(asterisk)로 표시된 것처럼 그 이름을 타이핑해 봐도 그중 어떤 것도 볼 수 없습니다. 이것들은 다음 중 하나로 읽을 수 있어요.
> getAnywhere("coef.aov")
A single object matching 'coef.aov' was found
It was found in the following places
registered S3 method for coef from namespace stats
namespace:stats
with value
function (object, complete = FALSE, ...)
{
cf <- object$coefficients
if (complete)
cf
else cf[!is.na(cf)]
}
> getS3method("coef", "aov")
function (object, complete = FALSE, ...)
{
cf <- object$coefficients
if (complete)
cf
else cf[!is.na(cf)]
}
gen.cl라는 이름의 함수는 클래스 cl을 가진 객체에 대해 제네릭 함수 gen에 의해 호출됩니다. 그러니 메서드로 쓰려는 게 아니라면 이런 스타일로 함수 이름을 짓지 마세요.
이 메커니즘에 대한 더 완전한 논의는 The R Language Definition(R-lang.html#Top)을 참고하세요.
더 알아보기
- 함수 작성의 기본 규칙(형식
name <- function(...) expression, 인자와 기본값,...인자)은 어떤 함수를 써도 똑같이 적용돼요. - 함수 안의 할당은 지역적이라는 점, 기억해 두면 디버깅할 때 한결 편해집니다. 전역에 영구히 남기고 싶다면
<<-나assign()을 쓰세요. - R의 렉시컬 스코프 덕분에 함수 안에 함수를 만들어 "가변 상태"를 가진 객체(위의 은행 계좌 예시)를 만들 수 있어요 — 클로저(closure)를 쓰는 대표적인 패턴이죠.
- 새 이항 연산자를 만들려면
%anything%형태의 이름을 따옴표로 감싸 정의하면 됩니다. - 제네릭 함수와 메서드는 R의 기본적인 객체 지향(특히 S3)의 핵심이에요.
methods(),getAnywhere(),getS3method()를 활용하면 클래스별로 어떤 처리가 이뤄지는지 추적할 수 있어요. - 시작 시 실행되는 초기화 파일(
Rprofile.site,.Rprofile)과.First()/.Last()를 활용하면 세션 환경을 원하는 대로 맞춰 둘 수 있습니다.