R語言編程藝術(3)R語言編程基礎


本文對應《R語言編程藝術》

第7章:R語言編程結構;

第9章:面向對象的編程;

第13章:調試

 

=========================================================================

R語言編程結構

控制語句:

循環:

for (n in x) { }
while (condition) { }
repeat { }

  


另外break也可以用在另兩種形式的循環語句中。注意repeat沒有跳出循環的判斷條件,因此使用break(或者類似return())的語句。

除此之外,next語句可以用來跳過本次迭代的剩余部分。具體應用情景,是可以替代循環中的條件嵌套語句,不致代碼看起來混亂。

需要注意的是,for結構可以用在任何向量上,無論向量是什么模式,注意靈活運用。

 

對非向量集合的循環:R並不支持直接對非向量集合的循環,但是有一些間接但簡單的方式可以做到這點:

使用lapply()。如果循環的每次迭代之間相互獨立,就是用lapply(),可以允許以任意順序執行;

使用get()。這個函數接受一個代表對象名字的字符串參數,然后返回該對象的內容,看起來簡單,但是十分有用。

#假設u, v都是兩列的矩陣,要分別在這兩個矩陣上應用lm函數
for (m in c(“u”, “v”)) {
z <- get(m)
print(lm(z[, 2] ~ z[, 1]))
}

  

if-else結構: 

#注意以下代碼
if (r == 4) {
  x <- 1
} else {
  x <- 3
  y <- 4
}

  

雖然if后面只有一條語句,但是它對應的大括號是不能省略的,原因在於R的解釋器是根據else前面的右括號判斷這是一個if-else結構,如果沒有大括號,R將認為這是一個if結構。

#if-else結構的簡化
v <- if (cond) expression1 else expression2

  

這是一個簡化代碼的技巧,因為if-else結構最終會返回的值取決於cond是否為真。如果expression是函數調用語句,可以讓代碼更為清晰,但是如果語句復雜,就要優先考慮代碼是否清晰易懂。

處理向量時,使用向量化的ifelse()函數有可能提高運行速度。

 

 

算術和邏輯運算符及數值:

之前提到,R中只有向量,沒有標量,標量可以看作是僅有一個值的向量。但是在邏輯運算時,特別是if語句的條件控制,其邏輯值不能為向量,只能有一個值,也就是可以看作標量。這時,就要區分邏輯與和邏輯或的向量標量運算了。

x && y

標量的邏輯與運算

x || y

標量的邏輯或運算

x & y

向量的邏輯與運算

x | y

向量的邏輯或運算

如果if語句里的控制條件里出現向量並且長度大於1,R的處理方式是將向量的第一個元素作為控制條件返回結果,然后提示warning message

 

 

參數的默認值:

具名實參(named argument):如果不使用默認值,那么在調用函數時必須給出參數的具體名稱;

惰性求值(lazy evaluation)原則:除非有需要,否則不會計算一個表達式的值,也就是說具名實參不一定會被使用。

 

 

返回值:

函數的返回值可以是任何R對象。通常為列表形式,但是如果有需要,甚至可以返回另一個函數。

一般來說,如果只在函數結尾返回值,要避免顯式地調用return()函數。如果想要將程序表達得更加清晰,在函數中間部分返回值的時候可以顯式地調用return()函數。

返回復雜對象:函數的返回值可以是另一個函數。如果函數需要返回多個返回值,可以把它們儲存在一個列表或其他容器變量中。

 

 

函數都是對象:

注意到函數是對象,也就是說對於對象的操作對函數也適用。包括創建、編輯,甚至在以函數組成的列表上做循環。

g <- function(h, a, b) h(a, b)
body(g) <- quote(2 * x + 3)
g(3)  # 9

  


首先,function函數創建了一個函數,並賦值給g;然后body函數修改了g的函數體。因為函數主體部分屬於”call”類,而這種類是由quote()函數生成。

 

環境和變量作用域的問題:

頂層環境:<environment: R_GlobalEnv>

變量作用域的層次:里層的函數可以使用更加頂層的變量,如果層次之間變量命名發生沖突,優先使用最里層的變量。一般來說,里層對於更加頂層的變量只讀不可寫。可寫的情況將單獨討論。

ls()函數:調用不帶參數的ls()可以返回當前的局部變量;使用envir參數可以返回函數調用鏈中任何一個框架的局部變量名。

函數代碼對於其非局部變量一般只讀不寫,即使重新賦值也是影響它們的備份而不是變量本身。事實上,直到局部變量發生變化前,與其對應的全局變量是共享一個內存地址的。

 

 

R語言中沒有指針:

不能直接改變一個函數的參數,只能重新賦值。

其他因為沒有指針造成的問題將在下文討論。

 

 

向上級層次進行寫操作:

利用超賦值運算符對非局部變量進行寫操作:<<-

注意,超賦值運算符進行寫操作的時候,首先逐層向上級環境層次查找,遇到該變量的第一個層次即進行操作,如果沒有遇到,將在全局層次上新建變量。

用assign()函數對非局部變量進行寫操作:

可以通過設置函數參數指定向上層的哪一層次的變量進行寫操作。注意引用變量是使用字符串實現的。

全局變量的使用:一般來說不推薦在函數中寫全局變量,但是有時為了簡化代碼,這么做也是可以的,靈活運用即可。對於大型程序不推薦這樣做,因為存在着重寫不相關卻同名變量的風險。一種折中的方案是,在頂層環境中新建一個封裝全局變量的包,再使用assign()和get()函數通過索引到這個包來訪問全局變量。

