欢迎光临
我们一直在努力

高级R编程-第2章:数据结构

R中常见的5种数据类型:

维数元素同质元素异质
1 维 原子向量 列表
2 维 矩阵 数据框
多 维 数组 ——

2.1 向量

下面构造向量数据,包括:原子向量、列表

# 构造原子向量
(myVector <- 1:3)

[1] 1 2 3

# 列表也是向量
(myList <- list(10, 'A', pi))

[[1]]

[1] 10

[[2]] [1] “A”

[[3]] [1] 3.141593

向量有3个属性:元素类型、元素个数、附加的元数据

# 原子向量的类型
typeof(myVector)

[1] “integer”

# 列表的类型
typeof(myList)

[1] “list”

# 原子向量的元素个数
length(myVector)

[1] 3

# 列表的元素个数
length(myList)

[1] 3

# 原子向量的所有属性,附加的元数据
attributes(myVector)

NULL

# 列表的所有属性
attributes(myList)

NULL

attr()可以给向量添加属性,或获取某个具体的属性值

# 给原子向量添加属性
attr(myVector, 'author') <- 'Forton'
attributes(myVector)

$author [1] “Forton”

# 给列表添加属性
attr(myList, 'created_date') <- Sys.Date()
attributes(myList)

$created_date

[1] “2026-06-11”

判断是否向量:is.vector()只能准确判断不带属性的对象是否向量

# 当对象没有添加属性时,确实能返回正确的结果
is.vector(1:3)

[1] TRUE

# 但如果向量添加了属性,则总是会返回false,得到错误结果
is.vector(myVector)
is.vector(myList)

[1] FALSE

[1] FALSE

正确判断对象是否为向量的方法:检测是否为原子向量或列表

# 检测原子向量或列表
is.atomic(myVector) || is.list(myVector)
is.atomic(myList) || is.list(myList)

[1] TRUE

[1] TRUE

2.1.1 原子向量的种类
类别说明
常见4种 整数、浮点数、字符、逻辑
罕见2种 复合型(复数)、原始型(raw)

原子向量总是被展开的,即使用c()嵌套

# 嵌套的原子向量
c(1, c(2, c(3,4)))

[1] 1 2 3 4

另外,NA是长度为1的逻辑向量

可用typeof()判断向量类型,其实就是向量元素的类型

# 返回向量类型
myReal <- c(1.1, 2.2, 3.3)
typeof(myReal)

[1] “double”

当然,也可以直接测试向量是否为某个具体类型

# 判断是否为实数
is.numeric(myReal)
is.numeric(myVector)

[1] TRUE

[1] TRUE

# 判断是否为字符
is.character(myVector)

[1] FALSE

以下表格对比不同测试函数的异同:

测试函数 1测试函数 2对比
is.list() is.character() 必须是原子向量才能通过 is.character()测试,而原子向量必然不是列表;同理,列表必然不是原子向量;这两个测试函数的结果是互斥的。
is.vector() is.numeric() 只有原子向量才能通过这两函数的测试,但带属性值的原子向量也无法通过 is.vector()测试,而数值型的原子向量总是能通过 is.numeric()测试。这两个测试函数的结果并不互斥。

以下是几个类型测试案例:

# 是原子向量,就肯定不是列表;反在亦然
myLetters <- c('a','b','c')
is.character(myLetters)

[1] TRUE

# 不是列表
is.list(myLetters)

[1] FALSE

# 测试带属性值的数值型原子向量
attr(myReal,'version') <- 1.0
is.vector(myReal)

[1] FALSE

# 实数型向量
is.numeric(myReal)

[1] TRUE

有些函数或算法会强制执行不同数据类型之间的转换(详细转换规则见“2.1.3节”)

# 逻辑值TRUE底层存储为1, 而FALSE底层存储为0
# 故下面等同于sum(c(1,0,1))
sum(c(T,F,T))

[1] 2

# 任何元素同字符在一起,都会被转换为字符
c('A', 1)

[1] “A” “1”

# 显示地将浮点数PI转换为整数
as.integer(pi)

[1] 3

# 逻辑判断,1等同于TRUE
1 | F

[1] TRUE

2.1.2 列表

