溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

R語言取子集

發(fā)布時間:2020-06-29 03:43:03 來源:網絡 閱讀:1589 作者:h2appy 欄目:編程語言

https://haoeric.gitbooks.io/r-advanced/content/qu_zi_ji.html

http://adv-r.had.co.nz/Subsetting.html


取子集

R的取子集操作非??旖蒽`活。掌握R中的取子集操作能讓你用簡潔的方式對數(shù)據進行復雜的操作,這是其他編程語言所望成莫及的。R的取子集不是那么容易學習,這之前你需要先了解幾個相關的概念:

  • 三個取子集操作符。

  • 六種取子集的索引方法。

  • 對不同數(shù)據類型(比如向量,列表,因子,矩陣和數(shù)據框)取子集結果的不同。

  • 取子集和任務分派的結合使用。

這章將幫助你一步一步掌握R的取子集操作。首先我們從最簡單的取子集(即使用[對原子向量取子集)開始講解,然后慢慢展開,學習對較復雜的數(shù)據結構比如數(shù)組和列表取子集以及使用其他取子集操作符[[$。接下來會講解如何結合取子集和任務分派來修改對象的內容。最后我們來看看一些有用的取子集應用實例。

取子集是str()函數(shù)的補充。str()函數(shù)幫助你了解對象的數(shù)據結構,取子集讓你從對象中提取感興趣的數(shù)據片段。

測試

做做這個簡單的測試看看你是否需要閱讀本章內容。如果你能很快地得到答案,你可以輕松地跳過本章。本章最后提供參考答案。

  1. 使用正數(shù)索引,負數(shù)索引,邏輯向量索引以及字符串向量索引取子集分別有什么不同?

  2. 對列表使用[,[[或者$有什么不同?

  3. 什么時候需要使用drop = FALSE

  4. 如果x是一個矩陣,x[] <- 0會得到什么結果,這和x <- 0的結果有什么不同?

  5. 如何使用向量的名字來重新標記分類變量?

概要
  • 數(shù)據結構 首先介紹如何使用[以及六種對原子向量取子集的索引方法。然后講解如何將這六種索引方法應用到列表,矩陣,數(shù)據框和S3對象。 

  • 取子集操作符 介紹另外兩種取子集操作符[[$,著重介紹簡化與保留的原則。

  • 在取子集和任務分派學習子分配的藝術,結合使用取子集和分派來修改對象的部分類容。 

  • 實例運用 帶你了解數(shù)據分析中取子集的八種常見運用。

索引類型 

學習原子向量的取子集是最簡單的,原子向量的取子集操作可以很容易地被引申運用到高維和其他更復雜的數(shù)據結構。這里我們將從最常用的取子集操作符[開始講解。后面的取子集操作符一節(jié)會介紹另外兩種操作符,[[$。

原子向量

以下用一個簡單的向量x來講解不同的取子集方式。

x <- c(2.1, 4.2, 3.3, 5.4) #注意:小數(shù)點后面的數(shù)實際標明了向量中元素的位置。

你可以用如下六種索引方式對一個向量進行取子集操作:

  • 正整數(shù)索引 返回向量中特定位置的元素: 

    x[c(3, 1)]
    x[order(x)]# 重復的索引返回重復的值x[c(1, 1)]# 實數(shù)默認被去尾為整數(shù)x[c(2.1, 2.9)]
  • 負整數(shù)索引 去除向量中特定位置的元素:

    x[-c(3, 1)]

    正整數(shù)和負整數(shù)不可以在同一個取子集操作中結合使用:

    x[c(-1, 2)]
  • 邏輯向量索引 選擇對應值為TRUE的元素。這可能是最有用的取子集操作,因為你在代碼中常常得到邏輯向量。

    x[c(TRUE, TRUE, FALSE, FALSE)]
    x[x > 3]

    如果使用的邏輯向量的長度比被取子集的向量長度短,邏輯向量會被循環(huán)到與該向量相同的長度。

    x[c(TRUE, FALSE)]# 等同于x[c(TRUE, FALSE, TRUE, FALSE)]

    索引中如果出現(xiàn)缺失值,結果中也會對應返回缺失值:

    x[c(TRUE, TRUE, NA, FALSE)]
  • 空索引 返回原向量。這對向量取子集沒有什么用處,可是對于矩陣,數(shù)據框和數(shù)組卻非常有用。并且還可以和任務分派聯(lián)合使用。

    x[]
  • 零索引 返回一個長度為零的向量。這個不常用,但是可以用來生成測試數(shù)據。

    x[0]
  • 字符串向量索引 如果向量有名字,你也可以使用字符串向量索引返回與名字相匹配的元素:

    (y <- setNames(x, letters[1:4]))
    y[c("d", "c", "a")]# 和整數(shù)索引一樣,你也可以使用重復字符串y[c("a", "a", "a")]# 使用[取子集時,名字必須是完全匹配的z <- c(abc = 1, def = 2)
    z[c("a", "d")]

列表

對列表取子集與對原子向量取子集原理相同。使用[將會始終返回一個向量;后面要講解的[[$則會提取一個向量中的元素。

矩陣和數(shù)組 

可以使用如下三種方法對高維數(shù)據取子集:

  • 多個向量

  • 單個向量

  • 矩陣

最常用的對矩陣和數(shù)組取子集就是對一維向量取子集的簡單衍生,即對每一個維度提供一個用逗號彼此隔開的索引。空索引這時就有用處了,它意味著保留所有行,或者所有列。

a <- matrix(1:9, nrow = 3)
colnames(a) <- c("A", "B", "C")
a[1:2, ]
a[c(T, F, T), c("B", "A")]
a[0, -2]

默認情況下,使用[會對結果進行簡化和降維。查看簡化與保留一節(jié)來學習如何避免這種情況。

因為矩陣和數(shù)組是由帶特殊屬性的向量構建成的,這也就意味著你也可以使用一個簡單的向量來對它們進行取子集。這中情況下矩陣和數(shù)組可以被視為一個向量,注意R中的數(shù)組是按列優(yōu)先順序排列存儲的:

vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")
vals[c(4, 15)]

你也可以使用×××矩陣來對高維數(shù)據進行取子集(如果高維數(shù)據有名字屬性,也可以使用字符串類矩陣)。矩陣中的每一行標明一個元素在高維數(shù)據中的坐標,每一列則對應著該高維數(shù)據的某一個維度。也就是說,你要使用一個兩列的矩陣來對一個矩陣取子集,一個三列的矩陣來對一個三維數(shù)組取子集,依此類推。它們輸出的結果是一個向量:

vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")
select <- matrix(ncol = 2, byrow = TRUE, c(  1, 1,  3, 1,  2, 4))
vals[select]

數(shù)據框 

數(shù)據框同時擁有列表和矩陣的特性:如果你用單個向量來取子集,那么數(shù)據框就表現(xiàn)為列表;如果使用兩個向量,數(shù)據框則表現(xiàn)為矩陣。

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])

df[df$x == 2, ]
df[c(1, 3), ]# 有兩種方法對一個數(shù)據框的列取子集# 同列表一樣df[c("x", "z")]# 同矩陣一樣df[, c("x", "z")]# 如果僅取數(shù)據框的某一列:使用同矩陣一樣的方法則返回值會被簡化為向量,但是使用同列表一樣方法則不會簡化。str(df["x"])
str(df[, "x"])

S3對象

S3對象是由原子向量,數(shù)組和列表構成的,因此你可以使用上面介紹的方法以及str()的幫助來對S3對象取子集。

S4對象

對于S4對象有另外的兩種取子集的操作符:@(等同于$)和slot()(等同于[[)。@相對于$更嚴謹,如果對應所取位置不存在則會報錯。這在面相對象指南一章中會詳細介紹。

練習

  1. 找出并修改如下代碼中的錯誤:

    mtcars[mtcars$cyl = 4, ]
    mtcars[-1:4, ]
    mtcars[mtcars$cyl <= 5]
    mtcars[mtcars$cyl == 4 | 6, ]
  2. 為什么x <- 1:5; x[NA]會返回五個缺失值?(提示:這和x[NA_real_]有什么不同)

  3. upper.tri()返回什么值?使用它對矩陣取子集是如何操作的?我們需要其他的取子集原則來描述它么?

    x <- outer(1:5, 1:5, FUN = "*")
    x[upper.tri(x)]
  4. 為什么mtcars[1:20]會報錯,它和mtcars[1:20, ]有什么不同?

  5. 自己編寫一個對矩陣取對角元素的函數(shù)(要和對矩陣x使用diag(x)的返回值相同)。

  6. df[is.na(df)] <- 0做了什么操作,怎么解釋這個代碼?

取子集操作符 

另外兩種取子集操作符分別是[[$。[[[相似,使用[[可以提取列表中的元素,但是每次只能返回單個元素。$可以看做是[[的簡化,同時它還能結合字符串取子集。

對列表使用[返回值始終是一個列表,然而使用[[則返回列表中的元素。因此,提取列表中的元素時要使用[[:

“如果列表x是一個滿載貨物的火車,x`5`表示在第五節(jié)車廂 中的貨物,而x[4:6]則表示由四,五,六號車廂組成的小火車。” --- @RLangTip

因為使用[[只能返回單個值,所以使用的索引必須是正整數(shù)或者字符串。

a <- list(a = 1, b = 2)
a`1`
a[["a"]]# 如果[[里是一個向量則會迭代索引b <- list(a = list(b = list(c = list(d = 1))))
b[[c("a", "b", "c", "d")]]# 等同于b[["a"]][["b"]][["c"]][["d"]]

因為數(shù)據框本質上是由多個列向量構成的列表,所以你也可以使用[[來提取數(shù)據框中的某一列,比如mtcars`1`,mtcars[["cyl"]]

使用[[[對S3和S4對象進行操作時,他們的結果會因受對象的重寫而不同。關鍵的不同在于簡化與保留。所以知道什么是默認操作很重要。

簡化與保留 

理解簡化與保留的不同非常重要。對結果進行簡化會將輸出信息轉化為最簡單的數(shù)據結構。簡化有時候很有用,因為很多時候簡化后的返回值會恰好是你想要的結構。對結果進行保留則會保證輸出與輸入的數(shù)據結構類型一致,這對提高程序的穩(wěn)定性非常重要。在對矩陣和數(shù)據框取子集時忽略drop = FALSE是導致程序出錯的一種常見原因。(可能在你的測試數(shù)據中不會有錯誤,當別人輸入單列的數(shù)據框時則會出現(xiàn)錯誤)

如何切換簡化或者保留因數(shù)據類型的差異而不同。具體的操作概括如下表:


簡化保留
向量x`1`x[1]
列表x`1`x[1]
因子x[1:4, drop = T]x[1:4]
數(shù)組x[1, ] or x[, 1]x[1, , drop = F] or x[, 1, drop = F]
數(shù)據框x[, 1] or x`1`x[, 1, drop = F] or x[1]

保留操作對于所有數(shù)據類型都是一樣的:你得到和輸入同樣類型的輸出。簡化操作則對不同的數(shù)據類型會有些不同:

  • 原向量:去除名字。

    x <- c(a = 1, b = 2)
    x[1]
    x`1`
  • 列表:返回列表中的元素而不是單個元素的列表。

    y <- list(a = 1, b = 2)
    str(y[1])
    str(y`1`)
  • 因子:去掉多余的水平。

    z <- factor(c("a", "b"))
    z[1]
    z[1, drop = TRUE]
  • 矩陣數(shù)組:去掉長度為一的維度。

    a <- matrix(1:4, nrow = 2)
    a[1, , drop = FALSE]
    a[1, ]
  • 數(shù)據框:若返回值是單列,則返回一個向量而不是數(shù)據框。 

    df <- data.frame(a = 1:2, b = 1:2)
    str(df[1])
    str(df`1`)
    str(df[, "a", drop = FALSE])
    str(df[, "a"])

$

$是一個簡化操作符,x$y等同于x[["y", exact = FALSE]]。多用于對數(shù)據框取子集,比如mtcars$cyldiamonds$carat。

使用$的一個常用錯誤是使用一個變量替代某一列的名字:

var <- "cyl"# mtcars$var等同于mtcars[["var"]],這樣返回nullmtcars$var# 換用[[mtcars`var`

$[[使用上最大的不同是,$采用不完整配對:

x <- list(abc = 1)
x$a
x[["a"]]

你可以修改全域設置,將warnPartialMatchDollar設為TRUE來避免這種操作。但是小心這樣設置給其他導入代碼(比如其他包中的代碼)帶來的影響。

缺失索引與出界索引

當使用的索引超出范圍(OOB)時,使用[[[會表現(xiàn)的有所不同。比如,你試圖提取一個長度為四的向量的第五個元素,或者使用NANULL作為索引:

x <- 1:4str(x[5])
str(x[NA_real_])
str(x[NULL])

下面的表格歸納了在對向量或列表使用[[[時,當出現(xiàn)出界索引(OOB)或缺失索引時結果的差異:

操作符索引原子向量列表
[OOBNAlist(NULL)
[NA_real_NAlist(NULL)
[NULLx[0]list(NULL)
[[OOBErrorError
[[NA_real_ErrorNULL
[[NULLErrorError

如果輸入向量有名字,那么出界索引(OOB)或缺失索引的名字為"<NA>"

numeric()[1]
numeric()[NA_real_]
numeric()[NULL]
numeric()`1`
numeric()`NA_real_`
numeric()`NULL`

list()[1]
list()[NA_real_]
list()[NULL]
list()`1`
list()`NA_real_`
list()`NULL`

練習

  1. 比如一個線性模型mod <- lm(mpg ~ wt, data = mtcars),如何對它提取模型中的殘余自由度,如何提取summary(mod)中的R平方值。

取子集與任務分派 

所有的取子集操作都可以和任務分派結合起來對輸入的向量進行選擇性地修改。

2


x <- 1:5x[c(1, 2)] <- 2:3x# LHS的長度必須和RHS一致x[-1] <- 4:1x# 注意:重復的索引不會被除掉,會覆蓋前面的賦值x[c(1, 1)] <- 2:3x# 整型索引不能和NA一同使用x[c(1, NA)] <- c(1, 2)# 但是NA可以和邏輯索引一同使用 (這時,NA會被視為false)x[c(T, F, NA)] <- 1x# 這對修改向量中修改符合某種條件的元素很有用處df <- data.frame(a = c(1, 10, NA))
df$a[df$a < 5] <- 0df$a

使用空索引取子集搭配任務分派能保有原對象的類型和結構。比較如下兩行代碼。第一行中mtcars將保持原類型為數(shù)據框,而第二行中mtcars將成為一個列表。

mtcars[] <- lapply(mtcars, as.integer)
mtcars <- lapply(mtcars, as.integer)

對于列表,可以使用取子集+任務分派+NULL來去除向量中的某個特定元素。如果要添加一個NULL到一個列表,則可以使用[list(NULL)

x <- list(a = 1, b = 2)
x[["b"]] <- NULLstr(x)

y <- list(a = 1)
y["b"] <- list(NULL)
str(y)

實例運用 

上面介紹的取子集的基礎知識能夠被應用到很多的場景中。以下我們會介紹其中最重要的幾個運用。有些特定的運用雖然有對應的專門的函數(shù)(比如,subset()merge()plyr::arrange()),但是了解這些函數(shù)是如何通過基礎取子集操作來實現(xiàn)的對我們非常有幫助。這讓我們能夠應對那些沒有專門函數(shù)來處理的新環(huán)境。

查尋表 (字符串取子集) 

字符匹配為制作查詢表提供了一個強大的機制。比如你想轉換一些縮寫:

x <- c("m", "f", "u", "f", "f", "m", "m")
lookup <- c(m = "Male", f = "Female", u = NA)
lookup[x]
unname(lookup[x])# 或者更簡單的輸出c(m = "Known", f = "Known", u = "Unknown")[x]

如果不想在結果匯總出現(xiàn)名字,你可以使用unname()來把它們去掉。

手動匹配和融合 (×××取子集) 

你可能有一個更復雜的多列的查詢表。比如我們有一個表示成績的向量,和一個描述它的特性表:

grades <- c(1, 2, 2, 3, 1)

info <- data.frame(
  grade = 3:1,
  desc = c("Excellent", "Good", "Poor"),
  fail = c(F, F, T)
)

我們想要得到每個成績在特性表中對應的信息。我們有兩種途徑來獲得,一種是使用match()做×××取子集,另外一種是使用rownames()做字符串取子集: 

# 使用 matchid <- match(grades, info$grade)
info[id, ]# 使用 rownamesrownames(info) <- info$grade
info[as.character(grades), ]

如果你有多列需要匹配,那么你需要先使用interaction()paste()或者plyr::id()將它們轉換成單列。你也可以使用merge()plyr::join()來做同樣的事。請查看對應函數(shù)的源代碼來學習如何實現(xiàn)。

隨機取樣/自助法 (整型取子集)

你可以使用×××索引來對一個向量或者數(shù)據框進行隨機取樣和自助取樣。首先使用sample()函數(shù)生成一個隨機索引向量,然后對對象取子集。 

df <- data.frame(x = rep(1:3, each = 2), y = 6:1, z = letters[1:6])# 為可重復性操作設置種子set.seed(10)# 隨機重排df[sample(nrow(df)), ]# 隨機取3排df[sample(nrow(df), 3), ]# 取6個自助樣本df[sample(nrow(df), 6, rep = T), ]

設置sample()函數(shù)的參數(shù)來調整取樣的個數(shù),以及是否重復取樣。

排序 (×××取子集)

order()函數(shù)的輸入是一個向量,返回一個存儲該向量排列順序的整型向量。

x <- c("b", "c", "a")
order(x)
x[order(x)]

可以給order()函數(shù)提供額外參數(shù)來重排并列值的順序??梢允褂?code >decreasing = TRUE將返回結果變成降序排列。默認情況下,缺失值會被排在最后;可以使用na.last = NA來去除它們,或者使用na.last = FALSE將它們放在最前面。

當目標對象是二維或更高維時,可以使用order()和×××索引來簡單地對行或者列排序:

# 隨機重排dfdf2 <- df[sample(nrow(df)), 3:1]
df2

df2[order(df2$x), ]
df2[, order(names(df2))]

使用sort()可以對向量進行排序,plyr::arrange()則可以對數(shù)據框排序。

展開匯總計數(shù) (整型取子集)

有時候你的數(shù)據框中的重復行可能被匯總為一行,同時添加一列來標記重復的次數(shù)??梢允褂?code >rep()生成有重復的行×××索引來展開匯總計數(shù):

df <- data.frame(x = c(2, 4, 1), y = c(9, 11, 6), n = c(3, 5, 1))
rep(1:nrow(df), df$n)
df[rep(1:nrow(df), df$n), ]

去除數(shù)據框中的某列 (字符串取子集)

有兩種方法來去除數(shù)據框中的某列。一種是將該列設為NULL:

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df$z <- NULL

另外一種是生成只包含你想要的列的新數(shù)據框:

df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3])
df[c("x", "y")]

如果你知道你不想要的列信息,使用setdiff篩選出你想要保留的列:

df[setdiff(names(df), "z")]

有條件的行篩選 (邏輯型取子集)

因為我們能很容易地整合多列的條件判斷,所以邏輯型取子集應該是對數(shù)據框進行行篩選的最常用的方法。

mtcars[mtcars$gear == 5, ]
mtcars[mtcars$gear == 5 & mtcars$cyl == 4, ]

注意使用向量型邏輯運算符&|, 而不是縮短的標量型邏輯運算符&&||,&&||if條件判斷中比較有用。靈活運用德摩根定律可以大大簡化否定的邏輯操作。

  • !(X & Y) 等同于 !X | !Y

  • !(X | Y) 等同于 !X & !Y

比如 !(X & !(Y | Z)) 可以簡化成 !X | !!(Y|Z),更進一步成!X | Y | Z

subset()是專門用來對數(shù)據框取子集的速記函數(shù)。使用subset()可以免掉重復輸入數(shù)據框的名字從而節(jié)省代碼。在非標準評估一章,你會學習subset()的工作原理。

subset(mtcars, gear == 5)
subset(mtcars, gear == 5 & cyl == 4)

邏輯運算 vs. 集合運算 (邏輯型 & ×××取子集)

認識邏輯運算(邏輯型取子集)和集合運算(整型取子集)本質上的相同點非常有用,而使用集合運算更為高效:

  • 你想知道第一個(或最后一個)TRUE

  • 你有很多的FALSE卻比較少的TRUE;使用集合運算更快更節(jié)省內存。

which()可以幫助你將邏輯型轉換為×××表示。在基礎R中沒有which()的逆操作,但是我們可以很容易的編寫一個:

x <- sample(10) < 4which(x)

unwhich <- function(x, n) {
  out <- rep_len(FALSE, n)
  out[x] <- TRUE
  out
}
unwhich(which(x), 10)

我們創(chuàng)建兩個邏輯型向量和對應的整型向量來探索一下邏輯運算和集合運算之間的關系。

(x1 <- 1:10 %% 2 == 0)
(x2 <- which(x1))
(y1 <- 1:10 %% 5 == 0)
(y2 <- which(y1))# X & Y <-> intersect(x, y)x1 & y1
intersect(x2, y2)# X | Y <-> union(x, y)x1 | y1
union(x2, y2)# X & !Y <-> setdiff(x, y)x1 & !y1
setdiff(x2, y2)# xor(X, Y) <-> setdiff(union(x, y), intersect(x, y))xor(x1, y1)
setdiff(union(x2, y2), intersect(x2, y2))

剛開始學習取子集的一個常見錯誤是使用x[which(y)]而不是x[y]。這里的which()沒有什么意義:它將邏輯型轉換為×××索引,可是結果確實完全一樣的。同時注意x[-which(y)]等同于x[!y]:當y全是FALSE時,which(y)會返回integer(0),那么-integer(0)依然是integer(0),因此你會得到空值而不是所有的值。因此,除非你確實需要(比如提取第一個或最后一個TRUE值),盡量避免將邏輯型取子集轉換為整型取子集。

練習

  1. 如何隨機的打亂一個數(shù)據框的列?(這在隨機深林方法中是非常重要的一步)你又如何同時將數(shù)據框的行和列打亂? 

  2. 如何從一個數(shù)據框中隨機的提取一個m行的子集?

  3. 如何使數(shù)據框的列按字符順序排列?

參考答案 

  1. 正整數(shù)索引提取特定位置的元素,而負整數(shù)索引去除特定位置的元素;邏輯型索引保留對應位置為TRUE的元素;字符串索引篩選和名字匹配的元素。

  2. [用來取子列表,并且總是返回列表;如果使用長度為1的×××索引,它將返回長度為1的一個列表。[[提取列表中的某個元素。$是一個便捷的速記符,x$y等同于x[["y"]]。

  3. 在對一個矩陣、數(shù)組或者數(shù)據框取子集時,如果你想要保留原有的數(shù)據維度,使用drop = FALSE。在某個函數(shù)中取子集,最好總是設置drop = FALSE。

  4. 如果x是一個矩陣,x[] <- 0會將每一個元素替換為0,保留原有的行數(shù)和列數(shù)。x <- 0則將整個矩陣替換為0。

  5. 一個帶有名字的向量可以被用來作為一個簡單的查詢表: c(x = 1, y = 2, z = 3)[c("y", "z", "x")]


向AI問一下細節(jié)
推薦閱讀:
  1. 子集生成
  2. 遞歸求子集

免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI