你知道R中的賦值符號箭頭和等號的區別嗎?
作者:陳亮
單位:中國科學院微生物研究所
你知道R中的賦值符號箭頭(<-)和等號(=)的區別嗎?
作為一門高階語言,R語言擁有獨特的語法,比如今天說道的賦值符號。在其他語言裡,賦值符合通常用一個等號(=)表示,而在R語言裡,承擔這個任務的可以是箭頭(<-)符號,也可以是等號(=)。這就導致許多R語言初學者,分不清R語言中的賦值到底是使用箭頭(<-)還是等號(=)?許多早期學習R的童鞋都比較喜歡使用等號(=)進行賦值。畢竟,簡簡單單的a = 5用起來比較符合大多數現有語言的習慣。出於對某種賦值方式的偏好,甚至出現了等號黨和箭頭黨,但是到底孰好孰壞,顯然爭不出任何結果,相對來說更重要的是瞭解這兩者的區別。只有我們深刻理解了其相同與不同之後,才能更好的運用他們。
R語言最開始設計的時候,是採用箭頭(<-)作為賦值符號的,這是從APL語言繼承而來的(箭頭表示賦值,等號表示判斷)。之後的S語言也沿用了這個用法,再之後R語言為了保持和S語言的相容性保留了這個箭頭。直到2001年,R的更新版本中 才加入了等號(=)賦值。因此,對於一般的賦值語句,箭頭(<-)與 等號(=)在 功能上是沒有區別的,可以通用。但是等號(=)的作用有兩個:它既可以賦值,也可以傳遞函式引數(實際上傳參可以看作一種特殊形式的賦值,給引數賦值)。通常情況下,如果等號(=)出現在單獨的環境中,它就是賦值;如果寫在函式的引數位置,它就是傳參。如果你在設定引數的時候使用了箭頭(<-),那麼你會發現在全域性變數裡,會多出一個和引數名相同的賦值的變數,容易導致歧義和錯誤,而且佔用名稱空間。
下面,我們通過幾個個例子來具體講一下這兩個函式的區別。
箭頭(<-)和等號(=)賦值在作用域上的不同。
箭頭(<-)建立的變數的作用範圍為整個全域性環境(Global environment),而等號(=)通常在一個區域性環境(Local environment)。例如:> rm(x) ## 如果變數 x 存在的話,先刪除此變數 > mean(x = 1:10) [1] 5.5 > x Error: object 'x' not found
在以上範例裡,變數 x 是在函式的作用域裡進行宣告的,所以它只存在於此函式中,一旦運算完成便“消失”。
> mean(x <- 1:10) [1] 5.5 > x [1] 1 2 3 4 5 6 7 8 9 10
而採用箭頭(<-)賦值,x 變數則出現在了Global Environment 裡,並且我們可以呼叫它。 在此例中,實際上是先構建了x變數,再將x傳遞給mean函式的第一個引數,我們看到,採用這種方式,程式也正確運行了,但是採用箭頭(<-)賦值的方式去傳參時要非常小心。可以看下面例子中引起錯誤地情況。
箭頭(<-)和等號(=)在引數傳遞時的區別
> x <- rnorm(100) # 採用箭頭(<-)進行變數賦值 > y <- 2*x + rnorm(100) # 採用箭頭(<-)進行變數賦值 > lm(formula=y~x) #上面的程式碼完全等價於下面的程式碼 > x = rnorm(100) # 採用等號(=)進行變數賦值 > y = 2*x + rnorm(100) # 採用等號(=)進行變數賦值 > lm(formula=y~x)
兩段程式碼中前兩行都是賦值語句,分別為x變數和y變數賦值,此時等號(=)與箭頭(<-)的功能相同,作用域也相同,因為等號(=)賦值是在全域性環境中進行的,而程式碼第三行中的等號(=)則是呼叫函式時規定命名引數,這就是通常情況下,我們直接將y~x這個公式直接傳遞給lm函式的第一個引數,也就是formula引數的用法。如果此時我們將等號(=)替換成箭頭(<-),則會在全域性環境中定義出一個新的formula變數,然後再將這個變數傳遞給了lm函式的第一個引數。如果是我們有意這麼做的話,就需要保證命名引數的順序和函式中定義引數的順序相同,否則就會出現錯誤,或者將名稱相同的變數傳遞給了錯誤的引數(但程式可能正常執行),導致結果錯誤。下面的例子可以突出了這種差別:
> x <- rnorm(100) > y <- 2*x+rnorm(100) > z <- 3*x+rnorm(100) > data <- data.frame(z,x,y) > rm(x, y, z)
此時,環境中已經沒有x,y,z變數,就只有變數data可以用來做z~x+y的線性迴歸。標準寫法:
> lm(formula=z~x+y,data = data) #也可以寫成如下形式: > lm(data=data,formula=z~x+y)
當我們將等號(=)替換成箭頭(<-)時,正確的命名引數傳遞應該按函式引數順序來逐個傳參:
> lm(formula <- z~x+y, data <- data) Call: lm(formula = formula <- z ~ x + y, data = data <- data) Coefficients: (Intercept) x y 0.069869 3.062565 0.007503 > formula z ~ x + y
執行也不會出錯,但是我們會發現函式實際上是呼叫的lm(formula = formula <- z ~ x + y, data = data <- data),這時產生了一個新的變數formula到環境中,並且在全域性環境中就可以使用(實際上data變數也被更新了)。
但是如果我們對lm函式的引數順序不瞭解或者由於馬虎搞錯了引數順序,這個時候就會容易出現錯誤。#錯誤的寫法: > lm(data <- data,formula <- z~x+y) Error in as.data.frame.default(data) : cannot coerce class ""formula"" to a data.frame
執行時會報告異常,說明data被當作第一個引數formula傳遞,而formula被當作第二個引數data傳遞,而引數型別不匹配因而導致異常。因此,在函式的命名引數傳遞時,儘量不要用箭頭(<-),因為既會產生副作用(建立新變數),也無法利用命名引數傳遞的功能。上面的例子是程式提示了錯誤,但是有時候程式並不一定會提示錯誤,就很容易讓我們忽視結果實際上是錯誤的結果。例如:我們構建矩陣時,
# 構建一個3列的矩陣 > matrix(c(1:12),ncol=3) [,1] [,2] [,3] [1,] 1 5 9 [2,] 2 6 10 [3,] 3 7 11 [4,] 4 8 12 > matrix(c(1:12),ncol<-3) [,1] [,2] [,3] [,4] [1,] 1 4 7 10 [2,] 2 5 8 11 [3,] 3 6 9 12
我們可以看到,儘管兩種方法,都執行成功,且得到了一個矩陣,但是第二個結果是一個錯誤的結果,此處出錯的原因就是,ncol<-3是將3賦值給變數ncol,然後再傳遞給函式對應位置的引數,而在函式內第二個引數實際上是對應的nrow引數。在實際編寫程式碼時,遇到這種情況,如果我們不注意,就會導致後續所有結果都出錯。
此外,還需要注意的一點就是,在傳參中採用箭頭(<-)進行賦值的變數只有在需要使用時才會改變其值。例如:
> a <- 1 > f <- function(x) return(TRUE) > f(a <- a + 1); a [1] TRUE [1] 1
請注意,以上範例裡, a 的值並沒有改變,也就是a並沒有加1,還是原來的a值,這是在函式內部並未用到引數a。這會導致程式裡出現一些不可預期的結果並且降低程式碼可讀性,所以不推薦在函式引數裡使用箭頭(<-)這種賦值方式。在看下面的例子:
> a <- 1 > f <- function(x) { + if(runif(1)>0.5) TRUE + else print(x) + } > f(a <- a+1);a [1] TRUE [1] 1 > f(a <- a+1);a [1] TRUE [1] 1 > f(a <- a+1);a [1] TRUE [1] 1 > f(a <- a+1);a [1] 2 [1] 2 > f(a <- a+1);a [1] TRUE [1] 2 > f(a <- a+1);a [1] 3 [1] 3
上述程式碼中,向函式 f() 傳遞傳遞引數 a <- a + 1 後,只有在隨機數 runif(1) 小於0.5的時候,a 的值才會改變,即執行+1操作,然後列印a。否則傳遞TRUE值。因此,因為隨機數 runif(1) 的隨機性,每次呼叫函式 f()後 a 的值是不確定的。
現在大家應該清楚瞭解箭頭(<-)和等號(=)的區別了吧!個人建議,大家寫賦值語句時採用箭頭(<-),傳參時使用等號(=)。這也是大部分老師都會強烈推薦的用法。是因為使用箭頭(<-)賦值,意義清晰,可以保持程式碼良好的可讀性,尤其是書寫複雜函式時,避免造成混亂。Google 的 R style guide(https://google.github.io/styleguide/Rguide.xml)也推薦使用箭頭(<-)賦值。 況且有些情況下,只能採用箭頭(<-)賦值,例如:system.time(c<-1:10)中就不能使用等號(=)。而從數學的角度來說,等號兩邊是相等的,即等號左邊的等於等號右邊的,等號右邊的也等於等號左邊的。等號本身並沒有指向性,因此並沒有辦法體現”賦值“這一含義。而在R中,箭頭(<-)符號生動的闡釋了賦值的含義,一個非等號(=)的賦值符從根本上向學習者暗示這樣一個真理: 賦值操作與數學上的等於是完全不同的。此外,箭頭(<-)符號可以雙向賦值,即x <- 10與10 -> x等價。習慣 <- 和 -> 的使用以後,也對後來習慣使用更為複雜的 <<- 以及 ->> 這兩個賦值符號(<<-或->>一般用於函式內部,表示給上一層環境中的變數賦值)做好鋪墊,而 = 無法實現類似的功能。
另外也有等號黨提出異議,認為採用箭頭(<-)不如使用等號(=)。例如:如果我想判斷一個變數是否小於10,可以寫成 x<10;如果我想判斷一個變數是否小於-10,然後順手寫成x<-10,這時候就會產生歧義。關於處理負數時產生歧義的說法,只能說是沒有正確養成良好的空格習慣造成的,句號逗號後加空格,括號外圍加空格,運算子號兩邊加空格,這些應該是學習程式碼前就應該懂得的常識。會犯出 a <- 5 和 a < -5 混淆的錯誤只能說明自己的程式碼風格糟糕,建議大家Google 的 R style guide(https://google.github.io/styleguide/Rguide.xml )中其他的一些程式碼寫作規則。
Reference
猜你喜歡
寫在後面
為鼓勵讀者交流、快速解決科研困難,我們建立了“巨集基因組”專業討論群,目前己有國內外1400+ 一線科研人員加入。參與討論,獲得專業解答,歡迎分享此文至朋友圈,並掃碼加主編好友帶你入群,務必備註“姓名-單位-研究方向-職稱/年級”。技術問題尋求幫助,首先閱讀《如何優雅的提問》學習解決問題思路,仍末解決群內討論,問題不私聊,幫助同行。
學習擴增子、巨集基因組科研思路和分析實戰,關注“巨集基因組”