在處理資料時,除了數值資料之外,文字資料也是很常見的資料類型,尤其是在整理第一手的原始資料時,通常都會有非常大量的文字資料需要處理,而因子則是用於儲存類別型式的資料(categorical data),它的性質介於整數與字元變數之間,以下我們將介紹 R 的字串與因子使用方式。

字串(String)

在 R 中文字的資料都是儲存在字元向量中,而字元向量中的每一個元素都是一個完整的字串(string)。

這裡我們使用字串(string)這個名稱來稱呼字元向量的元素。

建立與輸出字串

字元向量跟一般向量一樣可以使用 c 函數來建立,我們可以使用雙引號或單引號包住字串:

c("Hello", 'World')
[1] "Hello" "World"

若遇到字串中包含雙引號或單引號的情況,可以用反斜線(\)來跳脫處理:

c("Hello, \"World\"")
[1] "Hello, \"World\""

或是使用單引號包住含有雙引號的字串(反之亦可):

c('Hello, "World"')
[1] "Hello, \"World\""

如果要將多個字串連接起來,可以使用 paste 函數:

paste("Hello", "World")
[1] "Hello World"

paste 預設會使用一個空白字元當作分隔符號,將所有的字串連接起來,我們可以使用 sep 參數自行指定分隔字元:

paste("Hello", "World", sep = "-")
[1] "Hello-World"

如果不想要有任何分隔符號,可以使用 paste0 這個函數:

paste0("Hello", "World")
[1] "HelloWorld"

如果遇到不同長度的字元向量時,較短的字元向量就會被重複使用:

paste(c("red", "green"), "apple")
[1] "red apple"   "green apple"

若指定 collapse 參數,paste 就會使用這個參數所指定的內容當作分隔符號,將字元向量中所有的字串全部串接成一個字串:

paste(c("red", "green"), "apple", collapse = ", ")
[1] "red apple, green apple"

toString 是一個類似 paste 的函數,他可以將各種向量轉為字串:

x <- (1:10)^2
toString(x)
[1] "1, 4, 9, 16, 25, 36, 49, 64, 81, 100"

toString 加上 width 可以限制輸出字串的長度上限:

toString(x, width = 20)
[1] "1, 4, 9, 16, 25,...."

cat 函數是一個類似 paste 的低階函數,一般使用者不太會有機會需要使用到它,不過由於大部分 print 函數內部都會使用 cat 來輸出,所以多少了解一下會比較好。cat 會直接將所有的元素直接以字串輸出(不管向量長度):

cat(c("red", "green"), "apple", 1:3)
red green apple 1 2 3

在 R 中一般的字串輸出時都會以雙引號包住,如果不想要讓字串出現雙引號,可以使用 noquote 函數來處理:

x <- c("If", "people", "do", "not", "believe",
  "that", "mathematics", "is", "simple,",
  "it", "is", "only", "because", "they",
  "do", "not", "realize", "how",
  "complicated", "life", "is");
x
[1] "If"          "people"      "do"          "not"
 [5] "believe"     "that"        "mathematics" "is"
 [9] "simple,"     "it"          "is"          "only"
[13] "because"     "they"        "do"          "not"
[17] "realize"     "how"         "complicated" "life"
noquote(x)
 [1] If          people      do          not         believe
 [6] that        mathematics is          simple,     it
[11] is          only        because     they        do
[16] not         realize     how         complicated life
[21] is

格式化數值

若要將數值的資料依據指定的格式轉換為字串,可以使用 formatC 這個函數,這個函數可以讓我們使用類似 C 語言的方式來指定輸出格式。

x <- exp(1:3)
x
[1]  2.718282  7.389056 20.085537

formatC 函數的 digits 參數可以指定輸出數值的位數:

formatC(x, digits = 5)
[1] "2.7183" "7.3891" "20.086"

format 參數可以指定輸出數值格式:

formatC(x, digits = 5, format = "e")
[1] "2.71828e+00" "7.38906e+00" "2.00855e+01"

width 可指定輸出字串長度,不足的部分則會以空白補齊:

formatC(x, digits = 5, width = 8)
[1] "  2.7183" "  7.3891" "  20.086"

在 R 中也有一個 sprintf 函數,它跟一般程式語言中的 sprintf 函數用法一樣,其第一個參數是輸出的樣板,第二個以後的參數則是要輸出的各種資料變數。

item <- "the apple"
weight <- 3.2
sprintf("The weight of %s is %f kg.", item, weight)
[1] "The weight of the apple is 3.200000 kg."

sprintf 也可以直接處理向量的輸出:

items <- c("the apple", "the banana")
weights <- c(3.2, 2.5)
sprintf("The weight of %s is %f kg.", items, weights)
[1] "The weight of the apple is 3.200000 kg."
[2] "The weight of the banana is 2.500000 kg."

