第 8 章 Function and Conditional Execution

反覆執行的程序可定義成為函數function物件。

8.1 函數定義

函數名稱 <- function(input1, input2,...){

函數程序內容

return(output)
}

8.1.1 Input argument與Output value

範例1

\[ u(x,y|\alpha,\beta)=x^{\alpha}y^{\beta} \]

定義函數

utility_cobb_douglass <- function(x,y,.alpha,.beta){
  utilValue <- x^.alpha*y^.beta # 程序產生output值
  return(utilValue) # 傳回output值
}

查詢utility_cobb_douglass物件的class

class(utility_cobb_douglass)

使用函數

utility_cobb_douglass(1,2,0.5,0.8)

沒有<-儲存output時,output value會顯示在螢幕。

<-儲存output時, output value不會顯示在螢幕。

utility_cobb_douglass(1,2,0.5,0.8) -> utilityValue

供給函數: \[ Qs=a+b*P \] 請寫一個供給函數supply_fun(),它有三個inputs:a,b及P; output為Qs。


需求函數: \[ Qd=c-d*P \] 請寫一個供給函數demand_fun(),它有三個inputs:c,d及P; output為Qd。

範例2

library(readr)
transcriptDataFinal <- read_csv("https://raw.githubusercontent.com/tpemartin/github-data/master/transcriptDataFinal.csv")

以下是算出學號“92aS67fee”的平均成績(GPA)的程序

studentID <-"92aS67fee" 
transcriptDataFinal$學號==studentID -> logiChosenStudent

subSample <- transcriptDataFinal[logiChosenStudent,]

sum(subSample$學期成績*subSample$學分數)/
  sum(subSample$學分數) -> GPA

如果常常要算學生的GPA,我們可以定義一個「input學號便可output其GPA」的函數,由前面程序,我們知道

  • studentID是我們的input

  • GPA是我們要的output

所以函數會是如下形式:

gpa_fun <- function(studentID){

  # 把先前程序中除studentID指定部份全放過來,
  
  # `studentID <-"92aS67fee"` 是 studentID指定部份,不用放,因為函數的目的
  #   就是要使用者可以自由指定studentID值。
  
  return(GPA)
}
gpa_fun <- function(studentID){
  transcriptDataFinal$學號==studentID -> logiChosenStudent
  
  subSample <- transcriptDataFinal[logiChosenStudent,]
  
  sum(subSample$學期成績*subSample$學分數)/
    sum(subSample$學分數) -> GPA
  return(GPA)
}

使用gpa_fun()計算“479W9ee8e”, “9efW9aea5”兩位同學平均成績

gpa_fun("479W9ee8e")
gpa_fun("9efW9aea5")

input argument studentID是用來代表輸入值,並不是指global environment裡的studentID物件值。

studentID <-"92aS67fee" 
gpa_fun("479W9ee8e")
gpa_fun("9efW9aea5")
gpa_fun(studentID) # 使用global environmen裡物件studentID的值當輸入值
gpa_fun("92aS67fee")

所有的程式都是為了某個目的在執行,為得到這個目的,會使用某些必要的資訊。若資訊常會改變,可以寫一個函數:

  • Input: 會需要改變的資料物件或值

  • Output: 代表目的結果的物件

在前述例子:計算某個學號的平均成績。

某個學號 -> studentID
開始步驟
:
:
平均成績 -> GPA

則可嘗試定義函數如下

function(studentID){
  開始步驟
  :
  :
  平均成績 -> GPA
  return(GPA)
}

執行以下程序會隨機產生民國年月資料:

library(stringr)
sample(99:103,15,replace = T)-> yy
sample(1:12,15,replace=T)-> mm
str_c("民國",str_pad(yy,3,"left","0"),"年",mm,"月") -> twDates

執行以下程序會把民國年月字串物件twDates轉成date class的西元年月日物件yyyymmdd,其中dd為01。

