作為一門高級語言,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
https://www.cnblogs.com/loca/p/4301344.html
https://google.github.io/styleguide/Rguide.xml
http://stat.ethz.ch/R-manual/R-patched/library/base/html/assignOps.html
https://stackoverflow.com/questions/1741820/what-are-the-differences-between-and-in-r
http://bbs.pinggu.org/thread-1247151-1-1.html
https://cran.r-project.org/doc/manuals/R-lang.html#Argument-matching
猜你喜歡10000+:腸道細菌 人體上的生命 寶寶與貓狗 梅毒狂想曲 提DNA發Nature 實驗分析誰對結果影響大 Cell微生物專刊
系列教程:微生物組入門 Biostar 微生物組 宏基因組
專業技能:生信寶典 學術圖表 高分文章 不可或缺的人
一文讀懂:宏基因組 寄生蟲益處 進化樹
必備技能:提問 搜索 Endnote
文獻閱讀 熱心腸 SemanticScholar Geenmedical
擴增子分析:圖表解讀 分析流程 統計繪圖
16S功能預測 PICRUSt FAPROTAX Bugbase Tax4Fun
在線工具:16S預測培養基 生信繪圖
科研經驗:雲筆記 雲協作 公眾號
編程模板 Shell R Perl
生物科普 生命大躍進 細胞暗戰 人體奧秘
寫在後面為鼓勵讀者交流、快速解決科研困難,我們建立了「宏基因組」專業討論群,目前己有國內外150+ PI,1300+ 一線科研人員加入。參與討論,獲得專業解答,歡迎分享此文至朋友圈,並掃碼加主編好友帶你入群,務必備註「姓名-單位-研究方向-職稱/年級」。技術問題尋求幫助,首先閱讀《如何優雅的提問》學習解決問題思路,仍末解決群內討論,問題不私聊,幫助同行。
學習16S擴增子、宏基因組科研思路和分析實戰,關注「宏基因組」
點擊閱讀原文,跳轉最新文章目錄閱讀