%.1f 可以指定浮點數輸出至小數點以下第一位:

sprintf("The weight of %s is %.1f kg.", items, weights)
[1] "The weight of the apple is 3.2 kg."
[2] "The weight of the banana is 2.5 kg."

以科學記號輸出,精確度到小數點下第二位:

sprintf("The weight of %s is %.2e kg.", items, weights)
[1] "The weight of the apple is 3.20e+00 kg."
[2] "The weight of the banana is 2.50e+00 kg."

另外還有兩個用於格式化數值輸出的 formatprettyNum 函數,formatformatC 類似,只是參數的用法有些小差異而已。

format(x, digits = 5)
[1] " 2.7183" " 7.3891" "20.0855"
format(x, digits = 5, trim = TRUE)
[1] "2.7183"  "7.3891"  "20.0855"
format(x, digits = 3, scientific = TRUE)
[1] "2.72e+00" "7.39e+00" "2.01e+01"

prettyNum 函數則適用於輸出比較大或比較小的數值:

prettyNum(2^30, big.mark = ",")
[1] "1,073,741,824"
prettyNum(1/2^20, small.mark = " ", scientific = FALSE)
"0.00000 09536 743"

特殊字元

在字串中若要加入一些特殊字元(例如 tab 字元),可以運用跳脫字元的方式來處理:

cat("foo\tbar")
foo	bar

換行字元可使用 \n

cat("foo\nbar")
foo
bar

如果要輸入反斜線,則使用 \\

cat("foo\\bar")
foo\bar

單引號與雙引號也可以利用跳脫字元輸入:

cat("foo\"bar")
foo"bar
cat('foo\'bar')
foo'bar

如果是以雙引號包住單引號,就可以不需要跳脫字元,反之亦然:

cat('foo"bar')
foo"bar
cat("foo'bar")
foo'bar

另一個比較特別的字元是 alarm 字元(\a),輸出這個字元時會讓喇叭產生聲響:

cat("\a")

輸出 alarm 字元的效果等同時呼叫 alarm 函數:

alarm()

改變大小寫

如果要改變字串中英文字母的大小寫,可以運用 touppertolower 函數:

toupper("Foo Bar")
[1] "FOO BAR"
tolower("Foo Bar")
[1] "foo bar"

子字串

若要從字串中取出一部分的子字串,可以使用 substrsubstring 函數,這兩個函數的用法幾乎都相同,第一個參數是輸入的字串,第二與第三個參數則是子字串的開始與結束位置:

substr("abcdef", 2, 4)
[1] "bcd"
substring("abcdef", 2, 4)
[1] "bcd"

substrsubstring 只有在處理向量時會有一些小差異:

x.strings <- c(
  "abcdefghij",
  "ABCDEFGHIJ",
  "1234567890"
)
substr(x.strings, 1:4, 8)
[1] "abcdefgh" "BCDEFGH"  "345678"
substring(x.strings, 1:4, 8)
[1] "abcdefgh" "BCDEFGH"  "345678"   "defgh"

分割字串

若要將一個字串分割成多個字串,可以使用 strsplit 函數,第一個參數是輸入的字串,而第二個參數則是分隔字串:

strsplit("foo,bar,Foo,BAR", ",")
[[1]]
[1] "foo" "bar" "Foo" "BAR"

strsplit 函數預設會使用正規表示法(regular expression)來匹配分隔字串,如果要將指定的分隔字串視為一般的文字,可以加上 fixed = TRUE 參數。

strsplit("foo+bar+Foo+BAR", "+", fixed = TRUE)
[[1]]
[1] "foo" "bar" "Foo" "BAR"

strsplit 函數的輸出是一個列表(list),這樣設計的原因在於處理向量時,每一個結果的長度可能會不同:

x.str <- c("foo bar Foo BAR", "abc def")
strsplit(x.str, " ", fixed = TRUE)
[[1]]
[1] "foo" "bar" "Foo" "BAR"

[[2]]
[1] "abc" "def"

這是一個使用正規表示法的例子,使用逗號、句點或空白作為分隔字元:

strsplit("foo,bar.Foo BAR", "[,. ]")
[[1]]
[1] "foo" "bar" "Foo" "BAR"

檔案路徑

在 R 中我們可以藉由 getwd 函數取得目前的工作目錄,在存取檔案時若沒有特別指定路徑的話,就會以這個路徑為準。

getwd()
[1] "d:/workspace"

我們可以使用 setwd 函數來更改工作目錄:

setwd("c:/mypath")
getwd()
[1] "c:/mypath"

這裡我們使用正斜線(/)來區隔路徑,而如果要使用 Windows 慣用的反斜線也可以,只是在輸入反斜線時,要使用跳脫字元:

setwd("c:\\mypath")

另外我們也可以使用 file.path 函數來從各個目錄名稱來建立完整的路徑:

