函數
R 的函數也是一種特別的變數,它除了可以接受輸入的變數、並在執行一些運算之後傳回結果之外,也可以當成一般的變數使用,例如指定新的函數內容,或是當成別的函數的輸入參數等,以下介紹函數的使用方式。
建立與呼叫函數
在建立函數之前,我們先來看一下函數的內容。直接輸入函數的名稱,即可顯示函數的內容:
rt
function (n, df, ncp) { if (missing(ncp)) .Call(C_rt, n, df) else rnorm(n, ncp)/sqrt(rchisq(n, df)/df) } <bytecode: 0x27f97f0> <environment: namespace:stats>
上面這些輸出就是 rt
函數(用來產生 t 分佈隨機變數的函數)的內容,function
關鍵字之後用小括號包起來的就是此函數的參數,從中可以看出 rt
這個函數的輸入參數有三個,分別是 n
、df
與 ncp
。
function
內的參數名稱則稱為正式參數(formal arguments),這兩者的區別在一般的情況下並不是很重要,所以在以下的教學內容中,我們不會特別去區分這兩種參數。在參數之後,以大括號包起來的部分就是函數的主體(body),也就是每次該函數被呼叫時會執行的程式碼。
在 R 的函數中若要將計算的結果傳回,可以使用 return
這個關鍵字再加上要傳回的資料,除此之外,若沒有明確呼叫 return
指定傳回值的時候,R 會把此函數中最後一個運算式的結果當作傳回值。以這個 rt
函數來說,如果沒有指定 ncp
參數,R 就會執行 .Call
並以其執行結果作為傳回值,反之若 ncp
餐數有被指定,則會執行 rnorm
那一行運算,並將其運算結果作為傳回值。
若要建立一個自訂的函數,就只要將函數的整個內容指定給一個變數即可:
hypotenuse <- function(x, y) { sqrt(x ^ 2 + y ^ 2) }
這裡我們建立了一個 hypotenuses
函數,而其包含兩個正式參數,分別是 x
與 y
,在大括號中的程式碼就是函數的主體。
如果是像這樣只有一行程式碼的 R 函數,我們可以將大括號省略,以更簡潔的寫碼來指定函數:
hypotenuse <- function(x, y) sqrt(x ^ 2 + y ^ 2)
這樣只要一行程式碼即可建立一個 R 函數。
自訂的函數在建立之後,就可以立即使用:
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(hypotenuse)
function (x = 5, y = 12) NULL
而 formalArgs
則會傳回簡單的參數名稱:
formalArgs(hypotenuse)
[1] "x" "y"
至於函數的主體可以使用 body
來取得:
body(hypotenuse)
{ sqrt(x^2 + y^2) }
若要將函數主體的程式碼都轉為字串,可以搭配 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
的計算結果也都變成缺失值。若要修正這個問題,可以在 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
這裡我們在 normalize
函數中加入一個 na.rm
這個正式參數,並且讓這個參數直接傳入 mean
與 sd
函數中,所以當我們在呼叫 normalize
函數值,將 na.rm
設定為 TRUE
,這個設定就會自動套用至 mean
與 sd
函數中。
雖然缺失值的問題解決了,但這樣的寫法非常冗長,而且如果遇到還有其它的參數也需要這樣傳遞的話,就更麻煩了,對於這種只用於參數列內部傳遞的參數,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)
。