Advanced R -- 數據結構

[譯] Advanced R by Hadley Wickhamhtml

數據結構

本章主要總結base R中最重要的數據結構。你以前頗有可能已經使用到它們,或者它們中的一部分,可是可能歷來沒有用心思考過它們之間有什麼關聯。這裏,咱們將不會更深地去討論單個數據結構的類型。而是,展現它們是如何由個體構成總體的。若是,你想了解更多的細節,請參照R語言官方文檔。R的基礎數據結構能夠按照維度和同構性進行劃分:git

  • 維度:1維、2維或者n維github

  • 同構性:某一數據結構中的數據類型是否要求一致數組

下面給出了5種數據分析中最經常使用的數據結構:數據結構

# 同構 異構
1維 原子向量 (Atomic vector) 列表 (List)
2維 矩陣 (Matrix) 數據框 (Data frame)
n維 數組 (Array) -

幾乎全部的對象都是創建在這幾種數據結構基礎之上的。在面向對象領域指南中(OO field guide)你將會看到這些簡單的基礎數據結構是如何構建起更加複雜的對象的。請注意:R沒有0維數據結構,或者標量。你認爲是標量的單個數字或者字符串,其實是長度爲1的向量。ide

給定一個對象,理解它是由哪些數據結構組成的最好方法是使用str()函數。str()是structure的縮寫,它輸出任何R數據結構的歸納描述,並且這些描述是簡潔易讀的。函數

小測試

利用這些小測試來決定你是否有必要閱讀本章內容。若是你能當即想到答案,那麼你能夠放心地跳過本章。你能夠在這裏檢驗你的答案。測試

  1. 除了所包含的內容,向量(vector)的三個屬性是什麼?ui

  2. 原子向量(atomic vector)的四種常見類型是什麼?兩種罕見類型是什麼?atom

  3. 屬性(attributes)是什麼?如何獲取屬性和設置屬性?

  4. 列表(list)和原子向量(atomic vector)的區別是什麼?矩陣(matrix)和數據框(data frame)的區別是什麼?

  5. 列表(list)能夠是矩陣(matirx)嗎?矩陣(matrix)能夠做爲數據框(data frame)的一個列嗎?

提綱
  • 向量(Vectors)介紹原子向量和列表,R的1維數據結構。

  • 屬性(Attributes)繞了一個小小的彎路去討論屬性,R的靈活的元數據規範。這裏,你將會了解到因子(factors),經過設置原子向量的屬性而獲得的一種重要的數據結構。

  • 矩陣和數組(Matrix and array)介紹矩陣和數組,儲存2維和高維數據的數據結構。

  • 數據框(Data frames)介紹數據框,R中儲存數據的最重要的數據結構。數據框結合了列表和矩陣的行爲,很是適合統計數據的需求。

向量(Vectors)

R語言中最基礎的數據結構是向量。向量能夠分爲:原子向量和列表。它們有3個共同的屬性:

  • 類型,typeof(),它是什麼。

  • 長度,length(),它包含多少個元素。

  • 屬性,attributes(),附加的任意元數據。

它們在元素類型上有所區別:原子向量的全部元素必須是相同類型的,而列表的元素能夠是不一樣類型的。

附註:is.vector()不能驗證一個對象是不是向量。相反,只要對象是除了名稱以外不含有任何屬性的向量(原子向量和列表),它就返回TRUE。能夠用is.atomic(x) || is.list(x)去驗證一個對象是否真的是向量。

原子向量(Atomic vector)

在這,咱們將詳細討論原子向量的四種常見類型:logical, integer, double (常稱爲 numeric), 以及character。另外,還有兩個罕見的類型將不作討論:complex和raw。
原子向量一般用c()建立,combine的縮寫。

dbl_var <- c(1, 2.5, 4.5)
# 使用L後綴, 你將會獲得integer型,而並不是double型
int_var <- c(1L, 6L, 10L)
# 使用TRUE和FALSE (或T和F)建立邏輯向量
log_var <- c(TRUE, FALSE, T, F)
chr_var <- c("these are", "some strings")

原子向量老是水平排列的,即便你使用c()進行嵌套:

c(1, c(2, c(3, 4)))
#> [1] 1 2 3 4
# 和下面方法相同
c(1, 2, 3, 4)
#> [1] 1 2 3 4

注:上面建立的原子向量爲列向量。