file.path("c:", "Program Files", "R")
[1] "c:/Program Files/R"

R.home 可以顯示 R 的安裝路徑:

R.home()
[1] "C:/PROGRA~1/R/R-32~1.4"

basename 可以從一串完整路徑中取出不包含路徑的檔案名稱:

basename("C:/Users/GTWang/Documents")
[1] "Documents"

因子(Factor)

R 的因子(factor)變數是專門用來儲存類別資料的變數,它同時具有字串與整數的特性。

建立因子變數

若要建立一個因子變數,可以使用 factor 函數:

colors <- c("red", "yellow", "green", "red", "green")
colors.factor <- factor(colors)
colors.factor
[1] red    yellow green  red    green
Levels: green red yellow

因子變數在輸出時,看起來跟一般的字元向量類似,而最後一行的 levels 會列出這個因子變數所有的類別。我們可以使用 levels 這個函數取得因子的 levels:

levels(colors.factor)
[1] "green"  "red"    "yellow"

nlevels 則可以取得因子 levels 的數量:

nlevels(colors.factor)
[1] 3

在建立因子變數時,可以使用 levels 參數指定 levels 的排列順序:

colors.factor2 <- factor(colors,
  levels = c("red", "yellow", "green"))
colors.factor2
[1] red    yellow green  red    green
Levels: red yellow green

labels 參數則可以指定個類別的名稱:

colors.factor3 <- factor(colors,
  levels = c("red", "yellow", "green"),
  labels = c("R", "Y", "G"))
colors.factor3
[1] R Y G R G
Levels: R Y G

在建立 data frame 時,R 預設會將文字的資料轉為因子:

values <- c(12, 54, 23, 83, 2)
colors <- c("red", "yellow", "green", "red", "green")
my.table <- data.frame(values, colors)
my.table
values colors
1     12    red
2     54 yellow
3     23  green
4     83    red
5      2  green
class(my.table$colors)
[1] "factor"

改變因子的 Levels

因子變數中的元素值只能是 levels 中的其中一種或是缺失值(NA),如果將因子的元素指定為其他的字串,該元素就會被設定為缺失值,並產生警告訊息:

colors.factor[1] <- "blue"
Warning message:
In `[<-.factor`(`*tmp*`, 1, value = "blue") :
  invalid factor level, NA generated
colors.factor
[1] <NA>   yellow green  red    green
Levels: green red yellow

如果要新增一個 levels 中沒有的類別資料,可以先使用 levels 函數先新增一個 level,再指定新的值:

colors.levels <- levels(colors.factor)
levels(colors.factor) <- c(colors.levels, "blue")
colors.factor[1] <- "blue"
colors.factor
[1] blue   yellow green  red    green
Levels: green red yellow blue

在更動因子變數的 levels 時,要注意其排列的順序,新增的 level 要放在最後面,如果順序搞錯會造成資料錯誤,若要避免順序錯亂的問題,可以使用 list 的方式指定:

levels(colors.factor) <- list(
  blue = "blue", green = "green",
  red = "red", yellow = "yellow")
colors.factor[1] <- "blue"
colors.factor
[1] blue   yellow green  red    green
Levels: blue green red yellow

relevel 可以重新排序因子變數的 levels,將指定的 level 放在第一個位置:

relevel(colors.factor, "red")
[1] blue   yellow green  red    green
Levels: red blue green yellow

移除因子的 Levels

有時候在處理一些原始的文字資料時,會出現類似這樣英文大小寫不統一的狀況:

colors.raw <- c("Red", "yellow", "green", "red", "green")
colors.factor <- factor(colors.raw)
colors.factor
[1] Red    yellow green  red    green
Levels: green red Red yellow

而經過修正之後,就會出現一些沒有用的 levels。

colors.factor[1] <- "red"
unique(colors.factor)
[1] red    yellow green
Levels: green red Red yellow

若要移除因子變數中沒有用到的 levels,可以使用 droplevels 函數:

colors.factor <- droplevels(colors.factor)
colors.factor
[1] red    yellow green  red    green
Levels: green red yellow

droplevels 也可以直接處理 data frame 之內的因子欄位:

colors.raw <- c("Red", "yellow", "green", "red", "green")
values <- c(12, 54, 23, 83, 2)
my.table2 <- data.frame(values, colors.raw)
my.table2$colors.raw
[1] Red    yellow green  red    green
Levels: green red Red yellow
my.table2$colors.raw[1] <- "red"
unique(my.table2$colors.raw)
[1] red    yellow green
Levels: green red Red yellow
my.table2 <- droplevels(my.table2)
my.table2$colors.raw
[1] red    yellow green  red    green
Levels: green red yellow

有序因子