列表也是一种向量,它的元素可以是任意类型

# 构造列表
myList <- list(1:3, 'B', c(T,T,F), c(5.6, 7.8))
str(myList)

List of 4 $ : int [1:3] 1 2 3 $ : chr “B” $ : logi [1:3] TRUE TRUE FALSE $ : num [1:2] 5.6 7.8 列表的元素也可以是列表(称为子列表),如下两个例子:

# 元素是列表,称为子列表
y <- list(list(1,2), list(3,4,5), c(6,7))
str(y)

List of 3 $ :List of 2 ..$ : num 1 ..$ : num 2 $ :List of 3 ..$ : num 3 ..$ : num 4 ..$ : num 5 $ : num [1:2] 6 7

# 层层嵌套的列表
myList2 <- list(list(list(list('C'))))
str(myList2)

List of 1 $ :List of 1 ..$ :List of 1 .. ..$ :List of 1 .. .. ..$ : chr “C”

因此列表也被称为递归向量

# 测试是否递归向量
is.recursive(myList2)

[1] TRUE

# 普通列表也是递归向量
is.recursive(myList)

[1] TRUE

c()代表的单词是combine,其含义是Combine Values into a Vector or List(将值组合成向量或列表),其会将多个列表合并成一个大列表

# 里面的原子向量会强制转换为列表
x <- c(list(1,2), list(3,4,5), c(6,7))
str(x)

List of 7 $ : num 1 $ : num 2 $ : num 3 $ : num 4 $ : num 5 $ : num 6 $ : num 7

列表的格式转换,unlist()将列表转换为原子向量

# 若列表元素是不同数据类型,则转换规则与c()一致,详细规则见下文
unlist(y)

[1] 1 2 3 4 5 6 7

列表转换为原子向量存在两种方法,以下是异同对比:

列表转为原子向量说明
as.vector()

1. 只剥离外层列表结构,但不递归处理内部嵌套

2. 尽可能不改变底层表示

3. 结果仍是一个列表(可能去除属性值),列表也是一种向量

unlist()

1. 专为递归地将列表拆解为原子向量而设计

2. 会遍历所有嵌套层级

3. 必要时进行类型转换(使用与 c()相同的规则)

结论 unlist()会递归压平列表,返回原子列表

请看下面的示例:

# 先构造一个列表
z <- list('a',1,2.3)

# 尝试转换为原子向量,但这是没有效果的,得到的还是列表
as.vector(z)

[[1]]

[1] “a”

[[2]] [1] 1

[[3]] [1] 2.3

# 这才是正确的递归转换
unlist(z)

[1] “a” “1” “2.3”

原子向量也可以转换为列表

# 原子向量转换为列表
as.list(c('A','B','C'))

[[1]]

[1] “A”

[[2]] [1] “B”

[[3]] [1] “C”

测试一个对象是否为列表

# 测试是否列表
is.list(x)
2.1.3 数据类型强制转换

转换级别排序是:逻辑 < 整数 < 实数 < 字符

强制转换时,总会转换至元素中最高级的格式,如下例子:

# 逻辑值F转换为整数0
c(1, F)

[1] 1 0

# 逻辑值T转换为整数,1后面的L代表这是一个(长)整数
c(T, 1L)

[1] 1 1

# 整数1转换为字符
c('a', 1)

[1] “a” “1”

若c()参数中存在列表,则会组合成大列表(同样触发强制类型转换)

# 有一个元素是列表
c(list(1), 'a')

[[1]]

[1] 1

[[2]] [1] “a”

不同类型向量比较也会触发强制类型转换

# 左边1转换为字符'1'
1 == '1'

[1] TRUE

# 右边F转换为整数0
-1 < F

[1] TRUE

# 右边转换为字符'2',同:'one' < '2'
'one' < 2

[1] FALSE

2.2 属性

所有对象都可以通过附加任意属性来存储元数据

函数说明
attr() 访问对象的单一属性
attributes() 访问对象的所有属性
# 构造对象
myObject <- 1:10

# 添加属性
attr(myObject, 'create_date') <- Sys.Date()
attr(myObject, 'author') <- 'Forton'

# 查看所有属性
str(attributes(myObject))

