這是 R 語言的簡介,說明評估、剖析、物件導向程式設計、語言運算等。
本手冊適用於 R 4.3.3 版(2024-02-29)。
版權所有 © 2000–2023 R Core Team
在所有副本上保留版權公告及此許可公告,即准許製作和散發本手冊的逐字副本。
在以下條件下,准許複製和散發本手冊的修改版本:整個衍生作品必須在與本許可公告相同的許可公告條款下散發。
在修改版本符合上述條件下,授予複製和散布本手冊翻譯版本的權限,但此許可通知可陳述在 R 核心團隊核准的翻譯中。
R 是用於統計運算和繪圖的系統。它提供的內容包括程式語言、高階繪圖、與其他語言的介面和除錯功能。本手冊詳述並定義 R 語言。
R 語言是 S 的方言,S 是在 1980 年代設計的,自此廣泛用於統計社群。它的主要設計者約翰·M·錢伯斯因 S 而獲得 1998 年 ACM 軟體系統獎。
此語言的語法與 C 語言有表面的相似性,但語意是 FPL(函數式程式語言)的變種,與 Lisp 和 APL 的關聯性更強。特別是,它允許「對語言進行運算」,這反過來使得撰寫以表達式作為輸入的函數成為可能,這對於統計建模和繪圖通常很有用。
透過互動式使用 R,從命令列執行 簡單的表達式,就可以完成很多事情。有些使用者可能永遠不需要超越那個層級,而其他人會想要撰寫自己的函數,以臨時的方式系統化重複性的工作,或以撰寫附加套件來獲得新功能的角度進行撰寫。
本手冊的目的是記錄語言本身。也就是說,它所運作的物件,以及表達式評估程序的詳細資料,在編寫 R 函數時會很有用。專門用於特定任務的主要子系統,例如圖形,在本手冊中僅簡要說明,並將另外編寫文件。
儘管大部分文字同樣適用於 S,但仍有一些實質差異,為了不混淆問題,我們將專注於描述 R。
語言的設計包含許多細微之處和常見的陷阱,可能會讓使用者感到驚訝。大部分都是由於較深層次的相容性考量,我們將會說明。還有一些有用的捷徑和慣用語,讓使用者能夠簡潔地表達相當複雜的操作。一旦熟悉基礎概念,許多這些都會變得自然而然。在某些情況下,有許多執行任務的方法,但有些技術會依賴語言實作,而其他技術則在較高層次的抽象化中運作。在這種情況下,我們將指出建議使用的用法。
假設已經熟悉 R。這不是 R 簡介,而是程式設計師參考手冊。其他手冊提供補充資訊:特別是前言在R 簡介中提供 R 簡介,而系統和外國語言介面在撰寫 R 延伸套件中詳細說明如何使用已編譯的程式碼延伸 R。
在每種電腦語言中, 變數提供一種存取儲存在記憶體中的資料的方法。R 不提供直接存取電腦記憶體的方式,而是提供許多我們將稱為 物件的特殊資料結構。這些物件透過符號或變數來參照。然而,在 R 中,符號本身就是物件,而且可以像任何其他物件一樣進行操作。這與許多其他語言不同,並且有廣泛的影響。
在本章中,我們提供 R 中提供的各種資料結構的初步說明。後續章節中將會針對許多資料結構進行更詳細的討論。R 特有的函數 typeof
傳回 R 物件的 類型。請注意,在 R 底層的 C 程式碼中,所有物件都是指向具有 typedef SEXPREC
的結構的指標;不同的 R 資料類型在 C 中由 SEXPTYPE
表示,它決定結構中各個部分的資訊如何使用。
下表說明 typeof
傳回的可能值及其代表的意義。
使用者無法輕易取得標記為「***」的類型物件。
函式 mode
提供有關物件 模式 的資訊,在 Becker、Chambers 和 Wilks (1988) 的意義上,並且與 S 語言的其他實作更相容。 最後,函式 storage.mode
傳回其引數的 儲存模式,在 Becker 等人 (1988) 的意義上。通常在呼叫以另一種語言(例如 C 或 FORTRAN)編寫的函式時使用,以確保 R 物件具有被呼叫常式所預期的資料類型。(在 S 語言中,具有整數或實數值的向量都具有模式 "numeric"
,因此需要區分其儲存模式。)
> x <- 1:3 > typeof(x) [1] "integer" > mode(x) [1] "numeric" > storage.mode(x) [1] "integer"
R 物件在計算期間通常會強制轉換為不同的 類型。也有許多函式可執行明確的 強制轉換。在 R 語言中進行程式設計時,物件的類型通常不會影響計算,但處理外國語言或作業系統時,通常需要確保物件為正確的類型。
向量可以視為包含資料的連續儲存格。儲存格透過 編製索引操作,例如 x[5]
,來存取。更多詳細資料說明請參閱 編製索引。
R 有六種基本的(「原子」)向量類型:邏輯、整數、實數、複數、字元(在 C 中又稱為「字串」)和原始。不同向量類型的模式和儲存模式列於下表。
typeof mode storage.mode 邏輯
邏輯
邏輯
整數
數字
整數
雙精度
數字
雙精度
複數
複數
複數
字元
字元
字元
原始
原始
原始
單一數字,例如 4.2
,以及字串,例如 "four point two"
仍為長度為 1 的向量;沒有更基本的類型。長度為零的向量是可能的(且有用的)。
字元向量的單一元素通常稱為字元字串 或簡稱字串。 1
清單(「一般向量」)是另一種資料儲存。清單有元素,每個元素都可以包含任何類型的 R 物件,亦即清單的元素不必是同種類型。清單元素透過三個不同的索引操作存取。這些操作在索引中詳細說明。
清單是向量,而基本向量類型稱為原子向量,在有必要排除清單時使用。
R 語言由三種類型的物件組成。它們是 呼叫、表達式和 名稱。 由於 R 有 "expression"
類型的物件,因此我們將盡量避免在其他情境中使用表達式這個字。特別是語法正確的表達式將稱為 陳述式。
這些物件的模式分別為 "call"
、"expression"
和 "name"
。
可以使用 quote
機制直接從表達式建立這些物件,並使用 as.list
和 as.call
函數將其轉換為清單或從清單轉換為這些物件。 可以使用標準索引運算提取 解析樹的組成部分。
符號是指 R 物件。任何 R 物件的 名稱通常是符號。符號可以使用 as.name
和 quote
函數建立。
符號的模式為 "name"
,儲存模式為 "symbol"
,類型為 "symbol"
。可以使用 as.character
和 as.name
將其 轉換為字元字串或從字元字串轉換為符號。 它們自然會顯示為已解析表達式的原子,例如嘗試 as.list(quote(x + y))
。
在 R 中,可以有 "expression"
類型的物件。表達式 包含一個或多個陳述式。陳述式是語法正確的 符號集合。 表達式物件是特殊語言物件,其中包含已剖析但未評估的 R 陳述式。主要差異在於表達式物件可以包含多個此類表達式。另一個較為細微的差異是,"expression"
類型的物件僅在明確傳遞給 eval
時才會 評估,而其他語言物件在某些意外情況下可能會被評估。
表達式物件的行為很像清單,其組成部分的存取方式應與清單的組成部分相同。
在 R 中,函數是物件,而且可以像其他物件一樣進行處理。函數(或更精確地說,函數封閉)有三個基本組成部分:一個形式引數清單、一個主體和一個環境。引數清單是一個以逗號分隔的引數清單。一個引數可以是一個符號,或一個‘符號 = 預設值’結構,或特殊引數...
。第二種形式的引數用於指定引數的預設值。如果函數在呼叫時未指定任何值,則會使用此值。 ...
引數很特別,可以包含任意數量的引數。通常在引數數量未知或引數將傳遞給另一個函數的情況下使用它。
主體是一個已剖析的 R 陳述式。它通常是大括號中的陳述式集合,但它可以是一個單一陳述式、一個符號甚至是一個常數。
函數的 環境是在建立函數時處於活動狀態的環境。在該環境中繫結的任何符號都是擷取的,並且可供函數使用。函數程式設計理論中將函數程式碼和其環境中的繫結結合稱為「函數封閉」。在本文件中,我們通常使用術語「函數」,但使用「封閉」來強調附加環境的重要性。
可以使用formals
、body
和environment
結構(所有三個結構也可以用於指定的一側)來擷取和處理封閉物件的三個部分。 最後一個結構可以用於移除不需要的環境擷取。
當呼叫函數時,會建立一個新的環境(稱為評估環境),其封閉(請參閱環境)是函數封閉中的環境。這個新環境最初會以函數中未評估的參數填充;隨著評估進行,會在其中建立局部變數。
還有一個功能,可以用 as.list
和 as.function
將函數轉換成清單結構,反之亦然。 這些功能已包含在內,以提供與 S 的相容性,但建議不要使用。
有一個稱為 NULL
的特殊物件。每當需要指出或指定一個物件不存在時,就會使用它。它不應與長度為零的向量或清單混淆。
NULL
物件沒有類型和可修改的屬性。R 中只有一個 NULL
物件,所有實例都指向它。若要測試 NULL
,請使用 is.null
。您無法在 NULL
上設定屬性。
這兩種物件包含 R 的內建 函數,亦即在程式碼清單中顯示為 .Primitive
的函數(以及透過 .Internal
函數存取的函數,因此使用者無法將其視為物件)。兩者的差異在於引數處理方式。內建函數會評估其所有引數,並依照 依值呼叫 傳遞給內部函數,而特殊函數會將未評估的引數傳遞給內部函數。
從 R 語言來看,這些物件只是另一種函數。 is.primitive
函數可以將它們與已詮釋的 函數區分開來。
承諾物件是 R 延遲評估機制的一部分。它們包含三個槽:一個值、一個表達式和一個 環境。當呼叫 函數時,會比對引數,然後將每個形式引數繫結到一個承諾。會將提供給該形式引數的表達式和呼叫函數的環境指標儲存在承諾中。
直到存取該參數前,承諾中沒有與之關聯的值。存取參數時,會在儲存的環境中評估儲存的表達式,並傳回結果。承諾也會儲存結果。substitute
函式會萃取表達式槽的內容。這讓程式設計師可以存取與承諾關聯的值或表達式。
在 R 語言中,承諾物件幾乎只會隱含地看到:實際的函式參數是這種型別。還有一個 delayedAssign
函式,會讓表達式變成承諾。在 R 程式碼中通常沒有辦法檢查物件是不是承諾,也沒有辦法使用 R 程式碼來判斷承諾的環境。
...
物件型別儲存為一種類型的 pairlist。可以在 C 程式碼中以慣用的 pairlist 方式存取 ...
的元件,但 ...
不容易在已詮釋的程式碼中作為物件存取,甚至通常不應該假設有這種物件的存在,因為這在未來可能會改變。
可以將物件擷取為清單(強制承諾!)例如在 table
中可以看到
args <- list(...) ## .... for (a in args) { ## ....
請注意,實作 ...
作為 pairlist 物件 並非 R API 的一部分,且 base R 之外的程式碼不應依賴 ...
的此一目前說明。另一方面,上述 list(...)
存取,以及其他「點存取」函數 ...length()
、...elt()
、...names()
,以及「保留字」..1
、..2
等,請參閱說明頁面 ?dots
,是穩定 R API 的一部分。
如果函數具有 ...
作為形式參數,則任何與形式參數不符的實際參數都會與 ...
相符。
環境可以視為由兩件事組成。一個 框架,包含一組符號值對,以及一個 封裝,指向封裝環境的指標。當 R 查詢符號的值時,會檢查框架,如果找到相符的符號,則會傳回其值。如果沒有,則會存取封裝環境,然後重複此程序。環境形成一個樹狀結構,封裝扮演父項的角色。環境樹的根部是一個空的 環境,可透過 emptyenv()
取得,它沒有父項。它是基本套件環境 的直接父項(可透過 baseenv()
函數取得)。
環境會由函數呼叫隱含建立,如 函數物件 和 詞法環境 所述。在此情況下,環境包含函數的局部變數(包括參數),且其封閉環境是目前呼叫函數的環境。環境也可以由 new.env
直接建立。 可以使用 ls
、names
、$
、[
、[[
、get
和 get0
存取環境的框架內容, 並且可以使用 $<-
、[[<-
和 assign
以及 eval
和 evalq
進行操作。
可以使用 parent.env
函數存取環境的封閉環境。
與大多數其他 R 物件不同,將環境傳遞給函數或用於指定時,不會複製環境。因此,如果您將相同的環境指定給多個符號並變更其中一個,其他符號也會變更。特別是,將屬性指定給環境可能會造成意外結果。
配對清單物件類似於 Lisp 的點對點清單。它們廣泛用於 R 的內部,但在解譯的程式碼中很少見,儘管它們是由 formals
傳回,並且可以由 (例如) pairlist
函數建立。零長度的配對清單為 NULL
,這在 Lisp 中是可以預期的,但與零長度清單相反。 這樣的每個物件都有三個槽位,一個 CAR 值、一個 CDR 值和一個 TAG 值。TAG 值為文字字串,而 CAR 和 CDR 通常分別代表清單項目 (頭部) 和清單的其餘部分 (尾部),並以 NULL 物件作為終結符 (CAR/CDR 術語是傳統 Lisp,最初是指 60 年代早期 IBM 電腦上的位址和遞減暫存器)。
配對清單在 R 語言中以與一般向量(「清單」)完全相同的方式處理。特別是,使用相同的 [[]]
語法存取元素。配對清單的使用已不建議,因為一般向量通常使用起來更有效率。當從 R 存取內部配對清單時,通常會將其轉換為一般向量(包括子集)。
在極少數情況下,配對清單對使用者可見:一個是 .Options
。
除了 NULL
以外的所有物件都可以附加一個或多個屬性。屬性儲存在所有元素都已命名的配對清單中,但應視為一組 name=value 對。可以使用 attributes
取得屬性清單,並使用 attributes<-
設定, 使用 attr
和 attr<-
存取個別元件。
有些屬性具有特殊的存取器 函數(例如,因子的 levels<-
),如果可用,應使用這些函數。除了隱藏實作細節之外,它們還可能執行其他作業。R 會嘗試攔截呼叫 attr<-
和 attributes<-
,這些呼叫涉及特殊屬性,並強制執行一致性檢查。
矩陣和陣列只是附加了屬性 dim
(以及選用 dimnames
)的向量。
屬性用於實作 R 中使用的類別結構。如果物件有 class
屬性,則會在 評估期間檢查該屬性。R 中的類別結構在 物件導向程式設計 中有詳細說明。
當存在時,names
屬性會標籤向量或清單的個別元素。當列印物件時,如果存在,names
屬性會用於標籤元素。names
屬性也可以用於索引目的,例如 quantile(x)["25%"]
。
可以使用 names
和 names<-
建構來取得和設定名稱。 後者會執行必要的相容性檢查,以確保名稱屬性具有適當的類型和長度。
配對清單和一維陣列會特別處理。對於配對清單物件,會使用虛擬 names
屬性;names
屬性實際上是由清單組件的標籤建構的。對於一維陣列,names
屬性實際上會存取 dimnames[[1]]
。
dim
屬性用於實作陣列。陣列的內容儲存在向量中,以欄優先順序排列,而 dim
屬性是整數向量,用於指定陣列各自的範圍。R 確保向量的長度為維度長度的乘積。一個或多個維度的長度可以為零。
向量與一維陣列不同,因為後者有長度為一的 dim
屬性,而前者沒有 dim
屬性。
R 有精密的類別系統2,主要透過 class
屬性控制。此屬性是包含物件繼承的類別清單的字元向量。這形成 R 中「通用方法」功能的基礎。
使用者幾乎可以不受限制地存取和操作此屬性。並未檢查物件是否實際包含類別方法預期的元件。因此,變更 class
屬性時應謹慎,且在有特定建立和 轉換函數時應優先使用這些函數。
變更物件時是否應複製屬性是一個複雜的領域,但有一些一般規則 (Becker, Chambers & Wilks,1988 年,第 144–6 頁)。
純量函數(對向量進行逐元素運算,其輸出與輸入類似)應保留屬性(可能除了類別)。
二元運算通常會從較長的引數複製大部分屬性(如果長度相同,則優先使用第一個引數的值)。這裡的「大部分」表示除了 names
、dim
和 dimnames
之外的一切,這些屬性會由運算子的程式碼適當地設定。
子集化(除了空索引)通常會捨棄所有屬性,除了 names
、dim
和 dimnames
,這些屬性會適當地重新設定。另一方面,子指派通常會保留屬性,即使長度已變更。強制轉換會捨棄所有屬性。
排序的預設方法會捨棄所有屬性,除了名稱,名稱會與物件一起排序。
因子用於描述可以有有限數值(性別、社會階級等)的項目。因子具有 levels
屬性和類別 "factor"
。此外,它也可以包含 contrasts
屬性,該屬性控制在建模函數中使用因子時所使用的參數化。
因子可能是純粹的名稱,或是有序的類別。後者的情況下,應定義為 class
向量 c("ordered"," factor")
。
目前使用整數陣列來實作因子,以指定實際層級,以及第二個名稱陣列,對應到整數。很不幸的是,使用者經常利用實作來讓某些計算更容易。然而,這是一個實作問題,無法保證在 R 的所有實作中都成立。
資料框是 R 中最接近 SAS 或 SPSS 資料集的結構,也就是「案例對變數」資料矩陣。
資料框是由向量、因子和/或矩陣組成的清單,所有長度都相同(矩陣的情況下為列數)。此外,資料框通常有一個 names
屬性,標示變數,以及一個 row.names
屬性,標示案例。
資料框可以包含一個與其他元件長度相同的清單。清單可以包含不同長度的元素,因此提供了一個用於不規則陣列的資料結構。然而,在撰寫本文時,此類陣列通常無法正確處理。
當使用者在提示字元輸入命令(或從檔案讀取表達式)時,第一個步驟是命令會由剖析器轉換成內部表示。評估器會執行已剖析的 R 表達式,並傳回表達式的值。所有表達式都有值。這是語言的核心。
本章節說明評估器的基本機制,但避免討論特定函數或函數群組,這些內容會在後續的章節中個別說明,或說明文件頁面已提供足夠的說明。
使用者可以建構表達式,並呼叫評估器對其進行評估。
直接在提示中輸入的任何數字都是常數,並且會被評估。
> 1 [1] 1
也許出乎意料的是,從表達式 1
返回的數字是數字。在大多數情況下,整數和數字值之間的差異並不重要,因為 R 在使用數字時會做正確的事。然而,有時我們希望明確為常數創建一個整數值。我們可以通過調用函數 as.integer
或使用其他各種技術來做到這一點。但也許最簡單的方法是用後綴字符「L」限定我們的常數。例如,要創建整數值 1,我們可以使用
> 1L [1]
我們可以使用「L」後綴限定任何數字,以使其成為明確的整數。因此,「0x10L」從十六進制表示法中創建整數值 16。常數 1e3L
給出 1000 作為整數而不是數字值,並且等於 1000L
。(請注意,「L」被視為限定術語 1e3
而不是 3
。)如果我們使用「L」限定一個不是整數值的值,例如 1e-3L
,我們會收到一個警告,並且會創建數字值。如果數字中有一個不必要的十進制點,例如 1.L
,也會創建一個警告。
當我們對複數使用「L」時,我們會得到一個語法錯誤,例如 12iL
會給出一個錯誤。
常數相當無聊,要了解更多,我們需要符號。
當建立一個新變數時,它必須有一個名稱,以便它可以被參照,而且它通常有一個值。名稱本身是一個符號。當一個符號被評估時,它的值會被傳回。稍後我們將詳細說明如何確定與符號關聯的值。
在這個小範例中,y
是一個符號,它的值是 4。符號也是一個 R 物件,但除了在進行「語言程式設計」(語言程式設計)時,很少需要直接處理符號。
> y <- 4 > y [1] 4
在 R 中執行的大部分運算都涉及函式的評估。我們也將這稱為函式呼叫。函式以名稱呼叫,並附有以逗號分隔的引數清單。
> mean(1:10) [1] 5.5
在這個範例中,函式mean
以一個引數呼叫,即從 1 到 10 的整數向量。
R 包含大量具有不同用途的函式。大多數用於產生 R 物件的結果,但其他則用於其副作用,例如列印和繪製函式。
函數呼叫可以有標記(或命名)參數,如同 plot(x, y, pch = 3)
中的。沒有標記的參數稱為位置,因為函數必須從呼叫參數的順序位置中區分其意義,例如 x
表示橫軸變數,而 y
表示縱軸變數。對於具有大量選用參數的函數而言,使用標記/名稱顯然很方便。
一種特殊類型的函數呼叫可以出現在 指定運算符的左側,如下所示
> class(x) <- "foo"
此結構實際上所做的,是用原始物件和右側呼叫函數 class<-
。此函數執行物件修改,並傳回結果,然後將結果儲存回原始變數中。(至少在概念上,這是發生的事情。會做一些額外的努力來避免不必要的資料複製。)
R 允許使用算術表達式,其中運算符類似於 C 程式語言的運算符,例如
> 1 + 2 [1] 3
可以使用括號對表達式進行分組,與函數呼叫混合,並以直接的方式指定給變數
> y <- 2 * (a + log(x))
R 包含多個運算符。它們列在下表中。
-
減號,可以是一元或二元 +
加號,可以是一元或二元 !
一元非 ~
波浪號,用於模型公式,可以是一元或二元 ?
說明 :
序列,二元(在模型公式中:交互作用) *
乘法,二元 /
除法,二元 ^
指數,二元 %x%
特殊二元運算子,x 可以替換為任何有效名稱 %%
模數,二元 %/%
整數除法,二元 %*%
矩陣乘積,二元 %o%
外積,二元 %x%
克羅內克積,二元 %in%
匹配運算子,二元(在模型公式中:巢狀) <
小於,二元 >
大於,二元 ==
等於,二元 >=
大於或等於,二元 <=
小於或等於,二元 &
與,二元,向量化 &&
與,二元,非向量化 |
或,二元,向量化 ||
或,二元,非向量化 <-
左賦值,二元 ->
右賦值,二元 $
清單子集,二元
除了語法之外,應用運算子和呼叫函數之間沒有區別。事實上,x + y
可以等效地寫成 `+`(x, y)
。請注意,由於「+」是非標準函數名稱,因此需要加上引號。
R 一次處理整個資料向量,而大多數基本運算子和基本數學函數(例如 log
)都是向量化的(如上表所示)。這表示例如新增兩個相同長度的向量會建立一個包含元素和的向量,並隱式地迴圈遍歷向量索引。這也適用於其他運算子,例如 -
、*
和 /
,以及較高維度的結構。特別注意,將兩個矩陣相乘不會產生一般的矩陣乘積(%*%
運算子用於此目的)。與向量化運算相關的一些較細微的重點將在 基本算術運算 中討論。
若要存取原子向量的個別元素,通常會使用 x[i]
結構。
> x <- rnorm(5) > x [1] -0.12526937 -0.27961154 -1.03718717 -0.08156527 1.37167090 > x[2] [1] -0.2796115
清單組件較常使用 x$a
或 x[[i]]
存取。
> x <- options() > x$prompt [1] "> "
如同其他運算子,索引實際上是由函式完成,可以使用 `[`(x, 2)
取代 x[2]
。
R 的索引運算包含許多進階功能,在 索引 中有進一步說明。
R 中的運算包含循序評估 陳述式。陳述式,例如 x<-1:10
或 mean(y)
,可以用分號或新行分隔。每當 評估器遇到語法完整的陳述式時,就會評估該陳述式並傳回 值。評估陳述式的結果可以稱為陳述式的值3 這個值總是能指派給符號。
分號和新行都可以用來分隔陳述式。分號總是表示陳述式的結尾,而新行 可能 表示陳述式的結尾。如果目前的陳述式語法不完整,評估器會忽略新行。如果工作階段是互動式的,提示字元會從「>」變更為「+」。
> x <- 0; x + 5 [1] 5 > y <- 1:10 > 1; 2 [1] 1 [1] 2
陳述式可以使用大括號「{」和「}」進行分組。一組陳述式有時稱為區塊。當在語法上完整的陳述式結尾輸入新行時,會評估單一陳述式。在閉合大括號後輸入新行之前,不會評估區塊。在本節的其餘部分中,陳述式是指單一陳述式或區塊。
> { x <- 0 + x + 5 + } [1] 5
if
/else
陳述式有條件地評估兩個陳述式。有一個會被評估的條件,如果值為TRUE
,則會評估第一個陳述式;否則會評估第二個陳述式。if
/else
陳述式會傳回所選陳述式的值作為其值。正式語法為
if ( statement1 ) statement2 else statement3
首先,會評估 statement1 以產生 value1。如果 value1 是第一個元素為 TRUE
的邏輯向量,則會評估 statement2。如果 value1 的第一個元素為 FALSE
,則會評估 statement3。如果 value1 是數值向量,則當 value1 的第一個元素為零時,會評估 statement3,否則會評估 statement2。只會使用 value1 的第一個元素。所有其他元素都會被忽略。如果 value1 的類型不是邏輯或數值向量,則會發出錯誤訊號。
if
/else
陳述式可用於避免數值問題,例如取負數的對數。由於 if
/else
陳述式與其他陳述式相同,因此您可以指定它們的值。以下兩個範例是等效的。
> if( any(x <= 0) ) y <- log(1+x) else y <- log(x) > y <- if( any(x <= 0) ) log(1+x) else log(x)
else
子句是可選的。陳述式 if(any(x <= 0)) x <- x[x <= 0]
是有效的。當 if
陳述式不在區塊中時,else
(如果存在)必須出現在與 statement2 結尾相同的行上。否則,statement2 結尾的新行會完成 if
,並產生一個語法上完整的陳述式,該陳述式會被評估。一個簡單的解決方案是使用包在括號中的複合陳述式,將 else
放在與標記陳述式結尾的閉合括號相同的行上。
if
/else
陳述式可以巢狀。
if ( statement1 ) { statement2 } else if ( statement3 ) { statement4 } else if ( statement5 ) { statement6 } else statement8
一個偶數編號的陳述式將會被評估,並傳回結果值。如果省略選用的 else
子句,且所有奇數編號的 陳述式 評估為 FALSE
,則不會評估任何陳述式,並傳回 NULL
。
奇數編號的 陳述式 會依序評估,直到其中一個評估為 TRUE
,然後評估關聯的偶數編號 陳述式。在此範例中,陳述式6 僅會在 陳述式1 為 FALSE
、陳述式3 為 FALSE
且 陳述式5 為 TRUE
時評估。允許的 else if
子句數量沒有限制。
R 有三個提供明確迴圈的陳述式。4 它們是 for
、while
和 repeat
。兩個內建建構,next
和 break
,提供對評估的額外控制。R 提供其他函式進行隱式迴圈,例如 tapply
、apply
和 lapply
。此外,許多運算,特別是算術運算,都是向量化的,因此您可能不需要使用迴圈。
有兩個陳述式可用於明確控制迴圈。它們是 break
和 next
。 break
陳述式會導致退出目前正在執行的最內層迴圈。next
陳述式會立即導致控制權傳回迴圈的開頭。然後執行迴圈的下一次反覆運算(如果有的話)。不會評估目前迴圈中 next
下方的任何陳述式。
迴圈陳述式傳回的值總是 NULL
,而且會隱藏傳回。
repeat
陳述式會導致重複評估主體,直到特別要求中斷。這表示您在使用 repeat
時需要小心,因為有無限迴圈的危險。repeat
迴圈的語法是
repeat statement
使用 repeat
時,statement 必須為區塊陳述式。您需要同時執行一些運算,並測試是否要中斷迴圈,而這通常需要兩個陳述式。
while
陳述式與 repeat
陳述式非常類似。 while
迴圈的語法為
while ( statement1 ) statement2
其中 statement1 會被評估,如果其值為 TRUE
,則會評估 statement2。此程序會持續進行,直到 statement1 評估為 FALSE
為止。
for
迴圈的語法為
for ( name in vector ) statement1
其中 vector 可以是向量或清單。對於 vector 中的每個元素,變數 name 會設定為該元素的值,並評估 statement1。副作用是變數 name 在迴圈結束後仍然存在,且具有迴圈評估的 vector 最後一個元素的值。
技術上來說,switch
只是另一個函式,但其語意接近其他程式語言的控制結構。
語法為
switch (statement, list)
其中 list 的元素可以命名。首先,statement 會被評估,並取得結果 value。如果 value 是 1 到 list 長度之間的數字,則會評估 list 中對應的元素,並傳回結果。如果 value 太大或太小,則會傳回 NULL
。
> x <- 3 > switch(x, 2+2, mean(1:10), rnorm(5)) [1] 2.2903605 2.3271663 -0.7060073 1.3622045 -0.2892720 > switch(2, 2+2, mean(1:10), rnorm(5)) [1] 5.5 > switch(6, 2+2, mean(1:10), rnorm(5)) NULL
如果 value 是字元向量,則會評估 ...
中名稱與 value 完全相符的元素。如果沒有相符的項目,則會使用單一未命名參數作為預設值。如果未指定預設值,則會傳回 NULL
。
> y <- "fruit" > switch(y, fruit = "banana", vegetable = "broccoli", "Neither") [1] "banana" > y <- "meat" > switch(y, fruit = "banana", vegetable = "broccoli", "Neither") [1] "Neither"
常見的 switch
用法是根據函式其中一個參數的字元值進行分支。
> centre <- function(x, type) { + switch(type, + mean = mean(x), + median = median(x), + trimmed = mean(x, trim = .1)) + } > x <- rcauchy(10) > centre(x, "mean") [1] 0.8760325 > centre(x, "median") [1] 0.5360891 > centre(x, "trimmed") [1] 0.6086504
switch
會傳回已評估陳述式的值,或是在沒有評估任何陳述式時傳回 NULL
。
若要從已存在的選項清單中進行選擇,switch
可能不是用來選取一個選項進行評估的最佳方式。通常會透過 eval
和子集運算子 [[
直接使用 eval(x[[condition]])
會比較好。
在此區段中,我們將探討適用於基本運算(例如兩個向量或矩陣的加法或乘法)的規則的細微差別。
如果嘗試將兩個具有不同元素數量的結構相加,則會將最短的結構循環利用至最長的結構長度。也就是說,例如將 c(1, 2, 3)
加到六個元素的向量中,則實際上會將 c(1, 2, 3, 1, 2, 3)
相加。如果較長向量的長度不是較短向量的倍數,則會發出警告。
從 R 1.4.0 開始,任何涉及零長度向量的算術運算都會產生零長度的結果。
統計意義上的缺失值,即值未知的變數,其值為 NA
。這不應與未提供函數參數的 missing
屬性混淆(請參閱 參數)。
由於原子向量的元素必須具有相同的類型,因此有多種 NA
值類型。在特定情況下,這對使用者而言特別重要。 NA
的預設類型為 logical
,除非強制轉換為其他類型,因此缺失值可能會觸發邏輯索引而不是數字索引(有關詳細資訊,請參閱 索引)。
使用 NA
進行的數字和邏輯計算通常會傳回 NA
。如果運算結果對於 NA
可能採取的所有值都相同,則運算可能會傳回此值。特別是,「FALSE & NA」為 FALSE
,「TRUE | NA」為 TRUE
。 NA
不等於任何其他值或自身;使用 is.na
測試 NA
。 但是,NA
值會在 match
中與另一個 NA
值匹配。
結果未定義的數字計算,例如「0/0」,會產生 NaN
值。這只存在於 double
類型和複數類型的實數或虛數部分。提供 is.nan
函數來特別檢查 NaN
,is.na
也會傳回 TRUE
給 NaN
。 將 NaN
轉換為邏輯或整數類型會產生適當類型的 NA
,但轉換為字元會產生字串 "NaN"
。 NaN
值無法比較,因此涉及 NaN
的等式或排序測試將會產生 NA
。 match
將其視為與任何 NaN
值相符(且沒有其他值,甚至 NA
)。
字元類型的 NA
自 R 1.5.0 起與字串 "NA"
不同。需要指定明確字串 NA
的程式設計師應使用「NA_character_」而非 "NA"
,或使用 is.na<-
將元素設定為 NA
。
有常數 NA_integer_
、NA_real_
、NA_complex_
和 NA_character_
,它們會在 (剖析器中) 產生適當類型的 NA
值,且在無法識別 NA
類型時會在反剖析中使用 (而且 control
選項要求執行此動作)。
原始向量沒有 NA
值。
R 包含多個建構,允許透過編製索引運算來存取個別元素或子集。對於基本向量類型,可以使用 x[i]
存取第 i 個元素,但也可以編製清單、矩陣和多維陣列的索引。除了使用單一整數編製索引外,還有多種編製索引的形式。編製索引可用於擷取物件的一部分,也可以用於取代物件的一部分(或新增部分)。
R 有三個基本的編製索引運算子,其語法由下列範例顯示
x[i] x[i, j] x[[i]] x[[i, j]] x$a x$"a"
對於向量和矩陣,[[
形式很少使用,儘管它們與 [
形式有一些細微的語意差異(例如,它會捨棄任何 names
或 dimnames
屬性,而且會對字元索引使用部分比對)。使用單一索引編製多維結構的索引時,x[[i]]
或 x[i]
會傳回 x
的第 i
個順序元素。
對於清單,通常使用 [[
來選取任何單一元素,而 [
則會傳回所選元素的清單。
[[
形式只允許使用整數或字元索引選取單一元素,而 [
則允許使用向量編製索引。不過請注意,對於清單或其他遞迴物件,索引可以是向量,而且向量的每個元素會依序套用至清單、所選元件、該元件的所選元件,以此類推。結果仍然是單一元素。
使用 $
的形式適用於遞迴物件,例如清單和配對清單。它只允許文字字串或符號作為索引。也就是說,索引無法計算:對於需要評估表達式以尋找索引的情況,請使用 x[[expr]]
。將 $
套用至非遞迴物件會產生錯誤。
R 允許使用向量作為索引的一些強大建構。我們將首先討論簡單向量的索引。為簡化起見,假設表達式為 x[i]
。然後根據 i
的類型存在以下可能性。
i
的所有元素都必須具有相同的符號。如果它們為正,則選取那些索引號碼的 x
元素。如果 i
包含負元素,則選取除那些元素之外的所有元素。
如果 i
為正且超過 length(x)
,則對應的選取為 NA
。對於 i
的負超出邊界值,自 R 2.6.0 版本開始,會默不作聲地忽略,因為它們表示要捨棄不存在的元素,而這是一個空操作(「no-op」)。
特殊情況是零索引,它沒有任何效果:x[0]
是個空向量,否則在正負索引中包含零的效果與省略它們的效果相同。
i
通常應該與 x
長度相同。如果它較短,則其元素將會像 基本算術運算 中討論的那樣被循環使用。如果它較長,則 x
在概念上會用 NA
延伸。所選取的 x
值是 i
為 TRUE
的那些值。
i
中的字串會與 x
的 names 屬性進行比對,並使用產生的整數。對於 [[
和 $
,如果完全比對失敗,則會使用部分比對,因此如果 x
不包含名為 "aa"
的元件,而 "aabb"
是唯一具有前綴 "aa"
的名稱,則 x$aa
將比對 x$aabb
。對於 [[
,部分比對可透過 exact
參數進行控制,其預設值為 NA
,表示允許部分比對,但發生時應產生警告。將 exact
設定為 TRUE
可防止部分比對發生,FALSE
值允許部分比對且不會發出任何警告。請注意,[
始終需要完全比對。字串 ""
具有特殊用途:它表示「沒有名稱」且不比對任何元素(甚至沒有名稱的元素)。請注意,部分比對僅在擷取時使用,而不在替換時使用。
x[as.integer(i)]
相同。從未使用因子層級。如果需要,請使用 x[as.character(i)]
或類似的結構。
x[]
會傳回 x
,但會從結果中移除「不相關」的屬性。只有 names
,以及在多維陣列中會保留 dim
和 dimnames
屬性。
integer(0)
。
以遺失(即 NA
)值進行索引會產生 NA
結果。此規則也適用於邏輯索引的情況,即 x
中有 i
中 NA
選擇器的元素會包含在結果中,但其值會是 NA
。
不過,請注意,NA
有不同的模式—文字常數的模式為 "logical"
,但它經常會自動強制轉換為其他類型。這會產生一個影響,即 x[NA]
的長度為 x
,但 x[c(1, NA)]
的長度為 2。這是因為在前一種情況下適用邏輯索引的規則,但在後一種情況下適用整數索引的規則。
使用 [
進行索引也會對任何名稱屬性執行相關的子集化。
子集化多維度結構通常遵循與單維度索引相同的規則,針對每個索引變數,dimnames
的相關組成部分取代 names
。不過,有幾項特殊規則適用
通常,結構會使用對應於其維度的索引數目來存取。不過,也可以使用單一索引,這種情況下會忽略 dim
和 dimnames
屬性,而結果實際上等同於 c(m)[i]
。請注意,m[1]
通常與 m[1, ]
或 m[, 1]
非常不同。
可以使用整數矩陣作為索引。在這種情況下,矩陣的欄位數目應與結構的維度數目相符,而結果會是一個向量,長度與矩陣的列數相同。以下範例顯示如何一次提取元素 m[1, 1]
和 m[2, 2]
。
> m <- matrix(1:4, 2) > m [,1] [,2] [1,] 1 3 [2,] 2 4 > i <- matrix(c(1, 1, 2, 2), 2, byrow = TRUE) > i [,1] [,2] [1,] 1 1 [2,] 2 2 > m[i] [1] 1 4
索引矩陣可能不包含負索引。NA
和零值是允許的:包含零的索引矩陣中的列會被忽略,而包含 NA
的列會在結果中產生 NA
。
在使用單一 索引和矩陣索引的情況下,如果存在 names
屬性,則會使用該屬性,就像結構是一維的一樣。
如果索引運算導致結果有一個長度為一的範圍,例如選擇一個三維矩陣的單一區塊(例如 m[2, , ]
),對應的維度通常會從結果中刪除。如果產生單一維度結構,就會取得一個向量。這偶爾是不需要的,而且可以透過在索引運算中加入「drop = FALSE」來關閉。請注意,這是 [
函式的附加引數,而且不會新增到索引計數。因此,選擇矩陣第一列為 1 乘以 n 矩陣的正確方式為 m[1, , drop = FALSE]
。忘記停用刪除功能是常在一般子常式中失敗的原因,其中索引偶爾但通常長度為一。這個規則仍然適用於一維陣列,其中任何子集都會產生向量結果,除非使用「drop = FALSE」。
請注意,向量與一維陣列不同,後者具有 dim
和 dimnames
屬性(長度皆為一)。無法輕易從子集運算取得一維陣列,但可以明確建構它們,而且會由 table
傳回。這有時很有用,因為 dimnames
清單的元素本身可以命名,但 names
屬性不行。
某些運算(例如 m[FALSE, ]
)會產生結構,其中一個維度具有零範圍。R 通常會試著合理處理這些結構。
運算子 [
是一個通用函數,允許新增類別方法,以及 $
和 [[
運算子也是如此。因此,可以為任何結構設定使用者定義的索引運算。此類函數(例如 [.foo
)會呼叫一組引數,其中第一個引數為要索引的結構,其餘為索引。在 $
的情況下,即使使用 x$"abc"
形式,索引引數的模式仍為 "symbol"
。務必注意,類別方法不一定會以相同的方式運作,例如在部分比對方面。
類別方法 [
最重要的範例是資料框所使用的範例。本文不會詳細說明(請參閱 [.data.frame
的說明頁面),但廣義來說,如果提供兩個索引(即使其中一個為空),它會為結構建立類似矩陣的索引,而此結構基本上是長度相同的向量清單。如果提供單一索引,它會被解釋為索引欄位清單,在這種情況下,drop
引數會被忽略,並會顯示警告。
基本運算子 $
和 [[
可以套用至環境。只允許字元索引,且不會執行部分比對。
指定結構子集等於複雜指定的一般機制中的特殊情況
x[3:5] <- 13:15
此指令的結果等於執行下列指令
`*tmp*` <- x x <- "[<-"(`*tmp*`, 3:5, value=13:15) rm(`*tmp*`)
請注意,指標會先轉換為數字指標,然後沿著數字指標順序替換元素,就好像使用了 for
迴圈一樣。任何現有的變數名為 `*tmp*`
都會被覆寫並刪除,且此變數名稱不應在程式碼中使用。
同樣的機制也可以套用在 [ 以外的其他函式。替換函式的名稱與 <-
貼在一起。其最後一個引數必須稱為 value
,這是要指派的新值。例如,
names(x) <- c("a","b")
等於
`*tmp*` <- x x <- "names<-"(`*tmp*`, value=c("a","b")) rm(`*tmp*`)
複雜指派的巢狀結構會遞迴評估
names(x)[3] <- "Three"
等於
`*tmp*` <- x x <- "names<-"(`*tmp*`, value="[<-"(names(`*tmp*`), 3, value="Three")) rm(`*tmp*`)
在封閉環境中進行的複雜指派(使用 <<-
)也允許
names(x)[3] <<- "Three"
等於
`*tmp*` <<- get(x, envir=parent.env(), inherits=TRUE) names(`*tmp*`)[3] <- "Three" x <<- `*tmp*` rm(`*tmp*`)
也等於
`*tmp*` <- get(x,envir=parent.env(), inherits=TRUE) x <<- "names<-"(`*tmp*`, value="[<-"(names(`*tmp*`), 3, value="Three")) rm(`*tmp*`)
只有目標變數會在封閉環境中評估,因此
e<-c(a=1,b=2) i<-1 local({ e <- c(A=10,B=11) i <-2 e[i] <<- e[i]+1 })
在 LHS 和 RHS 使用 i
的區域值,並在超指派陳述式的 RHS 使用 e
的區域值。它將外部環境中的 e
設定為
a b 1 12
也就是說,超指派等於四行
`*tmp*` <- get(e, envir=parent.env(), inherits=TRUE) `*tmp*`[i] <- e[i]+1 e <<- `*tmp*` rm(`*tmp*`)
類似地
x[is.na(x)] <<- 0
等於
`*tmp*` <- get(x,envir=parent.env(), inherits=TRUE) `*tmp*`[is.na(x)] <- 0 x <<- `*tmp*` rm(`*tmp*`)
而不是
`*tmp*` <- get(x,envir=parent.env(), inherits=TRUE) `*tmp*`[is.na(`*tmp*`)] <- 0 x <<- `*tmp*` rm(`*tmp*`)
這兩種候選詮釋只有在也有區域變數 x
時才有所不同。最好避免使用與超指派的目標變數相同名稱的區域變數。由於這個案例在 1.9.1 及更早版本中處理不正確,因此一定沒有這種程式碼的嚴重需求。
幾乎每種程式語言都有一組作用域規則,允許不同的物件使用相同的名稱。這允許,例如,函式中的局部變數與全域物件具有相同的名稱。
R 使用與 Pascal 等語言類似的詞彙作用域模型。然而,R 是一種函式程式語言,允許動態建立和操作函式和語言物件,並具有反映此事實的其他功能。
每次呼叫函式都會建立一個框架,其中包含函式中建立的局部變數,並在環境中評估,而環境與框架結合會建立一個新的環境。
注意術語:框架是一組變數,環境是框架的巢狀(或等效地:最內層框架加上封閉環境)。
環境可以指定給變數或包含在其他物件中。但是,請注意它們不是標準物件,特別是它們在指定時不會被複製。
封閉(模式 "函數"
)物件將包含它在其中建立的環境,作為其定義的一部分(預設。可以使用 environment<-
來處理環境)。當函數隨後被呼叫時,它的 評估環境會以封閉的環境作為封閉來建立。請注意,這不一定是呼叫者的環境!
因此,當在 函數內部請求變數時,會先在 評估環境中搜尋,然後在封閉中搜尋,封閉的封閉中搜尋,等等;一旦到達全域環境或套件的環境,搜尋會沿著搜尋路徑繼續進行,直到基本套件的環境。如果在那裡找不到變數,搜尋將會繼續進行到空環境,並且會失敗。
每次呼叫 函數時,都會建立一個新的評估框架。在運算的任何時間點,都可以透過 呼叫堆疊 存取目前作用中的環境。每次呼叫函數時,都會在內部建立一個稱為內容的特殊建構,並將其置於內容清單中。當函數完成評估時,其內容會從呼叫堆疊中移除。
讓呼叫堆疊中較高層級定義的變數可用,稱為 動態範圍。變數的繫結會由變數最近(時間上)的定義決定。這與 R 中的預設範圍規則相矛盾,後者使用函數定義所在 環境中的繫結(詞彙範圍)。某些函數,特別是使用和處理模型公式的函數,需要透過直接存取呼叫堆疊來模擬動態範圍。
可透過一系列函數存取 呼叫堆疊,這些函數的名稱以「sys.」開頭。它們簡要列示如下。
sys.call
取得指定內容的呼叫。
sys.frame
取得指定內容的評估框架。
sys.nframe
取得所有作用中內容的環境框架。
sys.function
取得在指定內容中呼叫的函數。
sys.parent
取得目前函數呼叫的父呼叫。
sys.calls
取得所有作用中內容的呼叫。
sys.frames
取得所有作用中內容的評估框架。
sys.parents
取得所有作用中環境的數字標籤。
sys.on.exit
設定一個函數,在指定環境退出時執行。
sys.status
呼叫 sys.frames
、sys.parents
和 sys.calls
。
parent.frame
取得指定父環境的評估框架。
除了評估 環境結構之外,R 有一個環境搜尋路徑,用於搜尋其他地方找不到的變數。這用於兩件事:函數套件和附加的使用者資料。
搜尋路徑的第一個元素是全域環境,最後一個是基礎套件。一個 Autoloads
環境用於保存可以按需載入的代理物件。其他環境使用 attach
或 library
插入路徑。
具有 命名空間 的套件有不同的搜尋路徑。當從此類套件中的物件開始搜尋 R 物件時,會先搜尋套件本身,然後搜尋其匯入,然後搜尋基礎命名空間,最後搜尋全域環境和其餘的常規搜尋路徑。其效果是對同一個套件中其他物件的參考將解析為套件,而且物件不會被全域環境或其他套件中同名的物件遮罩。
儘管 R 可作為資料分析工具非常有用,但大多數使用者很快地發現自己想要撰寫自己的 函數。這是 R 的真正優勢之一。使用者可以對其進行編程,而且如果他們願意,可以將系統層級函數變更為他們認為更適當的函數。
R 也提供一些功能,讓您可以輕鬆地記錄您建立的任何函數。請參閱 撰寫 R 文件 中的 撰寫 R 擴充套件。
function ( arglist ) body
函數宣告的第一個組成部分是關鍵字 function
,用來向 R 指示您想要建立一個函數。
一個 參數清單是一個以逗號分隔的正式參數清單。一個正式參數可以是一個符號、一個「符號 = 表達式」形式的陳述,或一個特殊正式參數 ...
。
主體可以是任何有效的 R 表達式。通常,主體是一組包含在花括號(「{」和「}」)中的表達式。
通常 函數會指定給符號,但並非必要。由呼叫 函數
回傳的值是一個函數。如果沒有給予名稱,它會被稱為 匿名函數。匿名函數最常作為其他函數的參數,例如 apply
系列或 outer
。
這裡有一個簡單的函數:echo <- function(x) print(x)
。因此 echo
是接受單一參數的函數,當呼叫 echo
時,它會列印其參數。
函數的正式參數定義在函數呼叫時將提供值的變數。這些參數的名稱可以在函數主體中使用,在函數呼叫時取得提供的數值。
預設參數值可以使用特殊格式「name = expression」指定。在此情況下,如果使用者在呼叫函數時未指定參數值,則會將表達式與對應符號關聯。當需要值時,會在函數的評估架構中評估expression。
也可以使用函數 missing
指定預設行為。當使用正式參數的名稱呼叫 missing
時,如果正式參數未與任何實際參數配對,且在函數主體中未進行後續修改,則會傳回 TRUE
。因此,missing
的參數將具有預設值(如果有)。missing
函數不會強制評估參數。
特殊參數類型 ...
可包含任何數量的提供參數。它用於各種目的。它允許您撰寫函數,該函數會接收任意數量的參數。它可用於將一些參數吸收至中間函數中,然後可由後續呼叫的函數萃取。
當呼叫或執行 函數時,會建立一個新的 評估框架。在此框架中,正式參數會根據 參數比對 中提供的規則與提供的參數進行比對。函數主體中的陳述會依序在此 環境框架中評估。
評估框架的封閉框架是與被呼叫函數相關聯的環境框架。這可能與 S 不同。儘管許多函數都有 .GlobalEnv
作為其環境,但這並非一定正確,且在具有命名空間的套件中定義的函數(通常)具有套件命名空間作為其環境。
此小節適用於封閉函數,但不適用於基本函數。後者通常會忽略標籤並執行位置配對,但應參閱其說明頁面以了解例外,其中包括 log
、round
、signif
、rep
和 seq.int
。
在 函數評估中發生的第一件事是將形式引數配對到實際或提供的引數。這是透過三階段程序完成的
f <- function(fumble, fooey) fbody
,則 f(f = 1, fo = 2)
是非法的,即使第二個實際引數只配對到 fooey
。 f(f = 1, fooey = 2)
是合法的,因為第二個引數完全配對,且已從部分配對的考量中移除。如果形式引數包含 ...
,則部分配對只會套用於其前面的引數。
...
參數,它會使用剩餘的參數,無論是否有標記。
如果任何參數仍然不匹配,就會宣告錯誤。
參數配對會透過函數 match.arg
、match.call
和 match.fun
來擴充。 存取 R 使用的部分配對演算法的方式是透過 pmatch
。
關於 評估 函數參數最重要的資訊之一,就是提供的參數和預設參數的處理方式不同。提供的函數參數會在呼叫函數的評估架構中評估。函數的預設參數會在函數的評估架構中評估。
在 R 參數中呼叫函數的語意是呼叫傳值。一般來說,提供的參數會像是在使用提供值初始化的區域變數,以及對應正式參數的 名稱。在函數中變更提供的參數值,不會影響呼叫架構中變數的值。
R 有一種延遲評估函數參數的形式。參數在需要時才會評估。重要的是,在某些情況下,參數將永遠不會被評估。因此,使用函數參數來導致副作用是一種不好的風格。在 C 中,使用 foo(x = y)
這種形式來呼叫 foo
,並同時將 y
的值賦值給 x
,這種風格不應在 R 中使用。無法保證參數將永遠被評估,因此 賦值可能不會發生。
同樣值得注意的是,如果評估參數,foo(x <- y)
的效果是改變呼叫 環境中的 x
的值,而不是在 foo
的 評估環境中。
可以在函數內部存取用作參數的實際(非預設)表達式。這種機制是透過承諾來實作的。當評估 函數時,用作參數的實際表達式會儲存在承諾中,並儲存指向函數被呼叫的環境的指標。當(如果)評估參數時,儲存的表達式會在函數被呼叫的環境中評估。由於只使用指向環境的指標,因此對該環境所做的任何變更都會在此評估期間生效。然後,結果值也會儲存在承諾中的另一個位置。後續評估會擷取這個儲存的值(不會執行第二次評估)。也可以使用 substitute
存取未評估的表達式。
當呼叫 函數時,每個形式參數會在呼叫的當地環境中分配一個承諾,其中包含實際參數的表達式槽(如果存在)和包含呼叫者環境的環境槽。如果呼叫中未提供形式參數的實際參數,且有預設表達式,則會以類似方式將其分配給形式參數的表達式槽,但將 環境設定為當地環境。
透過在承諾的環境中評估表達式槽的內容來填入承諾的值槽的程序稱為 強制 承諾。承諾只會被強制一次,之後會直接使用值槽內容。
當需要承諾的值時,會強制承諾。這通常發生在內部 函數中,但也可以透過直接評估承諾本身來強制承諾。當預設表達式依賴於當地環境中另一個形式參數或其他變數的值時,偶爾會用到這個方式。以下範例中可以看到,單獨的 label
確保標籤是在下一行變更 x
的值之前根據 x
的值建立的。
function(x, label = deparse(x)) { label x <- x + 1 print(label) }
承諾的表達式槽本身也可能包含其他承諾。當未評估的參數作為參數傳遞給另一個函數時,就會發生這種情況。在強制承諾時,其表達式中的其他承諾也會在評估時遞迴強制。
範圍或範圍規則僅為評估器用來尋找符號值的規則組。 每個電腦語言都有一組此類規則。在 R 中,規則相當簡單,但確實存在用於顛覆通常或預設規則的機制。
R 遵守稱為詞彙範圍的一組規則。這表示在建立表達式時有效的變數繫結用於提供表達式中任何未繫結符號的值。
大多數範圍的有趣屬性都與評估函數有關,而我們專注於此問題。符號可以是繫結或未繫結。函數的所有形式參數都在函數主體中提供繫結符號。函數主體中的任何其他符號都是局部變數或未繫結變數。局部變數是在函數中定義的變數。由於 R 沒有變數的正式定義,因此僅在需要時使用它們,因此難以確定變數是否為局部變數。局部變數必須先定義,這通常透過在指定左側來完成。
在評估過程中,如果偵測到未繫結符號,則 R 會嘗試為其尋找值。範圍規則決定此程序如何進行。在 R 中,會先搜尋函數的環境,然後是其封閉區域,依此類推,直到到達全域環境。
全域環境會引導環境搜尋清單,此清單會依序搜尋符合的符號。然後使用第一個符合項的值。
當這組規則與函數可以從其他函數傳回為值的事實結合時,就會獲得一些相當不錯但乍看之下很奇特的屬性。
一個簡單的範例
f <- function() { y <- 10 g <- function(x) x + y return(g) } h <- f() h(3)
一個相當有趣的疑問是當評估 h
時會發生什麼事。當評估函數主體時,並不會有問題決定局部變數或繫結變數的值。範圍規則決定語言將如何尋找未繫結變數的值。
當評估 h(3)
時,我們看到它的主體是 g
的主體。在那個主體內,x
繫結到形式參數,而 y
未繫結。在一個具有 詞彙範圍的語言中,x
將會與值 3 關聯,而 y
將會與值 10 關聯,而這值是 f
的局部變數,所以 h(3)
應該會傳回值 13。在 R 中,這的確會發生。
在 S 中,因為不同的範圍規則,會得到一個錯誤,指出未找到 y
,除非在你的工作區中有一個變數 y
,在這種情況下,它的值會被使用。
物件導向程式設計是一種在近年來變得流行的程式設計風格。它的流行性很大一部分來自於它讓撰寫和維護複雜系統變得更容易。它透過幾個不同的機制做到這一點。
任何物件導向語言的核心概念是類別和方法。一個 類別 是物件的一個定義。通常一個類別包含幾個 欄位,這些欄位用來儲存類別特定的資訊。語言中的物件必須是某個類別的實例。程式設計是基於類別的物件或實例。
計算透過方法執行。方法基本上是函數,專門執行物件上的特定計算,通常是特定類別的物件。這就是讓程式語言成為物件導向的原因。在 R 中,一般函數用於決定適當的方法。一般函數負責決定其引數的類別,並使用該資訊選擇適當的方法。
大多數物件導向語言的另一個特點是繼承概念。在大多數程式設計問題中,通常有許多物件彼此相關。如果可以重複使用某些元件,程式設計將會大幅簡化。
如果一個類別繼承自另一個類別,則通常會取得父類別中的所有欄位,並可透過新增新欄位來擴充。在方法調度(透過一般函數)中,如果不存在類別的方法,則會尋找父類別的方法。
在本章中,我們將討論如何將此一般策略實作到 R 中,並討論目前設計中的一些限制。大多數物件系統賦予的最大優點之一是一致性更高。這是透過編譯器或直譯器檢查的規則來達成。很不幸地,由於物件系統整合到 R 中的方式,無法獲得此優點。建議使用者以直接的方式使用物件系統。雖然可以執行一些相當有趣的壯舉,但這些壯舉往往會導致混淆的程式碼,而且可能依賴於不會繼續執行的實作細節。
在 R 中,物件導向程式設計最棒的用途是透過 print
方法、summary
方法和 plot
方法。這些方法讓我們可以有一個泛用的 函數呼叫,例如 plot
,它會根據其引數的類型進行分派,並呼叫特定於所提供資料的繪製函數。
為了讓概念清楚,我們將考慮實作一個小型系統,用於教授學生機率。在這個系統中,物件是機率函數,我們將考慮的方法是找出矩和繪製的方法。機率永遠可以用累積分配函數來表示,但通常可以用其他方式表示。例如,當它存在時,可以用機率密度函數表示,或當它存在時,可以用矩母函數表示。
R 沒有完整的 物件導向系統,而是有一個類別系統和一個根據物件類別進行分派的機制。已詮釋程式碼的分派機制依賴儲存在評估架構中的四個特殊物件。這些特殊物件是 .Generic
、.Class
、.Method
和 .Group
。有一個獨立的分派機制用於內部函數和類型,將在其他地方討論。
類別系統是透過 class
屬性來促成的。此屬性是一個類別名稱的字元向量。因此,若要建立一個 "foo"
類別的物件,只要附加一個類別屬性,其中包含字串 ‘"foo"’ 即可。因此,幾乎任何東西都可以轉換成 "foo"
類別的物件。
物件系統使用 泛函,透過兩個調度函式 UseMethod
和 NextMethod
。物件系統的典型用法是從呼叫泛函開始。這通常是一個非常簡單的函式,只包含一行程式碼。系統函式 mean
就是這樣的函式,
> mean function (x, ...) UseMethod("mean")
當呼叫 mean
時,它可以有任意數量的引數,但其第一個引數是特殊的,且該第一個引數的類別用於決定應呼叫哪個方法。變數 .Class
設定為 x
的類別屬性,.Generic
設定為字串 "mean"
,並搜尋要呼叫的正確方法。mean
的任何其他引數的類別屬性都會被忽略。
假設 x
有包含 "foo"
和 "bar"
的類別屬性,依此順序。然後,R 會先搜尋一個稱為 mean.foo
的函式,如果找不到,它會再搜尋一個函式 mean.bar
,如果搜尋也不成功,則會進行最後一次搜尋,尋找 mean.default
。如果最後一次搜尋也不成功,R 會報告錯誤。最好永遠撰寫一個預設方法。請注意,在此脈絡中,函式 mean.foo
等會稱為方法。
NextMethod
提供另一種執行機制。函數 可在其中任何位置呼叫 NextMethod
。決定應呼叫哪個方法主要根據 .Class
和 .Generic
的目前值。這有點問題,因為方法實際上是一個一般函數,而使用者可能會直接呼叫它。如果他們這樣做,.Generic
或 .Class
將沒有值。
如果直接呼叫方法,且其中包含呼叫 NextMethod
,則會使用 NextMethod
的第一個引數來決定 一般函數。如果未提供此引數,則會發出錯誤訊號;因此,最好總是提供此引數。
如果直接呼叫方法,則會將方法第一個引數的類別屬性用作 .Class
的值。
方法本身使用 NextMethod
來提供一種繼承形式。一般而言,特定方法會執行一些作業來設定資料,然後透過呼叫 NextMethod
來呼叫下一個適當的方法。
考慮以下簡單範例。二維歐幾里得空間中的點可以用其笛卡兒 (x-y) 或極座標 (r-theta) 來指定。因此,若要儲存有關點位置的資訊,我們可以定義兩個類別,"xypoint"
和 "rthetapoint"
。所有「xypoint」資料結構都是具有 x 分量和 y 分量的清單。所有「rthetapoint」物件都是具有 r 分量和 theta 分量的清單。
現在,假設我們要從任一類型的物件取得 x 位置。這可透過 一般函數輕鬆達成。我們將一般函數 xpos
定義如下。
xpos <- function(x, ...) UseMethod("xpos")
現在,我們可以定義方法
xpos.xypoint <- function(x) x$x xpos.rthetapoint <- function(x) x$r * cos(x$theta)
使用者只需使用任一表示法作為引數來呼叫函數 xpos
。內部執行機制會找到物件的類別,並呼叫適當的方法。
要新增其他表示法相當容易。不需要撰寫新的泛函,只要撰寫方法即可。這使得新增到現有系統變得容易,因為使用者只需要負責處理新的表示法,而不需要處理任何現有的表示法。
這種方法的大部分用途是提供不同型別物件的特殊列印;print
大約有 40 種方法。
物件的類別屬性可以包含多個元素。當呼叫泛函時,第一個繼承主要是透過NextMethod
處理。NextMethod
決定目前正在評估的方法,從中尋找下一個類別
FIXME:這裡遺漏了一些內容
泛函應該包含一個陳述式。它們通常應該採用foo <- function(x, ...) UseMethod("foo", x)
的形式。呼叫UseMethod
時,它會決定適當的方法,然後使用相同的引數,按照與呼叫泛函相同的順序來呼叫該方法,就好像直接呼叫該方法一樣。
為了確定正確的方法,會取得泛型第一個引數的 class 屬性,並用來尋找正確的方法。泛型函式的 名稱會與 class 屬性的第一個元素組合成 generic.class
的形式,並尋找具有該名稱的函式。如果找到函式,就會使用它。如果找不到此類函式,就會使用 class 屬性的第二個元素,以此類推,直到用盡 class 屬性的所有元素。如果在那個時候仍未找到方法,就會使用 generic.default
方法。如果泛型函式的第一個引數沒有 class 屬性,就會使用 generic.default
。自從引入命名空間後,可能無法透過名稱存取方法(即 get("generic.class")
可能會失敗),但可透過 getS3method("generic","class")
存取。
任何物件都可以有 class
屬性。此屬性可以有任意數量的元素。每個元素都是定義類別的字串。當呼叫泛型函式時,會檢查其第一個引數的類別。
UseMethod
是特殊函式,其行為與其他函式呼叫不同。呼叫它的語法為 UseMethod(generic, object)
,其中 generic 是泛型函式的名稱,object 是用來確定應選取哪個方法的物件。只能從函式主體呼叫 UseMethod
。
UseMethod
以兩種方式變更評估模型。首先,在呼叫它時,它會決定要呼叫的下一個方法(函數)。然後,它使用目前的評估 環境呼叫該函數;此程序將在稍後說明。 UseMethod
變更評估環境的第二個方式是它不會將控制權傳回呼叫函數。這表示,呼叫 UseMethod
之後的任何陳述式保證不會被執行。
呼叫 UseMethod
時,一般函數是呼叫 UseMethod
中指定的數值。要分派的物件是提供的第二個引數或目前函數的第一個引數。會決定引數的類別,並將其第一個元素與一般名稱合併,以決定適當的方法。因此,如果一般名稱為 foo
,而物件的類別為 "bar"
,則 R 會搜尋名稱為 foo.bar
的方法。如果沒有此方法,則會使用上述的繼承機制來找出適當的方法。
一旦決定方法後,R 會以特殊的方式呼叫它。R 不會建立新的評估 環境,而是使用目前函數呼叫的環境(呼叫泛函)。在呼叫 UseMethod
之前所做的任何 指定或評估都會生效。在呼叫泛函時所使用的參數會重新配對到所選方法的形式參數。
呼叫方法時,會使用與呼叫泛函時數量相同且名稱相同的參數來呼叫它。它們會根據 R 的標準參數配對規則配對到方法的參數。不過,物件(即第一個參數)已經過評估。
呼叫 UseMethod
的效果會在評估框架中放置一些特殊物件。它們是 .Class
、.Generic
和 .Method
。這些特殊物件會由 R 用來處理方法調用和繼承。.Class
是物件的類別,.Generic
是泛函名稱,.Method
是目前呼叫的方法名稱。如果方法是透過其中一個內部介面呼叫的,那麼也可能有一個稱為 .Group
的物件。這將在第 群組方法 節中說明。在最初呼叫 UseMethod
之後,這些特殊變數(而非物件本身)會控制後續方法的選取。
然後以標準方式評估方法主體。特別是主體中的變數查詢遵循方法的規則。因此,如果方法具有關聯的環境,則將使用該環境。實際上,我們已用對方法的呼叫取代對通用呼叫的呼叫。通用架構中的任何區域 指派都將傳遞到對方法的呼叫中。不建議使用此 功能。重要的是要了解控制權永遠不會返回通用,因此呼叫 UseMethod
之後的任何表達式永遠不會執行。
在呼叫 UseMethod
之前評估的任何通用引數仍保持評估狀態。
如果未提供 UseMethod
的第一個引數,則假設它是目前函數的名稱。如果提供兩個引數給 UseMethod
,則第一個是方法的名稱,第二個假設是將派送的物件。它會被評估,以便可以確定所需的方法。在此情況下,呼叫通用中的第一個引數不會被評估,並會被捨棄。沒有辦法變更呼叫方法中的其他引數;這些引數保持在呼叫通用中的狀態。這與 NextMethod
相反,其中可以變更呼叫下一個方法中的引數。
NextMethod
用於提供一個簡單的繼承機制。
呼叫 NextMethod
所引發的方法,其行為就像從前一個方法呼叫的一樣。繼承方法的引數順序與呼叫目前方法的順序相同,名稱也相同。這表示它們與呼叫泛型的引數相同。不過,引數的表達式是目前方法對應形式引數的名稱。因此,引數的值會對應到呼叫 NextMethod 時的值。
未評估的引數仍保持未評估。遺失的引數仍保持遺失。
呼叫 NextMethod
的語法為 NextMethod(generic, object, ...)
。如果未提供 generic
,則會使用 .Generic
的值。如果未提供 object
,則會使用呼叫目前方法的第一個引數。...
引數中的值用於修改下一個方法的引數。
請務必了解,下一個方法的選擇取決於 .Generic
和 .Class
的目前值,而不是物件。因此,在呼叫 NextMethod
時變更物件會影響下一個方法收到的引數,但不會影響下一個方法的選擇。
方法可以被直接呼叫。如果這樣做,將不會有 .Generic
、.Class
或 .Method
。在此情況下,generic
參數的 NextMethod
必須指定。 .Class
的值會被視為是物件的類別屬性,而該物件是目前函式的第一個參數。 .Method
的值是目前函式的名稱。這些預設值的選擇確保方法的行為不會因為是直接呼叫或透過呼叫泛型而改變。
一個可供討論的問題是 ...
參數對 NextMethod
的行為。白皮書描述行為如下
- 命名參數取代呼叫目前方法中對應的參數。未命名參數會出現在參數清單的開頭。
我想做的是
- 首先對 NextMethod 進行參數比對;- 如果物件或泛型有變更,則沒問題 - 首先,如果命名的清單元素與參數(命名或未命名)相符,則清單值會取代參數值。- 第一個未命名的清單元素
查詢值:類別:首先來自 .Class,其次來自方法的第一個參數,最後來自 NextMethod 呼叫中指定的物件
泛型:首先來自 .Generic,如果沒有,則來自方法的第一個參數,如果仍然遺失,則來自 NextMethod 呼叫
方法:這應該只是目前的函式名稱。
對於多種 內部函數,R 提供一個運算子的調度機制。這表示運算子,例如 ==
或 <
,可以修改其對特殊類別成員的行為。函數和運算子已分組為三類,並且可以為每一類別撰寫群組方法。目前沒有新增群組的機制。可以撰寫特定於群組內任何函數的方法。
下表列出不同群組的函數。
abs, acos, acosh, asin, asinh, atan, atanh, ceiling, cos, cosh, cospi, cumsum, exp, floor, gamma, lgamma, log, log10, round, signif, sin, sinh, sinpi, tan, tanh, tanpi, trunc
all, any, max, min, prod, range, sum
+
, -
, *
, /
, ^
, <
, >
,
<=
, >=
, !=
, ==
, %%
, %/%
,
&
, |
, !
對於 Ops 群組中的運算子,如果兩個運算元一起建議使用單一方法,則會呼叫特殊方法。具體來說,如果兩個運算元都對應於相同的方法,或者一個運算元對應於優先於另一個運算元的方法。如果它們沒有建議單一方法,則使用預設方法。如果另一個運算元沒有對應的方法,則群組方法或類別方法會佔優。類別方法優於群組方法。
當群組為 Ops 時,特殊變數 .Method
是具有兩個元素的字元向量。如果對應的引數是決定方法所使用的類別的成員,則 .Method
的元素會設定為方法名稱。否則,.Method
的對應元素會設定為零長度字串 ""
。
使用者可以輕鬆撰寫自己的方法和一般函數。 一般函數只是一個呼叫 UseMethod
的函數。方法只是一個透過方法調用所呼叫的函數。這可能是呼叫 UseMethod
或 NextMethod
的結果。
值得注意的是,方法可以被直接呼叫。這表示它們可以在沒有呼叫 UseMethod
的情況下輸入,因此特殊變數 .Generic
、.Class
和 .Method
將不會被實例化。在這種情況下,將使用上面詳述的預設規則來判斷這些變數。
最常見的 一般函數用途是為統計物件提供 print
和 summary
方法,通常是一些模型擬合程式的輸出。為此,每個模型都會將類別屬性附加到其輸出,然後提供一個特殊方法,該方法會擷取該輸出並提供其可讀版本。然後,使用者只需要記住 print
或 summary
會為任何分析結果提供良好的輸出。
R 屬於一類程式語言,其中子常式有能力修改或建構其他子常式,並將結果評估為語言本身不可分割的一部分。這類似於 Lisp 和 Scheme,以及其他「函數式程式設計」類型的語言,但與 FORTRAN 和 ALGOL 家族不同。Lisp 家族將此功能發揮到極致,採用「所有事物都是清單」的範例,其中程式和資料之間沒有區別。
R 提供比 Lisp 更友善的程式設計介面,至少對習慣數學公式和類似 C 的控制結構的人來說是如此,但引擎實際上非常類似 Lisp。R 允許直接存取已剖析的表達式和函數,並允許您修改它們,然後執行它們,或從頭建立全新的函數。
此功能有許多標準應用,例如計算表達式的分析導數,或從係數向量產生多項式函數。然而,也有一些用途對 R 解釋部分的運作更為基本。其中一些對於將函數重新用作其他函數中的組成部分至關重要,例如在幾個建模和繪圖常式中建構的(公認不是很漂亮的)對 model.frame
的呼叫。其他用途僅允許優雅的介面使用有用的功能。舉例來說,考慮 curve
函數,它允許您繪製以表達式給定的函數圖形,例如 sin(x)
或用於繪製數學表達式的工具。
在本章中,我們將介紹可用于計算語言的一組工具。
有三個類型的語言物件可以修改,呼叫、表達式和函數。在這個階段,我們將專注於呼叫物件。這些有時稱為「未評估的表達式」,儘管這個術語有點令人困惑。取得呼叫物件的最直接方法是對表達式參數使用quote
,例如:
> e1 <- quote(2 + 2) > e2 <- quote(plot(x, y))
參數不會被評估,結果只是已剖析的參數。物件e1
和e2
稍後可以使用eval
評估,或僅作為資料操作。最明顯的是,e2
物件具有模式"call"
,因為它涉及對plot
函數的呼叫以及一些參數。然而,e1
實際上具有與二元運算子+
的呼叫完全相同的結構,兩個參數,這個事實由以下內容清楚顯示:
> quote("+"(2, 2)) 2 + 2
呼叫物件的組成部分使用類似清單的語法存取,並且可以使用as.list
和as.call
轉換為清單和從清單轉換。
> e2[[1]] plot > e2[[2]] x > e2[[3]] y
當使用關鍵字參數匹配時,關鍵字可以用作清單標籤
> e3 <- quote(plot(x = age, y = weight)) > e3$x age > e3$y weight
在前面的範例中,呼叫物件的所有元件都有模式 "name"
。這對呼叫中的識別碼來說是正確的,但呼叫的元件也可以是常數,儘管第一個元件最好是函數,如果呼叫要成功評估的話,或者其他呼叫物件,對應於子表達式。模式為 name 的物件可以使用 as.name
從字元串建立,因此可以修改 e2
物件如下
> e2[[1]] <- as.name("+") > e2 x + y
為了說明子表達式只是本身就是呼叫的元件,請考慮
> e1[[2]] <- e2 > e1 x + y + 2
輸入中的所有分組括號都保留在已剖析的表達式中。它們表示為帶有一個引數的函數呼叫,因此 4 - (2 - 2)
在前置表示法中變為 "-"(4, "(" ("-"(2, 2)))
。在評估中,‘(’ 運算子只傳回其引數。
這有點不幸,但要撰寫一個 剖析器/去剖析器組合並不容易,它既能保留使用者輸入,又能以最簡化的形式儲存它,並確保剖析去剖析的表達式會傳回相同的表達式。
事實上,R 的剖析器並非完全可逆,其去剖析器也不是,如下面的範例所示
> str(quote(c(1,2))) language c(1, 2) > str(c(1,2)) num [1:2] 1 2 > deparse(quote(c(1,2))) [1] "c(1, 2)" > deparse(c(1,2)) [1] "c(1, 2)" > quote("-"(2, 2)) 2 - 2 > quote(2 - 2) 2 - 2
去剖析的表達式應該評估為等於原始表達式的值(直到捨入誤差)。
...流程控制建構的內部儲存...注意 Splus 不相容性...
事實上,並非經常會想要修改表達式的內部結構,如同前一節所述。更常發生的情況是,只想要取得表達式,以便進行解析並用於標示圖形,例如。在 plot.default
開頭處便可看到一個範例:
xlabel <- if (!missing(x)) deparse(substitute(x))
這會導致將作為 x
引數傳遞給 plot
的變數或表達式用於標示 x 軸。
用來達成此目的的功能是 substitute
,它接受表達式 x
,並替換透過形式參數 x
傳遞的表達式。請注意,要執行此操作,x
必須載有建立其值的表達式資訊。這與 R 的 延遲求值方案有關(請參閱 Promise 物件)。形式參數實際上是一個 promise,一個有三個槽位的物件,一個用於定義它的表達式,一個用於評估該表達式的環境,以及一個用於評估後該表達式的值。 substitute
會辨識一個 promise 變數,並替換其表達式槽位的數值。如果 substitute
在一個函式內被呼叫,函式的區域變數也會受到替換的影響。
傳遞給 substitute
的參數不一定要是一個簡單的識別碼,它可以是一個包含多個變數的表達式,而且會替換這些變數中的每一個。此外,substitute
有另一個參數,可以是一個環境或一個用於查詢變數的清單。例如
> substitute(a + b, list(a = 1, b = quote(x))) 1 + x
請注意,替換 x
時需要加上引號。這種建構在將數學表達式放入圖表時會很方便,如下面的範例所示
> plot(0) > for (i in 1:4) + text(1, 0.2 * i, + substitute(x[ix] == y, list(ix = i, y = pnorm(i))))
了解替換純粹是字面上的非常重要;沒有檢查結果呼叫物件在評估時是否有意義。 substitute(x <- x + 1, list(x = 2))
會很開心地傳回 2 <- 2 + 1
。不過,R 的某些部分會建立自己的規則來判斷是否有意義,並且可能實際上會使用這種格式錯誤的表達式。例如,使用「圖表中的數學」功能通常會涉及語法正確但評估後會沒有意義的建構,例如「{}>=40*" years"」。
替換不會評估其第一個引數。這會導致一個難題,即如何對儲存在變數中的物件進行替換。解決方案是再次使用 替換
,如下所示
> expr <- quote(x + y) > substitute(substitute(e, list(x = 3)), list(e = expr)) substitute(x + y, list(x = 3)) > eval(substitute(substitute(e, list(x = 3)), list(e = expr))) 3 + y
替換的確切規則如下:第一個 符號在 解析樹中與第二個引數相匹配,後者可以是標記清單或環境框架。如果它是一個簡單的本地物件,則會插入其值,除非與全局環境匹配。如果它是一個承諾(通常是函數引數),則會替換承諾表達式。如果符號不匹配,則保持不變。公認,在頂層進行替換的特殊例外情況很奇怪。它繼承自 S,其基本原理很可能是無法控制在該層級中可能會繫結哪些變數,因此最好讓替換充當 引號
。
如果在使用 替換
之前修改了本地變數,則承諾替換規則與 S 的規則略有不同。然後,R 將使用變數的新值,而 S 將無條件使用引數表達式,除非它是一個常數,這會導致一個奇怪的後果,即在 S 中 f((1))
可能與 f(1)
非常不同。R 規則相當簡潔,儘管它確實會對 延遲評估產生影響,這讓一些人感到驚訝。考慮
logplot <- function(y, ylab = deparse(substitute(y))) { y <- log(y) plot(y, ylab = ylab) }
這看起來很簡單,但人們會發現 y 標籤變成了一個醜陋的 c(...)
表達式。這是因為延遲評估的規則導致 ylab
表達式的評估發生在 y
被修改 之後。解決方案是強制先評估 ylab
,即
logplot <- function(y, ylab = deparse(substitute(y))) { ylab y <- log(y) plot(y, ylab = ylab) }
請注意,不應在這種情況下使用 eval(ylab)
。如果 ylab
是語言或表達式物件,那麼這將導致物件也進行評估,如果傳遞了類似 quote(log[e](y))
的數學表達式,這根本不可取。
substitute
的變體是 bquote
,用於使用其值替換某些子表達式。上述範例
> plot(0) > for (i in 1:4) + text(1, 0.2 * i, + substitute(x[ix] == y, list(ix = i, y = pnorm(i))))
可以更緊湊地寫為
plot(0) for(i in 1:4) text(1, 0.2*i, bquote( x[.(i)] == .(pnorm(i)) ))
除了 .()
子表達式的內容外,表達式被引用,這些子表達式被其值替換。有一個可選引數可以在不同的環境中計算值。 bquote
的語法從 LISP 反引號巨集借用。
本章前面介紹了 eval
函數,作為評估呼叫物件的方法。然而,這並不是全部。也可以指定評估要發生的 環境。預設情況下,這是呼叫 eval
的評估框架,但經常需要將其設定為其他內容。
通常,相關的評估架構是目前架構的父架構(請參閱 ???)。特別是,當要評估的物件是函數引數的 substitute
操作結果時,它將包含只有呼叫者才理解的變數(請注意,沒有理由預期呼叫者的變數在呼叫者的 語法範圍內)。由於經常在父架構中進行評估,因此 eval.parent
函數存在,作為 eval(expr, sys.frame(sys.parent()))
的簡寫。
另一個經常發生的情況是在清單或資料架構中進行評估。例如,這會在提供 data
引數時,與 model.frame
函數連線時發生。一般而言,模型公式的用語需要在 data
中評估,但它們偶爾也可能包含 model.frame
呼叫者中項目的參考。這在模擬研究中偶爾很有用。因此,為了這個目的,不僅需要在清單中評估表達式,還需要指定一個封閉範圍,如果變數不在清單中,則在其中繼續搜尋。因此,呼叫的格式為
eval(expr, data, sys.frame(sys.parent()))
請注意,在給定的環境中進行評估實際上可能會改變該環境,最明顯的情況是涉及 指派運算子的情況,例如
eval(quote(total <- 0), environment(robert$balance)) # rob Rob
在清單中進行評估時也是如此,但原始清單不會改變,因為實際上是在處理副本。
模式為 "expression"
的物件定義於 表達式物件 中。它們與呼叫物件清單非常類似。
> ex <- expression(2 + 2, 3 + 4) > ex[[1]] 2 + 2 > ex[[2]] 3 + 4 > eval(ex) [1] 7
請注意,評估表達式物件會依序評估每個呼叫,但最終值是最後一個呼叫的值。在這方面,它的行為幾乎與複合語言物件 quote({2 + 2; 3 + 4})
完全相同。不過,有一個細微的差異:呼叫物件與解析樹中的子表達式無法區分。這表示它們會自動以與子表達式相同的方式評估。表達式物件可以在評估期間被辨識,並在某種意義上保留它們的引號。評估器不會遞迴評估表達式物件,只有當它像上面那樣直接傳遞給 eval
函數時才會評估。差異可以這樣看出
> eval(substitute(mode(x), list(x = quote(2 + 2)))) [1] "numeric" > eval(substitute(mode(x), list(x = expression(2 + 2)))) [1] "expression"
解譯器會以建立表達式物件的呼叫來表示表達式物件。這與它處理數值向量和幾個沒有特定外部表示的其他物件的方式類似。不過,它確實會導致以下一點混淆
> e <- quote(expression(2 + 2)) > e expression(2 + 2) > mode(e) [1] "call" > ee <- expression(2 + 2) > ee expression(2 + 2) > mode(ee) [1] "expression"
也就是說,列印時 e
和 ee
看起來相同,但一個是產生表達式物件的呼叫,另一個是物件本身。
函數可以透過檢視 sys.call
的結果來找出它如何被呼叫,就像以下範例中只回傳它自己的呼叫的函數
> f <- function(x, y, ...) sys.call() > f(y = 1, 2, z = 3, 4) f(y = 1, 2, z = 3, 4)
然而,這除了除錯之外並沒有什麼用處,因為它需要函數追蹤參數比對才能詮釋呼叫。例如,它必須能夠看出第 2 個實際參數會比對到第一個形式參數(在上述範例中為 x
)。
更常需要的是呼叫所有實際參數都繫結到對應的形式參數。為此,會使用函數 match.call
。以下是前一個範例的變體,一個會回傳它自己的呼叫且參數已比對的函數
> f <- function(x, y, ...) match.call() > f(y = 1, 2, z = 3, 4) f(x = 2, y = 1, z = 3, 4)
請注意,第二個參數現在會比對到 x
,並出現在結果的對應位置。
此技術的主要用途是用相同的參數呼叫另一個函數,可能會刪除一些並新增其他參數。典型的應用會出現在 lm
函數的開頭
mf <- cl <- match.call() mf$singular.ok <- mf$model <- mf$method <- NULL mf$x <- mf$y <- mf$qr <- mf$contrasts <- NULL mf$drop.unused.levels <- TRUE mf[[1]] <- as.name("model.frame") mf <- eval(mf, sys.frame(sys.parent()))
請注意,產生的呼叫會在父框架中評估,在其中可以確定相關表達式有意義。呼叫可以視為清單物件,其中第一個元素是函數名稱,其餘元素是實際參數表達式,對應的形式參數名稱作為標籤。因此,消除不需要參數的技術是指定 NULL
,如第 2 和第 3 行所示,並使用標籤清單指定(這裡傳遞 drop.unused.levels = TRUE
)新增參數,如第 4 行所示。若要變更所呼叫函數的名稱,請指定到清單的第一個元素,並確保該值為名稱,在此處使用 as.name("model.frame")
結構或 quote(model.frame)
。
函數 match.call
有 expand.dots
參數,如果將此參數設定為 FALSE
,則所有 ...
參數會收集為一個參數,並標記為 ...
。
> f <- function(x, y, ...) match.call(expand.dots = FALSE) > f(y = 1, 2, z = 3, 4) f(x = 2, y = 1, ... = list(z = 3, 4))
參數 ...
是清單(精確來說是配對清單),而不是像在 S 中呼叫 list
> e1 <- f(y = 1, 2, z = 3, 4)$... > e1 $z [1] 3 [[2]] [1] 4
使用這種形式的 match.call
的原因之一,只是為了移除任何 ...
參數,以避免傳遞未指定的參數給可能不認識這些參數的函數。以下是從 plot.formula
釋義的範例
m <- match.call(expand.dots = FALSE) m$... <- NULL m[[1]] <- "model.frame"
更精細的應用在 update.default
中,其中一組額外的選用參數可以新增、取代或取消原始呼叫的參數
extras <- match.call(expand.dots = FALSE)$... if (length(extras) > 0) { existing <- !is.na(match(names(extras), names(call))) for (a in names(extras)[existing]) call[[a]] <- extras[[a]] if (any(!existing)) { call <- c(as.list(call), extras[!existing]) call <- as.call(call) } }
請注意,如果 extras[[a]] == NULL
,則會小心地個別修改現有參數。未經顯示的強制轉換,串接無法在呼叫物件上運作;這可以說是錯誤。
還有兩個函數可用於建構函數呼叫,分別是 call
和 do.call
。
函數 call
允許從函數名稱和參數清單建立呼叫物件
> x <- 10.5 > call("round", x) round(10.5)
如您所見,呼叫中插入的是 x
的值,而不是 符號,因此與 round(x)
截然不同。這種形式使用得相當少,但偶爾會在函數名稱可用作字元變數時派上用場。
函數 do.call
相關,但會立即評估呼叫,並從包含所有參數的模式 "list"
的物件取得參數。當您想要將函數(例如 cbind
)套用至清單或資料框的所有元素時,這是一個自然的用途。
is.na.data.frame <- function (x) { y <- do.call(cbind, lapply(x, is.na)) rownames(y) <- row.names(x) y }
其他用途包括在建構中變更,例如 do.call("f", list(...))
。不過,您應該知道這會在實際函式呼叫之前評估引數,這可能會破壞函式本身中延遲評估和引數替換的層面。類似的說明也適用於 call
函式。
能夠處理 函式或封閉的組成元件通常很有用。R 提供一組介面函式來執行這項工作。
body
¶傳回函式的本體表達式。
formals
¶傳回函式的形式引數清單。這是一個 pairlist
。
environment
¶傳回與函式關聯的環境。
body<-
¶這會將函式的本體設定為提供的表達式。
formals<-
¶將函式的形式引數設定為提供的清單。
environment<-
¶將函式的環境設定為指定的環境。
您也可以使用類似 evalq(x <- 5, environment(f))
的程式碼變更函式環境中不同變數的繫結。
也可以使用 as.list
將 函數轉換為清單。結果是形式參數清單與函數主體的串接。反之,可以使用 as.function
將此類清單轉換為函數。此功能主要包含在 S 相容性中。請注意,使用 as.list
時環境資訊會遺失,而 as.function
有個參數允許設定環境。
透過 R 函數 system
存取作業系統殼層。 詳細資訊會因平台而異(請參閱線上說明),而唯一可以安全假設的是,第一個參數會是字串 command
,該字串會傳遞以執行(不一定要透過殼層),而第二個參數會是 internal
,如果為 true,會將命令的輸出收集到 R 字元向量中。
函數 system.time
與 proc.time
可用於計時(儘管在類 Unix 平台上可用的資訊可能有限)。
作業系統環境中的資訊可以用
Sys.getenv
OS 環境變數 Sys.putenv
Sys.getlocale
系統地區設定 Sys.putlocale
Sys.localeconv
Sys.time
目前時間 Sys.timezone
時區
在所有平台上都提供一組統一的文件存取函式
file.access
確定檔案可存取性 file.append
串接檔案 file.choose
提示使用者輸入檔名 file.copy
複製檔案 file.create
建立或截斷檔案 file.exists
測試是否存在 file.info
其他檔案資訊 file.remove
移除檔案 file.rename
重新命名檔案 file.show
顯示文字檔 unlink
移除檔案或目錄。
還有一些函數可以用於以與平台無關的方式處理檔案名稱和路徑。
basename
不含目錄的檔案名稱 dirname
目錄名稱 file.path
建構檔案路徑 path.expand
展開 Unix 路徑中的 ~
請參閱 撰寫 R 擴充套件 中的 系統和外國語言介面,以取得透過編譯程式碼新增 R 功能的詳細資料。
函數 .C
和 .Fortran
提供一個標準介面,用於連結到 R 的編譯程式碼,無論是在建置時或透過 dyn.load
。它們主要分別用於編譯的 C 和 FORTRAN 程式碼,但 .C
函數可以用於其他可以產生 C 介面的語言,例如 C++。
函數 .Call
和 .External
提供介面,允許編譯程式碼(主要是編譯的 C 程式碼)處理 R 物件。
.Internal
和 .Primitive
介面用於呼叫在建置時編譯成 R 的 C 程式碼。請參閱 R 內部 中的 .Internal 與 .Primitive。
R 中的例外處理功能是透過兩種機制提供的。函式(例如 stop
或 warning
)可以直接呼叫,或選項(例如 "warn"
)可以用於控制問題的處理方式。
函數 warning
接受一個字元字串的單一參數。呼叫 warning
的行為取決於選項 "warn"
的值。如果 "warn"
為負值,則會忽略警告。如果為零,則會儲存警告並在頂層函數完成後列印。如果為一,則會在警告發生時列印;如果為 2(或更大),則會將警告轉換為錯誤。
如果 "warn"
為零(預設值),則會建立變數 last.warning
,並將與每個呼叫 warning
相關的訊息依序儲存在這個向量中。如果警告少於 10 則會在函數評估完畢後列印。如果多於 10 則會列印一則訊息,指出發生了多少警告。在任一種情況下,last.warning
都包含訊息向量,而 warnings
提供存取和列印它的方法。
函數可以在函數主體的任何一點插入呼叫 on.exit
。呼叫 on.exit
的效果是儲存主體的值,以便在函數結束時執行。這允許函數變更一些系統參數,並確保在函數完成時將其重設為適當的值。保證在函數直接結束或因警告而結束時執行 on.exit
。
評估 on.exit
程式碼時,錯誤會導致立即跳轉到頂層,而不會進一步處理 on.exit
程式碼。
on.exit
接受單一參數,此參數為函式結束時要評估的表達式。
有許多 options
變數可用於控制 R 如何處理錯誤和警告。它們列在下方表格中。
控制警告列印。
設定在發生警告時要評估的表達式。如果設定此選項,將抑制警告的正常列印。
安裝在發生錯誤時要評估的表達式。錯誤訊息和警告訊息的正常列印會先於表達式評估。
由 options("error")
安裝的表達式會在執行 on.exit
呼叫之前評估。
可以使用 options(error = expression(q("yes")))
讓 R 在發出錯誤訊號時退出。在此情況下,錯誤將導致 R 關閉,且會儲存全域環境。
除錯程式碼一直以來都是一種藝術。R 提供了多種工具,協助使用者找出程式碼中的問題。這些工具會在程式碼中的特定點暫停執行,並可檢查運算的目前狀態。
大多數除錯工作都是透過呼叫 browser
或 debug
進行。這兩個函式都仰賴相同的內部機制,並都提供使用者一個特殊提示。任何指令都可以在提示中輸入。指令的評估 環境是目前作用中的環境。這讓您可以檢查任何變數等的目前狀態。
有五個特殊指令會被 R 以不同的方式詮釋。它們是:
如果正在除錯函式,則移至下一個陳述式。如果呼叫瀏覽器,則繼續執行。
繼續執行。
執行函式中的下一個陳述式。這也適用於瀏覽器。
顯示呼叫堆疊
暫停執行並立即跳至頂層。
如果有一個與上述特殊指令同名的區域變數,則可以使用 get
存取其值。呼叫 get
並在引號中輸入名稱,將會在目前 環境中擷取值。
偵錯器僅提供對已詮釋表達式的存取。如果函數呼叫外國語言(例如 C),則無法存取該語言中的陳述式。執行將在 R 中評估的下一則陳述式上暫停。可以使用 gdb
等符號偵錯器來偵錯已編譯的程式碼。
呼叫函數 browser
會導致 R 在該點暫停執行,並提供使用者特殊提示。會略過 browser
的引數。
> foo <- function(s) { + c <- 3 + browser() + } > foo(4) Called from: foo(4) Browse[1]> s [1] 4 Browse[1]> get("c") [1] 3 Browse[1]>
可以使用指令 debug(fun)
對任何函數呼叫偵錯器。隨後,每次評估該函數時,就會呼叫偵錯器。偵錯器允許您控制函數主體中陳述式的評估。在執行每則陳述式之前,都會列印該陳述式並提供特殊提示。可以給予任何指令,上表中的指令具有特殊意義。
呼叫 undebug
並將函數作為引數,即可關閉偵錯。
> debug(mean.default) > mean(1:10) debugging in: mean.default(1:10) debug: { if (na.rm) x <- x[!is.na(x)] trim <- trim[1] n <- length(c(x, recursive = TRUE)) if (trim > 0) { if (trim >= 0.5) return(median(x, na.rm = FALSE)) lo <- floor(n * trim) + 1 hi <- n + 1 - lo x <- sort(x, partial = unique(c(lo, hi)))[lo:hi] n <- hi - lo + 1 } sum(x)/n } Browse[1]> debug: if (na.rm) x <- x[!is.na(x)] Browse[1]> debug: trim <- trim[1] Browse[1]> debug: n <- length(c(x, recursive = TRUE)) Browse[1]> c exiting from: mean.default(1:10) [1] 5.5
監控 R 行為的另一種方法是透過 trace
機制。 trace
呼叫一個單一參數,也就是您要追蹤的函數名稱。名稱不需要加引號,但對於某些函數,您需要加上引號以避免語法錯誤。
當 trace
已在函數上呼叫,則每次評估該函數時,都會列印對它的呼叫。這個機制會透過呼叫 untrace
並將函數當成參數來移除。
> trace("[<-") > x <- 1:10 > x[3] <- 4 trace: "[<-"(*tmp*, 3, value = 4)
當錯誤導致跳至頂層時,會將一個稱為 .Traceback
的特殊變數放入基礎環境中。 .Traceback
是個字元向量,每個函數呼叫在錯誤發生時都有個條目。可以透過呼叫 traceback
來檢查 .Traceback
。
剖析器會將 R 程式碼的文字表示轉換成內部形式,然後傳遞給 R 評估器,讓它執行指定的指令。內部形式本身就是一個 R 物件,可以在 R 系統中儲存和操作。
R 中的剖析有以下三種不同的變異
讀取-評估-列印迴圈形成 R 的基本命令列介面。文字輸入會一直讀取,直到完整的 R 表達式可用。表達式可以分隔在數個輸入行中。主要提示(預設為 ‘> ’)表示剖析器已準備好接受新的表達式,而延續提示(預設為 ‘+ ’)表示剖析器預期有未完成表達式的其餘部分。表達式在輸入期間會轉換成內部形式,剖析後的表達式會傳遞給評估器,而結果會列印出來(除非特別設定為不可見)。如果剖析器發現自己處於與語言語法不相容的狀態,就會標示「語法錯誤」,剖析器會重設自己,並從下一個輸入行的開頭繼續輸入。
文字檔案可以使用 parse
函數來解析。特別是,這是在執行 source
函數時完成的,它允許將指令儲存在外部檔案中,並執行它們,就好像它們已在鍵盤上輸入一樣。不過,請注意,在進行任何評估之前,整個檔案都會被解析並進行語法檢查。
字元字串或其向量可以使用 parse
的 text=
參數來解析。這些字串的處理方式完全如同它們是輸入檔案的行。
已解析的表達式儲存在包含解析樹的 R 物件中。可以在 語言物件 和 表達式物件 中找到這些物件的更完整描述。簡而言之,每個基本 R 表達式都儲存在 函數呼叫形式中,作為一個清單,第一個元素包含函數名稱,其餘元素包含參數,這些參數反過來可能是進一步的 R 表達式。清單元素可以命名,對應於形式參數和實際參數的標記匹配。請注意,所有 R 語法元素都以這種方式處理,例如,賦值 x <- 1
被編碼為 "<-"(x, 1)
。
任何 R 物件都可以使用 deparse
轉換成 R 表達式。這通常用於結果輸出,例如標示繪圖。請注意,只有模式為 "expression"
的物件,才能預期在重新剖析 deparse 的輸出後保持不變。例如,數字向量 1:5
會剖析為 "c(1, 2, 3, 4, 5)"
,這會重新剖析為呼叫函式 c
。在可能的情況下,評估剖析和重新剖析的表達式會產生與評估原始表達式相同的結果,但有幾個例外,大多數涉及一開始並未從文字表示產生之表達式。
R 中的註解會被剖析器忽略。從 #
字元到該行結尾的任何文字都被視為註解,除非 #
字元在引號字串內。例如,
> x <- 1 # This is a comment... > y <- " #... but this is not."
符號是程式語言的基本構件。它們在詞法分析中被辨識,而詞法分析(至少在概念上)發生在解析器本身執行的語法分析之前。
有五種常數:整數、邏輯、數字、複數和字串。
此外,還有四個特殊常數:NULL
、NA
、Inf
和 NaN
。
NULL
用於表示空物件。 NA
用於表示不存在(「Not Available」)的資料值。 Inf
表示無限大,而 NaN
在 IEEE 浮點運算中表示非數字(例如,運算 1/0 和 0/0 的結果)。
邏輯常數為 TRUE
或 FALSE
。
數字常數遵循與 C 語言類似的語法。它們包含由零個或多個數字組成的整數部分,後接一個選用的「.」和一個由零個或多個數字組成的分數部分,後接一個選用的指數部分,該部分由一個「E」或「e」、一個選用的符號和一個由一個或多個數字組成的字串組成。分數部分或小數部分可以為空,但不能同時為空。
Valid numeric constants: 1 10 0.1 .2 1e-7 1.2e+7
數字常數也可以是十六進制,以「0x」或「0x」開頭,後面接零個或多個數字、「a-f」或「A-F」。十六進制浮點常數使用 C99 語法支援,例如「0x1.1p1」。
現在有一個獨立的整數常數類別。它們是使用數字末尾的限定詞 L
建立的。例如,123L
會產生整數值,而不是數字值。後綴 L
可用於限定任何非複數數字,目的是建立一個整數。因此,它可以用於由十六進制或科學記號給出的數字。但是,如果值不是有效的整數,則會發出警告並建立數字值。以下顯示有效的整數常數、會產生警告並產生數字常數和語法錯誤的值的範例。
Valid integer constants: 1L, 0x10L, 1000000L, 1e6L Valid numeric constants: 1.1L, 1e-3L, 0x1.1p-2 Syntax error: 12iL 0x1.1
對於包含不必要的十進位小數點的十進位值會發出警告,例如 1.L
。在沒有二進位指數的情況下,十六進制常數中出現小數點是錯誤的。
另請注意,前置符號(+
或 -
)會被視為單元運算子,而不是常數的一部分。
可以在 ?NumericConstants
中找到目前接受格式的最新資訊。
複數常數的形式為十進位數字常數,後面接「i」。請注意,只有純虛數才是實際常數,其他複數會被解析為數字和虛數上的單元或二元運算。
Valid complex constants: 2i 4.1i 1e-2i
字串常數以一對單引號(''')或雙引號('"')為界,並可包含所有其他可列印字元。字串中的引號和其他特殊字元使用跳脫序列來指定
\'
單引號
\"
雙引號
\n
換行(又稱「換行符」,LF)
\r
回車(CR)
\t
跳格字元
\b
退格
\a
鈴聲
\f
換頁
\v
垂直跳格
\\
反斜線本身
\nnn
具有給定八進位碼的字元 - 接受範圍0 ... 7
中的一、二或三組數字的序列。
\xnn
具有給定十六進位碼的字元 - 一或二組十六進位數字的序列(輸入0 ... 9 A ... F a ... f
)。
\unnnn \u{nnnn}
(支援多位元組區域設定,否則會產生錯誤)。具有給定十六進位碼的 Unicode 字元 - 最多四組十六進位數字的序列。字元必須在目前的區域設定中有效。
\Unnnnnnnn \U{nnnnnnnn}
(支援多位元組區域設定,否則會產生錯誤)。具有給定十六進位碼的 Unicode 字元 - 最多八組十六進位數字的序列。
單引號也可以直接嵌入在雙引號分隔的字串中,反之亦然。
字元字串中不允許出現「空字元」(\0
),因此在字串常數中使用\0
會終止常數(通常會顯示警告):會掃描到結束引號之前的其他字元,但會略過這些字元。
識別碼由一連串字母、數字、句點(.)和底線組成。它們不能以數字或底線開頭,也不能以句點後接數字開頭。
字母的定義取決於目前的區域設定:允許的精確字元組由 C 語法 (isalnum(c) || c == ‘.’ || c == ‘_’)
給出,並且會在許多西歐區域設定中包含重音字母。
請注意,預設情況下,ls
函數不會列出以句點開頭的識別碼,而且 ...
和 ..1
、..2
等是特殊的。
另請注意,物件可以有非識別碼的名稱。這些通常透過 get
和 assign
存取,儘管在某些沒有歧義的有限情況下,它們也可以用文字字串表示(例如 "x" <- 1
)。由於 get
和 assign
不限於識別碼名稱,因此它們不識別下標運算子或替換函數。下列配對不等價
x$a<-1
assign("x$a",1)
x[[1]]
get("x[[1]]")
names(x)<-nm
assign("names(x)",nm)
下列識別碼具有特殊含義,不能用於物件名稱
if else repeat while function for in next break
TRUE FALSE NULL Inf NaN
NA NA_integer_ NA_real_ NA_complex_ NA_character_
... ..1 ..2 etc.
R 允許使用者定義中綴運算子。這些運算子的形式為字元字串,以「%」字元分隔。字串可以包含任何可列印字元,但「%」除外。字串的跳脫序列在此不適用。
請注意,下列運算子已預先定義
%% %*% %/% %in% %o% %x%
儘管空白字元(空格、標籤和換頁符,在 Windows 和 UTF-8 區域設定中為其他 Unicode 空白字元5)並非嚴格意義上的記號,但在有歧義的情況下,它們用於分隔記號(比較 x<-5
和 x < -5
)。
換行符號的功能是記號分隔符號和表達式終止符的組合。如果表達式可以在行尾終止,解析器將假設它會這樣做,否則換行符號將被視為空白。分號(「;」)可用於分隔同一行上的基本表達式。
特殊規則適用於else
關鍵字:在複合表達式內,else
之前的換行符號會被捨棄,而在最外層,換行符號會終止if
結構,而後續的else
會導致語法錯誤。這種有點異常的行為發生是因為 R 應該可以在互動模式下使用,然後它必須在使用者按下RET時立即決定輸入表達式是否完整、不完整或無效。
逗號(「,」)用於分隔函數參數和多重索引。
R 使用下列運算子令牌
+ - * / %% ^
算術 > >= < <= == !=
關係 ! & |
邏輯 ~
模型公式 -> <-
指定 $
清單索引 :
序列
(幾個運算子在模型公式內有不同的意義)
R 程式由一系列 R 表達式組成。表達式可以是僅包含常數或識別碼的簡單表達式,或者可以是由其他部分(它們本身可能是表達式)構成的複合表達式。
以下各節詳述可用的各種語法結構。
函數呼叫的形式為函數參照,後面接一組括號中以逗號分隔的引數清單。
function_reference ( arg1, arg2, ...... , argn )
函數參照可以是
每個引數都可以標記(標記=表達式
),或僅為一個簡單的表達式。它也可以是空的,或者可以是特殊符號之一 ...
、..2
等等。
標記可以是識別碼或文字字串。
範例
f(x) g(tag = value, , 5) "odd name"("strange tag" = 5, y) (function(x) x^2)(5)
運算子的優先順序(最高優先順序在前)為
:: $ @ ^ - + (unary) : %xyz% |> * / + - (binary) > >= < <= == != ! & && | || ~ (unary and binary) -> ->> <- <<- = (as assignment)
注意 :
在二進制 +/-, 但不在 ^
之前。因此,1:3-1
是 0 1 2,但 1:2^3
是 1:8
。
冪運算子 ‘^’ 和 左賦值加減運算子 ‘<- - = <<-’ 從右至左分組,所有其他運算子從左至右分組。也就是說,2 ^ 2 ^ 3
是 2 ^ 8,而不是 4 ^ 3,而 1 - 1 - 1
是 -1,而不是 1。
請注意運算子 %%
和 %/%
用於整數餘數和除法,其優先順序高於乘法和除法。
儘管它不是嚴格意義上的運算子,但它也需要提到,‘=’ 符號用於標記函數呼叫中的參數,以及在函數定義中指定預設值。
‘$’ 符號在某種意義上是一個運算子,但不允許任意右邊,並在 索引結構 中進行討論。它的優先順序高於任何其他運算子。
一元或二元運算的解析形式與函數呼叫完全相同,其中運算子作為函數名稱,運算元作為函數參數。
括號被記錄為等於一元運算子,名稱為 "("
,即使在可以從運算子優先順序推斷出括號的情況下(例如,a * (b + c)
)。
請注意, 賦值符號是運算子,就像算術、關係和邏輯運算子一樣。就解析器而言,任何表達式也允許在賦值的目標側(2 + 2 <- 5
就解析器而言是一個有效的表達式。不過,評估器會提出異議)。類似的註解適用於模型公式運算子。
R 有三個索引結構,其中兩個在語法上很相似,但語意有些不同
object [ arg1, ...... , argn ] object [[ arg1, ...... , argn ]]
object 正式來說可以是任何有效的表達式,但它被理解為表示或評估為一個可子集化的物件。參數通常評估為數字或字元索引,但其他類型的參數也是可能的(特別是 drop = FALSE
)。
在內部,這些索引結構分別儲存為函式呼叫,函式名稱為 "["
和 "[["
。
第三個索引結構是
object $ tag
這裡,object 與上述相同,而 tag 是識別碼或文字字串。在內部,它儲存為函式呼叫,名稱為 "$"
R 包含下列控制結構作為特殊的語法結構
if ( cond ) expr if ( cond ) expr1 else expr2 while ( cond ) expr repeat expr for ( var in list ) expr
這些結構中的表達式通常會是複合式。
在迴圈結構 (while
、repeat
、for
) 中,可以使用 break
(終止迴圈)和 next
(跳到下一個迭代)。
在內部,結構儲存為函式呼叫
"if"(cond, expr) "if"(cond, expr1, expr2) "while"(cond, expr) "repeat"(expr) "for"(var, list, expr) "break"() "next"()
function ( arglist ) body
函式主體是一個表達式,通常是複合表達式。arglist 是逗號分隔的項目清單,每個項目可以是識別碼,或格式為「identifier = default」的格式,或特殊符號 ...
。default 可以是任何有效的表達式。
請注意,函式引數與清單標籤等不同,不能有給定為文字字串的「奇怪名稱」。
在內部,函式定義儲存為函式呼叫,函式名稱為 function
,並有兩個引數,arglist 和 body。arglist 儲存為標記配對清單,其中標籤是引數名稱,而值是預設表達式。
剖析器目前僅支援一個指令,#line
。這類似於同名的 C 預處理器指令。語法為
#line nn [ "filename"
]
其中 nn 是整數行號,而選用的 filename(在必要的雙引號中)命名來源檔案。
與 C 指令不同,#line
必須出現在一行中的前五個字元。與 C 相同,nn 和 "filename"
項目可以用空白字元隔開。與 C 不同,行中任何後續文字將被視為註解並忽略。
此指令告訴剖析器,後續行應假設為檔案 filename 的第 nn 行。(如果未提供檔案名稱,則假設與前一個指令相同。)這通常不會由使用者使用,但預處理器可以使用,以便診斷訊息參照原始檔案。
跳至: | .
[
#
$
A B D E F G I M N O P Q R S T U W |
---|
跳至: | .
[
#
$
A B D E F G I M N O P Q R S T U W |
---|
跳至: | .
#
A B C E F I M N O P S T V |
---|
跳至: | .
#
A B C E F I M N O P S T V |
---|
Richard A. Becker、John M. Chambers 和 Allan R. Wilks(1988 年),The New S Language. Chapman & Hall,紐約。這本書通常稱為「Blue Book」。