R學習筆記 第四篇:函數,分支和循環
變量用於臨時存儲數據,而函數用於操作數據,實現代碼的重復使用。在R中,函數只是另一種數據類型的變量,可以被分配,操作,甚至把函數作為參數傳遞給其他函數。分支控制和循環控制,和通用編程語言的風格很相似,但是,不要因為R具有這些元素,就把R作為通用編程語言來看待,R的最小變量是向量,是一種面向數組(Array-Oriented)的語言。在編程時,盡量用array的方式思考,避免使用循環(for,while,repeat)控制,而使用apply函數家族實現計算的叠代,這是R語言的特色,把特定的函數應用於向量,列表或數組中的每個元素上。
一,R風格的函數
R通過function關鍵字定義函數,函數主要由函數名稱,參數,運行的代碼塊和返回值
1,創建函數
例如,創建一個函數,並把函數賦值給avg變量,可以認為函數的名稱是avg,其有兩個參數a和b,大括號內的代碼是函數體,實現的功能是求兩個數的平均值。在R中創建函數時,如果函數體只有一行代碼,可以省略大括號。
avg=function(a,b)
{
mead(c(a,b))
}
該函數沒有顯式調用return關鍵字,函數代碼塊種計算的最後一次自動最為返回值,在avg函數種,代碼塊調用系統函數mean,返回參數a和b的平均值,調用格式是函數名稱(形式參數):
> avg(2,3) [1] 2.5
調用函數時,如果不命名參數,則R按照位置匹配參數,因此,2對應形式參數a,3對應形式參數b。如果要改變傳遞參數的順序,則可以傳入命名參數:
> avg(b=2,a=3) [1] 2.5
可以為函數的參數設置默認值,當調用函數時,如果沒有為參數傳遞相應的值,那麽函數將自動使用默認值作為函數代碼塊執行的當前值。
> avg=function(a=1,b=1)mean(c(a,b)) > avg() [1] 1
2,把函數作為參數傳遞給其他函數
函數本質上是一個函數類型的變量,和其他類型的變量一樣,能夠作為函數的形式參數,傳遞給其他函數,例如:
> fn_call=function(x,a,b)x(a,b) > fn_call(avg,1,2) [1] 1.5
R提供一個系統函數do.call(fun,args),用於調用其他函數,第一個參數是函數變量,第二個參數是有參數構成的列表:
> do.call(avg,list(1,2)) [1] 1.5
3,查看函數的定義
通過函數args用於顯示函數的頭部文本,用於顯示函數的頭部定義,例如:
> args(avg) function (a = 1, b = 1) NULL
body(fun)函數用於返回函數的代碼塊,例如:
> body(avg)
mean(c(a, b))
除了這兩個函數,還有函數formals(fun),用於查看函數的參數及其默認值,如果函數沒有定義參數的默認值,那麽只顯示參數名,formals顯示的參數名格式是$參數名。函數deparse(fun)用於查看函數調用的函數。
二,分支和循環
分支和循環是通用編程必不可少的兩大流程控制元素,分支語句根據條件,執行不同的代碼,而循環語句重復執行代碼塊,可以根據條件結束循環。
1,分支控制
經典流程控制關鍵字是if-else,特殊的關鍵字ifelse(test, yes, no),當test條件為TRUE時,返回yes值,當test條件為FALSE時,返回no值。
> ifelse(c(1,2,3)>=2,‘Greater‘,‘Less‘) [1] "Less" "Greater" "Greater"
如果分支較多,可以使用switch函數實現分支的選擇,switch函數的第一個參數是表達式(exp),通常是一個字符串;當表達式(exp)匹配後續的參數名(即變量名)時,返回參數的值:
> color=function(t) switch(t,r=‘red‘,g=‘green‘,b=‘blue‘) > color(‘r‘) [1] "red"
如果不匹配任何參數名,switch函數不返回任何值,可以添加一個匿名的參數,當表達式(exp)匹配不上任意一個命名參數時,switch函數將返回匿名參數的值:
> color=function(t) switch(t,r=‘red‘,g=‘green‘,b=‘blue‘, ‘error‘) > color(‘d‘) [1] "error"
2,循環控制
循環控制語句是repeat、while和for,R的向量化自帶循環特性,減少了循環語句使用的場景,但是在重復執行代碼時,循環控制還是非常有用。
repeat 循環:先執行代碼,遇到break關鍵字,結束循環,也可以在break關鍵字前增減if(test)語句,當指定的條件成立(為TRUE)時,執行break關鍵字,結束循環:
repeat { code if(test) break }
while 循環:先檢測條件,如果條件為TRUE,執行code;如果條件為FALSE,結束循環:
while(test) { code }
for 循環:使用叠代器和一個向量參數,在每個循環中,叠代器變量從向量中取得一個值,直到叠代所有得向量:
for(i in c(1:5)) { code }
三,高級循環控制
高級循環控制主要是指編寫向量化的代碼,apply系列的函數使得循環更加向量化,更加高效。
1,重復調用表達式
rep函數把輸入的參數重復多次,如果參數是表達式,rep函數會把表達式的結果重復多次;而replicate函數是重復調用表達式,每次調用表達式的過程都是獨立的,這意味著,如果產生隨機數,rep函數產生的隨機數是”偽隨機的“,重復第一次產生的隨機數,而replicate產生的隨機數是真正隨機的,每次都不相同。
> rep(runif(1),5) [1] 0.8721105 0.8721105 0.8721105 0.8721105 0.8721105 > replicate(5,runif(1)) [1] 0.9426709 0.1280271 0.1926333 0.7091503 0.5404846
2,遍歷數組
R 文檔中,apply函數返作用於數組或矩陣,按照相應的維度(邊,margin)提取參數,調用函數執行計算,並返回的結果,結果的類型是向量,數組,或列表。
Returns a vector or array or list of values obtained by applying a function to margins of an array or matrix.
apply函數的最大特色是用於調用函數,第一個參數是數組或矩陣,第二個參數是邊,即維度的序號或維度名稱,1是指按照第一個維度(row),2是指按照第二個維度(column),3是指按照第三個維度(high),以此類推,也可以是維度的向量,例如,c(1,2);第三個參數是函數變量,函數變量必須能夠接收相應維度的所有項:
apply(X, MARGIN, FUN, ...)
例如,對矩陣按照行(row)調用求和函數,統計每行的加和,即把同一行中的所有列的值相加求和:
> a_matrix=matrix(data=1:12,nrow=4)
> a_matrix
[,1] [,2] [,3]
[1,] 1 5 9
[2,] 2 6 10
[3,] 3 7 11
[4,] 4 8 12
> apply(a_matrix,1,sum)
[1] 15 18 21 24
2,遍歷列表
遍歷列表是指遍歷列表的所有列表項,最常用的apply函數是lapply,該函數名是”list apply“的縮寫,該函數的定義是:lapply(x, FUN, ...),參數x是列表變量,參數FUN是應用在列表項上的函數,lapply函數返回的結果是一個列表,其長度和參數x(參數是一個列表)的長度相同,每一個列表項對應參數x的列表項,只不過,lapply函數把參數FUN指定的函數,作用於參數x的每個列表項,把FUN處理的結果,作為新的列表項返回。
例如,定義一個列表,該列表有兩個列表項,每個列表項中都有重復的元素值:
> a_list=list(c(‘a‘,‘b‘,‘a‘),rep(c(1:2),5)) > a_list [[1]] [1] "a" "b" "a" [[2]] [1] 1 2 1 2 1 2 1 2 1 2View Code
方法1,通過for循環,逐個遍歷列表項,使用unique函數,去除列表項中元素的重復值:
> b_list=list() > for(i in seq_along(a_list)){ + b_list[[i]]=unique(a_list[[i]]); + }
方法2,使用lapply函數,應用unique函數,去除列表項中元素的重復值:
> lapply(a_list,unique)
3,多參數列表應用
mapply是多參數列表應用(multiple argument list apply)的簡稱,mapply函數的第一個參數是函數變量(fun),函數變量可以接收多個向量作為參數。mapply函數的作用是把函數變量應用於參數的各個元素。
> va=c(1:5) > vb=c(6:10) > mapply(sum,va,vb) [1] 7 9 11 13 15
四,分組聚合
在研究數據時,有時需要對數據按照特定的字段進行分組,然後統計各個分組的數據,這就是SQL語法中的分組聚合。在R語言中,可以通過三步實現:拆分-應用-合並(Split-Apply-Combine)
對玩家的遊戲成績進行統計和分析,創建示例數據:
> players_scores=data.frame( + player=rep(c(‘Tom‘,‘Dick‘,‘Jim‘),times=c(2,5,3)), + score=round(runif(10,1,100),-1) + )
計算每個玩家的平均得分,首先對玩家分組,split函數的作用是按照特定的字段對數據框進行分組,第一個參數是數據框對象,第二個參數是分組字段,split函數返回的結果是列表對象。
例如,split(score,player)函數的作用是按照player字段把數據框中的score拆分成一組,也就是說,player 相同的score是同一個分組,填充到同一個列表項中:
> (scores_by_player=with(players_scores,split(score,player))) $Dick [1] 70 20 30 70 70 $Jim [1] 80 90 50 $Tom [1] 80 90
第二步是對每個分組計算平均分,利用lapply函數,把函數引用於列表的每個列表項中:
list_mean_by_player=lapply(scores_by_player,mean)
第三步是把結果合並到單個向量中,也就是把列表轉換成向量,
> unlist(list_mean_by_player) Dick Jim Tom 52.00000 73.33333 85.00000
在數據分析中,”拆分-應用-合並“ 顯示十分繁瑣,tapply函數一次完成所有的三個步驟,一氣呵成:
with(players_scores,tapply(score,player,mean))
tapply函數常用的參數共有三個,第一個參數是:數據框對象或向量,第二個參數是因子列表,也就是分組字段,第三個參數是指對單個分組應用的函數變量:
tapply(X, INDEX, FUN = NULL, ...)
by函數和aggregate函數是tapply函數的包裝函數,功能相同,接口稍微不同。
參考文檔:
R Document
R語言apply函數族筆記
R學習筆記 第四篇:函數,分支和循環