缺失值用NA來指定,長度爲1的邏輯向量。在c()NA將被強制轉換爲正確的類型,或者你可使用NA_real_ (雙精度浮點型向量), NA_integer_ 以及NA_character_來建立特定類型的缺失值。

類型及測試

給定一個向量,你可使用typeof()來肯定其類型,或者使用"is"函數驗證它是不是某種特定的類型:is.character(), is.double(), is.integer(), is.logical(), 或更通常的 is.atomic()

int_var <- c(1L, 6L, 10L)
typeof(int_var)
#> [1] "integer"
is.integer(int_var)
#> [1] TRUE
is.atomic(int_var)
#> [1] TRUE
dbl_var <- c(1, 2.5, 4.5)
typeof(dbl_var)
#> [1] "double"
is.double(dbl_var)
#> [1] TRUE
is.atomic(dbl_var)
#> [1] TRUE

附註:is.numeric()是對向量是不是「數值」類型通常的檢驗方法,它對整型(double),雙精度浮點型(double)的向量都返回TRUE。它並非針對雙精度浮點型(double)向量的檢驗方法,咱們一般稱雙精度浮點型(double)爲「數值型」。

is.numeric(int_var)
#> [1] TRUE
is.numeric(dbl_var)
#> [1] TRUE

強制轉換

原子向量中的全部元素必須是同種類型的。因此,當你嘗試合併多種類型數據時,將強制轉換爲最靈活的那種類型。靈活度從小到大依次爲:邏輯型(logical), 整型(integer), 雙精度浮點型(double), 字符型(character)。

例如,合併字符型和整型輸出字符型:

str(c("a", 1))
#> chr [1:2] "a" "1"

當邏輯型(logical)強制轉換爲整型(integer)和雙精度浮點型(double)時,TRUE將會轉換爲1,FALSE轉換爲0。這在結合sum()mean()使用時將會很是有用。

x <- c(FALSE, FALSE, TRUE)
as.numeric(x)
#> [1] 0 0 1
# TRUE的總數
sum(x)
#> [1] 1
# TRUE所佔的比例
mean(x)
#> [1] 0.3333333

強制轉換常常自動執行。大部分數學函數(+logabs等。)將強制轉換爲雙精度浮點型(double)或者整型(integer),大部分邏輯運算符(&|any等)將強制轉換爲邏輯型(logical)。若是強制過程當中共有信息的丟失,你將會獲得警告信息。若是轉換遇到歧義,可使用as.character(), as.double(), as.integer(), 或者 as.logical()進行明確的轉換。

列表(Lists)

列表不一樣於原子向量,由於它們的元素能夠是任何類型的,包括列表類型。使用list(),而不是c()來建立列表。

x <- list(1:3, "a", c(TRUE, FALSE, TRUE), c(2.3, 5.9))
str(x)
#> List of 4
#>  $ : int [1:3] 1 2 3
#>  $ : chr "a"
#>  $ : logi [1:3] TRUE FALSE TRUE
#>  $ : num [1:2] 2.3 5.9

列表有時被稱爲遞歸向量,由於一個列表能夠包含其它表。這使它從根本上不一樣於原子向量。

x <- list(list(list(list())))
str(x)
#> List of 1
#>  $ :List of 1
#>   ..$ :List of 1
#>   .. ..$ : list()
is.recursive(x)
#> [1] TRUE

c()能夠將多個列表合併成一個列表。若是給定一個原子向量和列表組合,c()將會在合併它們以前把原子向量強制轉換爲列表。比較list()c()兩種結果:

x <- list(list(1, 2), c(3, 4))
y <- c(list(1, 2), c(3, 4))
x
#> [[1]]
#> [[1]][[1]]
#> [1] 1

#> [[1]][[2]]
#> [1] 2


#> [[2]]
#> [1] 3 4
str(x)
#> List of 2
#>  $ :List of 2
#>   ..$ : num 1
#>   ..$ : num 2
#>  $ : num [1:2] 3 4
y
#> [[1]]
#> [1] 1

#> [[2]]
#> [1] 2

#> [[3]]
#> [1] 3

#> [[4]]
#> [1] 4
str(y)
#> List of 4
#>  $ : num 1
#>  $ : num 2
#>  $ : num 3
#>  $ : num 4

對列表調用typeof()函數,將會輸出列表。你可使用is.list()來驗證一個對象是否爲列表,使用as.list()把一個對象強制轉換爲列表。你也可使用unlist()將列表轉換爲原子向量。若是一個列表中的元素是不一樣數據類型的,unlist()使用與c()相同的強制規則,根據靈活度對數據進行強制轉換。
列表用來建立R語言中較爲複雜的數據結構。例如:數據框(data frames)和線性模型對象(由lm()產生)都是列表。

