R 學習筆記:數據類型與存儲
R 的數據存儲類型
基本類型
最基本的類型是存儲單一數值的類型. 主要包括 Numeric, Integer, Complex, Character, Logical 等.
數字
Numeric 或者 "double" 是 R 優先選擇的存儲數值的方式, 相當於 C 中的 "double". 需要注意的是, 有的時候認為 Numeric 是 "integer" 和 "double" 的統稱. .Machine$double.double.eps 等變數給出環境中的存儲 double 的限度.
Integer 是整數, 相當於 C 中的 "int". 一般不管有沒有小數點的數字, R 默認存成 Numeric, 這個時候需要使用 as.integer 函數強制去把數存為 Integer. .Machine$integer.max 給出能夠存儲的最大的整數, 總是 2^31 - 1 = 2147483647 .
Complex 是複數的存儲形式.
字元
Character 是存儲字元和字元串的類型. 字元串和字元都可以存儲.
邏輯
Logical 是存儲 bool 值的類型, 只有 TRUE (T) 和 FALSE (F) 兩個值.
時間
Date 類型是專門為存儲時間設計的. POSIXct 把時間保存為一個整數, 為某時間距離 1970 年 1 月 1 日的時間. POSIXlt 則保存一個列表, 其中有年月日時分秒的信息. 使用 unclass 可以把相應的類轉換為基本類. 相應函數有 as.POSIXct, as. POSIXlt, strptime, strftime, ISOdate, ISOdatetime 等, 還有 chron 包可以處理時間.
數據結構
數據往往不是單一的值, R 有很好的存儲多值數據的結構.
向量
vector, 向量, 或者說是原子向量, 類似一維的 array, 存儲相同的基本類型, 可以是邏輯型 (logical), 整型 (integer), 浮點型 (numeric, double), 字元型 (character) 等, 如果有字元型的元素, 則所有的值都會轉為字元型. 向量有三個特徵: 一個是基本類型, 可以使用 typeof() 等函數查看; 一個是長度, 可以使用 length() 函數查看; 還有一個是屬性, 可以使用 attributes() 函數查看. 類型指的是構成向量的元素的基本類型, 長度指的是元素的個數, 而屬性指的是有關向量的名稱等其他方面的特徵, 如可以使用 names() 去查看或者設置每個元素的名稱.
需要注意的是, 長度為 1 的也可以是 vector, 所以使用 is.vector 函數進行判斷也會顯示 TRUE,
也就是說所有單一的基本元素變數都會被認為是一個 vector. 而對於不同長度的 vector, 使用 class 或者 mode 等函數去判斷其類型的話, 都會給出其基本元素的類型, 如果一個 vector 中所有元素都是 character 則返回 character. 這樣的方式給人的策略是 R 語言試圖忽視單複數的差別. 其實把單一數據和向量的類型不做太多區分正是 R 語言的特點的反應. R 語言用於處理大量數據, 那麼其構造和邏輯就更符合這樣的需求. 例如, 如果對一個實際上長度不為 1 的整數向量加 1, 得到的是和原向量的每個值加 1 的新的向量.x <- 1:6x + 1 [1] 2 3 4 5 6 7
如果是兩個等長向量相加, 則是對應的元素一個個相加.
x <- 1:6y <- 1:6x + y[1] 2 4 6 8 10 12
如果兩個向量長度不等長, 那麼只有一個長度是另一個長度的倍數才能相加. 而短的向量則會重複為長度和長向量一樣長. 其實這樣看也就理解了當短向量長度為 1 的時候的結果其實是個特例. 這也是 R 語言淡化單一值和向量之間區別的一個特點.
x <- 1:6z <- 1:2x + z[1] 2 4 4 6 6 8
判斷一個變數是不是向量可以使用若干函數. 可以使用 is.character(), is.double(), is.interger(),
is.logical() 等函數直接判斷其類型, 也可以用 is.atomic() 函數來判斷是不是向量. 需要注意的是, is.vector() 只能判斷向量沒有屬性 (attributes) 或者僅有 names 屬性的向量, 有其他屬性的向量, 其也會給出 FALSE.構建向量, 如果元素的基本類型不一樣, 那麼會進行強制類型轉換, 例如如果有字元, 則所有的元素都強制轉換為字元. 也可以使用類型轉換函數自己轉換, as.character(), as.double(), as.interger(), as.logical().
因子
Factor 是一種能夠幫助節省內存空間的方式. 如果一系列的值中有較多重複的值, 可以使用 factor, factor 中只會存儲一份原值, 而原來的值本身會存為數字, 這樣就會節省空間.
原值被稱為水平 (level), 可以自己去設置順序等. levels 函數可以返回一個 factor 所有可能的 level, 而 nlevels 則可以返回 level 的個數.
把一個向量轉換成 factor 只要使用 as.factor 函數. 有的時候需要我們把數字先轉換成 factor,
經過一定處理之後需要再把 factor 轉換稱為數字, 這個時候不能直接使用 as.numeric, 因為 as.numeric 會直接返回 factor 內部的值, 而不是原來的值. 我們需要先使用 as.character 或者 levels 先得到字元, 然後再轉換為數字.myfactor <- factor(c(10, 20, 20, 50, 20, 10), levels=c(10,20,50), ordered=TRUE)as.numeric(levels(myfactor)[myfactor])as.numeric(as.character(myfactor))
有的時候我們會需要去生成某種類型的 factor 來做參數或者測試數據, 那麼可以使用 gl 函數. gl 函數可以記成 "generate levels" 的縮寫. gl 函數的參數主要有: n 用來設置 level 的個數; k 用來設置每個 level 重複的次數; length 來設置長度, 其實有了前兩個參數這個可以忽略; labels 用來設置 level 的值; ordered 接受 bool 值, 設置是否 level 是排列好順序的.
需要注意的是, 在使用 c 函數把若干個因子組合在一起的時候, 需要先把因子轉換為原來的值再使用 c 函數, 否則 c 函數直接把因子當作存在內存中的數字而丟失原來的意思.
如果我有一個向量, 其中是連續的值, 我現在想要畫直方圖, 我可以直接用相應的函數畫出來. 如果我不需要看到圖, 而只想要知道這些值都怎麼分布在區間內, 我就可以使用 cut 函數. cut 函數把數值分成不同的區間, 然後把原來的向量轉化為 level 為區間的因子, 這樣就能知道某個值屬於哪個區間. 原向量和因子的長度相等, 因子的 level 是自己設定的. 使用 table 函數就可以統計每個區間中數值的個數.
aaa <- c(1,2,3,4,5,2,3,4,5,6,7) cut(aaa, 3) # [1] (0.994,3] (0.994,3] (0.994,3] (3,5] (3,5] (0.994,3] (0.994,3] [8] (3,5] (3,5] (5,7.01] (5,7.01] # Levels: (0.994,3] (3,5] (5,7.01] cut(aaa, 3, dig.lab = 4, ordered = TRUE) # [1] (0.994,3] (0.994,3] (0.994,3] (3,5] (3,5] (0.994,3] (0.994,3] [8] (3,5] (3,5] (5,7.006] (5,7.006]# Levels: (0.994,3] < (3,5] < (5,7.006]
有的時候, 我需要去了解兩個因子之間有多少組合, 這時候就可以選擇 interaction 函數, interaction 函數可以給出多個因子 level 的組合. 這些組合併不都有數據, 如果設置 drop = TRUE 則會扔掉沒有數據的 level, 而只保留真的有數據的 level.
a <- gl(2, 4, 8)b <- gl(2, 2, 8, labels = c("ctrl", "treat")) interaction(a, b, drop = TRUE, sep = ".") #[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
矩陣
matrix, 二維的 array, 所有的元素都是相同類型. 函數 matrix(), as.matrix(), is.matrix(), 可以用來產生矩陣, 類型轉換為矩陣和判斷是否是矩陣.
在取 matrix 中的元素的時候, 可以使用下標來操作. 一般情況下, 下標按照 [ROW, COL] 來取, 其中 ROW 和 COL 都可以是向量, 可以是指示想要取出的行號或者列號的向量, 也可以是 bool 值的向量. 如果方括弧中沒有逗號, 而按照 [NUM] 來取矩陣中的元素, 則會返回把矩陣當作一維的向量排列後對應位置的值, 如果是 2 乘 2 矩陣, [3] 則會返回 [2,1] 的值, 原矩陣會按照列優先延展成向量.
在取矩陣的一個行或者列後, 其返回值的維度會減少, 在取下標的時候, 使用參數 [,, drop = FALSE] 則不會讓矩陣所取結果的值的維度減少.
矩陣在內存中是按照行優先或者列優先存儲的一維的向量, 所以如果需要建立一個矩陣, 最好是先建立一個足夠大的矩陣, 然後向其中填數, 而不應該去建立一個小的矩陣, 然後使用 rbind 或著 cbind 去補充. 因為如果矩陣的數量增加, R 需要重新申請空間, 並且, 如果添加的行或者列和存儲的優先不一樣的話, 則需要重新對矩陣元素進行排序, 這樣的話就會使得效率十分低下. 所以, 建立矩陣應該建立足夠大的矩陣, 如果最後不需要這麼大的矩陣, 只要重新賦值一次就好.
數組
array, 可以有很多維. 函數 array(), as.array(), is.array(), 可以用來產生數組, 類型轉換為數組和判斷是否是數組. 數組也可以使用 dim() 函數去獲得或者改變數據的維度屬性.
列表
list, 列表, 可以把不同類型的變數組合在一起, list 中也可以包含子 list. 可以使用 list() 來建立列表, 通過 is.list() 來判斷變數是否是列表, 或者使用 as.list() 把其他類型的變數轉換為列表.
列表實際上是一維的數據結構, 取列表中的元素就需要特別注意, 如果使用單方括弧 "[]", 所取出的結果就是該列表的一個子列表, 而如果想要獲得其本身內容, 則需要使用雙方括弧 "[[]]" 或者美元符號 "$". 取元素可以使用名稱, 也可以使用數字.
mylist <- list(one = "one", two = c(2, 2))mylist#$one #[1] "one" #$two #[1] 2 2mylist["one"] #$one #[1] "one"mylist[["one"]] #[1] "one"mylist$one#[1] "one"
由於列表中可以放各種類型的對象, 這就為把多種多樣相關的數據整合在一起提供了便利. 一個 list 可以不準確地看作是 C++ 語言中專門用來存儲數據的 class. 一些相關變數, 為了和其他的變數區分, 往往會取相似的變數名, 當變數十分多的時候, 這樣的方法依然不方便查詢. 我們就可以把這些相關的變數都放在一個列表中, 然後通過取下標的方法訪問變數. 如果忘記了變數名, 還可以通過 names 函數或者 str 函數去查詢包含在列表中的變數名. 這樣的方法十分適合保存對多個數據集進行相同或相似的處理結果, 可以使用 for 循環來完成數據的保存.
rna.gene.fpkm <- list() # 需要提前建立空的列表.for (nam in dir(rna.cuffnorm.result.dir)) { rna.gene.fpkm[[nam]] <- read.table( # 建立列表元素並賦值 file.path("./", nam, "genes.fpkm_table"), header = TRUE, sep = " ")}
如果想要刪除列表中的一個元素, 則只要向該元素賦值 NULL 即可. 如果想要設置一個值為 NULL 的元素, 則應該向該元素賦值一個 list(NULL).
x <- list(a = 1, b = 2)x[["b"]] <- NULLstr(x) #> List of 1 #> $ a: num 1y <- list(a = 1)y["b"] <- list(NULL)str(y) #> List of 2 #> $ a: num 1 #> $ b: NULL
列表在內存中的保存並不是連續的, 而是像 C 語言中的鏈表一樣的分散開的, 所以對列表增加元素並不像矩陣那樣效率低,
對於列表或者相應的數據框, 使用 rbind 則不會像對矩陣使用那麼緩慢. 這也是為什麼, 我們可以先建立一個空列表 (或數據框), 然後通過
for loop 等方式逐漸向其中高效地添加元素.數據框
data.frame, 數據框, 是一種特別的 list, 其像 matrix 一樣限制了每列的變數長度必須要一樣, 但同時也像 list
一樣, 每一列的變數類型可以不同. 可以通過 data.frame() 函數建立數據框, 通過 as.data.frame() 把矩陣等轉換成為數據框, 或者通過 is.data.frame() 來判斷類型是否是數據框. names() 和 colnames() 都可以返回或改變數據框的列名, 而 rownames() 則可以返回或改變數據框的行名.數據框實際上看著非常像我們常用的 Excel 中的一個表, 數據框的列名和行名也分別對應於 Excel 表格中的列名和行名.
由於數據框具有 list 和 matrix 的特點, 所以對數據框進行取元素也有 list 和 matrix 取元素的特點, 我們既可以像 list 一樣使用 "$" 取一個列, 也可以像 matrix 一樣使用 "[ROW, COL]" 取其中一個特定的值.在創建數據框的時候, 如果使用 cbind() 對向量作用, 除非已經有一個向量是數據框, 否則 cbind() 會輸出矩陣, 而不是數據框, 這時候就需要使用 as.data.frame() 顯式轉換.
查詢變數的類型
常用的查詢變數類型的函數有: mode, storage.mode, class 和 typeof. 這些函數有一些差別.
- storage.mode 是數據實際在內存中存儲所採用的方式.
- class 是面向對象的 R, 比如說 data.frame 實際上的存儲方式 (storage.mode) 是 list, 但是為了更好處理表單數據, 就包裝成為了 data.frame 類型.
- mode 和 typeof 給出的結果很接近, 是實際上的類型, 但是在 mode 中, "integer" 和 "double" 都被認為是 "numeric".
如果需要知道環境中每個基本類型能存儲的大小, 可以查詢 .Machine 這個 list, 相應的大小存儲在該 list 中.
NA
由於種種原因, 數據中可能會出現缺失值的情況, R 會用 NA 來替代相應的空值. 如果是計算後產生的空值, 則會用 Inf 或者 NaN 來代替. 處理空值是數據分析中的必要部分.
- 判斷 NA 可以使用 is.na 或者 is.nan 函數, 其中 is.na 會把 NA, Inf, NaN 都認為是 NA, 而 is.nan 則只關注 NaN.
- mean, var, sum, min, max, 等函數都有 na.rm 參數, 設置成真, 則會在計算的時候把 NA 給除去.
- lm, glm, gam 等函數有 na.action 參數, 該參數接受函數作為變數, 如 na.omit, na.fail. na.pass, na.exculde 等.
- na.omit 和 complete.cases 都可以返回一個只包含完整數據行的 data.frame, 也就是說如果一行中有一個或多個 NA, 該行就會被剔除.
- 對於 read.table 等函數可以使用 na.strings 可以把特定的數值或字元認為是 NA.
總結
- R 中的基本類型:
- 數字 (numeric, as.numeric), 包括整數 (integer, as.integer) 和浮點數 (double, as.double) 等
- 字元(character, as.character)
- 邏輯, TRUE (T) 和 FALSE (F), T 和 F 是簡寫, 交互時方便操作
- 時間
- 特殊值, NA, NaN, Inf
- R 中常見數據結構:
- 向量 (vector, as.vector), 為一維, 向量中的每一個元素都是相同的類型. 有數字向量 (numeric, as.numeric), 字元串向量(character, as.character)等.
- 因子 (factor), 有重複值的向量可以存儲為因子, 節約內存, 因子有 level 和 label 屬性.
- 數組, 多維度, 每一個元素都是相同的類型. 矩陣為二維的數組.
- 列表 (list), 一維, 元素可以是不同的類型.
- 數據框 (data.frame), 具有列表和矩陣的特點, 二維, 不同列可以是不同的類型, 同列的數據是相同的類型.
推薦閱讀:
※如何使用管道操作符優雅的書寫R語言代碼
※R語言可視化——多邊形與數據地圖填充
※第二章 創建數據集
※記錄數據可視化的每一個瞬間