有一些資料雖然是類別型的資料,但是不同類別之間是可以比較的,這種有順序性的類別資料可以使用有序因子來儲存,最常見的例子就是問卷調查的資料,例如滿意度的評價可分為非常差(worst)、差(bad)、普通(so-so)、好(good)與非常好(perfect):

choices <- c("worst", "bad", "so-so", "good", "perfect")
samples <- sample(choices, 10, replace = TRUE)
samples.factor <- factor(samples, levels = choices)
samples.factor
 [1] perfect so-so   perfect good    perfect
 [6] worst   worst   so-so   good    so-so
Levels: worst bad so-so good perfect

若遇到這樣的資料,可以改用 ordered 函數來建立有序的因子變數(或是在呼叫 factor 函數時加入 ordered = TRUE 也可以):

samples.ordered <- ordered(samples, levels = choices)
samples.ordered
 [1] perfect so-so   perfect good    perfect worst
 [7] worst   so-so   good    so-so
Levels: worst < bad < so-so < good < perfect

有序因子也是屬於因子變數:

is.factor(samples.ordered)
[1] TRUE

有序的因子跟一般的因子變數的使用方式都相同,唯一的差異就只是它的 levels 有順序性而已。

將連續型變數轉換為離散型變數

如果要看一群連續型資料大致的分佈,可以將其轉換為離散的群組,這樣會比較容易一眼看出整個分布狀況:

grouped <- cut(iris$Sepal.Length, seq(4.3, 7.9, 4))
head(grouped)
[1] (4.3,5.2] (4.3,5.2] (4.3,5.2] (4.3,5.2] (4.3,5.2] (5.2,6.1]
Levels: (4.3,5.2] (5.2,6.1] (6.1,7] (7,7.9]
table(grouped)
grouped
(4.3,5.2] (5.2,6.1]   (6.1,7]   (7,7.9]
       45        50        43        12

這個作用跟使用 hist 函數畫直方圖相同。

將離散型變數轉換為連續型變數

有些時候我們拿到的原始資料有一些錯誤(例如打字上的錯誤),造成 R 在讀入資料時,將數值當成字串讀入,然後自動轉換為因子,而這種變數若直接使用 as.numeric 轉換為數值的話,會出現錯誤:

raw <- data.frame(
  x = c("1.23", "4..56", "7.89")
)
as.numeric(raw$x)
[1] 1 2 3

這裡的 as.numeric 是將因子變數內部代表各類別的數值資料直接取出,但這並不是我們想要的結果。我們可以將因子先轉換為字串,再轉為數值:

as.numeric(as.character(raw$x))
[1] 1.23   NA 7.89
Warning message:
強制變更過程中產生了 NA

而根據 R 的 FAQ 說明文件,比較好的做法是像下面這種,這個寫法的執行效率會比較高:

as.numeric(levels(raw$x))[as.integer(raw$x)]
[1] 1.23   NA 7.89
Warning message:
強制變更過程中產生了 NA

產生因子的 Levels

gl 函數可以依據指定的樣式來產生因子變數,第一個參數 n 是指定因子的 level 數目,而第二個參數 k 則是指定每個 level 出現的次數:

gl(n = 2, k = 8)
[1] 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2

labels 參數可以用來指定各個 levels 的名稱:

gl(n = 2, k = 8, labels = c("Control", "Treat"))
 [1] Control Control Control Control Control Control Control Control Treat
[10] Treat   Treat   Treat   Treat   Treat   Treat   Treat
Levels: Control Treat

length 參數可以指定產生的因子變數長度:

gl(n = 2, k = 2, length = 8,
  labels = c("Control", "Treat"))
[1] Control Control Treat   Treat   Control Control Treat   Treat
Levels: Control Treat

結合因子變數

如果要將兩個(或多個)因子變數結合,可以使用 interaction 函數:

a <- gl(2, 4, 8)
a
[1] 1 1 1 1 2 2 2 2
Levels: 1 2
b <- gl(2, 2, 8, labels = c("ctrl", "treat"))
b
[1] ctrl  ctrl  treat treat ctrl  ctrl  treat treat
Levels: ctrl treat
interaction(a, b)
[1] 1.ctrl  1.ctrl  1.treat 1.treat 2.ctrl  2.ctrl  2.treat 2.treat
Levels: 1.ctrl 2.ctrl 1.treat 2.treat

超過兩個因子變數的情況也可以使用 interaction 處理,而 sep 參數可以指定結合 level 時的分隔符號。

s <- gl(2, 1, 8, labels = c("M", "F"))
s
[1] M F M F M F M F
Levels: M F
interaction(a, b, s, sep = ":")
[1] 1:ctrl:M  1:ctrl:F  1:treat:M 1:treat:F 2:ctrl:M  2:ctrl:F  2:treat:M
[8] 2:treat:F
8 Levels: 1:ctrl:M 2:ctrl:M 1:treat:M 2:treat:M 1:ctrl:F ... 2:treat:F