為什麼我們看不懂別人寫的 R 程式?

外界在提到 R 軟體時,在褒獎之餘,總是人云亦云地會順便提到「R 軟體的學習曲線頗陡峭」。我思考了幾個可能原因,想到以前我在學 Perl 程式語言的經驗。當時我買了好幾本 Perl 的書,但後來放棄的原因,是我看到網路上有經驗的人寫的 Perl 程式時,經常會在書上找不到他們用的特殊奇怪語法,這讓我非常氣餒。

關於「R 很難學」的迷思,可能的原因之一,或許就是這種「初學者看不懂程式」的挫折使然。所以這裡我列出一些 R 軟體有別於其他程式語言的常見特殊寫法,或許能幫忙一些初學者度過第一關。

R 軟體有別於其他程式語言的特殊語法:

1. 「=」, 「<-」, 與「<<-」三個運算符號

(1) 「=」跟「<-」是一樣的。R 語言是源自於 AT&T 的 S 語言。在 S 語言中,「<-」的意義就是「=」。從 R-1.4.0 版之後,R 軟體開始導入「=」號,目前兩者皆可使用。

(2) 「<<-」只在 funciton 中使用,可以從 function 內部改變 function 外面的全域變數(Global Variable) 的值。若在函數內只有使用「=」或「<-」,則函數之外的全域變數值不會被改變。例如:

x1 = 100 ; x2 = 100
f2 = function(y)
{
  x1 <<- y + 10
  x2 <- y + 10
  w = y + 20
  return(w)
}
f2(6)
  [1] 26    # w =  6 + 20 = 26
x1
  [1] 16    # x1 <<- 6 + 10
x2
  [1] 100    # x2 未被改變

2. 使用指標時,到底要不要加 c(  …. ) ?

原則:只要用到 2 個或 2 個以上指標時,都必須用 c(….) 把這些指標包起來,唯一的例外是 「:」,例如 1:3 外面就不用加 c(….),但加了也不會出錯, 如 c(1:3)

x = 11:20
names(x) = paste0("k",11:20)  # 為每個元素加上名稱
x
  k11 k12 k13 k14 k15 k16 k17 k18 k19 k20
   11  12  13  14  15  16  17  18  19  20
x[3]
  k13
   13
x[1:3]
  k11 k12 k13
   11  12  13
x[c(2,4,9)]
  k12 k14 k19
   12  14  19
x["k13"]
  k13
   13
x[c("k12","k14","k19")]
  k12 k14 k19
   12  14  19

3. 指標外面該用 [ … ] 還是 [[ … ]] ?

[ … ] 傳回的資訊,[[ … ]] 傳回的資訊
[[ … ]] 雙重方刮號有 2 個原則:

(1)  傳回比較精簡濃縮的資訊
(2) 只能放一個數字指標或名稱指標,不能放兩個以上

some.dataFrame[3]       # 傳回第 3 個變數,但被包在新的 data-frame 中
some.dataFrame[[3]]     # 傳回第 3 個變數 (vector 或 factor)
some.List[3]            # 傳回第 3 個元素,但被包在一個新的 list 變數裡面
some.List[[3]]          # 傳回第 3 個元素,沒有被 list 結構包起來
x = 1:10
x[1]
   [1] 1
x[[1]]                  # 與 x[1] 相同
   [1] 1
names(x) = letters[1:10]     # 幫 x 的元素加上名稱
x
   a b c d e f g h i j
   1 2 3 4 5 6 7 8 9 10
x[1]               # 多出了元素名稱 "a"
   a
   1
x[[1]]
   [1] 1

4. 向量/矩陣之間的向量化(Vetorized)加減乘除運算

x = c(1,2,3,4,5)
y = c(10,20,30,40,50)
x + y
  [1] 11 22 33 44 55
x - y
  [1] -9 -18 -27 -36 -45
x * y
  [1] 10 40 90 160 250
y ^ x
  [1] 10 400 27000 2560000 312500000
x + 100
  [1] 101 102 103 104 105

5. 在指標區域用邏輯條件做快速的資料(元素/橫列(rows))篩選

score = c(43,56,74,61,61,55)
sex = c("男","男","女","女","女","男")
blood = c("A","O","A","O","O","A")
score[score <= 60]
  [1] 43 56 55
score[sex == "女"]
  [1] 74 61 61
mean(score[sex == "女"])
  [1] 65.33333
mean(score[sex == "男"])
  [1] 51.33333
# 所有 A 型男性的平均成績
mean(score[sex == "男" & blood == "A"])
  [1] 49
# 找出所有 Sepal.Length > 4.0 的橫列(rows), 並儲存在 iris2
iris2 = iris[iris$Sepal.Length > 4.0, ]

6. 矩陣/資料框架行列指標的省略

M = matrix(c(1,2,3,4,5,6),nrow=2,ncol=3,byrow=TRUE)
M
       [,1] [,2] [,3]
  [1,]   1    2    3
  [2,]   4    5    6
M[1,2]   # 取出單一元素值
  [1] 2
M[1,]    # 取出第 1 列(row)
  [1] 1 2 3
M[2,]    # 取出第 2 列(row)
  [1] 4 5 6
M[,1]    # 取出第 1 行(column)
  [1] 1 4
M[,3]    # 取出第 3 行(column)
  [1] 3 6

7.「$」符號是什麼東東?

「$」是 R 軟體中的 List (串列) 變數跟 Data Frame (資料框架) 變數在抓取個別內部元素或變數時所用的運算元。例如:

friend = list(fname="林大旺",age=25,child=c("林小華","林小珍"))
names(friend)
  [1] "fname" "age" "child"
friend$fname
  [1] "林大旺"
friend$age
  [1] 25
friend$child
  [1] "林小華" "林小珍"
friend$child[2]
  [1] "林小珍"
# 或
childs = friend$child
childs[2]
  [1] "林小珍"

List 變數是很多 R 的大型計算函數傳回的慣用變數型態,因為 List 變數內可以包山包海,什麼資料都可以放進去。例如

result = lm(Sepal.Length ~ Petal.Length,data=iris)
names(result)
  [1] "coefficients" "residuals" "effects" "rank"
  [5] .........
result$coefficients
  (Intercept) Petal.Length
   4.3066034     0.4089223

SL = iris$Sepal.Length  # 取出 iris 資料框架內的 Sepal.Length 變數

8. 那些奇怪的「%>%」符號是什麼東東?

「%>%」是 dplyr 套件常用的「向前(其實是向右)管道」(forward pipe) 運算子,可以把原本分作好幾行的運算結合在一起。例如

iris %>% subset(select=Sepal.Length) %>% summary
  Sepal.Length
  Min. :4.300
  1st Qu.:5.100
  Median :5.800
  Mean :5.843
  3rd Qu.:6.400
  Max. :7.900

上面的程式相當於以下這一行程式「由內而外」的運算順序改成「由左而右」:

summary(subset(iris, select=Sepal.Length))

其實原本分開寫成兩行運算,需要佔用記憶體多用一個臨時變數 iris2:

iris2 = subset(iris, select=Sepal.Length)
summary(iris2)

dplyr 套件這個符號源自於 magrittr 套件,除了「%>%」符號外,還有「%<>%」、「%$%」、「%T>%」三種運算子可用。但是,這類屬於某特定套件的運算符號,目前尚未被納入 R 軟體核心,因此國外也有些人認為,在寫 R 的套件時,不適合使用這類運算符號,以免造成新套件對其他套件的過度依賴。