List of 2 $ create_date: Date[1:1], format: “2026-06-11” $ author : chr “Forton”

structure()可以返回属性被修改后的新对象

# 修改属性后的新对象
myObject2 <- structure(myObject,
author='Lily',
create_date=as.Date('2000-01-01'))
myObject2

[1] 1 2 3 4 5 6 7 8 9 10 attr(,”create_date”) [1] “2000-01-01” attr(,”author”) [1] “Lily”

修改后返回一个新的对象副本,而原对象不受影响

# 原来对象不受影响
myObject

[1] 1 2 3 4 5 6 7 8 9 10 attr(,”create_date”) [1] “2026-06-11” attr(,”author”) [1] “Forton”

另外,comment(注释)等一部分属性被认为不属于数据的一部分,大部分打印方法不会显示该属性

# 未能显示comment(注释)属性
myComment <- structure(1:5, comment='我的属性')
myComment

[1] 1 2 3 4 5

但comment属性真实存在,只是默认不打印显示而已

# 但其真实存在
attributes(myComment)

$comment [1] “我的属性”

# 也可直接访问comment()属性
comment(myComment)

[1] “我的属性”

复制向量后,大多数属性会消失,但也有例外

# []是提取函数,除names属性外,其余属性均丢弃,
# 因为子集可能不再适合拥有一些属性了(比如一些时间变量的属性)
# 通过索引取子集会触发写时复制机制,
# 也即:两个变量初始时共享同一片内存,但更新其中一个变量后会导致两个向量不再一致,
# 此时R会将更新的向量复制到新内存中
# 故子集本质上可认为是新向量,原来的大部分属性会消失
attributes(myObject[1:3])

NULL

# 对向量进行运算,返回一个新的向量,属性同样不会被保留
attributes(sum(myObject))

NULL

使用赋值符号“<-”复制向量时,所有属性都会被保留

“<-”是替换操作,其语义是:修改myObject某些元素,并返回与原对象类型、属性相同的新对象(属性也是对象的一部分)

# 当myObject未被其它变量共享(引用计数为1),则原地修改数据
# 当myObject被其它变量引用,也即被复制(引用计数大于1),则复制数据和新属性到新内存,再修改副本元素
myObject[1] <- -1
myObject

[1] -1 2 3 4 5 6 7 8 9 10 attr(,”create_date”) [1] “2026-06-11” attr(,”author”) [1] “Forton”

但有 3 个属性不会在复制向量时消失,如下所示:

属性说明存取函数
name 每个元素对应名字的字符向量 names()
dimension 维度,将向量转变为矩阵或数组 dim()
class 类,实现S3对象系统(S3介绍见后文) class()

name属性:创建或更新向量中的名称属性

# 创建向量时命名
c(a=1, b=2, c=3)

a b c 1 2 3

# 修改现有向量的名字
names(myVector) <- c('X','Y','Z')
myVector

X Y Z 1 2 3 attr(,”author”) [1] “Forton”

# 复制向量的同时修改名字
setNames(1:3, c('a','b','c'))

a b c 1 2 3

name属性:取消部分或全部向量元素名称的示例

# 有些元素可以没有名字
names(c(a=1,2,3))

[1] “a” “” “”

# 复制没有名字的新向量
unname(c(a=1,b=2,c=3))

[1] 1 2 3

# 取消向量中的全部名称
names(myVector) <- NULL
myVector

[1] 1 2 3 attr(,”author”) [1] “Forton”

2.2.1 因子

因子是只能包含预先定义值的向量,常用于分类数据

因子底层是整型向量,但具有两个属性:class(factor类)和levels(定义因子所有可能的取值)

# class定义了该对象属于因子类
myFactor <- factor(c('a','b','b','a','c'))
class(myFactor)

[1] “factor”

# 水平值,也即因子全部可能取的值
levels(myFactor)

[1] “a” “b” “c”

# 取赋值不存在于因子中,则只能得到NA
myFactor[6] <- 'z'
myFactor

