數聚點

數聚點

Share this post

數聚點
數聚點
約維安計畫:R 語言自行定義函數
中級約維安

約維安計畫:R 語言自行定義函數

第三十週

Yao-Jen Kuo's avatar
Yao-Jen Kuo
Jun 24, 2022
∙ Paid

Share this post

數聚點
數聚點
約維安計畫:R 語言自行定義函數
Share
來源:giphy.com

解析哈囉世界

在約維安計畫:R 語言起步走我們透過 print("Hello, World!") 來確認 R 語言的開發環境及執行環境是否已經安裝妥當,在這一段哈囉世界的程式碼中,其實就能體會我們引用 Greg Martin 對於 R 語言核心精神的詮釋:對資料結構應用函數。

The way R works is pretty straightforward, you apply functions to objects.

Greg Martin

print 是函數的名稱,print() 則表示要應用該函數的功能,而 “Hello, World!“ 則是一個被稱呼為文字(Character)的向量資料結構;換言之,哈囉世界可以表達為:對 “Hello, World!“ 這個文字向量應用了 print() 函數。

print("Hello, World!")

什麼是函數

函數是構成所有程式語言的基本要素,一個函數是一段「被命名」的程式碼,這段程式碼可以用於執行某個特定任務,可能是數值的運算或者文字的處理,當使用者想要應用(使用、呼叫)某一個函數之前,必須先確定在欲產生作用的範疇中該函數已經被定義或者被載入。簡而言之,在哈囉世界的程式碼中,之所以能夠對字串 “Hello, World!“ 應用 print() 函數,是因為 print() 函數屬於 R 語言的內建函數,在 R 語言啟動的當下就會被載入供我們使用。

函數的來源

R 語言使用者除了能夠應用內建函數以外,還可以從其他兩個管道取得所需要的函數:

  1. 套件(Packages)。

  2. 自行定義。

自行定義函數需要考慮五個組成要件:

  1. 函數名稱。

  2. 輸入。

  3. 參數。

  4. 函數主體。

  5. 輸出。

看似抽象,不過想想平日購買珍珠鮮奶茶的流程,就像是函數的運作一般:

  1. 函數名稱:購買珍珠鮮奶茶。

  2. 輸入:中杯 35 元;大杯 50 元。

  3. 參數:甜度(無糖、微糖、半糖、少糖、全糖)與冰塊(去冰、少冰、全冰)。

  4. 函數主體:點餐、貼標籤、加珍珠、加冰塊、倒茶、加鮮奶一直到封口。

  5. 輸出:珍珠鮮奶茶。

function_name <- function(INPUTS, PARAMETERS){
    # body of the function
    # ...
    # ...sequence of statements
    return(OUTPUTS)
}

首先要給函數取個名字(function_name),利用保留字 function 告訴 R 語言這是一個函數,接著在小括號中放入想好的輸入(INPUTS)與參數(PARAMETERS),然後在大括號內縮排撰寫我們主要的程式(body of the function),最後是將輸出(OUTPUTS)放在 return() 裡頭。# 是用來標記註解的符號,有 # 標記的該行敘述將不會被 R 語言執行,這讓寫程式的人能夠用口語來描述這段程式的目的是什麼,如果將哈囉世界改寫為函數形式,每次只需要輸入函數名稱並加上小括號 hello_world(),就可以獲得 “Hello, World!“ 這個文字向量為輸出。

hello_world <- function(){
    return("Hello, World!")
}
print(hello_world())

輸入與參數的差別

function_name <- function(INPUTS, PARAMETERS){
    # body of the function
    # ...
    # ...sequence of statements
    return(OUTPUTS)
}

在 R 語言中輸入會設計為「無」或者「必要」,參數則會給予預設值,例如 Sys.Date() 函數或 getwd() 都是無需輸入的函數,函數名稱後的小括號留空白。

print(Sys.Date())
print(getwd())

例如 sort() 函數具有 decreasing 參數,預設值給定為 FALSE。所以在沒有傳入的狀況下,其實就是採用 decreasing = FALSE 的參數設定,假使不想採用預設值,就能指定參數 decreasing = TRUE。

primes <- c(11, 5, 7, 2, 3)
print(sort(primes))
print(sort(primes, decreasing = TRUE))

更動結果的機制

使用「函數」可以讓應用的對象物件造成更動,更動方式是以「回傳值」型態輸出更動後的結果,延續前述 sort() 函數的例子,sort(primes) 是以回傳值輸出排序後的向量,如果沒有將回傳值更新原本命名的向量,排序的更動並不會被保留。

