函數

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 這個函數的輸入參數有三個,分別是 ndfncp

一般在呼叫函數時所傳入的值稱為該函數的參數(arguments),而這裡在函數內容中 function 內的參數名稱則稱為正式參數(formal arguments),這兩者的區別在一般的情況下並不是很重要,所以在以下的教學內容中,我們不會特別去區分這兩種參數。

在參數之後,以大括號包起來的部分就是函數的主體(body),也就是每次該函數被呼叫時會執行的程式碼。

在 R 的函數中若要將計算的結果傳回,可以使用 return 這個關鍵字再加上要傳回的資料,除此之外,若沒有明確呼叫 return 指定傳回值的時候,R 會把此函數中最後一個運算式的結果當作傳回值。以這個 rt 函數來說,如果沒有指定 ncp 參數,R 就會執行 .Call 並以其執行結果作為傳回值,反之若 ncp 餐數有被指定,則會執行 rnorm 那一行運算,並將其運算結果作為傳回值。

若要建立一個自訂的函數,就只要將函數的整個內容指定給一個變數即可:

hypotenuse <- function(x, y)
{
  sqrt(x ^ 2 + y ^ 2)
}

這裡我們建立了一個 hypotenuses 函數,而其包含兩個正式參數,分別是 xy,在大括號中的程式碼就是函數的主體。

如果是像這樣只有一行程式碼的 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

會出現這樣的狀況主要是由於 meansd 若遇到缺失值,其計算的結果就會是缺失值,進而導致 normalize 的計算結果也都變成缺失值。若要修正這個問題,可以在 meansd 中加入 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 這個正式參數,並且讓這個參數直接傳入 meansd 函數中,所以當我們在呼叫 normalize 函數值,將 na.rm 設定為 TRUE,這個設定就會自動套用至 meansd 函數中。

雖然缺失值的問題解決了,但這樣的寫法非常冗長,而且如果遇到還有其它的參數也需要這樣傳遞的話,就更麻煩了,對於這種只用於參數列內部傳遞的參數,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 函數的正式參數當中(既不是 xm 也不是 s),所以就會被納入 ... 中,而在呼叫 mean(x, ...) 這樣有包含 ... 的函數時,na.rm 就會被傳入,也就是相當於 mean(x, na.rm = TRUE)