Function
function {base}: Function Definition
Description
- These functions provide the base mechanisms for defining new functions in the R language.
Usage
function( arglist ) expr return(value)
Arguments
arglist
- Empty or one or more name or name=expression terms.
- expr
- An expression.
value
- An expression.
Examples
norm <- function(x) sqrt(x%*%x)
norm(1:4)
## An anonymous function:
(function(x, y){ z <- x^2 + y^2; x+y+z })(0:7, 1)
[R 環境空間與函數 - 頁2,共3 - G. T. Wang]
R 的函數也是一種特別的變數,
- 可以接受輸入的變數、並在執行一些運算之後傳回結果之外, - 也可以當成一般的變數使用,例如
- 指定新的函數內容
- 當成別的函數的輸入參數等
- 可以接受輸入的變數、並在執行一些運算之後傳回結果之外, - 也可以當成一般的變數使用,例如
以下介紹函數的使用方式。
建立與呼叫函數
- 看一下函數的內容。直接輸入函數的名稱,即可顯示函數的內容
rt # 產生 t 分佈隨機變數的函數
function (n, df, ncp)
{
if (missing(ncp))
.Call(C_rt, n, df)
else rnorm(n, ncp)/sqrt(rchisq(n, df)/df)
}
# 如果沒有指定 ncp 參數,R 就會執行 .Call 並以其執行結果作為傳回值
# 反之若 ncp 餐數有被指定,則會執行 rnorm 那一行運算,並將其運算結果作為傳回值。
<bytecode: 0x1082bb8b0>
<environment: namespace:stats>
rt
- 產生 t 分佈隨機變數的函數
- 參數有三個,分別是
- n
- df
- ncp
- 一般在呼叫函數時所傳入的值稱為該函數的
- 參數(arguments)
- 而這裡在函數內容中 function 內的參數名稱則稱為
- 正式參數(formal arguments)
這兩者的區別在一般的情況下並不是很重要,所以在以下的教學內容中,我們不會特別去區分這兩種參數。
參數之後,以大括號包起來的部分就是 函數的主體(body),
- 也就是每次該函數被呼叫時會執行的程式碼
return()
- 在 R 的函數中若要將計算的結果傳回,可以使用 return 這個關鍵字再加上要傳回的資料,
- 若沒有明確呼叫 return 指定傳回值的時候,R 會把此函數中最後一個運算式的結果當作傳回值。
- 以這個 rt 函數來說,如果沒有指定 ncp 參數,R 就會執行 .Call 並以其執行結果作為傳回值,
- 反之若 ncp 餐數有被指定,則會執行 rnorm 那一行運算,並將其運算結果作為傳回值。
- 若要建立一個自訂的函數,就只要將函數的整個內容指定給一個變數即可:
hypotenuse <- function(x, y)
{
sqrt(x ^ 2 + y ^ 2)
}
建立了一個 hypotenuses 函數
- 包含兩個正式參數,分別是 x 與 y
- 在大括號中的程式碼就是函數的主體
像這樣只有一行程式碼的 R 函數,我們可以將大括號省略,以更簡潔的寫碼來指定函數:一行程式碼即可建立一個 R 函數。
hypotenuse <- function(x, y) sqrt(x ^ 2 + y ^ 2)
- 自訂的函數在建立之後,就可以立即使用:
hypotenuse(x = 3, y = 4)
# [1] 5
- 有明確指定參數名稱的狀況下,參數的順序可以任意排列:
hypotenuse(y = 4, x = 3)
# [1] 5
- 當呼叫 R 的函數時,若沒有明確指定參數的名稱,R 就會依照輸入參數的位置來判別,所以上面的函數呼叫也可以寫成這樣:
hypotenuse(3, 4)
# [1] 5
# 第一個參數 3 就會被指定給函數中的 x,而第二個參數 4 就會被指定給 y。
- 在建立函數時,也可以指定每個參數的預設值:
hypotenuse <- function(x = 5, y = 12)
{
sqrt(x ^ 2 + y ^ 2)
}
# 在呼叫函數時,若沒有指定輸入的參數,R 就會使用參數的預設值進行運算:
hypotenuse()
[1] 13
- formals 函數可以列出函數的每個參數以及預設值:
formals(hypotenuse)
$x
[1] 5
$y
[1] 12
# formals 函數的傳回值是一個列表變數,若是要給人閱讀的話,可以改用 args 函數:
- args() 函數
args(hypotenuse)
function (x = 5, y = 12)
NULL
# function (x = 5, y = 12)
# NULL
- formalArgs(): 傳回簡單的參數名稱:
formalArgs(hypotenuse)
# [1] "x" "y"
- body(): 函數的主體可以使用 body 來取得:
body(hypotenuse)
{
sqrt(x^2 + y^2)
}
- deparse()
- 若要將函數主體的程式碼都轉為字串,可以搭配 deparse 使用:
deparse(body(hypotenuse))
[1] "{" " sqrt(x^2 + y^2)"
[3] "}"
參數的預設值
- 除了一般的常數值
- 也可以使用各種的 R 運算式
- 還可以依據其他的正式參數來計算參數值
以下自訂一個簡單的標準化函數,
- 將一個數值向量標準化,讓平均數為 0,而標準差為 1:
normalize <- function(x, m = mean(x), s = sd(x))
{
(x - m) / s
}
x <- c(1.2, 3.5, 6.1, 4.3)
x.normalized <- normalize(x)
[1] -1.2672025 -0.1353323 1.1441731 0.2583617
mean(x.normalized)
[1] -5.551115e-17
sd(x.normalized)
[1] 1
- 這個自訂的標準化函數有個小問題,如果輸入的數值向量當中含有缺失值,會讓整個結果都變成缺失值:
y <- c(1.2, 3.5, 6.1, NA)
normalize(y)
[1] NA NA NA NA
- 會出現這樣的狀況主要是由於 mean 與 sd 若遇到缺失值,其計算的結果就會是缺失值,進而導致 normalize 的計算結果也都變成缺失值。
- na.rm: 若要修正這個問題,可以在 mean 與 sd 中加入 na.rm 參數,讓它們在計算平均數、以及標準差時,將缺失值排除:
normalize <- function(x,
m = mean(x, na.rm = na.rm),
s = sd(x, na.rm = na.rm),
na.rm = FALSE)
{
(x - m) / s
}
normalize(y, na.rm = TRUE)
[1] -0.97898042 -0.04079085 1.01977127 NA
- ...: 這種只用於參數列內部傳遞的參數,R 提供了一個簡單的 ... 寫法,
- 所有使用名稱或位置都無法匹配的參數都會被納入其中,直接傳遞給參數列內部的函數:
normalize <- function(x,
m = mean(x, ...),
s = sd(x, ...), ...)
{
(x - m) / s
}
normalize(y)
[1] NA NA NA NA
normalize(y, na.rm = TRUE)
[1] -0.97898042 -0.04079085 1.01977127 NA
這裡 normalize 函數中的 na.rm 參數並沒有在 normalize 函數的正式參數當中(
- 既不是 x 或 m 也不是 s),
- 所以就會被納入 ... 中,
- 而在呼叫 mean(x, ...) 這樣有包含 ... 的函數時,na.rm 就會被傳入,
- 也就是相當於 mean(x, na.rm = TRUE)。
- 既不是 x 或 m 也不是 s),
函數的傳遞與使用
- R 的函數也可以像一般的變數一樣,
- 當作參數傳入其他的函數中使用,
- 或是作為函數的傳回值。
- R 的函數也可以像一般的變數一樣,
函數當作參數傳入其他的函數中使用
- 最簡單的例子就是 do.call 函數
- 提供了另外一種函數的呼叫方式,可以讓使用者將參數以列表變數的方式傳入:
- 最簡單的例子就是 do.call 函數
do.call(hypotenuse, list(x = 3, y = 4))
[1] 5
這樣的效果相當於直接呼叫:
hypotenuse(x = 3, y = 4)
- 在將函數當作參數使用時,不一定要先建立具名的函數,
- 假設我們自訂一個函數為 my.plus,將其傳入 do.call 中使用:
my.plus <- function(x, y) x + y
do.call(my.plus, list(1, 2))
[1] 3
- 我們可以改用匿名函數的方式,讓程式碼更簡潔:
do.call(function(x, y) x + y, list(1, 2))
[1] 3
- 將函數作為傳回值
- 比較少見,
- ecdf() : 計算 empirical cumulative distribution function 的函數是一個比較有可能會遇到的例子:
x <- rnorm(50)
x.ecdf <- ecdf(x)
is.function(x.ecdf)
[1] TRUE
x.ecdf(-1.3)
[1] 0.02
變數範圍(variable scope)
- 指一個變數的存在範圍,亦即變數可以被使用的區域。
- R 在取用變數時,會依循環境空間的繼承性原則,先從目前的環境空間中尋找變數,若目前的環境空間中沒有要找的變數,就會循著母環境空間持續往上尋找,直到找到符合的變數為止。
依據 R 環境空間的繼承性原則,
- 在函數中我們可以直接取用外部全域的變數:
x.out <- 8
my.func <- function() {
message("x.out is ", x.out)
}
my.func()
x.out is 8
當我們在 my.func 函數中取用 x.out 這個變數時,
- R 會先在 my.func 的環境空間中尋找 x.out 這個變數,
- 當發現這個變數不存在時,就會繼續從它的母環境空間中尋找,也就是全域環境空間(global environment),
- 最後取得在全域環境空間中所定義的 x.out。
全域變數(global variables):
- 全域環境空間中所定義的變數,在任何地方都可以被使用,所以定義在全域環境空間中的變數也稱為全域變數(global variables),
區域變數(local variables)
- 而定義在一般函數中的變數,則稱為區域變數(local variables)。
若在一個函數中建立一個變數後,可以在函數的內部使用該變數,但在此函數的外部就無法使用:
my.func <- function() {
x.in.func <- 12
message("x.in.func is ", x.in.func)
}
my.func()
x.in.func is 12
- 如果在函數外部就無法存取 x.in.func 這個函數內部的變數:
message("x.in.func is ", x.in.func)
Error in message("x.in.func is ", x.in.func) : 找不到物件 'x.in.func'
- 在一個函數中又建立了一個子函數,則在子函數中可以直接取用上層函數中的變數:
f <- function(x)
{
y <- 1
g <- function(x)
{
(x + y) / 2
}
g(x)
}
f(5)
[1] 3
- 若我們將 g 函數移到 f 函數之外
- g 函數就不是 f 的子函數
- 無法取得 f 函數內部的變數:
f <- function(x)
{
y <- 1
g(x)
}
g <- function(x)
{
(x + y) / 2
}
f(5)
Error in g(x) : 找不到物件 'y'
環境空間的繼承性原則可以讓 R 的程式開發者更容易撰寫程式,不過也時常會帶來一些困擾,讓程式碼變得難以維護,
假設有一個 h 函數定義如下:
h <- function(x) {
x + y
}
- 乍看之下,這個 h 函數中的 y 變數沒有定義,應該無法執行,若在一個乾淨的 R 執行環境之下,會產生找不到 y 變數的錯誤:
h(3)
Error in h(3) : 找不到物件 'y'
- 如果我們在全域環境空間中建立一個 y 變數,狀況就會改觀了:
y <- 1:3
h(3)
[1] 4 5 6
- 原本應該會產生錯誤的函數,現在變成可以正常執行了,這是因為 R 在執行 h 函數時,當發現 h 函數中無法找到 y 這個變數的定義時,就會循著 h 函數上層的環境空間來尋找,由於我們在全域環境空間中建立了一個 y 變數,所以 R 就會直接取用這個 y 全域變數。
- 當開發大型程式時,要小心使用全域變數,否則很容易讓程式產生很多 bugs,而且也非常難維護。我們來看下面這個例子:
h2 <- function(x)
{
if (runif(1) > 0.5) y <- 2
x * y
}
- 在這個例子,y 有一半的機率會在 h2 被定義,
- 如果 h2 函數中有定義 y,它就會使用 h2 自己定義的 y,
- 若 h2 函數中沒有定義自己的 y 變數,就會使用全域的 y 變數,
這樣的程式碼架構是很容易出現 bugs
通常在撰寫 R 程式時,應該
盡可能將所有需要使用到的變數都透過參數來傳遞,
- 不要使用全域變數的方式來取得,
- 以降低程式出錯的機率與維護的成本。
Reference:
- R 環境空間與函數 - G. T. Wang
- R 基本函數 - G. T. Wang
- 撰寫 R 語言函數:Learn from the Wickhams – DataInPoint – Medium
- R 建立函數 | 龍崗山上的倉鼠
- Chapter 6 Functions - Technical Foundations of Informatics
- Functions in R | Gain Expertise in its Usage with Various Methods! - DataFlair
- R Recursive Function (Recursion) 遞歸函數 - A Complete Tutorial for Beginners! - DataFlair