primes <- c(11, 5, 7, 2, 3)
print(sort(primes))
sort(primes) # Apply sort function to primes
print(primes) # primes is not sorted 
primes <- sort(primes) # Update primes with function output
print(primes) # primes is sorted

作用域

在約維安計畫:R 語言的流程控制我們提到了程式區塊(Code blocks)的觀念,不論是條件判斷或者迴圈,都仰賴一對大括號(A curly-brace pair)建立出程式區塊,並以此作為敘述的附屬。在自行定義函數時也同樣以程式區塊建構出函數主體,不過更特別的是,附屬於 function 保留字的程式區塊具有一個稱為作用域(或稱範疇 Scope)的觀念,在附屬於函數下的程式區塊被稱為 Local scope,函數以外的部分則稱為 Global scope。在 Local scope 中我們可以運用 INPUTS、PARAMETERS 來進行運算並宣告新的物件,但是這都侷限於 Local scope 之中,一但在 Global scope 意圖運用前述的這些物件,都會遭遇到錯誤,意即在 Global scope 中這些物件「沒有作用」,這也是作用域一詞的由來。例如在 Global scope 中試圖印出任何在 Local scope 中可以運用的物件都會遭遇錯誤。

is_factor <- function(a, b){
  modulo <- a %% b
  out <- modulo == 0
  return(out)
}

print(is_factor(4, 2))
print(a)
## Error in print(a) : object 'a' not found
print(b)
## Error in print(b) : object 'b' not found
print(modulo)
## Error in print(modulo) : object 'modulo' not found
print(out)
## Error in print(out) : object 'out' not found

return() 的作用

初次學習自行定義函數多半都不甚暸解 return() 作用,原因是先前太過頻繁地使用 print() 函數將文字處理、數值計算或者資料結構操作的結果顯示出來,導致在自行定義函數時不習慣去使用 return()。事實上,return() 具有兩個作用,第一個跟字面上意義相同的將函數結果「輸出」,假如在自行定義函數時只有將運算結果用 print() 函數顯示出來,該函數就不具備能將結果儲存在物件中的能力,變成只能夠在使用時顯示結果,卻不能將結果儲存起來。第一個特性在 R 語言中可能比較難感受到,原因是 R 語言為了幫助使用者,會預設將函數主體區塊中的最後一行物件輸出,例如下列自行定義函數並沒有指定將判斷因數的結果輸出,依然能得到預期結果。

is_factor <- function(a, b){
  modulo <- a %% b
  out <- modulo == 0
}

function_output <- is_factor(4, 2)
print(function_output)

第二個是終止程式區塊的執行,在函數程式區塊中寫在 return() 之後的所有內容在使用函數的時候都不會有任何作用。

is_factor <- function(a, b){
  modulo <- a %% b
  return(modulo)
  out <- modulo == 0 # will not be returned
  print(out) # # will not be printed
}

function_output <- is_factor(4, 2)
print(function_output)

輸入與輸出的對應關係

自行定義函數的重點在於規劃輸入與輸出的對應關係,如同在數學課中學過的「函數映射關係」,只要釐清了自行定義函數針對輸入與輸出的設計機制,剩下的任務就僅是將程式區塊的函數主體完成而已。

從輸入開始,函數可以接受沒有輸入與參數的設計,在小括號中留空即可。

hello_world <- function(){
  return("Hello, World!")
}
print(hello_world())

函數當然可以接受有輸入與參數的設計,好的函數設計會在參數給予合適的預設值。

hello_someone <- function(someone = "World"){
  out <- sprintf("Hello, %s!", someone)
  return(out)
}

print(hello_someone())
print(hello_someone(someone="Function"))

設計接受多個輸入值的函數時,因為 R 語言的向量、元素運算(Element-wise)特性,直接使用合適的資料結構容納多個輸入資料即可。

square_multiple_integers <- function(x){
  return(x**2)
}

print(square_multiple_integers(c(2, 3)))
print(square_multiple_integers(c(2, 3, 5)))

多個輸出的處理同樣是採用適當的資料結構儲存,像是用 list 儲存兩個向量的相加與相減。

add_subtract_vectors <- function(a, b){
  out <- list(
    "add" = a + b,
    "subtract" = a - b
  )
  return(out)
}

function_output_as_list = add_subtract_vectors(3, 4)
print(function_output_as_list)

在認識了函數的各種眉眉角角之後,第三十週約維安計畫:R 語言自行定義函數來到尾聲,希望您也和我一樣期待下一篇文章。

對於這篇文章有什麼想法呢?喜歡😻、留言🙋‍♂️或者分享🙌

Leave a comment

約維安計畫學員專區

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 DATAINPOINT, INC.
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share