is.list(mtcars)
#> [1] TRUE
mod <- lm(mpg ~ wt, data = mtcars)
is.list(mod)
#> [1] TRUE

練習

  1. 原子向量的6種類型是什麼?列表和原子向量的區別是什麼?

  2. is.vector()is.numeric()is.list()is.character()的根本區別是什麼?

  3. 檢測你的向量強制轉換規則知識的瞭解:嘗試預測下面c()函數的強制輸出結果:

    c(1, FALSE)
       c("a", 1)
       c(list(1), "a")
       c(TRUE, 1L)
  4. 爲何要用unlist()將列表轉換爲原子向量,而不是用as.vector()

  5. 爲何 1 == "1" 輸出TRUE? -1 < FALSE 輸出TRUE? "one" < 2 輸出FALSE?

  6. 爲何默認的缺失值NA是邏輯向量?邏輯向量有什麼特殊地方?(提示:思考一下c(FALSE, NA_character_)

屬性(Attributes)

全部的對象均可以擁有任意的附加屬性,用於存儲與對象相關的元數據。屬性能夠看作爲具備惟一標識符的命名列表。屬性能夠經過attr()單獨訪問,也能夠經過attributes()以列表的形式一次性訪問。

y <- 1:10
attr(y, "my_attribute") <- "This is a vector"
attr(y, "my_attribute")
#> [1] "This is a vector"
str(attributes(y))
#> List of 1
#>  $ my_attribute: chr "This is a vector"

structure()函數返回一個已被修改屬性的新對象:

structure(1:10, my_attribute = "This is a vector")
#>  [1]  1  2  3  4  5  6  7  8  9 10
#> attr(,"my_attribute")
#> [1] "This is a vector"

默認狀況下,當修改一個向量時,大多數屬性都會丟失:

attributes(y[1])
#> NULL
attributes(sum(y))
#> NULL

可是,有三個重要的屬性不會丟失:

  1. 名字(Names),一個字符型向量,爲每個元素賦予一個名字,將在 名字(Names)中描述。

  2. 維度(Dimensions),用於把向量轉換爲矩陣和數組,將在 矩陣和數組(Matrix and arrays)中描述。

  3. 類(Class),用於實現S3對象系統,將在S3中描述。
    這些屬性中的任一屬性均可以經過指定的函數進行訪問和賦值。當訪問這些屬性的時候,使用names(x), dim(x), 和class(x), 而不是attr(x, "dim"), 和attr(x, "class")

名字(Names)

你能夠經過三種方式爲向量命名:

  1. 建立時命名:x <- c(a = 1, b = 2, c = 3)

  2. 修改現有的向量:x <- 1:3; names(x) <- c("a", "b", "c")

  3. 建立一個向量的修訂副本:x <- setNames(1:3, c("a", "b", "c"))
    名字(Names)不是必須惟一的。然而,使用名字(Names)的最重要的緣由是對字符取子集操做,當名字(Names)惟一時,取子集操做會更加有用。

並非向量中的全部元素都須要擁有一個名字。若是元素的名字缺失,names()將爲這些元素返回空字符串。若是全部的名字缺失,names()將返回空值NULL

y <- c(a = 1, 2, 3)
names(y)
#> [1] "a" ""  ""
z <- c(1, 2, 3)
names(z)
#> NULL

你可使用unname(x)建立一個沒有名字的向量,或者經過 names(x) <- NULL移除名字。

因子(Factors)

屬性的一個重要用途是定義因子。因子是隻包含預約義值的向量,用來存儲分類數據。因子創建在整型向量基礎之上,擁有兩個屬性:

  1. class():"factor",這使得因子和常規的整型向量有所差異

  2. levels(),定義了容許取值的集合

x <- factor(c("a", "b", "b", "a"))
x
#> [1] a b b a
#> Levels: a b
class(x)
#> [1] "factor"
levels(x)
#> [1] "a" "b"
# You can't use values that are not in the levels
x[2] <- "c"
#> Warning in `[<-.factor`(`*tmp*`, 2, value = "c"): invalid factor level, NA
#> generated
x
#> [1] a    <NA> b    a   
#> Levels: a b
# 注: 你不能夠合併因子
c(factor("a"), factor("b"))
#> [1] 1 1

當你知道變量的全部可能取值時,即便你在數據集中看不到這些取值,因子是很是有用的。使用因子而不是字符向量可使得沒有包含這些取值的分組看起來更加醒目。

sex_char <- c("m", "m", "m")
sex_factor <- factor(sex_char, levels = c("m", "f"))

table(sex_char)
#> sex_char
#> m 
#> 3
table(sex_factor)
#> sex_factor
#> m f 
#> 3 0

有時,你從文件中直接讀取數據框時,那些你認爲將產生數值型向量的列卻變成了因子向量。這是由該列中含有非數值型的數據形成的,一般是用來標記缺失值的特殊符號:.或者-。爲了糾正這種狀況,能夠先把因子向量強制轉換爲字符向量,而後再把字符向量轉換爲雙精度浮點型向量。(這個過程後,務必檢查缺失值。)固然,更好的方法是首先弄清問題產生的緣由,而後及時解決;使用read.csv()na.string參數一般是一個好的解決方法。

# 在這裏從"text"中讀取數據,而不是從文件中讀取:
z <- read.csv(text = "value\n12\n1\n.\n9")
z
#>   value
#> 1    12
#> 2     1
#> 3     .
#> 4     9
typeof(z$value)
#> [1] "integer"
as.double(z$value)
#> [1] 3 2 1 4
# 哎呦, 不對: 3 2 1 4 是因子的水平, 
# 並非咱們所讀取的數據。
class(z$value)
#> [1] "factor"
# 咱們如今能夠對它進行處理:
as.double(as.character(z$value))
#> Warning: NAs introduced by coercion
#> [1] 12  1 NA  9
# 或者改變咱們的讀取方式:
z <- read.csv(text = "value\n12\n1\n.\n9", na.strings=".")
typeof(z$value)
#> [1] "integer"
class(z$value)
#> [1] "integer"
z$value
#> [1] 12  1 NA  9
# Perfect! :)

不幸的是,R語言中,大部分的數據加載函數都會自動地將字符型向量轉換爲因子向量。這種方法是欠佳的,由於這些函數並無方法獲取因子的全部水平,以及這些因子的最優次序。然而,咱們能夠經過設置參數 stringAsFactors = FALSE 阻止自動因子轉換行爲,而後再根據你對數據的瞭解,手工地將字符向量轉換爲因子向量。全局設置,options(stringsAsFactors = FALSE)也能控制自動因子轉換行爲,但不建議使用這種方法。由於修改全局設置後,當你運行其餘代碼(不論是包仍是用source()引入代碼),你會獲得意想不到的結果。修改全局設置會使代碼變得更難理解,由於這增長了你須要閱讀的代碼量,並且你必須得弄清楚這些代碼起到什麼樣的做用。
儘管因子向量看起來像,並且常常表現的像字符向量,它們其實是整型向量。當把它們當作字符串處理的時候要多加當心。一些字符串處理方法(如gsub()grepl())將把因子強制轉換爲字符串,然而其它方法(如nchar())將會拋出異常,以及其它方法(如c())將會使用它們隱藏的整型數值(因子水平)。鑑於這些緣由,若是你想讓因子表現出相似字符串的行爲,最好的方法就是把因子顯式轉換爲字符向量。在R的早期版本中,使用因子向量比字符串向量有節省內存的優點,可是如今內存已再也不是問題了。

練習

  1. 早期使用如下代碼來舉例說明structure()

    structure(1:5, comment = "my attribute")
    #> [1] 1 2 3 4 5

    可是,當打印對象時,你卻看不到comment屬性。這是爲何呢?是屬性缺失?仍是有什麼特殊之處?(提示:嘗試使用幫助文檔。)

  2. 當你修改因子水平時,發生了什麼?

    f1 <- factor(letters)
    levels(f1) <- rev(levels(f1))
  3. 這段代碼有什麼做用?f2,f3與f1有什麼區別?

f2 <- rev(factor(letters))

    f3 <- factor(letters, levels = rev(letters))

矩陣和數組(Matrices and arrays)

正在更新.......

Hadley Wickham 是 RStudio 的首席科學家以及 Rice University 統計系的助理教授。他是著名圖形可視化軟件包 ggplot2 的開發者,以及其餘許多被普遍使用的軟件包的做者,表明做品如 plyr、reshape2 等。文章英文原著來自於 Hadley Wickham 的Advanced R
N|Solid

相關文章
相關標籤/搜索