閉包:這里的閉包指一種編程方法,具體做法是,先定義一個可創建局部變量的函數,再創建另一個函數可以訪問該變量。這樣,后面的一些函數創建的局部變量之間互不影響,各自獨立。

 

 

遞歸:

用於回歸和分類的遞歸分塊方法庫:rpart

 

 

置換函數:

任何左邊不是標識符(意味變量名)的賦值語句都可看作是“置換函數”。當遇到以下形式:

g(u) <- v

  

R語言會嘗試用以下方式執行:

u <- “g<-”(u, value = v)

  

直觀感受下,就是將值賦值給函數調用的結果。事實上,只要左邊不是變量名,就是一個置換語句,比如u[2] <- 8

 

 

寫函數代碼的工具:

快速載入環境:

 

source(“filename.R”)

  

 編輯函數:

f1 <- edit(f1)
f2 <- edit(f1)

  

 

創建自己的二元運算符:

> “%a2b%” <- function(a, b) return(a + 2 * b)
> 3 %a2b% 5
[1]  13

  

  

匿名函數:

如果函數不是很復雜,可以省去函數定義的過程,在使用function()命令建立函數后直接調用,因為沒有具體的函數名,因此稱為匿名函數。應用情況取決於具體情況,可以使代碼更加易讀緊湊。

 

 

=========================================================================

面向對象的編程

  1. 能接觸到的R中所有東西(從數字到字符串到矩陣)都是對象
  2. R支持“封裝”(encapsulation),即把獨立但相關的數據項目打包為一個類的實例。封裝可以幫助你跟蹤相關的變量,提高清晰度
  3. R類是“多態”(polymorphic)的,這意味着相同的函數使用不同類的對象時可以調用不同的操作。多態可以促進代碼可重用性
  4. R允許“繼承”,即允許把一個給定的類的性質自動賦予為其下屬的更特殊化的類

 

 

S3類:

一個S3類包含一個列表,再附加上一個類名屬性和調度(dispatch)的功能。

 

S3泛型函數:即同一個函數可以針對不同的類調用不同的操作。可以通過method()函數找到給定泛型函數的所有實現方法,返回值中,星號標注的是不可見函數(nonvisible function),即不在默認命名空間中的函數,可以通過getAnywhere()函數找到這些函數。另外可以查看一個類能應用的所有泛型函數,代碼method(, class = “classname”)

 

編寫S3類:“類”屬性可以通過attr()或class()函數手動設置。然后可以針對自己編寫的類,設定獨有的泛型函數,下面是一個例子:

#創建一個名為employee的類的對象
j <- list(name = “Joe”, salary = 55000, union = T)
class(j) <- “employee”
#編寫類employee的打印方法
print.employee <- function(wrkr) {
  cat(wrkr$name, “\n”)
  cat(“salary”, wrkr$salary, “\n”)
  cat(“union member”, wrkr$union, “\n”)
}

  


使用繼承:繼承的思想是在已有類的基礎上創建新的類: 

#創建類employee的子類hrlyemployee
k <- list(name = “Kate”, salary = 68000, union = F, hrsthismonth = 2)
class(k) <- c(“hrlyemployee”, “employee”)

  


新類包含了兩個字符串,分別代表新類和原有的類,新類繼承了原有類的方法,可以使用對應的泛型函數。泛型函數在調度時的工作原理是首先查找兩個類名稱的第一個,如果沒查到,則再去查找第二個類,實現了繼承。

 

S4類:

操作

S3類

S4類

定義類

在構造函數的代碼中隱式定義

setClass()

創建對象

創建列表,設置類屬性

new()

引用成員變量

$

@

實現泛型函數f()

定義f.classname()

setMethod()

聲明泛型函數

UseMethod()

setGeneric()

 

編寫S4類:

> #定義類employee
> setClass(“employee”,
+   representation(
+     name = “character”,
+     salary = “numeric”,
+     union = “logical”)
+ )

[1] “employee”

> #創建一個類的實例
> joe <- new(“employee”, name = “Joe”, salary = 55000, union = TRUE)
> joe

An object of class “employee”

Slot “name”:

[1] “Joe”

Slot “salary”:

[1] 55000

Slot “union”:

[1] TRUE

> #引用成員變量
> joe@salary

[1] 55000

> slot(joe, “salary”)

[1] 55000

> #賦值也可以使用這兩種方法
> joe@salary <- 88000
> slot(joe, “salary”) <- 88000

  

S4類的安全性在於,如果給不存在的成員變量賦值,會有報錯消息,而S3類將會新建一個成員變量。

> joe@salry <- 48000

Error in checkSlotAssignment(object, name, value) :

  “salry” is not a slot in class “employee”

  

在S4類上實現泛型函數:

#show()函數指在交互模式下,輸入變量名直接打印
setMethod(“show”, “employee”,
function(object) {
  inorout <- ifelse(object@union, “is”, “is not”)
  cat(object@name, “has a salary of”, object@salary,
  "and", inorout, "in the union", "\n")
}
)

  

S3類和S4類的對比:

S3具有較強的靈活性,而S4具有很好的安全性。一般來說推薦使用S3使代碼更加靈活,而當安全性更加重要時選擇S4(比如混合編程的情況下)。

 

 

對象的管理:

ls()函數:列出所有對象;

rm()函數:刪除特定對象;

save()函數:保存對象集合,可以用load()載入;

查看對象內部結構的函數:class(), mode(), names(), attributes(), unclass(), str(), edit();

exists()函數:判斷對象是否存在。

 

 

=========================================================================

調試

調試的基本原則:

調試的本質:確認原則;

從小處着手;

模塊化的、自頂向下的調試風格;

反漏洞。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM