Function

  • 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)。
  • 函數的傳遞與使用

    • R 的函數也可以像一般的變數一樣,
      • 當作參數傳入其他的函數中使用,
      • 或是作為函數的傳回值。
  • 函數當作參數傳入其他的函數中使用

    • 最簡單的例子就是 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:

results matching ""

    No results matching ""