Warning in [<-.factor(*tmp*, 6, value = “z”): invalid factor level, NA generated

[1] a b b a c <NA> Levels: a b c

合并因子,会自动合并水平值

c(factor('x'), factor('y'))

[1] x y Levels: x y

汇总各因子出现的频次

# 可以通过因子观察所有可能的取值
sex.char <- c('m','m','m')
table(sex.char)

sex.char m 3

# 即使某因子未取值,也能统计进来
sex.factor <- factor(sex.char, levels=c('f','m'))
table(sex.factor)

sex.factor f m 0 3

读取数据时,有时数值字段会变成因子字段(往往由特殊字符代表的缺失值引起)

此时可以指定哪些字符代表NA值

另外,有些数据加载函数会自动将字符型向量转换为因子向量,可关闭这种转换

# 模拟读取文本文件
csv.data <- read.csv(text='value\\n12\\n1\\n-\\n9', na.strings='-', stringsAsFactors=F)
typeof(csv.data$value)

[1] “integer”

# 再确认字段的class属性
class(csv.data$value)

[1] “integer”

# 查看取值
csv.data$value

[1] 12 1 NA 9

因子的底层是整型数值,不同处理函数对待因子的行为模式是不同的,有些函数会将因子转换为字符串,有些会报错,还有些会转换为整型。使用因子时需特别注意!

当翻转因子水平时,其显示值也会翻转,如下所示:

# 翻转水平标签的示例
myLetters <- factor(letters)
levels(myLetters) <- rev(levels(myLetters))
myLetters

[1] z y x w v u t s r q p o n m l k j i h g f e d c b a Levels: z y x w v u t s r q p o n m l k j i h g f e d c b a

当创建因子时,比如上例中执行:myLetters <- factor(letters)

1)R创建了一个整数向量,其值为 1, 2, 3, …, 26,该整数向量是levels的索引

2)同时保存一个属性levels=c(“a”, “b”, …, “z”)

当显示因子myLetters时,R查找每个整数值(索引)对应在levels中的标签,然后显示标签。而字符“a”从来没有作为字符存储在每个位置中,它只是在显示时通过映射计算出来而已。

上例子,当翻转水平标签时:

1)原始因子是 1…26 对应标签’a’…’z’,其中1对应’a’, 2 对应’b’,以此类推

2)翻转水平标签,所翻转的只是标签(变为 z…a),但整数编码(也即索引)不变,故变成了:1对应’z’,2对应’y’,以此类推

3)翻转之后,字母’a’的整数编码变为26, 被排到最后;而字母’z’的整数编码1, 被排到最前面

4)这种翻转并未改变原始数据的位置顺序,故 1…26 的整数数据就被显示为 z…a 了

对比下面的示例,区分翻转整型编号与水平标签的差异:

# 翻转整型编号
# 对于水平标签来说,a…z分别对应编号1…26
# 而由于因子的整型编号被翻转,第一个元素z对应编号26, 而编号26又反过来显示为标签z
(myLetter2 <- rev(factor(letters)))

[1] z y x w v u t s r q p o n m l k j i h g f e d c b a Levels: a b c d e f g h i j k l m n o p q r s t u v w x y z

# 查看每个位置的整型数字,也即每个位置对应在levels中的索引
as.integer(myLetter2)

[1] 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 [26] 1

# 作为对比:翻转水平标签
# 水平标签被翻转,故a…z分别对应编号26…1
# 因子第一个元素a对应编号26, 而编号26又反过来显示为标签a
(myLetters3 <- factor(letters, levels=rev(letters)))

[1] a b c d e f g h i j k l m n o p q r s t u v w x y z Levels: z y x w v u t s r q p o n m l k j i h g f e d c b a

# 查看底层的整型数值(也即对应levels中的索引)
as.integer(myLetters3)

[1] 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 [26] 1

2.3 矩阵和数组

给原子向量添加维度(dim)属性后,就成为数组(array);而矩阵(matrix)是数组的特例,总是2维的

数组的普适属性:维度

# 数组可以是任意维数
# 下面的案例是:2行、3列、4组,越右边的参数,反而是越大的组别
# 数值默认先纵向,再横向,一个组别填充完后再填充下一个组别
(myArray <- array(1:24, c(2,3,4)))

, , 1

[,1] [,2] [,3] [1,] 1 3 5 [2,] 2 4 6

