高级R编程-第3章:子集选取(上)
R 有一套强大而又快速的子集选取运算符,但又不容易学习上手,本章内容涵盖:
- 3 个子集选取运算符,比如:[、[[、$
- 6 种选取子集的方法
- 不同对象(原子向量、列表、因子、矩阵、数据框)选取子集的方法差异
- 赋值与子集选取结合,实现修改对象子集
3.1 数据类型
3.1.1 原子向量
[]运算符中正整数向量返回指定位置(从左往右)的元素
# 创建向量并取第3个和第1个元素 x <- c(5.1, 3.2, 4.3, 1.4, 2.5) x[c(3,1)] |
[1] 4.3 5.1
# order返回升序排列后的位置排列向量 # 比如排序后第1个数值是1.4,在原序列中是第4位; # 第2个数值是2.5, 在原序列中是第5位 # 也即order返回经排序后,元素在原向量中的索引位置 order(x) |
[1] 4 5 2 3 1
# order(x):元素在原索引中的位置,按排序后的顺序依次排列 # x[]:排序后的元素值 x[order(x)] |
[1] 1.4 2.5 3.2 4.3 5.1
# 重复索引会重复取子集 x[c(2,2,2)] |
[1] 3.2 3.2 3.2
# 小数索引会被截断为整数索引 x[c(1.9, 2.1)] |
[1] 5.1 3.2
负整数指排除该位置上的元素
# 排除第3个和第1个元素 x[-c(3, 1)] |
[1] 3.2 1.4 2.5
# 不能同时使用正数和负数 try(x[c(-3, 1)]) |
Error in x[c(-3, 1)] : only 0’s may be mixed with negative subscripts
逻辑向量只选取 TRUE 位置上的元素,这是常用的选取方法
# 逻辑向量选取子集 x[c(T,F,T,F,T)] |
[1] 5.1 4.3 2.5
# x>3会生成向量:TRUE TRUE TRUE FALSE FALSE # 结果就是取第1、2、3个元素 x[x>3] |
[1] 5.1 3.2 4.3
如果逻辑向量比母集短,则逻辑向量会被循环使用直到长度与母集相同,这是一项设计特性,但有时也会造成困惑
# 逻辑向量被循环为:T F F T(从头再来) F(长度结束) x[c(T,F,F)] |
[1] 5.1 1.4
对比下面两种缺失值索引返回结果差异:
# 返回5个NA x[NA] |
[1] NA NA NA NA NA
NA 索引是逻辑值(逻辑值包括 TRUE/FALSE/NA),子集选取遇到逻辑索引时,若索引长度不够,会将索引自动循环至与向量长度相同,故该例子等同于:x[c(NA,NA,NA,NA,NA)],返回 5 个结果
# 只返回一个NA x[NA_integer_] |
[1] NA
该例子中的 Na_integer_是数值型,子集选取采用的是数字索引规则,代表的是筛选出第 NA_integer_个位置的值,该位置不存在,故返回一个 NA(索引长度是 1 个,返回结果也是 1 个)
索引出现缺失值 NA,子集相同位置也会产生缺失值
# 索引中的NA,子集对应位置也会是NA x[c(T,F,F,NA,T)] |
[1] 5.1 NA 2.5
空索引返回原始向量,这对矩阵、数据框、数组很有用
# 空索引返回原始向量 x[] |
[1] 5.1 3.2 4.3 1.4 2.5
索引 0, 返回长度为 0 的同类型向量,有时在测试时用得着
# 索引为0 x[0] |
numeric(0)
元素名字作为索引
# 创建带名称的元素 (y <- setNames(x, letters[1:5])) |
a b c d e
5.1 3.2 4.3 1.4 2.5
# 用名字元素取子集,名字必须完全匹配(不能部分匹配) y[c('a','a','d')] |
a a d
5.1 5.1 1.4
3.1.2 列表
列表用[]选取子集的方式与原子向量相同,但列表总是返回列表类型的子集
# 用[]对列表取子集,总是返回一个列表 z <- list(a=1:2, b=letters[1:3], c=list('hello')) z[1] |
$a
[1] 1 2
若需剥离外层的列表,只返回里面的元素内容(更为常见的需求),可使用[[]]或$
# 使用[[]]对列表取子集 z[[1]] |
[1] 1 2
# 也可用元素名称取子集 z$b |
[1] “a” “b” “c”
3.1.3 矩阵和数组
有三种方法可从高维数据结构中选取子集
默认情况下,[]会将结果简化到尽量低的维度(常见场景),后文会介绍如何避免这种情况
第一种:多组向量,筛选行和列
# 先创建一个矩阵 myMatrix <- matrix(1:9, nrow=3) colnames(myMatrix) <- c('A','B','C') myMatrix |
A B C
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
# 逗号“,”左边代表行,右边代表列,选取逻辑是笛卡尔积 # 下面是指取第1、第2行与第2、第3列相交叉的元素 # 也即第1行第2列、第1行第3列、第2行第2列、第2行第3列 myMatrix[c(1,2), c(2,3)] |
B C
[1,] 4 7
[2,] 5 8
同样可以使用逻辑值和列名选取子集
# 选取第1、第2行(不要第3行)与B列、C列相交叉的元素 myMatrix[c(T,T,F), c('B','C')] |
B C
[1,] 4 7
[2,] 5 8
空代表选取全部
# 选取第3行所有列 myMatrix[3,] |
A B C
3 6 9
# 选取第3列所有行 myMatrix[,3] |
[1] 7 8 9
# 选取全部行、全部列 myMatrix[,] |
A B C
[1,] 1 4 7
[2,] 2 5 8
[3,] 3 6 9
同样可以选取 0 行或 0 列
# 选取0行(空行),并排除第2列(B列) myMatrix[0, -2] |
A C
第二种:单个向量,筛选列
这种情况下,子集像向量
# 选取第3行、第1列 myMatrix[c(3,1)] |
[1] 3 1
第三种:整数矩阵或字符矩阵(若元素已命名),筛选元素
# outer将前两个向量的所有组合(迪卡尔积,效果类似于坐标对)应用于FUN函数 (myMatrix2 <- 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”
进行矩阵选取,矩阵的列数不同,选取的方式也不同
注意:n 维数组的索引矩阵,要么是 n 列,要么是 1 列,不可以是其它列数
| 数组类型 | 索引矩阵列数 | 含义 |
|---|---|---|
| 矩阵(二维数组) | 1 列 | 将矩阵按列拉直成向量索引后,取第几个元素 |
| 矩阵(二维数组) | 2 列 | 索引矩阵每一行代表着(行,列)索引 |
| 更高维数组(n 维) | 1 列 | 将矩阵按列拉直成向量索引后,取第几个元素 |
| 更高维数组(n 维) | n 列 | 索引矩阵每一行代表着(1 维,2 维,…,n 维)索引 |
数组中较常用的是矩阵(二维),故索引矩阵是 2 列的情况较为常见,返回的结果是向量
# 索引矩阵是2列,代表着取:第1行第1列、第3行第5列、第2行第4列的元素 (index <- matrix(c(1,1, 3,5, 2,4), ncol=2, byrow=T)) |
[,1] [,2]
[1,] 1 1
[2,] 3 5
[3,] 2 4
# 矩阵筛选,用的是2列的索引矩阵 myMatrix2[index] |
[1] “1,1” “3,5” “2,4”
索引矩阵是 1 列,每行代表着元素位置,返回的结果还是向量
# 索引矩阵是1列,代表着按列优先顺序,选取第1、3、5、7个元素 (index2 <- matrix(c(1,3,5,7), ncol=1)) |
[,1]
[1,] 1
[2,] 3
[3,] 5
[4,] 7
# 矩阵筛选,索引是1列的矩阵 myMatrix2[index2] |
[1] “1,1” “3,1” “5,1” “2,2”
取矩阵的上三角/下三角,上/下三角矩阵在运算中较为常见
# 返回上三角矩阵的逻辑矩阵 (index.upper <- upper.tri(myMatrix2)) |
[,1] [,2] [,3] [,4] [,5]
[1,] FALSE TRUE TRUE TRUE TRUE
[2,] FALSE FALSE TRUE TRUE TRUE
[3,] FALSE FALSE FALSE TRUE TRUE
[4,] FALSE FALSE FALSE FALSE TRUE
[5,] FALSE FALSE FALSE FALSE FALSE
# 筛选出上三角元素,会按列优先被简化为向量 myMatrix2[index.upper] |
[1] “1,2” “1,3” “2,3” “1,4” “2,4” “3,4” “1,5” “2,5” “3,5” “4,5”
# 下三角矩阵同理 (index.lower <- lower.tri(myMatrix2)) |
[,1] [,2] [,3] [,4] [,5]
[1,] FALSE FALSE FALSE FALSE FALSE
[2,] TRUE FALSE FALSE FALSE FALSE
[3,] TRUE TRUE FALSE FALSE FALSE
[4,] TRUE TRUE TRUE FALSE FALSE
[5,] TRUE TRUE TRUE TRUE FALSE
# 索引矩阵进行筛选 myMatrix2[index.lower] |
[1] “2,1” “3,1” “4,1” “5,1” “3,2” “4,2” “5,2” “4,3” “5,3” “5,4”
当然也可以用 2 列的索引矩阵来筛选上/下三角矩阵元素
# 上三角矩阵为例,按列优先的次序构建索引矩阵 # 行数和列数相同,都是5 n <- 5 # 列索引,结果是:2 3 3 4 4 4 5 5 5 5 # 比如:2(第2列有1行)、3 3(第3列有2行)、4 4 4(第4列有3行) j <- rep(2:n, times=1:(n-1)) # 行索引,结果是:1 1 2 1 2 3 1 2 3 4 # 比如:1(第2列只有第1行)、1 2(第3列有第1和2行)、1 2 3(第4列有第1、2和3行) i <- sequence(1:(n-1)) # 组合成索引矩阵 index2.up <- cbind(i,j) # 再筛选子集,可以看出结果与使用upper.tri()是一样的 myMatrix2[index2.up] |
[1] “1,2” “1,3” “2,3” “1,4” “2,4” “3,4” “1,5” “2,5” “3,5” “4,5”
矩阵对角元素,常用函数是 diag()
# 主对角线上的元素 diag(myMatrix2) |
[1] “1,1” “2,2” “3,3” “4,4” “5,5”
也可以用一列的索引矩阵来提取对角元素
# 矩阵的行与列数量都是5 n <- 5 # 构建只有1列的索引矩阵(实质只使用了单个向量) # d的结果是:1 7 13 19 25,恰好是对角线元素的位置 d <- seq(from=1, to=n*n, by=n) d <- d + 0:(n-1) # 筛选子集,结果与使用diag()是一致的 myMatrix2[d] |
[1] “1,1” “2,2” “3,3” “4,4” “5,5”
3.1.4 数据框
兼具列表和矩阵的特点:
| 参数个数 | 说明 |
|---|---|
| 单一向量 | 类似列表 |
| 两个向量 | 类似矩阵 |
# 先创建一个数据框 (myFrame <- data.frame(x=1:3, y=10:8, z=letters[1:3])) |
x y z
1 1 10 a
2 2 9 b
3 3 8 c
两个向量进行选取,行为模式类似于矩阵(筛选出某个元素)
# 第1个向量代表行,第2个向量代表列, 结果会尽量简化 # 选取第1行、第3行分别与第2列交叉的元素 myFrame[c(1,3), 2] |
[1] 10 8
# 选取x等于2的全部行与所有列交叉的元素 # 结果无法进一步简化,还是返回数据框 # 因为各列的数据类型可能不同 myFrame[myFrame$x==2, ] |
x y z
2 2 9 b
# 筛选出所有行的x和z列 myFrame[ ,c('x','z')] |
x z
1 1 a
2 2 b
3 3 c
如果只筛选出一列,返回结果会尽量简化为一个向量
# 结果被简化成一个向量 # 同一列的数据类型肯定相同 str(myFrame[ , 'y']) |
int [1:3] 10 9 8
一个向量进行选取,行为模式类似于列表(筛选出某一列)
# 单个向量代表列 myFrame['x'] |
x
1 1
2 2
3 3
返回结果总是数据框,不会简化
# 返回结果不会简化 str(myFrame[1]) |
‘data.frame’: 3 obs. of 1 variable:
$ x: int 1 2 3
子集筛选叠加赋值,可实现对部分元素的更新
# 构造包含NA值的数据框 (myFrame2 <- data.frame(A=c(1,2,NA,4), B=c('甲',NA,'丙','丁'),C=c('A','B',NA,NA))) |
A B C
1 1 甲 A
2 2 B
3 NA 丙
4 4 丁 <NA>
# 筛选出NA的元素 (index.na <- is.na(myFrame2)) |
A B C
[1,] FALSE FALSE FALSE
[2,] FALSE TRUE FALSE
[3,] TRUE FALSE TRUE
[4,] FALSE FALSE TRUE
# NA替换为0 myFrame2[index.na] <- 0 myFrame2 |
A B C
1 1 甲 A
2 2 0 B
3 0 丙 0
4 4 丁 0
3.1.5 S3 和 S4 对象
| 类别 | 对象 | 说明 |
|---|---|---|
| S3 对象 | 原子向量、列表、数组(含矩阵) | 可通过上述方法选取子集 |
| S4 对象 | 将在 7.3 节介绍 | 还需要通过 @(等价于$)和 slot()(等价于[[]])选取子集 |
3.2 子集选取运算符
还有另外两个选取子集的运算符:[[和$,用于列表,并返回一个值(一个元素)
| 运算符 1 | 运算符 2 | 对比 |
|---|---|---|
| [[ ]] | [ ] | 应用于列表时: * [ ]返回一个列表 * [[ ]]返回列表里面的内容 * 如果将列表 x 比喻成一列火车,则 x[4]返回第 4 节车厢(含车厢本身),而 x[[4]]返回车厢里面的货物(不含车厢) |
| [[ ]] | $ | $ 可以理解为[[ ]]的缩写 |
列表子集的选取
# 构建列表 (myList <- list(a=1:3, b=c('a','b'), c=list(d='你好'))) |
$a
[1] 1 2 3
$b
[1] “a” “b”
$c
$c$d
[1] “你好”
# 通过元素索引选取子集,比如选取第2个元素的内容 myList[[2]] |
[1] “a” “b”
# 作为对比,[]仍返回一个列表 str(myList[2]) |
List of 1
$ b: chr [1:2] “a” “b”
# $可视为[[]]的简写,但需引用字符串名称 myList$b |
[1] “a” “b”
对于层层嵌套的列表,有简易方式选取子集
# 构建嵌套列表 (myList2 <- list(a=list(b=list(c=list(d=10))))) |
$a
$a$b
$a$b$c
$a$b$c$d
[1] 10
# 选取最内层的内容 myList2[[c('a','b','c','d')]] |
[1] 10
# 等同于 myList2$a$b$c$d |
[1] 10
# 也等同于原始表达方式 myList2[['a']][['b']][['c']][['d']] |
[1] 10
数据框本质是由各列组成的列表,同样可以使用[[ ]]提取列
# 提取数据框第2列内容 myFrame2[[2]] |
[1] “甲” “0” “丙” “丁”
注意:S3 和 S4 对象可以重写[ ]和[[ ]]的行为,不同对象在子集选取行为上略有差异,主要区别体现在如何简化输出结果或默认行为上
3.2.1 简化与保留
选取子集时,要特别关注返回的结果是否保持原来的数据结构,还是被简化过了
- 简化子集:交互分析时很有用,更人性化
- 保留原有结构:有利于编程
不同数据类型在选取子集时,简化或保留数据结构的方法有所不同,如下所示:
| 数据类型 | 简化例子 | 保留例子 |
|---|---|---|
| 向量 | * x[[1]] * [[ ]]只能选取单个元素,若多个选取参数则会报错 * [[ ]]的返回值不会保留任何属性,包括 names * R 最小的数据结构是向量(R 没有标量),简化后返回的还是向量 | * x[1] * 返回的仍然是向量 |
| 列表 | * x[[1]] * 返回元素内容 | * x[1] * 返回列表,内含元素内容 |
| 因子 | * x[1, drop=T] * drop 参数用于删除维度,但因子是一维结构,无法降维,故返回的仍然是列表,但水平标签会被缩减 | * x[1] * 返回列表,且水平标签维持原样 * drop 参数默认为 F,也即不降维 |
| 数组 | * x[1, ] 或 x[ ,1] * 数组是二维以上的数据结构,drop 可删除维度(也即降维) * 返回第 1 行或第 1 列 * drop 参数默认为 T | * x[1, , drop=F] 或 x[ , 1, drop=F] * drop=F 代表不降维 |
| 数据框 | * x[ , 1] 或 x[[1]] * 只有选择 1 列才会降维;而选择 1 行是不会降维的 * drop 参数默认为 T | * x[ , 1, drop=F] 或 x[1] * 不降维 |
- drop 是[ ]运算符的通用参数,其取 TRUE 时,当提取的结果只有一个维度,则删除该维度,也即降维
- 对于一维向量(原子向量、列表、因子),drop 参数会被忽略
以下是向量选取的例子:
# [[ ]]:向量的简化例子,返回的仍是向量,但属性被删除 setNames(1:10, letters[1:10])[[1]] |
[1] 1
# [[ ]]用于剥离容器层级并取出里面的内容,故只能选取单个元素,否则会报错 # 唯一的例外是列表层层嵌套子元素时,允许传入多个向量参数以选取内层子元素 try(c(1:10)[[1:2]]) |
Error in c(1:10)[[1:2]] :
attempt to select more than one element in vectorIndex
# [ ]:向量保留原始数据结构(含属性),且对长度无限制 setNames(1:10, letters[1:10])[1:2] |
a b
1 2
以下是列表选取的例子:
# 列表的简化选取,剥离了外部容器,且只能选出单个元素 str(myList[[1]]) |
int [1:3] 1 2 3
# 列表的保留选取,维持原有数据结构 str(myList[1]) |
List of 1
$ a: int [1:3] 1 2 3
以下是因子选取的例子:
# 因子的简化选取,扔掉不使用的水平,返回列表 str(factor(letters)[1:3, drop=T]) |
Factor w/ 3 levels “a”,”b”,”c”: 1 2 3
# 因子的保留选取,保留所有水平,返回列表 str(factor(letters)[1:3]) |
Factor w/ 26 levels “a”,”b”,”c”,”d”,..: 1 2 3
以下是数组和矩阵选取的例子:
# 矩阵的简化例子,只剩一行(一个维度)时,删除该维度 str(matrix(1:12,nrow=3)[1, ]) |
int [1:4] 1 4 7 10
# 只剩一列(一个维度)时,删除该维度 str(matrix(1:12,nrow=3)[ , 1]) |
int [1:3] 1 2 3
# 矩阵保留的例子,维持原矩阵格式(输出结果是:3行1列) str(matrix(1:12,nrow=3)[ , 1, drop=F]) |
int [1:3, 1] 1 2 3
以下是数据框选取的例子:
# 数据框简化的例子,当输出只有1列,会简化成向量 # 注意:若输出只有1行,也不会简化的(该行各元素类型可能不同,无法简化) str(myFrame[[1]]) |
int [1:3] 1 2 3
# 当输出只有1列,会简化成向量 str(myFrame[ ,1]) |
int [1:3] 1 2 3
# 数据框保留的例子 str(myFrame[1]) |
‘data.frame’: 3 obs. of 1 variable:
$ x: int 1 2 3
# 明确保留数据框结构 str(myFrame[ , 'x', drop=F]) |
‘data.frame’: 3 obs. of 1 variable:
$ x: int 1 2 3
3.2.2 运算符$
“$”是简写运算符,x$y 相当于 x[[‘y’, exact=F]],其中 exact=F 代表列名部分匹配 y(而不是完全匹配)
假如列名存储在变量中,则无法使用”$“引用该变量,如下例子:
# $无法引用变量中的内容 yName <- 'y' myFrame$yName |
NULL
# 需要使用[[]] myFrame[[yName]] |
[1] 10 9 8
“$”与[[ ]]的重要区别是:“$”部分匹配列名,而[[ ]]需完全匹配列名
# 完整的列名是cyl,$能部分匹配列名(只要该部分无重叠列名) mtcars$cy |
[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
# 而[[ ]]必须匹配完整列名,否则会返回空 mtcars[['cy']] |
NULL
3.2.3 缺失及越界引用
原子向量或列表的子集选取,若是越界引用(比如向量长度 4,但却引用第 5 个元素),或者用 NA 或 NULL 为索引进行子集选取时,[和[[的行为有所不同
需注意的是,NA 与 NULL 内涵不同:
- NA:这里有一个值,类型已知,只是不知道这个值是什么。其长度是 1
- NULL:这里完全是空的,什么也没有,自然也不存在什么数据类型。其长度是 0
另外还要注意,[和[[内涵也是有区别的:
- [:子集构建和选取,会创建一个新的容器(新向量/新列表),把返回值填入该容器中。当索引无效时,它可以在新容器里塞入一个占位符(NA 或 NULL)。其会保持容器原状
- [[:其语义是“提取”,而非“构建”。它要求直接取出内存中已存在的那个对象,不会创建任何新容器。因此,它无法凭空“制造”一个 NA 或 NULL 来充当返回值。其会剥离容器,只返回元素的内容
另一方面,原子向量与列表在内存中存储的方式是不同的:
- 原子向量:在内存中是连续的同质数据块,访问必须依赖确切的整数偏移量(比如第 3 个位置)
- 列表:在内存中是指向元素对象的数组指针。初始化指向元素的指针时,会先初始化为空指针(NULL)。若元素存在,再将指针指向元素;若元素不存在,则维持空指针
R 取子集的过程,有两步检查判断:
- 先检查索引类型是否合法,索引必须为整数、字符串或逻辑值。若无法通过检测,则直接报错
- 再判断取值逻辑是否正确
基于上述差异,向量与列表面对异常索引的行为存在差异,具体如下:
| 运算符 | 索引 | 原子向量 | 列表 |
|---|---|---|---|
| [ | 越界引用 | NA | list(NULL) |
| [ | NA_integer_ | NA | list(NULL) |
| [ | NULL | type(0) | list() |
| [[ | 越界引用 | 报错 | 报错 |
| [[ | NA_integer_ | 报错 | NULL |
| [[ | NULL | 报错 | 报错 |
补充:若存在元素名字,则越界、NA 或 NULL 引用的元素名称为
[ ]的越界引用示例:
# 构建只有3个元素的向量 (myVector <- setNames(11:13, c('a','b','c'))) |
a b c
11 12 13
# 引用第4个不存在的元素,返回NA # 元素名称为<NA>,代表这里有一个名称,但我们不知道它是什么 myVector[4] |
<NA>
NA
上例解释:向量要求每个元素的类型相同, 而 NA 本质上是一个有类型(默认是逻辑类型,但也可以是整型/浮点/字符)的占位符, 用在向量返回值中是恰好合适的:既与其它元素保持类型一致(向量的要求), 又能占用一个元素位置(选取了一个元素的子集)
如果返回 NULL 反而不合适,NULL 代表着什么都没有,既无法与其它元素保持类型一致,又无法表示选取了一个子元素
# 列表只有3个元素,但却引用第4个,返回list(NULL) myList[4] |
$<NA>
NULL
上例解释:列表的元素类型可以各不相同,若是返回 NA 则代表有确定的类型(默认是逻辑值),但这并不符合事实(也容易让人做出错误判断)。返回 NULL 则是合适的,代表着元素内容和类型都不存在
而列表的返回元素外面包着容器 list,足以代表着选取了一个子元素
[ ]的 NA_integer_引用示例:
# 向量的索引取整型的NA myVector[NA_integer_] |
<NA>
NA
# 列表的索引取整型的NA myList[NA_integer_] |
$<NA>
NULL
上例解释:NA_integer_是 NA 的整数形式,代表着有一个索引,但不知道具体值是多少
由于位置不确定,能选取的元素也就无法确定;但由于存在一个索引,子集肯定是一个元素(但不知道值是多少)
而向量与列表分别返回 NA 与 NULL 的原因,与上述越界索引相同
[ ]的 NULL 引用示例:
# 向量的空索引 myVector[NULL] |
named integer(0)
# 列表的空索引 myList[NULL] |
named list()
上例解释:NULL 代表什么都没有,既然索引不存在,那么子集也就完全是空的(长度为 0)
- 向量:由于必须维持元素类型相同,故返回的子集的数据类型与母集相同(此例都是整数),但长度是 0
- 列表:由于子集不存在,而返回结果外部会包裹一层 list 容器,故返回结果是 list()(长度为 0 的列表)
[[ ]]的越界选取示例:
# 向量的越界选取,报错 try(myVector[[4]]) |
Error in myVector[[4]] : subscript out of bounds
# 列表的越界选取,同样报错 try(myList[[4]]) |
Error in myList[[4]] : subscript out of bounds
为什么 myVector[4]返回 NA,而 myVector[[4]]却报错了?这是因为:
- [是子集选取,R 会构造一个新向量(数据类型与原向量相同)作为返回结果,然后往新向量填充原向量第 4 个元素的值(找不到则用 NA 填充)
- [[是元素提取,其语义是:把原向量第 4 个元素原样(已存在于内存中的值)输出,但由于位置 4 根本不存在,R 不知道该去哪找这个内部值,于是就报错了
- myList 同理
[[ ]]的 NA_integer_引用示例:
# 向量的缺失值索引 try(myVector[[NA_integer_]]) |
Error in myVector[[NA_integer_]] : subscript out of bounds
# 列表的缺失值索引 myList[[NA_integer_]] |
NULL
上例解释:NA_integer_意味着不知道索引是多少,自然也就不知道需返回的元素在哪里
对于原子向量:
- 无法返回 NA:[[只是提取已存在于内存中的元素(而不会构建新的返回值),没有内存空间填充 NA 值
- 不合适返回 NULL:向量要求所有元素(也包含子集)的元素类型相同,而 NULL 不存在元素类型,自然也就不相同
- 只能报错:报错是唯一的选择。而且向量是内存中的连续块,索引错误会引发无法预知的后果(就像 C 语言),故报错是合适的行为
对于列表:
- 本来应该报错,但设计人员故意让其返回 NULL
- 列表是指向元素对象的数组指针,且指针在初始化时是空值 NULL(若元素存在则再指向元素),当索引无效时,直接返回这个空指针(NULL)更为高效且语义明确(代表着不存在子集)。
- 列表常用来做映射或字典查询,返回 NULL 便于使用;若报错则还需额外捕获,较为麻烦
[[ ]]的 NULL 引用示例:
# 向量的空值引用 try(myVector[[NULL]]) |
Error in myVector[[NULL]] :
attempt to select less than one element in get1index
# 列表的空值引用 try(myList[[NULL]]) |
Error in myList[[NULL]] :
attempt to select less than one element in get1index
上例解释:NULL 代表什么都没有,既不存在内容,也无类型、无长度
对于向量来说:
- 索引 NULL 不是整数、字符或逻辑值,无法通过索引类型检查,直接报错
- 根本没机会进入第二步:取值逻辑检查
对于列表来说:
- 索引 NULL 无法通过索引类型检查,直接报错,根本没机会进入第二步(取值逻辑检查)
- 而上面例子 myList[[NA_integer_]]中的 NA_integer_是整型,是可以通过索引类型检查的(故未报错);出现异常的是在第二步取值逻辑检查(查无此值),故能返回 NULL
3.3 子集选取与赋值
所有子集选取预算符都可以与赋值结合使用,实现对指定元素的更新
# 直接修改第2、3个元素 myVector[2:3] <- c(-12,-13) myVector |
a b c
11 -12 -13
# 修改除第1个元素外的其它元素 myVector[-1] <- c(-120,-130) myVector |
a b c
11 -120 -130
# 甚至索引可以重复,后值会覆盖前值 myVector[c(1,1)] <- 1:2 myVector |
a b c
2 -120 -130
整数索引中夹带 NA 则不能赋值,此时 NA 作为整数类型,不知道赋值到哪个位置的元素,只能报错
# 整型索引中有NA try(myVector[c(1,NA)] <- 1:2) |
Error in myVector[c(1, NA)] <- 1:2 :
NAs are not allowed in subscripted assignments
逻辑索引中夹带 NA 却是可以赋值的,此时 NA 作为逻辑类型,视作 FALSE
# NA作为逻辑值,视同FALSE myVector[c(T,NA,F)] <- 22 myVector |
a b c
22 -120 -130
这个特性使得比较更新时不会更新到 NA 值
# 只更新第1个元素(原数值5),而不会更新到第3个元素(NA) myFrame3 <- data.frame(z=c(5,10,NA,20)) myFrame3$z[myFrame3$z<10] <- 0 myFrame3 |
z
1 0
2 10
3 NA
4 20
保留原有对象类型,但调整每列数据类型,如下是一个巧妙的例子:
# lapply:迭代函数,将mtcars每一列应用as.integer()函数,并返回新列组成的列表 # mtcars[]:核心在于[],意味着保留mtcars的所有结构(数据框、行/列数、行名等), # 赋值时会把右侧产生的列表的每个元素按顺序填入左侧数据框的每一列 mtcars[] <- lapply(mtcars, as.integer) head(mtcars) |
mpg cyl disp hp drat wt qsec vs am gear carb
Mazda RX4 21 6 160 110 3 2 16 0 1 4 4
Mazda RX4 Wag 21 6 160 110 3 2 17 0 1 4 4
Datsun 710 22 4 108 93 3 2 18 1 1 4 1
Hornet 4 Drive 21 6 258 110 3 3 19 1 0 3 1
Hornet Sportabout 18 8 360 175 3 3 17 0 0 3 2
Valiant 18 6 225 105 2 3 20 1 0 3 1
作为对比,若不加[],则结果是一个列表
# 左侧不加[],则将右边产生的列表直接赋值给变量mtcars,结果是个列表 mtcars <- lapply(mtcars, as.integer) str(mtcars) |
List of 11
$ mpg : int [1:32] 21 21 22 21 18 18 14 24 22 19 …
$ cyl : int [1:32] 6 6 4 6 8 6 8 4 4 6 …
$ disp: int [1:32] 160 160 108 258 360 225 360 146 140 167 …
$ hp : int [1:32] 110 110 93 110 175 105 245 62 95 123 …
$ drat: int [1:32] 3 3 3 3 3 2 3 3 3 3 …
$ wt : int [1:32] 2 2 2 3 3 3 3 3 3 3 …
$ qsec: int [1:32] 16 17 18 19 17 20 15 20 22 18 …
$ vs : int [1:32] 0 0 1 1 0 1 0 1 1 1 …
$ am : int [1:32] 1 1 1 0 0 0 0 0 0 0 …
$ gear: int [1:32] 4 4 4 3 3 3 3 4 4 4 …
$ carb: int [1:32] 4 4 1 1 2 1 4 2 2 4 …
对于列表,可以利用[[ ]]元素选取 + 赋 NULL 值来删除元素,如下:
# myList原有3个元素 myList |
$a
[1] 1 2 3
$b
[1] “a” “b”
$c
$c$d
[1] “你好”
# 删除第3个元素 myList[[3]] <- NULL myList |
$a
[1] 1 2 3
$b
[1] “a” “b”
另外可以利用[ ]子集选取 + 赋 NULL 值来添加空值子元素,如下:
# 将NULL分别添加到元素c和第4个元素中 myList['c'] <- list(NULL) myList[4] <- list(NULL) myList |
$a
[1] 1 2 3
$b
[1] “a” “b”
$c
NULL
[[4]]
NULL
本文是我阅读和学习《高级R语言编程指南》(作者Hadley Wickham,机械工业出版社出版)后的读书笔记,大部分内容与例子引用原书,并加上自己的一部分理解与补充。
推荐关注我的个人博客:R数据分析,不定期发布R语言经典书籍的读书笔记、数据分析案例,欢迎交流~