Advanced R学习笔记(三)Subsetting
Introduction
R的取子集操作是很快并且有效的.使用它们可以使得简便地操作一个复杂的操作.需要了解的是
-
有六种取原子向量的方法
-
有三个取子集的操作符.[[;[;$
-
取子集操作符与不同的向量类型的交互不同
-
取子集可以和赋值相结合
选取多个元素
使用[和多个数字可以从一个向量中选择多个元素.
原子向量
x <- c(2.1, 4.2, 3.3, 5.4)
这里小数点后代表数字原始的位置.
有六种方法去取一个向量的子集
- 正整数来返回在特定位置的元素
x[c(3, 1)]
#> [1] 3.3 2.1
x[order(x)]
#> [1] 2.1 3.3 4.2 5.4
# Duplicate indices will duplicate 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
不能混合使用正数和负数
- 逻辑向量来挑选对应位置逻辑值是TRUE的元素.
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的向量.
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
NOTE因子在取子集的时候没有被特殊处理,所以他们将会代表的是底层的整数向量,而不是字符串的水平.
y[factor("b")]
#> a
#> 2.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(TRUE, FALSE, TRUE), c("B", "A")]
#> B A
#> [1,] 4 1
#> [2,] 6 3
a[0, -2]
#> A C
默认情况下,[的结果会取尽可能少的维数.
a[1, ]
#> A B C
#> 1 4 7
a[1, 1]
#> A
#> 1
因为矩阵和数组都是一个带有特殊属性的向量,所以也可以使用单个向量来取子集,要注意的是数组是按列来存储的
vals <- outer(1:5, 1:5, FUN = "paste", sep = ",")
vals
#> [,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"
还可以通过一个矩阵来取
select <- matrix(ncol = 2, byrow = TRUE, c(
1, 1,
3, 1,
2, 4
))
vals[select]
#> [1] "1,1" "3,1" "2,4"
数据框和tibbles
数据框拥有列表和矩阵的特征
-
当时用单个索引取子集的时候,它的行为就像是一个列表,所以df[1:2]将会取前两列
-
当有两个索引的时候,就像是一个矩阵,df[1:3,]将会取前三行和所有的列
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
对tibble用[取子集同样会返回一个tibble
df <- tibble::tibble(x = 1:3, y = 3:1, z = letters[1:3])
str(df["x"])
#> Classes 'tbl_df', 'tbl' and 'data.frame': 3 obs. of 1 variable:
#> $ x: int 1 2 3
str(df[, "x"])
#> Classes 'tbl_df', 'tbl' and 'data.frame': 3 obs. of 1 variable:
#> $ x: int 1 2 3
保留维度
如果想要保留原始的维度,可以使用参数drop = FALSE
a <- matrix(1:4, nrow = 2)
str(a[1, ])
#> int [1:2] 1 3
str(a[1, , drop = FALSE])
#> int [1, 1:2] 1 3
df <- data.frame(a = 1:2, b = 1:2)
str(df[, "a"])
#> int [1:2] 1 2
str(df[, "a", drop = FALSE])
#> 'data.frame': 2 obs. of 1 variable:
#> $ a: int 1 2
在对2D的对象取子集的时候要养成设置drop=FALSE的习惯,而tibbles总是默认这个参数.
因子中也有这个参数,但是它是用来控制是否保留水平,而不是控制是否保留维度.
z <- factor(c("a", "b"))
z[1]
#> [1] a
#> Levels: a b
z[1, drop = TRUE]
#> [1] a
#> Levels: a
挑选单个元素
[[经常被用来挑选单个元素,$算是其简写
[[
[[对于列表来说很重要,因为[对于列表来说总是会返回一个更小的列表
image因为只能返回单个对象,所以只能对单一的正整数和字符串使用.如果对于一个向量使用.它将会对自己进行递归操作. x[[c(1, 2)]] 等于 x[[1]][[2]] 这是很少有人知道的,所以最好避免这种使用方法.建议使用purrr::pluck()
在列表中只能使用[[,但是在对于原子型向量时建议也这么写,因为这可以强调想要得到单个的值.
$
y大致相当于x[["y"]].经常出现的一个错误就是讲列的名称储存在变量中来使用
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
为了避免这一行为可以使用全局设置
options(warnPartialMatchDollar = TRUE)
x$a
#> Warning in x$a: partial match of 'a' to 'abc'
#> [1] 1
对于数据框来说,也可以使用tibble来避免这一行为
缺失/越界的索引
下面的表格汇总了通过一个out-of-bound 值,缺失值和长度为零的向量时的情况.
image如果一个向量被命名了,那么使用名字的OOB和缺失值或者NULL时将会返回"<NA>"
上表的不一致,导致了purrr::pluck()的和purrr:chuck()的发展pluck()总是返回NULL(或是.default参数的值).chunck()则总是抛出一个错误
pluck()则允许混用整数和字符索引,且当对象不存在时提供一个可替换的默认值
x <- list(
a = list(1, 2, 3),
b = list(3, 4, 5)
)
purrr::pluck(x, "a", 1)
#> [1] 1
purrr::pluck(x, "c", 1)
#> NULL
purrr::pluck(x, "c", 1, .default = NA)
#> [1] NA
@和slot()
有两种额外的取子集操作符用于S4类 @相当于$ slot()相当于[[
@相对于$更严谨,当位置不存在时返回一个错误
取子集和赋值
所有的取子集操作符都能和复制挑选的值相关联.基础的形式是x[i] <- value
x <- 1:5
x[c(1, 2)] <- c(101, 102)
x
#> [1] 101 102 3 4 5
应该确保被选取的对象和值的长度相等,如果不等则会循环赋值,但是R的循环赋值很复杂(特别是在包含缺失值和重复值的时候)
对于列表可以通过x[[i]] <- NULL来移除一个组分,如果想要添加一个NULL,可以通过x[i] <- list(NULL)来达成
x <- list(a = 1, b = 2)
x[["b"]] <- NULL
str(x)
#> List of 1
#> $ a: num 1
y <- list(a = 1, b = 2)
y["b"] <- list(NULL)
str(y)
#> List of 2
#> $ a: num 1
#> $ b: NULL
取子集时什么都不填和赋值连用是很有用的,因为这样子它就会被设计成保留原始数据结构的对象.相对于下面两种表达,在第一种mtcars将会保留数据框结构,因为只是改变mtcars的内容而不是mtcars本身,在第二种方法里,mtcars会变成一个列表,因为改变了这个对象绑定的数据
mtcars[] <- lapply(mtcars, as.integer)
is.data.frame(mtcars)
#> [1] TRUE
mtcars <- lapply(mtcars, as.integer)
is.data.frame(mtcars)
#> [1] FALSE
应用
这产生了许多有用的应用,大部分重要的都被描述在下面.许多这些基本技术都包含在更简洁的函数中(例如,subset(),merge(),dplyr :: 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()去移除它
unname(lookup[x])
#> [1] "Male" "Female" NA "Female" "Female" "Male" "Male"
手动匹配并整合
您可能有一个更复杂的查找表,其中包含多列信息。 假设我们有一个整数等级的向量,以及一个描述其属性的表:
grades <- c(1, 2, 2, 3, 1)
info <- data.frame(
grade = 3:1,
desc = c("Excellent", "Good", "Poor"),
fail = c(F, F, T)
)
我们想要复制信息表,以便我们的成绩中每个值都有一行。 一种优雅的方法是通过组合match()和整数子集(match(needle,haystack)返回位置)。
id <- match(grades, info$grade)
id
#> [1] 3 2 2 1 3
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
如果要匹配多个列,则需要先将它们折叠为单个列(例如,使用interaction()),但通常最好切换到专门为连接多个表(如merge())而设计的函数, 或者dplyr :: left_join().
随机样本
可以使用整数索引来执行向量或数据帧的随机采样或自举。 sample(n)生成1:n的随机排列,然后子集访问值:
df <- data.frame(x = c(1, 2, 3, 1, 2), y = 5:1, z = letters[1:5])
# Randomly reorder
df[sample(nrow(df)), ]
#> x y z
#> 1 1 5 a
#> 4 1 2 d
#> 2 2 4 b
#> 5 2 1 e
#> 3 3 3 c
# Select 3 random rows
df[sample(nrow(df), 3), ]
#> x y z
#> 3 3 3 c
#> 2 2 4 b
#> 1 1 5 a
# Select 6 bootstrap replicates
df[sample(nrow(df), 6, replace = TRUE), ]
#> x y z
#> 4 1 2 d
#> 4.1 1 2 d
#> 5 2 1 e
#> 1 1 5 a
#> 1.1 1 5 a
#> 2 2 4 b
sample()的参数控制提取的样本数,以及是否将样品返回
排序
order()以一个向量为输入,返回一个横竖向量描述应该如何排序
x <- c("b", "c", "a")
order(x)
#> [1] 3 1 2
x[order(x)]
#> [1] "a" "b" "c"
可以有一些其他的变量提供,可以使用decrease = TRUE将升序变为降序.默认情况下任何的缺失值都会被放在向量的末尾,但是可以通过na.last=NA来删除它们.或合适用na.last=FALSE来放在前面.
对于二维或是更高纬度的对象来说,order()可以很方便地对列或行进行排序
# Randomly reorder df
df2 <- df[sample(nrow(df)), 3:1]
df2
#> z y x
#> 3 c 3 3
#> 1 a 5 1
#> 2 b 4 2
#> 4 d 2 1
#> 5 e 1 2
df2[order(df2$x), ]
#> z y x
#> 1 a 5 1
#> 4 d 2 1
#> 2 b 4 2
#> 5 e 1 2
#> 3 c 3 3
df2[, order(names(df2))]
#> x y z
#> 3 3 3 c
#> 1 1 5 a
#> 2 2 4 b
#> 4 1 2 d
#> 5 2 1 e
也可以直接对向量使用sort()来排序,或者是对数据框使用dplyr::arrange()
扩大汇总计数
有时您会获得一个数据框,其中相同的行已折叠为一个.并且已添加计数列. rep()和整数子集使得很容易解开,因为我们可以利用rep()的向量化:rep(x,y)重复x [i] y [i]次.
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
从数据框中移除列
有两种方法从数据框中移除列,可以设置特定的列为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
#> Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
#> Lotus Europa 30.4 4 95.1 113 3.77 1.51 16.9 1 1 5 2
#> Ford Pantera L 15.8 8 351.0 264 4.22 3.17 14.5 0 1 5 4
#> Ferrari Dino 19.7 6 145.0 175 3.62 2.77 15.5 0 1 5 6
#> Maserati Bora 15.0 8 301.0 335 3.54 3.57 14.6 0 1 5 8
mtcars[mtcars$gear == 5 & mtcars$cyl == 4, ]
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> Porsche 914-2 26.0 4 120.3 91 4.43 2.14 16.7 0 1 5 2
#> Lotus Europa 30.4 4 95.1 113 3.77 1.51 16.9 1 1 5 2
应当使用&和|而不是&&和||提莫更适合在if语句中使用
-
!(X & Y) is the same as !X | !Y
-
!(X | Y) is the same as !X & !Y
布尔代数和集合
了解集合操作(整数子集)和布尔代数(逻辑子集)之间的自然等价是很有用的。使用集合操作时更有效
-
想找到第一个或是最后一个TRUE
-
有一些TRUE和大量的FALSE,用集合表示会更快且占用内存更少
which()允许将布尔值转换为整数值,但还没有相反地操作符,可是可以很简单的创建一个
x <- sample(10) < 4
which(x)
#> [1] 2 5 8
unwhich <- function(x, n) {
out <- rep_len(FALSE, n)
out[x] <- TRUE
out
}
unwhich(which(x), 10)
#> [1] FALSE TRUE FALSE FALSE TRUE FALSE FALSE TRUE FALSE FALSE
让我们创建两个逻辑向量及其整数,然后研究布尔运算和集合运算之间的关系。
(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()什么都得不到,他转换逻辑值到整数值,但是这结果是完全一样的..在更通常的情况下,有两个重要的区别
-
当逻辑值包含NA是,逻辑值将会用NA替代他们,而which()则会删除他们.这是挺常见的使用which()的副作用.
-
x[-which(y)]并不等于[x!y:如果y全部都是FALSE,which(y)将会是integer(0),但是-integer(0)仍然是integer(0),所以将会什么值都得不到
通常来说 避免转换逻辑值到整数值.