, , 2

[,1] [,2] [,3] [1,] 7 9 11 [2,] 8 10 12

, , 3

[,1] [,2] [,3] [1,] 13 15 17 [2,] 14 16 18

, , 4

[,1] [,2] [,3] [1,] 19 21 23 [2,] 20 22 24

以下的数组维度不同,代表各不相同的数组对象

# 1行、1列、5组
array(1:5, c(1,1,5))

, , 1 [,1] [1,] 1

, , 2 [,1] [1,] 2

, , 3 [,1] [1,] 3

, , 4 [,1] [1,] 4

, , 5 [,1] [1,] 5

# 1行、5列、1组
array(1:5, c(1,5,1))

, , 1

[,1] [,2] [,3] [,4] [,5] [1,] 1 2 3 4 5

# 5行、1列、1组
array(1:5, c(5,1,1))

, , 1

[,1] [1,] 1 [2,] 2 [3,] 3 [4,] 4 [5,] 5

直接修改数组或矩阵的维数

# 调整数组的维数
dim(myArray) <- c(4,6)
myArray

[,1] [,2] [,3] [,4] [,5] [,6] [1,] 1 5 9 13 17 21 [2,] 2 6 10 14 18 22 [3,] 3 7 11 15 19 23 [4,] 4 8 12 16 20 24

# 调整矩阵的维数
dim(myMatrix) <- c(3,2)
myMatrix

[,1] [,2] [1,] 1 4 [2,] 2 5 [3,] 3 6

数组的普适属性:元素个数

# 返回数组的元素个数
length(myArray)

[1] 24

# 返回矩阵的元素个数
length(myMatrix)

[1] 6

# 数组的行数
nrow(myArray)

[1] 4

# 数组的列数
ncol(myArray)

[1] 6

# 矩阵的行数
nrow(myMatrix)

[1] 3

# 矩阵的列数
ncol(myMatrix)

[1] 2

数组的普适属性:维度名称

注意:数组元素通过行列索引访问,是没有单个元素的名称(name)属性的

# 数组的维度名称
dimnames(myArray) <- list(c('R1','R2','R3','R4'), c('C1','C2','C3','C4','C5','C6'))
myArray

C1 C2 C3 C4 C5 C6 R1 1 5 9 13 17 21 R2 2 6 10 14 18 22 R3 3 7 11 15 19 23 R4 4 8 12 16 20 24

# 查看名称属性
names(myArray)

NULL

c()将数组打平为一维向量,按列优先填充

# 组合两个矩阵
c(myMatrix,myMatrix)

[1] 1 2 3 4 5 6 1 2 3 4 5 6

# 单个数组的效果相同
c(myArray)