library(stringr); library(lubridate)
yyyy<-as.integer(str_extract(twDates,"(?<=(民國))[:digit:]+"))+1911
mm<-str_extract(twDates,"[:digit:]+(?=月)")
yyyymmdd <- ymd(str_c(yyyy,mm,"01",sep="-"))

若要常常轉換不同民國年月成西元date,以上述程序為例,何者為input?何者為output?

請寫一個函數其名稱為date_convert_twYM,且date_convert_twYM(x)可以把一個民國年月字串物件x轉成date time class的西元年月日物件輸出。

8.1.2 預設值defaults

Input arguments一般分成兩類:

  • data arguments: 幾乎每次使用時的輸入值都會改變。(如上述的x,y)

  • detail arguments/parameters: 函數參數值或設定值,不會常常改變。(如上述的.alpha, .beta)

建議data arguments放前,detail/parameter arguments放後且設預設值。

utility_cobb_douglass2 <- function(x,y,.alpha=0.5,.beta=0.5){
  utilValue <- x^.alpha*y^.beta
  return(utilValue)
}
utility_cobb_douglass2(1,2) # 使用預設值
utility_cobb_douglass2(1,2,0.5,0.8) # 自定參數值

先前的date_convert_twYM()函數一律把年月資料加上「01」日,請改寫函數讓“01”為「日」的預設值。

8.1.3 return與input

函數程序並不一定要有return與input

範例3

nowAnnounce <- function(){
  currentDateTime <- Sys.time()
  hh<-hour(currentDateTime)
  mm<-minute(currentDateTime)
  cat("現在是",hh,"點",mm,"分")
}

cat()會將所有輸入元素串成一串字在螢幕顯示。

nowAnnounce()

browseURL()函數可以輸入網址後自動開啟瀏覽器連到該網頁,如:

browseURL("https://www.ntpu.edu.tw")

請寫一個textbookURL函數,執行textbookURL()會自動開啟課程網頁。

8.1.4 多個output值

R的函數只能輸出一個物件值,所以若有多個物件值要輸出,可以先併成一個list物件輸出。

範例4

gpa_fun <- function(studentID){
  transcriptDataFinal$學號==studentID -> logiChosenStudent
  
  subSample <- transcriptDataFinal[logiChosenStudent,]
  
  sum(subSample$學期成績*subSample$學分數)/
    sum(subSample$學分數) -> GPA
  return(GPA)
}

想要輸出GPA及該名學生成績單子樣本subSample,可改成。

gpa_fun2 <- function(studentID){
  transcriptDataFinal$學號==studentID -> logiChosenStudent
  
  subSample <- transcriptDataFinal[logiChosenStudent,]
  
  sum(subSample$學期成績*subSample$學分數)/
    sum(subSample$學分數) -> GPA
  return(
    list(
      平均成績=GPA,
      成績單=subSample
      )
  )
}

這裡return的value會是個list, 它包含兩個元素:平均成績(內容為GPA)、成績單(內容為subSample)。

gpa_fun2("92aS67fee") -> outputValue
outputValue$平均成績
outputValue$成績單

創造一個demand_supply_fun(P,a,b,c,d),它會輸出一個list包含三個元素:供給量,需求量,超額供給量

8.2 依條件執行

8.2.1 用法一:

if(邏輯條件){
  成立時執行
}
a <- readline(prompt = "請輸入一個數字: ")
if(a < 0) {
  print("a為負值")
}

範例5

供給函數價格不為負值

a<-0
b<-1
supply_fun <- function(P,a,b){
  Qs <- a+b*P
  return(Qs)
}
supply_fun(P=-1,0,1)
supply_fun2 <- function(P,a,b){
  Qs <- a+b*P
  if(P>=0) {
    return(Qs)
  }
}
supply_fun2(P=-1,0,1)
supply_fun2(P=2,0,1)

8.2.2 用法二:

