构造子集
R构造子集的操作功能强大而且速度快。精通构造子集者可以用简洁的方式表达复杂的操作,很少有其他语言能做到这一点。构造子集学习起来比较困难,因为需要掌握一系列相互关联的概念:
- 3种构造子集操作符。
- 6类子集。
- 不同对象(比如向量、列表、因子、矩阵、数据框)行为上的重要不同。
- 联合使用构造子集与赋值。
本章将帮助掌握构造子集,让我们从最简单的构造子集开始:用[构造原子向量的子集。然后逐渐扩展你的知识,首先学习更加复杂的数据类型(比如数组和列表),然后是其他构造子集操作符,[[和$。接着你将学习如何组合使用构造子集和赋值来修改对象的一部分,最后,你将看到很多有用的应用。
构造子集与str()相辅相成。str()展示任意对象的结构,构造子集让你选取你所感兴趣的部分。
测试
做下下面的测试,以确定你是否需要阅读本章。如果可以很快想到答案,你可以跳过本章。可以在answers中检查你的答案是否正确。
- 构造向量的子集时,使用正整数、负整数、一个逻辑向量或者一个字符向量,结果会如何?
- 当作用于列表时,[,[[,$有何不同?
- 什么时候使用
drop = FALSE
? - 如果x是一个矩阵,那么
x[] <- 0
会发生什么?这与x <- 0
有何不同? - 如何使用命名向量来重新标记分类变量?
概述
- 数据类型部分以学习[开始。首先学习可以用来构造原子向量子集的6种数据类型,然后学习这6种数据类型如何构造列表、矩阵、数据框和S3对象的子集。
- 构造子集操作符部分扩展关于构造子集操作符的知识,包括[[和$,关注点在于结构简化与保留的重要规律。
- 构造子集与赋值部分你将学习局部赋值的技巧,即联合使用构造子集和赋值来改变对象的一部分。
- 应用部分介绍8种重要但是不明显的构造子集应用,这些应用在数据分析中经常用到。
数据类型
构造原子向量子集是最简单的,然后归纳到高维和其他更加复杂的对象。我们从[开始,[是最常见的构造子集操作符。构造子集操作符部分将介绍[[和$,它们是另外2种主要的构造子集操作符。
原子向量
让我们看下对于一个简单的向量x,用不同数据类型来构造其子集会有何不同。
x <- c(2.1, 4.2, 3.3, 5.4)
注意,向量中每个元素小数点后的数字,表明该元素在向量中的位置。
可以用5样东西来构造向量的子集:
- 正整数,这时将得到对应位置的元素:
x[c(3, 1)] #> [1] 3.3 2.1 x[order(x)] #> [1] 2.1 3.3 4.2 5.4 # Duplicated indices yield duplicated values x[c(1, 1)] #> [1] 2.1 2.1 # Real numbers are silently truncated to integers x[c(2.1, 2.9)] #> [1] 4.2 4.2
- 负整数,这时会忽略对应位置的元素:
x[-c(3, 1)] #> [1] 4.2 5.4
不能混合使用正整数和负整数:
x[c(-1, 2)] #> Error in x[c(-1, 2)]: only 0‘s may be mixed with negative subscripts
- 逻辑向量,这时会选择对应的逻辑值为真的元素。这可能是最有用的构造子集方式,因为你可以利用创建逻辑向量的表达式:
x[c(TRUE, TRUE, FALSE, FALSE)] #> [1] 2.1 4.2 x[x > 3] #> [1] 4.2 3.3 5.4
如果逻辑向量的长度,比子集所在向量的长度短,逻辑向量将被循环复制直到达到相同的长度。
x[c(TRUE, FALSE)] #> [1] 2.1 3.3 # Equivalent to x[c(TRUE, FALSE, TRUE, FALSE)] #> [1] 2.1 3.3
缺失值所在的下标,一般对应的也会得到一个缺失值:
x[c(TRUE, TRUE, NA, FALSE)] #> [1] 2.1 4.2 NA
- 空,此时返回原向量。这对于向量来说没有用,但是对于矩阵、数据框和数组非常有用。当联合使用构造子集与赋值时也比较有用。
x[] #> [1] 2.1 4.2 3.3 5.4
- 0,此时返回一个长度为0的向量。这通常不是你的意图,但是对于产生测试数据是有用的。
x[0] #> numeric(0)
如果向量是命了名的,你也可以使用:
-
字符向量,此时返回匹配的元素。
(y <- setNames(x, letters[1:4])) #> a b c d #> 2.1 4.2 3.3 5.4 y[c("d", "c", "a")] #> d c a #> 5.4 3.3 2.1 # Like integer indices, you can repeat indices y[c("a", "a", "a")] #> a a a #> 2.1 2.1 2.1 # When subsetting with [ names are always matched exactly z <- c(abc = 1, def = 2) z[c("a", "d")] #> <NA> <NA> #> NA NA
列表
构造列表的子集与构造原子向量子集的方式相同。对列表使用[构造子集总是得到一个列表;[[和$得到列表的内容,如下所述。
矩阵和数组
对于高维数据结构,有3种方式构造子集:
- 使用多个向量。
- 使用单个向量。
- 使用矩阵。
构造矩阵(2维)和数组(>2维)子集最常见的方式,是对1维情况下构造子集的简单拓展:对于每个维度提供一个1维的下标,用逗号分隔。用空格构造子集此时比较有用,因为它允许你保留所有的行或者列。
a <- matrix(1:9, nrow = 3) colnames(a) <- c("A", "B", "C") a[1:2, ] #> A B C #> [1,] 1 4 7 #> [2,] 2 5 8 a[c(T, F, T), c("B", "A")] #> B A #> [1,] 4 1 #> [2,] 6 3 a[0, -2] #> A C
默认情况下,[会简化结果,即将结果的维度降到可能的最低维度。参见结构简化与保留部分介绍的如何避免这种情况。
因为矩阵和数组是通过向量实现的,即具有特殊属性的向量,所以可以用一个向量来对它们构造子集。这种情况下,矩阵和数组会表现的像向量。R中的数组以列为首要存储顺序。
(vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")) #> [,1] [,2] [,3] [,4] [,5] #> [1,] "1,1" "1,2" "1,3" "1,4" "1,5" #> [2,] "2,1" "2,2" "2,3" "2,4" "2,5" #> [3,] "3,1" "3,2" "3,3" "3,4" "3,5" #> [4,] "4,1" "4,2" "4,3" "4,4" "4,5" #> [5,] "5,1" "5,2" "5,3" "5,4" "5,5" vals[c(4, 15)] #> [1] "4,1" "5,3"
也可以利用整形矩阵来对更高维度的数据结构构造子集(或者,如果是命名的数据结构,可以用字符矩阵)。对于子集所在的数组,矩阵的每一行确定了值的位置,每一列对应一个维度。也就是说,用2列的矩阵来构造矩阵的子集,用3列的矩阵构造3维数组的矩阵。得到的结果是向量:
vals <- outer(1:5, 1:5, FUN = "paste", sep = ",") select <- matrix(ncol = 2, byrow = TRUE, c( 1, 1, 3, 1, 2, 4 )) vals[select] #> [1] "1,1" "3,1" "2,4"
数据框
数据框具有列表和矩阵的特征:如果用一个向量对数据框构造子集,数据框表现的像列表;如果使用2个向量,它表现的像矩阵。
df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3]) df[df$x == 2, ] #> x y z #> 2 2 2 b df[c(1, 3), ] #> x y z #> 1 1 3 a #> 3 3 1 c # There are two ways to select columns from a data frame # Like a list: df[c("x", "z")] #> x z #> 1 1 a #> 2 2 b #> 3 3 c # Like a matrix df[, c("x", "z")] #> x z #> 1 1 a #> 2 2 b #> 3 3 c # There‘s an important difference if you select a single # column: matrix subsetting simplifies by default, list # subsetting does not. str(df["x"]) #> ‘data.frame‘: 3 obs. of 1 variable: #> $ x: int 1 2 3 str(df[, "x"]) #> int [1:3] 1 2 3
S3对象
S3对象由原子向量、数组、列表组成,所以你可以使用上述方法以及str()得到的信息,来构造S3对象的子集。
S4对象
对于S4对象,还有另外2种构造子集的运算符:@(等同于$),slot()(等同于[[)。@比$有更加严格的限制,即如果槽不存在会返回错误。这些内容在the OO field guide介绍。
练习
- 下列代码试图构造数据框子集,请修复其中的错误:
mtcars[mtcars$cyl = 4, ] mtcars[-1:4, ] mtcars[mtcars$cyl <= 5] mtcars[mtcars$cyl == 4 | 6, ]
- 对于
x <- 1:5;为何
x[NA]
得到5个缺失值?(思路:这与x[NA_real_]
为何不同?) upper.tri()
返回什么?为何可以使用它对矩阵构造子集?对于这种情况,我们是否需要额外的构造子集规则来描述它?x <- outer(1:5, 1:5, FUN = "*") x[upper.tri(x)]
- 为何
mtcars[1:20]
返回错误?它与mtcars[1:20, ]
为何不同? - 自己动手写一个函数,取矩阵对角线上的元素(该函数应该类似于
diag(x)
,其中x是矩阵)。 df[is.na(df)] <- 0
什么意思?为何可以这样使用?
构造子集操作符
另外2种构造子集操作符是[[和$。[[类似于[,只不过[[只返回一个单独的值,而且允许构造列表的子集。$是[[的简化,它利用字符构造子集,很有用。
当构造列表子集时需要使用[[。因为[应用到列表时总是得到一个列表:而不是列表的内容。为了得到列表内容,需要使用[[:
“如果x是拉货的火车,x[[5]]是5号车厢中的货物;x[4:6]是4到6号车厢。”
— @RLangTip
因为[[只返回一个值,必须使用正整数或者字符串来使用[[:
a <- list(a = 1, b = 2) a[[1]] #> [1] 1 a[["a"]] #> [1] 1 # If you do supply a vector it indexes recursively b <- list(a = list(b = list(c = list(d = 1)))) b[[c("a", "b", "c", "d")]] #> [1] 1 # Same as b[["a"]][["b"]][["c"]][["d"]] #> [1] 1
因为数据框是列组成的列表,所以要使用[[来提取数据框的一列:mtcars[[1]]
,mtcars[["cyl"]]
。
S3对象和S4对象可以覆盖[和[[操作符的标准行为,所以对于不同的对象会有不同的表现。关键的不同点往往在于如何选择结构简化或者保留,以及默认情况。
结构简化和保留
理解子集结构简化和保留的区别非常重要。结构简化是指返回的子集的数据结构尽可能简单,只要能表示输出即可,对于交互来说这很有用因为这样往往可以得到想要的结果。结构保留是指输入与输出的结构相同,在编程中这往往更好些,因为结果一直是同类型的。构造矩阵和数据框子集时,忽略drop = FALSE是个最常见的编程错误。(在测试时可以正常工作,但是对于只有一列的数据框,会以不可预测且不清楚的方式失败。)
不幸的是,对不同数据类型,如何在简化与保留之间切换那,这里总结了下表。
简化 | 保留 | |
向量 | x[[1]] |
x[1] |
列表 | x[[1]] |
x[1] |
因子 | x[1:4, drop = T] |
x[1:4] |
数组 | x[1, ] or x[, 1] |
x[1, , drop = F] or x[, 1, drop = F] |
数据框 | x[, 1] or x[[1]] |
x[, 1, drop = F] or x[1] |
结构保留对于所有的数据类型都相同:输出与输入类型相同。不同数据类型的结构简化行为略有不同,如下:
- 原子向量:移除名称。
x <- c(a = 1, b = 2) x[1] #> a #> 1 x[[1]] #> [1] 1
- 列表:返回列表中的对象,而不是单个元素的列表。
y <- list(a = 1, b = 2) str(y[1]) #> List of 1 #> $ a: num 1 str(y[[1]]) #> num 1
- 因子:丢掉所有没有用到的因子水平。
z <- factor(c("a", "b")) z[1] #> [1] a #> Levels: a b z[1, drop = TRUE] #> [1] a #> Levels: a
- 矩阵和数组:如果某个维度的长度为1,丢掉该维度。
a <- matrix(1:4, nrow = 2) a[1, , drop = FALSE] #> [,1] [,2] #> [1,] 1 3 a[1, ] #> [1] 1 3
- 数据框:如果输出是单个列,返回一个向量而非数据框。
df <- data.frame(a = 1:2, b = 1:2) str(df[1]) #> ‘data.frame‘: 2 obs. of 1 variable: #> $ a: int 1 2 str(df[[1]]) #> int [1:2] 1 2 str(df[, "a", drop = FALSE]) #> ‘data.frame‘: 2 obs. of 1 variable: #> $ a: int 1 2 str(df[, "a"]) #> int [1:2] 1 2
$
$是个简化符号,x$y
等同于x[["y", exact = FALSE]]
。它常被用在数据框中访问变量,比如mtcars$cyl
or diamonds$carat
。
对于$常见的一个错误是,当将列名存储到一个变量,尝试并使用$和这个变量来访问数据:
var <- "cyl" # Doesn‘t work - mtcars$var translated to mtcars[["var"]] mtcars$var #> NULL # Instead use [[ mtcars[[var]] #> [1] 6 6 4 6 8 6 8 4 4 6 6 8 8 8 8 8 8 4 4 4 4 8 8 8 8 4 4 4 8 6 8 4
$与[[之间有一个重要的不同,即$支持部分匹配:
x <- list(abc = 1) x$a #> [1] 1 x[["a"]] #> NULL
如果要禁止这种情况,可以使用全局选项warnPartialMatchDollar
为TRUE
。使用时要小心:这可能影响你加载的其他代码(比如从包中加载的代码)。
缺失值/下标越界
当下标越界时(OOB),[和[[的行为略有不同,比如,对于一个长度为4的向量,试图取第5个元素,或者使用NA或NULL构造子集:
x <- 1:4 str(x[5]) #> int NA str(x[NA_real_]) #> int NA str(x[NULL]) #> int(0)
下表总结了利用[和[[,在不同类型下标、下标越界情况下,构造原子向量和列表子集的结果。
操作符 | 下标 | 原子向量 | 列表 |
[ |
OOB | NA |
list(NULL) |
[ |
NA_real_ |
NA |
list(NULL) |
[ |
NULL |
x[0] |
list(NULL) |
[[ |
OOB | Error | Error |
[[ |
NA_real_ |
Error | NULL |
[[ |
NULL |
Error | Error |
如果输入向量是命名的,那么下标越界、缺失值或者NULL,对应结果的名称为"<NA>"
。
测试
- 给定一个线性模型,比如
mod <- lm(mpg ~ wt, data = mtcars)
,提取残差的自由度。从模型摘要(summary(mod)
)中提取R squared。
构造子集和赋值
所有子集构造符都可以与赋值操作联合使用,来修改输入变量指定的值。
x <- 1:5 x[c(1, 2)] <- 2:3 x #> [1] 2 3 3 4 5 # The length of the LHS needs to match the RHS x[-1] <- 4:1 x #> [1] 2 4 3 2 1 # Note that there‘s no checking for duplicate indices x[c(1, 1)] <- 2:3 x #> [1] 3 4 3 2 1 # You can‘t combine integer indices with NA x[c(1, NA)] <- c(1, 2) #> Error in x[c(1, NA)] <- c(1, 2): NAs are not allowed in subscripted assignments # But you can combine logical indices with NA # (where they‘re treated as false). x[c(T, F, NA)] <- 1 x #> [1] 1 4 3 1 1 # This is mostly useful when conditionally modifying vectors df <- data.frame(a = c(1, 10, NA)) df$a[df$a < 5] <- 0 df$a #> [1] 0 10 NA
利用空来构造子集,并联合使用赋值操作比较有用,因为这会保留原始对象的类和结构。比较下列2个表达式。第一个,mtcars
仍然是一个数据框。第二个,mtcars
变成了一个列表。
mtcars[] <- lapply(mtcars, as.integer) mtcars <- lapply(mtcars, as.integer)
对于列表,可以联合使用构造子集、赋值、NULL来移除列表的一部分。利用[和list(NULL)
添加一个字符NULL到列表中:
x <- list(a = 1, b = 2) x[["b"]] <- NULL str(x) #> List of 1 #> $ a: num 1 y <- list(a = 1) y["b"] <- list(NULL) str(y) #> List of 2 #> $ a: num 1 #> $ b: NULL
应用
利用上述基本规则,可以创建很多有用的应用。下列描述的是一些最重要的应用。其中一些基本的技巧已经包装到了更加准确的函数中(比如subset(),
merge()
,plyr::arrange()
),但是理解其中的构造子集原理是很有用的。这样当你遇到新的情况而现有函数无法解决时,你知道该怎么做。
查阅表(字符构造子集)
字符匹配是创建查询表的一种强大方式。比如你要转换缩略词:
x <- c("m", "f", "u", "f", "f", "m", "m") lookup <- c(m = "Male", f = "Female", u = NA) lookup[x] #> m f u f f m m #> "Male" "Female" NA "Female" "Female" "Male" "Male" unname(lookup[x]) #> [1] "Male" "Female" NA "Female" "Female" "Male" "Male" # Or with fewer output values c(m = "Known", f = "Known", u = "Unknown")[x] #> m f u f f m m #> "Known" "Known" "Unknown" "Known" "Known" "Known" "Known"
如果不想要结果中的名称,使用unname()
将其移出。
手动匹配和合并(整形构造子集)
你可能有一个更加复杂的查询表,它包含多个列。假设我们有一个成绩向量,以及一个描述成绩属性的数据框:
grades <- c(1, 2, 2, 3, 1) info <- data.frame( grade = 3:1, desc = c("Excellent", "Good", "Poor"), fail = c(F, F, T) )
我们想复制信息表,需要对于grades中每个值创建一行。有2种方式实现,一是使用match()和整形构造子集,或者使用rownames和字符构造子集:
grades #> [1] 1 2 2 3 1 # Using match id <- match(grades, info$grade) info[id, ] #> grade desc fail #> 3 1 Poor TRUE #> 2 2 Good FALSE #> 2.1 2 Good FALSE #> 1 3 Excellent FALSE #> 3.1 1 Poor TRUE # Using rownames rownames(info) <- info$grade info[as.character(grades), ] #> grade desc fail #> 1 1 Poor TRUE #> 2 2 Good FALSE #> 2.1 2 Good FALSE #> 3 3 Excellent FALSE #> 1.1 1 Poor TRUE
如果有多个列需要匹配,你需要将它们折叠到一个单独的列(使用interaction()
,paste()
或者 plyr::id()
)。也可以用merge()或者
plyr::join(),作用是一样的——要了解如何实现的可以查阅源代码。
随机取样/放回取样(整数构造子集)
对向量和数据框,可以使用整数下标实现随机取样或者有放回取样。sample()
生成一个下标向量,然后构造子集来访问相应值:
df <- data.frame(x = rep(1:3, each = 2), y = 6:1, z = letters[1:6]) # Set seed for reproducibility set.seed(10) # Randomly reorder df[sample(nrow(df)), ] #> x y z #> 4 2 3 d #> 2 1 5 b #> 5 3 2 e #> 3 2 4 c #> 1 1 6 a #> 6 3 1 f # Select 3 random rows df[sample(nrow(df), 3), ] #> x y z #> 2 1 5 b #> 6 3 1 f #> 3 2 4 c # Select 6 bootstrap replicates df[sample(nrow(df), 6, rep = T), ] #> x y z #> 3 2 4 c #> 4 2 3 d #> 4.1 2 3 d #> 1 1 6 a #> 4.2 2 3 d #> 3.1 2 4 c
sample()的参数控制取样的数量,以及是否是有放回取样。
排序(整数构造子集)
order()以向量为输入,返回一个整形向量,该整形向量描述了该向量应该如何排序:
x <- c("b", "c", "a") order(x) #> [1] 3 1 2 x[order(x)] #> [1] "a" "b" "c"
为断绝关系,你可以在order()中使用额外的变量,还可以使用decreasing = TRUE
将升序修改为降序。缺省情况下,所有缺省值会被放在向量最后;然而可以使用na.last = NA
移除缺省值,或者使用na.last = FALSE
将缺省值放在前面。
对于2维和高维数据结构,利用order()
和整形构造子集,可以方便的按照对象的行或者列排序:
# Randomly reorder df df2 <- df[sample(nrow(df)), 3:1] df2 #> z y x #> 3 c 4 2 #> 1 a 6 1 #> 2 b 5 1 #> 4 d 3 2 #> 6 f 1 3 #> 5 e 2 3 df2[order(df2$x), ] #> z y x #> 1 a 6 1 #> 2 b 5 1 #> 3 c 4 2 #> 4 d 3 2 #> 6 f 1 3 #> 5 e 2 3 df2[, order(names(df2))] #> x y z #> 3 2 4 c #> 1 1 6 a #> 2 1 5 b #> 4 2 3 d #> 6 3 1 f #> 5 3 2 e
sort()可以更准确对向量排序,但是不够灵活,同样plyr::arrange()可以
对数据框进行更准确的排序,但是同样不够灵活。
扩展合计数量(整形构造子集)
有时你会得到这样一个数据框,它的相同的行被折叠到了一行,并且有一列表明行的数量。rep()和整形构造子集可以很容易将折叠的数据分开,这通过一个重复的行索引实现:
df <- data.frame(x = c(2, 4, 1), y = c(9, 11, 6), n = c(3, 5, 1)) rep(1:nrow(df), df$n) #> [1] 1 1 1 2 2 2 2 2 3 df[rep(1:nrow(df), df$n), ] #> x y n #> 1 2 9 3 #> 1.1 2 9 3 #> 1.2 2 9 3 #> 2 4 11 5 #> 2.1 4 11 5 #> 2.2 4 11 5 #> 2.3 4 11 5 #> 2.4 4 11 5 #> 3 1 6 1
移除数据框的列(字符构造子集)
有2种方式从数据框中移除列。可以通过将某一列设置为NULL来实现:
df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3]) df$z <- NULL
或者可以通过构造子集来获取想要的列:
df <- data.frame(x = 1:3, y = 3:1, z = letters[1:3]) df[c("x", "y")] #> x y #> 1 1 3 #> 2 2 2 #> 3 3 1
如果你知道哪些列是不需要的,可以使用设置操作来得到想要的列:
df[setdiff(names(df), "z")] #> x y #> 1 1 3 #> 2 2 2 #> 3 3 1
根据条件选择行(逻辑构造子集)
因为可以方便的合并多个列的条件,所以逻辑构造子集可能是提取数据框行的最常用方式。
mtcars[mtcars$gear == 5, ] #> mpg cyl disp hp drat wt qsec vs am gear carb #> 27 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 #> 28 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2 #> 29 15.8 8 351.0 264 4.22 3.170 14.5 0 1 5 4 #> 30 19.7 6 145.0 175 3.62 2.770 15.5 0 1 5 6 #> 31 15.0 8 301.0 335 3.54 3.570 14.6 0 1 5 8 mtcars[mtcars$gear == 5 & mtcars$cyl == 4, ] #> mpg cyl disp hp drat wt qsec vs am gear carb #> 27 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 #> 28 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2
记得使用&和|,而不是&&和||,&&和||在条件语句中更加有用。不要忘记摩根定律(De Morgan’s laws),它可以用来简化否定:
!(X & Y)
等同于!X | !Y
!(X | Y)
等同于!X & !Y
例如,!(X & !(Y | Z))
可以简化为!X | !!(Y|Z)
,进一步简化为!X | Y | Z
。
subset()是一个特殊的简化了的构造数据框子集的函数,用它可以少敲几下键盘,因为不需要重复数据框的名字。你将在非标准计算(non-standard evaluation)中看到它是如何工作的。
subset(mtcars, gear == 5) #> mpg cyl disp hp drat wt qsec vs am gear carb #> 27 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 #> 28 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2 #> 29 15.8 8 351.0 264 4.22 3.170 14.5 0 1 5 4 #> 30 19.7 6 145.0 175 3.62 2.770 15.5 0 1 5 6 #> 31 15.0 8 301.0 335 3.54 3.570 14.6 0 1 5 8 subset(mtcars, gear == 5 & cyl == 4) #> mpg cyl disp hp drat wt qsec vs am gear carb #> 27 26.0 4 120.3 91 4.43 2.140 16.7 0 1 5 2 #> 28 30.4 4 95.1 113 3.77 1.513 16.9 1 1 5 2
布尔代数与设置操作(逻辑和整形构造子集)
需要注意设置操作(整形构造子集)与布尔代数(布尔构造子集)之间的等效性。在某些情况下使用设置操作更加有效:
试图找出第一个(或者最后一个)
TRUE
。当有比较少的
TRUE
s和非常多的FALSE
s;设置操作可能更快而且需要更少的内存。
which()
可以将一个逻辑表示转换为整形表示。基础R中没有反转操作但是可以很容易创建一个:
x <- sample(10) < 4 which(x) #> [1] 3 7 10 unwhich <- function(x, n) { out <- rep_len(FALSE, n) out[x] <- TRUE out } unwhich(which(x), 10) #> [1] FALSE FALSE TRUE FALSE FALSE FALSE TRUE FALSE FALSE TRUE
让我们来创建2个逻辑向量和对等的整形向量,然后看下布尔值与设置操作的关系。
(x1 <- 1:10 %% 2 == 0) #> [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE (x2 <- which(x1)) #> [1] 2 4 6 8 10 (y1 <- 1:10 %% 5 == 0) #> [1] FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE FALSE TRUE (y2 <- which(y1)) #> [1] 5 10 # X & Y <-> intersect(x, y) x1 & y1 #> [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE intersect(x2, y2) #> [1] 10 # X | Y <-> union(x, y) x1 | y1 #> [1] FALSE TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE TRUE union(x2, y2) #> [1] 2 4 6 8 10 5 # X & !Y <-> setdiff(x, y) x1 & !y1 #> [1] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE FALSE setdiff(x2, y2) #> [1] 2 4 6 8 # xor(X, Y) <-> setdiff(union(x, y), intersect(x, y)) xor(x1, y1) #> [1] FALSE TRUE FALSE TRUE TRUE TRUE FALSE TRUE FALSE FALSE setdiff(union(x2, y2), intersect(x2, y2)) #> [1] 2 4 6 8 5
开始学习构造子集时的一个常见错误是使用x[which(y)]
替代x[y]
。这儿which()
什么都没干:它将逻辑构造子集转换成整形构造子集,但是结果完全一样。同样要注意x[-which(y)]
并不等同于x[!y]
:如果y全是FALSE,which(y)
会得到integer(0),而
integer(0)
还是integer(0)
,所以你没有获取任何值,而不是所有值。通常应该避免将逻辑构造子集转换成整形构造子集,除非你想这么做,比如获取第一个或者最后一个TRUE。
练习
- 如何随机排列数据框中的列?(在随机预测中这很重要。)你能否在一步内同时随机排列行和列?
- 如何随机选取数据框中的m行?如果要求这m行是连续的那(比如,一个首行,一个结束行,以及其中所有的行)?
- 如何将数据框的列按照字母表排序?
答案
- 正整数选取对应位置的元素,负整数去除对应位置的元素;逻辑向量保留TRUE对应位置的元素;字符向量按照名称匹配选取元素。
- [选取子列表。它总是得到一个列表;如果使用[和一个正整数来构造子集,会得到一个长度为1的列表。[[选取列表中的元素。$是为了方便起见的缩写符号:
x$y
等同于x[["y"]]
。 - 如果构造矩阵、数据或者数据框的子集时,想保留数据结构,使用
drop = FALSE
。在函数内容构造子集时,你应该总是这么做。 - 如果x是矩阵,
x[] <- 0
将替换每个元素为0,行和列的数量不变,x <- 0
会将矩阵替换为0。 - 一个命名的向量可以作为一个简单的查询表:
c(x = 1, y = 2, z = 3)[c("y", "z", "x")]
。