[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

合并矩阵的行和列

# 合并列
cbind(myMatrix,rep(9,3))

[,1] [,2] [,3] [1,] 1 4 9 [2,] 2 5 9 [3,] 3 6 9

# 合并行
rbind(myMatrix, rep(9,2))

[,1] [,2] [1,] 1 4 [2,] 2 5 [3,] 3 6 [4,] 9 9

矩阵转置

# 矩阵的行列转置,也即沿主对角线翻转
t(myMatrix)

[,1] [,2] [,3] [1,] 1 2 3 [2,] 4 5 6

判断是否为数组或矩阵

# 判断是否数组
is.array(myArray)

[1] TRUE

# 矩阵也是数组
is.array(myMatrix)

[1] TRUE

查看数组和矩阵的维数

# 数组的维数
dim(myArray)

[1] 4 6

# 矩阵的维数
dim(myMatrix)

[1] 3 2

向量转换为数组,遗憾的是,不像 array/matrix 函数,as.array/as.matrix 函数是没有维度参数的

# dim参数不起作用
as.array(1:12, dim=c(3,2,2))

[1] 1 2 3 4 5 6 7 8 9 10 11 12

# nrow和ncol参数不起作用
as.matrix(1:12, nrow=4, ncol=3)

[,1] [1,] 1 [2,] 2 [3,] 3 [4,] 4 [5,] 5 [6,] 6 [7,] 7 [8,] 8 [9,] 9 [10,] 10 [11,] 11 [12,] 12

只有一行/一列的矩阵、一维数组,也是一维数据结构,但行为模式与向量不同

# 一维向量
str(1:3)

int [1:3] 1 2 3

# 单行矩阵(一行三列)
str(matrix(1:3, nrow=1))

int [1, 1:3] 1 2 3

# 一列矩阵(三行一列)
str(matrix(1:3, ncol=1))

int [1:3, 1] 1 2 3

# 一维数组
str(array(1:3, dim=3))

int [1:3(1d)] 1 2 3

列表矩阵和列表数组

dim()可以设置任何向量(包括列表)的维度,使其转变为矩阵或数组(按列优先填充)

列表矩阵的元素可以是任意类型,不要求全部元素相同类型,因为 dim()只是给对象添加了 dim 属性,并未改变列表的性质

这是一种复杂的数据结构,在需要将对象(比如某些模型)按网格结构存储时有用

# 列表矩阵
myList3 <- list(1:3, 'a', T, 1.0)
dim(myList3) <- c(2,2)
myList3

[,1] [,2] [1,] integer,3 TRUE [2,] “a” 1

# 对象类型
typeof(myList3)

[1] “list”

# 是否矩阵
is.matrix(myList3)

[1] TRUE

上面案件中,设置myList3维度之后,其变成:

1列2列
1:3 TRUE
“a” 1

输出矩阵或数组时,R会调用对象的print方法打印对象摘要:

  • 长度大于1的对象,会显示其类型和长度(比如上例的“integer,3”)
  • 长度等于1的对象,直接显示其值

2.4 数据框

R中存储数据使用最多的就是数据框,二维结构,每一列由相同长度的向量组成,每列的数据类型可不相同,这使其既有矩阵的性质,又有列表的性质

数据框具有如下常用属性:

属性说明
colnames() 列名
rownames() 行名
names() 同 colnames(),返回列名
ncol() 列数
nrow() 行数
length() 同 ncol(),返回列数
2.4.1 格建数据框
# 创建数据框
# 如果需要将字符列转换为因子,可设置参数stringsAsFactors为T(默认F)
(myFrame <- data.frame(x=11:13, y=c('a','b','c')))

x y 1 11 a 2 12 b 3 13 c

# 列名,同colnames()
names(myFrame)

[1] “x” “y”

# 列名
colnames(myFrame)

[1] “x” “y”

# 返回行名
rownames(myFrame)

[1] “1” “2” “3”

# 列数,同ncol()
length(myFrame)

[1] 2

# 列数
ncol(myFrame)

[1] 2

# 行数
nrow(myFrame)

[1] 3

2.4.2 类型判断

data.frame是S3类,R语言中的S3类本质上是:对象+class属性,class属性标识对象属于哪个类

泛型函数会根据该class属性调用对应的方法(可以用 methods()查看泛型函数支持的方法),比如:

  • print(1:5)调用的是print.default或print.integer方法
  • print(myFrame)调用的是print.data.frame方法

类的方法命名格式是:泛型函数名.类名,比如:

  • print.data.frame:用于打印数据框。上例中的 print(myFrame),R会检查data.frame 类,然后调用 print.data.frame
  • summary.default:默认的summary方法
  • plot.lm:用于线性模型对象的绘图方法

S3类的方法派发机制,比如调用泛型函数 f(obj)时,R 按以下顺序查找方法:

  • 检查obj的class属性,属性可能不止单个,而有可能是一个向量(多个)
  • 按类名依次查找f.class函数,找到第一个就停止,然后执行该函数
  • 如果找不到,则回退执行默认方法:f.default
  • data.frame本质上是一个列表(每列都是一个向量,列表是对象),再附加 class=”data.frame”属性,这使得 data.frame 拥有很多专有方法(而不是一个普通的列表),包括:

    • print.data.frame:以表格形式打印数据框
    • summary.data.frame:统计摘要数据框的每一列
    • plot.data.frame:绘制散点图矩阵

    S3类是一种简单、灵活、基于泛型函数和 class属性的面向对象编程,R中绝大部分基础包都属于S3类。R 语言还有 S4、R6 等支持更严格、更复杂的面向对象系统。

    # 数据框本质上是一个列表
    typeof(myFrame)

    [1] “list”

    # 判断对象是否为数据框,要查看它的类
    class(myFrame)

    [1] “data.frame”

    # 也可以使用is.xx()测试函数
    is.data.frame(myFrame)

    [1] TRUE

    2.4.3 合并数据框

    合并行列与矩阵相同,使用 rbind()和 cbind()

    # 合并行:列的名字和数量必须一致
    # 如果列名不同,则无法合并
    rbind(myFrame, data.frame(y='d',x=14))

    x y 1 11 a 2 12 b 3 13 c 4 14 d

    # 合并列:行数必须一致,行的名称可忽略
    cbind(myFrame, data.frame(z=c('甲','乙','丙')))

    x y z 1 11 a 甲 2 12 b 乙 3 13 c 丙

    注意:合并向量时,cbind()默认返回矩阵(而非数据框),除非其中有一个参数是数据框才会返回数据框

    # 合并向量时,返回的是矩阵
    myBind1 <- cbind(1:5, 6:10)
    str(myBind1)

    int [1:5, 1:2] 1 2 3 4 5 6 7 8 9 10

    # 测试是否矩阵
    is.matrix(myBind1)

    [1] TRUE

    # 如果需要返回数据框,可以用数据框构造函数
    myBind2 <- data.frame(cbind(1:5, 6:10))
    str(myBind2)

    ‘data.frame’: 5 obs. of 2 variables: $ X1: int 1 2 3 4 5 $ X2: int 6 7 8 9 10

    # 或者直接创建为数据框
    myBind3 <- data.frame(a=1:5, b=6:10)
    str(myBind3)

    ‘data.frame’: 5 obs. of 2 variables: $ a: int 1 2 3 4 5 $ b: int 6 7 8 9 10

    2.4.4 特殊列

    数据框是一个向量列表,数据框的列本身也可以是一个列表

    myFrame2 <- data.frame(x=1:3)
    myFrame2$y <- list(1:2,1:3,1:4)
    myFrame2

    x y 1 1 1, 2 2 2 1, 2, 3 3 3 1, 2, 3, 4

    但如果使用 data.frame()构造则会报错,因为它会试着将列表每个元素分别当作独立的列来构造数据框

    # 尝试将列表元素1:2、1:3、1:4作为独立的三列,行数不同,故而报错
    try(data.frame(x=1:3, y=list(1:2,1:3,1:4)))

    Error in (function (…, row.names = NULL, check.rows = FALSE, check.names = TRUE, : arguments imply differing number of rows: 2, 3, 4

    可以使用 I()将列表作为一个单元整体,并会将该列添加一个 AsIs 的类属性

    # I()会将列表作为一个整体,且为该列添加一个AsIs类别
    myFrame3 <- data.frame(x=1:3, y=I(list(1:2,1:3,1:4)))
    str(myFrame3)

    ‘data.frame’: 3 obs. of 2 variables: $ x: int 1 2 3 $ y:List of 3 ..$ : int 1 2 ..$ : int 1 2 3 ..$ : int 1 2 3 4 ..- attr(*, “class”)= chr “AsIs”

    同理,数据框的列也可以是矩阵或数组

    # 数据框特殊列
    myFrame4 <- data.frame(x=1:3, y=I(matrix(11:19,nrow=3)))
    str(myFrame4)

    ‘data.frame’: 3 obs. of 2 variables: $ x: int 1 2 3 $ y: ‘AsIs’ int [1:3, 1:3] 11 12 13 14 15 16 17 18 19

    # 引用某一列
    myFrame4[1,'y']

    [,1] [,2] [,3] [1,] 11 14 17

    特别注意:很多函数会假定数据框的每列都是原子向量,可能无法处理列表列的情况


    本文是我阅读和学习《高级R语言编程指南》(作者Hadley Wickham,机械工业出版社出版)后的读书笔记,大部分内容与例子引用原书,并加上自己的一部分理解与补充。

    推荐关注我的个人博客:R数据分析,不定期发布R语言经典书籍的读书笔记、数据分析案例,欢迎交流~

    赞(0)
    未经允许不得转载:171主机测评 » 高级R编程-第2章:数据结构
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址