if(邏輯條件){
  成立時執行
} else {
  不成立時執行
}
a <-  readline(prompt = "請輸入一個數字: ")
if(a < 0) {
  print("a為負值")
} else {
  print("a不為負值")
}

範例6

supply_fun3 <- function(P,a,b){
  Qs <- a+b*P
  if(P < 0) {
    message("P不能為負值") # 在螢幕出現的「錯誤」提示,沒有輸出任何值
  } else {
    return(Qs)
  }
}
supply_fun3(P=-1,0,1)
supply_fun3(P=2,0,1)

print()是單純的訊息輸出,而message()是用在「錯誤」狀況下的訊息顯示。兩者在程式除錯時,會有明顯差別。

修改範例2的gpa_fun, 若輸入學號不存在則在螢幕顯示“查無此學號”。

gpa_fun("92aS67fee") # 顯示82.85276
gpa_fun("aa829838s") # 查無此學號

8.2.3 用法三:

if(條件A) {
  條件A成立執行
} else if(條件B) {
  上個條件不成立,條件B成立時執行
} else {
  若以上情境都不成立時執行
}
a <- readline(prompt = "請輸入一個數字: ")
if(a==1) {
  print("你輸入了1")
} else if(a==2) {
  print("你輸入了2")
} else if(a==3) {
  print("你輸入了3")
} else {
  print("你輸入了其他數字")
}

範例7

供給量不能為負數: \[ \begin{array}{lcl} Qs=a+b*P \geq 0 &\Rightarrow & P\geq -a/b \end{array} \]

supply_fun4 <- function(P,a,b){
  Qs <- a+b*P
  if(P < 0) {
    message("P不能為負值") # 在螢幕出現的「錯誤」提示,沒有輸出任何值
  } else if(P < -a/b) {
    message("P必需大於等於",-a/b,"(即-a/b值)")
  } else {
    return(Qs)
  }
}
supply_fun4(P=-1,-3,1)
supply_fun4(P=2,-3,1)
supply_fun4(P=3,-3,1)

Implement a fizzbuzz function. It takes a single number as input. If the number is divisible by three, it returns “fizz”. If it’s divisible by five it returns “buzz”. If it’s divisible by three and five, it returns “fizzbuzz”. Otherwise, it returns the number. Make sure you first write working code before you create the function. –From, R for Data Science.

注意所有的if邏輯條件產生值都必需是「單一」邏輯值。

a <- c(1,3,5)
a>2 # 此邏輯條件產生三個值
if(a>2) {
  print("a大於2")
}

你可以使用any()all()將logical vector變成一個logical值:

  • any: 只要有一個TRUE就是TRUE
a>2
any(a>2)
  • all: 要全部為TRUE才是TRUE
a>2
all(a>2)

8.3 Environment

當函數被呼喚(called) 執行時會R會創造一個新的Environment,它會是Global Environment的子層。

  • 此子層環境稱為「執行環境(execution environment)」。

  • 此子層用來執行函數{...}內的程序,中間如果有生成任何新的物件,都會活在子層中,並不會活在Global Environment。

  • 此子層執行中如果用到一個物件不存在子層它會往母層找,即這裡的Global Environment去找來使用。

  • 函數執行完「執行環境」就會被刪除,若有物件值要保留在Global environment,需要透過return輸出到Global environment。

a <- 3

testfun <- function(x){
  y <- a/4
  output <- x*y
  return(output)
}

testfun(2)

a <- 4
testfun(2)
  • y物件只短暫活在「執行環境」,不會出現在Global environment。

  • testfun被使用執行時「執行環境」才會出現,a的值是多少得看當下Global environment的a值為多少而定。

8.4 函數使用

不寫input argument名稱

input順序要依原始函數定義順序

utility_cobb_douglass(1,2,0.5,0.8)

寫input argument名稱

utility_cobb_douglass(1,2,.beta=0.8,.alpha=0.5)