第1章 基本数据结构

R语言借用了面向对象的结构,使用了“类”的抽象数据结构概念。

1.1 向量 vector

向量是R语言中最基本的数据结构, 其他类型的数据结构都可以由向量构成。

1.1.1 向量的类型与长度

最常见的向量有三种类型

  • 数值型
  • 字符型
  • 逻辑型

1.1.1.1 数值型向量 numeric

假设有这样一个数据集, 包含上海某互联网公司数据部门12个员工的相关信息。

这些员工的身高分别是:176cm, 168cm, 172cm, 165cm, 166cm, 162cm, 181cm, 174cm, 178cm, 171cm, 172cm, 169cm。

R语言中的向量, 实际上就是一列数据集合。比如这些员工的身高序列 {176, 168, 172, 165, 166, 162, 181, 174, 178, 171, 172, 169}, 将这些身高数值用c()这个函数将其拼接起来, 每个数值之间用英文逗号隔开,就构成了一个向量。

这个向量中的每个数值就是该向量的一个元素

在R语言中, <-赋值符号, 可以将值赋予给一个对象,或者说是变量


下面的示例代码,将这些员工的身高按顺序拼接组合起来,并赋值给名为 height 的这个对象。

执行这行代码后,这个 height 对象,就是一个向量。

向量根据其元素的数据类型,可以分为几种不同的数据类型,比如员工的身高都是数值,该类型的向量就是数值型

height <- c(173, 168, 172, 165, 166, 162, 181, 174, 178, 171, 172, 169)

上面的c()函数,其作用是将每个元素的值合并成一个向量。

R语言中,所谓的函数,就是执行某类操作。

控制台中直接输入对象的名字并运行, R语言程序就会将该对象包含的内容打印到显示屏幕上。

屏幕上显示的结果,每行都会以[1]这样标识开头,后面跟着具体的向量每个元素的值。

如果在控制台屏幕上一行显示不下,会继续在第二行显示,第二行开头也会以[n]开始,至于n具体是多少,就看第二行是从第几个元素开始显示。第三行、第四行以此类推。

height
##  [1] 173 168 172 165 166 162 181 174 178 171 172 169

直接运行对象的名字, 实际上等于使用了 print() 函数, 两者等价。

print(height)
##  [1] 173 168 172 165 166 162 181 174 178 171 172 169

1.1.1.2 字符型向量 character

这12名员工的姓名分别是:宋子启, 张伯仲, 孟轲舆, 张伟, 王雪梅, 陈梦妍, 李元礼, 杨伯侨, 赵蜚廉, 蒋欣, 沈约度, 陈淮阳。

他们的姓名也可以组成一个向量,姓名是字符,所以向量的类型是字符型

在R语言中, 字符串类型的值需要用引号括起来, 可以使用双引号, 也可以使用单引号。推荐使用双引号。

当然, 无论是哪种引号, 都是英文输入法下的引号, 中文下的引号是不被系统识别的。

实际上,所有以英文为自己的编程语言,所能识别的关键字符都是英文的,比如之前提到的c()函数中连接字符串的逗号,字符串的引号,以及后面陆续会使用的符号,都是在英文输入法环境下的符号,不再累述。

创建12名员工的字符创向量代码如下:

name <- c("宋子启", "张伯仲", "孟轲舆", "张伟", "王雪梅", "陈梦妍", "李元礼", "杨伯侨", "赵蜚廉", "蒋欣", "沈约度", "陈淮阳")

显示name这个向量中的所有名字,并在命令行中添加注释文本。

井号后面文本是代码注释,不会被执行。很多时候在大段的代码中,需要输入一些备注文本说明一些逻辑或者结构等。井号个数不限,井号开始后面的一整行都被注释。

name # 井号后面文本是代码注释,不会被执行
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳"

1.1.1.3 逻辑型向量 logical

在公司的员工信息登记表中,还会记录是否本地户口信息,便于办理社会保险等相关事项。相应的也会有该12名员工的是否本地户口信息,如{是,是,否,是,否,否,否,是,否,否,是,是}。

我可以直接使用“是”和“否”两个字符来表示该信息,作为一个字符型向量。

不过,大多数编程语言中都会有专门的一种逻辑型数据类型来表示这种是否问题,记录真假值,可以用来做条件判断和逻辑运算。

R语言中,用TRUE来表示逻辑真,用FALSE表示逻辑假,也就是计算机二进制的两种状态01。这两个英文字符都是大写,也可以使用TF代替。

这里的是否本地户口的原始数据“是”和“否”,我们这里就用TRUEFALSE来代替,并创建这个逻辑型向量如下:

islocal <- c(TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE)
islocal
##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE

1.1.1.4 向量的类属性 class

“类”是一种对象的属性,用来定义数据结构,是面对对象编程的概念。

这里的“类”,可以理解为对象的数据结构类型。

函数class() 获得对象的“类”属性。

对向量来说,向量的类,就是向量的类型。

class(height) # "numeric" 数值型
## [1] "numeric"
class(name) # "character" 字符型
## [1] "character"
class(islocal) # "logical" 逻辑型
## [1] "logical"

向量的类型,还可以用 typeof()mode() 这两个函数获得,返回的结果只在“数值型”的向量上略有区别,主要是角度不同。

1.1.1.4.1 检验向量的类型

R语言还提供了判断向量是否属于某种类型的一系列函数,对应上面三种类型分别是

  • is.numeric(x)
  • is.character(x)
  • is.logical(x)

其中参数 x 是待检验的对象,该函数返回类型检验结果的逻辑值,TRUE 或 FALSE.

is.numeric(height) # "numeric" 数值型
## [1] TRUE
is.character(name) # "character" 字符型
## [1] TRUE
is.logical(islocal) # "logical" 逻辑型
## [1] TRUE

判断其他两类是否为数值型

is.numeric(name) 
## [1] FALSE
is.numeric(islocal)
## [1] FALSE

判断其他两类是否为字符型

is.character(height)
## [1] FALSE
is.character(islocal)
## [1] FALSE

判断其他两类是否为逻辑型

is.logical(height)
## [1] FALSE
is.logical(name)
## [1] FALSE

1.1.1.5 向量的长度 length

向量的长度,就是向量中包含的元素的个数。

获得向量长度的函数是 length(x) ,返回的是一个整数。

这里“身高”、“姓名”、“是否本地”三个向量,都是记录了 12 名员工的信息,故而这三个向量长度的结果应该都是 12。

length(height) 
## [1] 12
length(name)
## [1] 12
length(islocal)
## [1] 12

1.1.1.6 单个元素的向量

假设这个数据分析团队现在新加入了一名员工,其身高是 174cm,姓名是“况天佑”,不是本地人。

将这三个数据分别赋值给三个对象,它们分别是只包含一个元素的向量。

h1 <- c(174)
h1
## [1] 174
n1 <- c("况天佑")
n1
## [1] "况天佑"
l1 <- c(FALSE)
l1
## [1] FALSE

对于单个元素的数据赋值给一个对象时,不需要c()函数,因为c()函数的作用是将多个元素合并成一个向量。直接将值赋给对象给方便。

h1 <- 174
n1 <- "况天佑"
l1 <- FALSE

有些地方会将只包含单个元素的向量,也就是长度为一的向量,另外起一个名字叫“标量”。

实际上,R语言没有标量这个说法,也没有判读一个对象是否为标量的函数。

所有的数据都是向量。

# 单个元素的向量,其长度为 1
length(h1)
## [1] 1
length(n1)
## [1] 1
length(l1)
## [1] 1

1.1.1.7 检验对象是否为向量 is.atomic

判断一个对象是否为向量的函数是 is.atomic() , 如果是向量, 则返回值为 TRUE, 如果不是则返回 FALSE

这里讨论的“向量”是狭义的概念,是原子向量(atomic vector)。

R中还有其他的广义的向量,如矩阵和数组,以及泛化的向量,如列表。

  • 故而检验对象是否为原子向量是用 is.atomic(),而不是is.vector()
# 长度为 12 的向量,判断结果返回的结果是 TRUE
is.atomic(height) 
## [1] TRUE
is.atomic(name)
## [1] TRUE
is.atomic(islocal)
## [1] TRUE
# 长度为 1 的向量,判断结果返回的结果也是 TRUE
is.atomic(h1) 
## [1] TRUE
is.atomic(n1)
## [1] TRUE
is.atomic(l1)
## [1] TRUE

1.1.1.8 向量的合并 combine

我们将第13名员工的信息,合并到前12名员工的向量中,依然使用 c() 函数。

# 这里讲合并后的结果还是还是赋值给原来的对象
# 新对象就包含13个元素
height <- c(height, h1)
height
##  [1] 173 168 172 165 166 162 181 174 178 171 172 169 174
length(height)
## [1] 13
name <- c(name, n1)
name
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳" "况天佑"
length(name)
## [1] 13
islocal <- c(islocal, l1)
islocal
##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
## [13] FALSE
length(islocal)
## [1] 13

假设,该团队又有两名新成员加入,依然使用c()函数添加,并且可以直接追加向量的值,而不需要先赋予给另一个对象。

# 新对象继续添加了两名员工的信息
# 现在向量的元素一共包含了 15 个员工的信息
height <- c(height, 170, 172)
height
##  [1] 173 168 172 165 166 162 181 174 178 171 172 169 174 170 172
length(height)
## [1] 15
name <- c(name, "王珍珍", "马小玲")
name
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳" "况天佑" "王珍珍" "马小玲"
length(name)
## [1] 15
islocal <- c(islocal, TRUE, TRUE)
islocal
##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
## [13] FALSE  TRUE  TRUE
length(islocal)
## [1] 15

1.1.1.9 不同类型向量转化

向量的一个内在要求是,其内部元素之间的类型要相同。

1.1.1.10 合并向量时类型不同自动转化

创建或者合并向量时,如果元素类型之间不相同,则会遵循优先兼容递升原则,自动转为兼容性更高的类型。

  • 逻辑型会被转为数值型
  • 数值型会被转为字符型

在数值型向量中,严格意义上来说,还区分两种类型,一个是子类:“整数型”,一个是父类“复数型”,而“数值型”实际上就是“实数型”。

在数学上,整数 ∈ 实数 ∈ 复数。

按类型转化原则,在数值型之间,整数型会转为实数型,数值型会转为复数型。

class(c(TRUE, 1, FALSE, 0)) # 逻辑型转为数值型
## [1] "numeric"
print(c(TRUE, 1, FALSE, 0)) # TRUE 转为 1, FASLE 转为 0
## [1] 1 1 0 0
class(c(TRUE, "真", FALSE, "假")) # 逻辑型转为数值型
## [1] "character"
print(c(TRUE, "真", FALSE, "假"))  # TRUE 转为 "TRUE"(字符), FASLE 转为 "FASLE"(字符)
## [1] "TRUE"  "真"    "FALSE" "假"
class(c(3.14, "PI")) # 数值型转为字符型
## [1] "character"
print(c(3.14, "PI")) # 3.14 转为 "3.14"(字符)
## [1] "3.14" "PI"

print(c(1, 3.14)) # 整数型转为数值型
## [1] 1.00 3.14
class(c(3.14, 2 + 5i)) # 数值型转为复数型
## [1] "complex"
print(c(3.14, 2 + 5i)) # 数值型转为复数型
## [1] 3.14+0i 2.00+5i
1.1.1.10.1 强制类型转化函数

类似于 is.FUN 一系列向量类型检验函数,强制类型转化也相似的一系列函数

  • as.logical(x)
  • as.number(x)
    • as.integer(x)
    • as.complex(x)
  • as.character(x)

强制类型转化遵循一定的原则,比如

  • 数值型可以转为逻辑型:0 转为逻辑值 FALSE, 非 0 都转为 TRUE
  • 字符型可以转为数值型的条件是:去掉字符符号(引号)之后是可以被识别的数值
  • 如果不符合转化条件,则强制转化后对应的元素的结果将变成 NA,这是 R 语言中的缺失值,因为系统不知道该显示什么值是正确的,相当于信息丢失;

在强制转化过程中如果产生了NA,则会出现系统“警告”。实际上这时候程序已经执行成功了,并不会等待确认或者要放弃执行,警告信息只是一种提醒。

as.logical(c(1,0,2)) # 0 转为逻辑值 FALSE, 非 0 都转为 TRUE
## [1]  TRUE FALSE  TRUE
as.logical(c(0, 0.68, 1, 3.14)) # 0 转为逻辑值 FALSE, 非 0 都转为 TRUE
## [1] FALSE  TRUE  TRUE  TRUE
as.numeric(name) # 将名字转为数值,得到的都是 NA,程序不知道该转为什么值合适
## Warning: NAs introduced by coercion
##  [1] NA NA NA NA NA NA NA NA NA NA NA NA NA NA NA
as.numeric("3.14") # 如果去掉字符型的符号后能被系统识别为数值,则可顺利转化
## [1] 3.14
class(as.numeric("3.14")) # 打印出转化后的类型
## [1] "numeric"

字符和数值之间类型转化是很常见的。

比如爬虫系统,从网页上爬取所需数据,最迟得到的数据是字符型的(网页是富文本),要做数值运行就需要将其转为数值型。

在需要输出特殊格式化的结果时,通常会将数值转为字符。最常见的例子是,将一个百分比数值显示为带有百分号的格式。

1.1.2 等差序列向量 sequence

对这 15 名员工,我想给他们编一个序号,从 1 到 15。

我可以用 c(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15) 这样的原始输入方式获得,但这种手动输入不是很方便,特别是数量很大的时候。

这些数字是有规律的,是等差递增向量,其步长为 1. 只要是有规律的,实际上程序就可以自动生成。

R语言中有一个专门的函数来生成这种等差序列的向量,该函数是 seq() ,其参数是 from 表示开始的数值, to 是结束的数值,by 是步长(每次增加的数值)。

我们用 seq() 函数来生成从 1 到 15 的向量,间隔为 1,并将结果赋值给 no 这个对象。

no <- seq(from = 1, to = 15, by = 1)
no
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

函数的参数名称是形式参数,其值是实际参数, 这里的 from, to, by 是形式参数,其值 1, 15, 1 是对象的实际参数。

在R语言中,形式参数是可以不直接写出来的,这种情况下,按照参数的值所在的位置去对应参数的名称,这样就可以简化一些常用函数的书写过程。

比如上面这个创建等差序列向量的过程,我们就可以简化为

no <- seq(1, 15, 1)

函数的参数有时候会定义默认值,比如 seq()by 的默认值为 1,则在使用该函数的不指定该参数的值,就会在运行过程中使用该参数的默认值,这时候函数的书写就可以进一步简化。

no <- seq(1, 15)

这也是之前在使用函数 head() 和 tail() 时,不指定参数 n 的情况下,只显示首尾 6 个元素的原因,因为参数 n 的默认值就是 6.

seq() 函数还有另外一个用法,当我们知道等差序列的起止数值,但不知道步长应该是多少,只知道应该产生多少个元素的向量,可以使用参数 length.out

产生等差序列还有一种情况:已知起始值 from 和 终止值 to ,但不知道步长 by 的值,但知道应该要产生多少个元素的向量,这时可以使用 seq() 函数的另一个参数 length.out , 表示要产生多少个元素。

seq(from = 1, to = 15, length.out = 15)
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15

但这里要注意的是,seq() 函数中 by 和 length.out 两个参数不能同时使用,只能使用一个,否则会报错。

实际上,seq() 函数中如果指定了 length.out 这个参数,则 by 的值会被自动计算出来, by = ((to - from)/(length.out - 1) 。

因为等差序列的关键参数还是 from, to 和 by ,程序最终还是需要知道步长是多少,否则无法计算。不能同时指定的原因是,可能会出现用户输入的 by 和从 length.out 计算的 by = ((to - from)/(length.out - 1) 两者不相等,就会出问题。

这种情况下 by = ((to - from)/(length.out - 1) 这个表达式,使用了 from, to, length.out 这三个参数的具体的值。R语言中可以允许这种一个参数调用其他参数值的情况,可以使得函数更为灵活。

步长为 1 的等差序列最为常用,故而还有一种更为便捷的书写方式,n:m,这种包含符号的写法成为表达式。比如 1:15 就等价于 seq(1,15) , 等价于 seq(from = 1, to = 15, by = 1)。

表达式,本质上也是一种函数。R语言中所有的操作,都是函数过程。

n <- 1:15

如果我们要等到递减的等差序列,则 to 的值大于 from 即可。

15:1
##  [1] 15 14 13 12 11 10  9  8  7  6  5  4  3  2  1

另外,等差序列可以产生实数,并不限于整数(上面的值都是整数)。

seq(from = 0.01, to = 0.15, by = 0.01)
##  [1] 0.01 0.02 0.03 0.04 0.05 0.06 0.07 0.08 0.09 0.10 0.11 0.12 0.13 0.14 0.15

对于函数的参数是否书写的问题,建议如下:如果已经对该函数中的参数名称、顺序、参数默认值等情况比较清楚,自己简化书写的时候不会产生困惑,则可以简化。在不熟悉的情况下,建议书写完整,容易阅读。

另外,函数的各参数之间,尽量空一格;参数名和参数值与等号之间,尽量空一格,使得代码更规范,便于阅读。

1.1.3 循环重复向量 replicate

有时候,我们需要产生一些重复的向量,R语言中有专门的函数来处理。

假设我们要对这15名员工做个分组,按照顺序分为3组,每个组给予1,2,3,4,5这样的序号。

R语言中有一个函数 rep() ,可以生成具有重复性质的向量,或者说将一个向量按重复循环生成更多元素的向量。

该函数有一个参数 times 可以,可以指定对一个向量重复的次数。

这里的分组,我们就可以先创建一个 c(1, 2, 3, 4, 5) 向量,然后将其循环 3 次到达目标。

three_group_3 <- rep(c(1, 2, 3, 4, 5), times = 3)
print(three_group_3)
##  [1] 1 2 3 4 5 1 2 3 4 5 1 2 3 4 5

假设我们做个不同的分组,共分为5组,前3名员工分为第1组,记录他们的组号为1,随后的3名分为第2组,记录为2,直到最后一组,也就是第5组,记录为5。

该函数还有另外一个参数 each,可以指定向量的每个元素按顺序循环的次数。上面的分组我们可以用 each 参数来实现。

three_group_5 <- rep(c(1, 2, 3, 4, 5), each = 3)
print(three_group_5)
##  [1] 1 1 1 2 2 2 3 3 3 4 4 4 5 5 5

rep() 函数中的 times 参数,是对整个向量循环重复;而 each 参数,是对向量中的元素循环重复。

times 和 each 两个参数是可以同时使用的, each 参数的优先级高于 times 参数,先循环重复每个元素得到新向量对象,然后再对此新结果整个向量做循环重复。

times 和 each 的默认参数都是 1 ,故而可以只设定一个即可。多数情况下,我们只选择一种用法,也就是只使用一个参数。

对于 seq() 函数的用法,可以使用 help() 函数来获得更进一步的信息。执行 help(seq) 就会打开一个页面,介绍关于seq()函数的各种信息,通常都会包括

  • 描述(Description)
  • 用法(Usage)
  • 参数(Arguments)
  • 详情(Details)
  • 参数值(Value)
  • 参考文献(References)
  • 参考更多信息(See Also)
  • 示例(Examples)

其中 Arguments 和 Examples 是最有用的部分。

help(seq) # 查看关于该函数的帮助信息
?seq # 也可以使用“?”开头紧接着函数名的方式

1.1.4 数值向量的算术运算

这个数据分析团队中的每个成员都已经有身高信息了,我们再来提供他们的体重信息,单位为 kg.

weight <- c(73, 70, 68, 60, 55, 52, 80, 75, 78, 54, 61, 62, 56, 53, 82)
print(weight)
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82

现在,我们想要计算每个人的“身高体重指数”(BMI, Body Mass Index),计算该指数用来反映一个人的整体营养状态,比如是否偏瘦,还是偏胖等。

身高质量指数的公式是

\[BMI = \frac{w}{h^2}\]

w = 体重, 单位:千克(kg);

h = 身高,单位:米(m);

BMI = 身高体重指数,单位:千克/平方米(kg/m^2)

之前的身高向量 height 的单位是 cm(厘米),按照公式需要,先转换单位为 m(米),只要每个元素除以100即可。

将一个数值向量,除以一个数值时,每个元素都会做相同的算术运算。

height <- height / 100 # / 是除法符号,左边除以右边
print(height)
##  [1] 1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 1.72 1.69 1.74 1.70 1.72

这时我们得到了单位为米的身高数据。

我们要计算每个人的身高质量指数,只要对身高和体重两个向量直接做算术运算即可。

当两个向量的长度相同(包含的元素相同)时,算术运算时每个元素都是一一对应的。

BMI <- weight / (height ^ 2) # ^ 是幂符号,^2就是2次方,也就是平方
print(BMI)
##  [1] 24.39106 24.80159 22.98540 22.03857 19.95936 19.81405 24.41928 24.77210
##  [9] 24.61810 18.46722 20.61925 21.70792 18.49650 18.33910 27.71769

最后,我们根据身高质量指数的统计分析对照表来做这 15 名员工的体质做分析。

总体来说,BMI值小于18.5属于偏瘦,大于25属于偏胖。

从结果上看,第10名员工的BMI约等于18.5,算是标准体质范围;有两位员工需要注意自己的体质健康状况:

  • 第14名员工就属于偏瘦,需要多补充点营养即可;
  • 第15名员工就属于偏胖,需要加强身体锻炼了。

R语言中关于数值的算术运算符号及含义见下表

算术运算符号 符号含义
+ 加号,数值相加
- 减号,数值相减
* 乘号,数值相乘
/ 除号,数值相除
%% 取模,数值相除取不能整除的余数
%/% 取整,数值相除取整除的部分
^ 幂,幂运算,x^y 表示 x 连续乘以 x 自身 y 次
15 + 6
## [1] 21
15 - 6
## [1] 9
15 * 6
## [1] 90
15 / 6
## [1] 2.5
15 %% 6
## [1] 3
15 %/% 6
## [1] 2
2^3 
## [1] 8

1.1.5 字符向量的字符处理

1.1.6 逻辑向量的逻辑运算

逻辑运算符号 符号含义
x == y 判断是否相等
x <= y 判断是否小于等于
x >= y 判断是否大于等于
x && y 标量的逻辑“与”运算
x &#124&#124 y 标量的逻辑“或”运算
x & y 向量的逻辑“与”运算(x、y以及运算结果都是向量)
X &#124 y 向量的逻辑“或”运算(x、y以及运算结果都是向量)
!x 逻辑非

1.1.7 向量运算的循环补齐

我们再回过头来看 height/100 一个向量直接除以一个数值的情况。

我们知道,一个数值,实际上就是一个只包含一个元素的向量。那么这里的情况是,一个包含 15 个元素的向量,除以一个只包含一个元素的向量。

向量的算术运算实际上是需要两个个数相同,每个元素之间相互对应做运算的。那么这里是怎么实现最终的运算的呢?

这里引出了一个R语言向量运算过程中的一个重要概念,向量循环补齐

100这个数值是只包含一个元素的向量,对应要运算的向量是包含15个元素的向量,根据循环补齐原则,这里先向量长度少的向量,按照循环重复的原则补齐到相同长度,使用 rep() 的规则,且重复的参数是 times

这里 100 这个单个元素(长度为1的向量),先处理为 rep(100, time = 15) 的向量,再做计算,等价于

height/rep(100, times = 15)

如果我们将包含15个元素的体重向量 weight,除以包含4个元素的向量c(1, 2, 3, 4),会发生什么呢?

weight/c(1, 2, 3, 4)
## Warning in weight/c(1, 2, 3, 4): longer object length is not a multiple of
## shorter object length
##  [1] 73.00000 35.00000 22.66667 15.00000 55.00000 26.00000 26.66667 18.75000
##  [9] 78.00000 27.00000 20.33333 15.50000 56.00000 26.50000 27.33333

运算后该表达式会得到一个结果值,但是同时会打印出一条红色警告信息:“长的对象长度不是短的对象长度的整倍数”

该计算过程会去重复循环长度短的向量,如果“长的对象长度不是短的对象长度的整倍数”,则重复次数 = (长的对象长度/短的对象长度)除数的整数部分 + 1。这里 15/4的除数3倍 + 1 ,times = 4.

c(1, 2, 3, 4) 重复 4 次之后,就得到长度为 16 的向量,比原来长的对象的长度还要多,则多出的部分会被丢弃,使得计算的两个向量长度相同。

因为有丢弃的情况出现,故而计算后会有一个警告信息,作为提示。

如果“长的对象长度”正好是“短的对象长度”的整倍数,则直接循环重复后计算,且不会提出警告信息。

weight/c(1, 2, 3)
##  [1] 73.00000 35.00000 22.66667 60.00000 27.50000 17.33333 80.00000 37.50000
##  [9] 26.00000 54.00000 30.50000 20.66667 56.00000 26.50000 27.33333

1.1.8 向量索引与子集筛选

向量是多个元素的集合,当我们只需要指定或者说提取该向量中的某个元素时,就可以使用向量的索引(Indexing)。

向量元素可以由三种基本类型的向量索引

  • 整数型,索引的是元素位置
  • 字符型,索引的是名称属性
  • 逻辑型,索引的是相同长度的逻辑向量对应的逻辑值为真的元素

1.1.8.1 通过元素位置索引向量

第一种元素的索引方式,是通过在向量后面加中括号,其中输入需要索引的元素的位置(第几个)。

比如我们想要获得该数据分析团队中的第2个人的身高,第5个人的名字,第9个人的体重,以及第14个人的身高体重指数,则直接索引对应向量的位置序号即可。

height[2] # 第2个人的身高
## [1] 1.68
name[5] # 第5个人的名字
## [1] "王雪梅"
weight[9] # 第9个人的体重
## [1] 78
BMI[14] # 第14个人的身高体重指数
## [1] 18.3391

还是回到单个数值本身就是向量的问题,这里的位置索引,只要求是整数向量即可,故而一次索引多个元素。

height[1:5] # 第1到第5个人的身高
## [1] 1.73 1.68 1.72 1.65 1.66
name[c(2, 6, 8)] # 第2,6,8个人的名字
## [1] "张伯仲" "陈梦妍" "杨伯侨"
weight[c(15, 4, 12, 1)] # 第15,4,12,1个人的体重,具体的顺序可以任意指定
## [1] 82 60 62 73
BMI[15:12] # 第15到12个人的身高体重指数
## [1] 27.71769 18.33910 18.49650 21.70792

这里需要注意的是,作为位置索引的数字整数,不能超过该向量的长度。否则会得到一个值为 NA 的结果,也就是一个空值。

height[16] # 第16个元素不存在
## [1] NA
name[c(-2, -6, -8)] # 第2,6,8个人的名字
##  [1] "宋子启" "孟轲舆" "张伟"   "王雪梅" "李元礼" "赵蜚廉" "蒋欣"   "沈约度"
##  [9] "陈淮阳" "况天佑" "王珍珍" "马小玲"
weight[-c(15, 4, 12, 1)] # 第15,4,12,1个人的体重,具体的顺序可以任意指定
##  [1] 70 68 55 52 80 75 78 54 61 56 53
BMI[15:12] # 第15到12个人的身高体重指数
## [1] 27.71769 18.33910 18.49650 21.70792

当我们想要获得排除某个位置元素的剩余其他元素向量的时候,位置索引数字变成负数即可。

height[-15] # 排除第15个元素的身高向量
##  [1] 1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 1.72 1.69 1.74 1.70
name[c(-2, -6, -8)] # 排除第2,6,8个人的名字
##  [1] "宋子启" "孟轲舆" "张伟"   "王雪梅" "李元礼" "赵蜚廉" "蒋欣"   "沈约度"
##  [9] "陈淮阳" "况天佑" "王珍珍" "马小玲"
weight[-c(15, 4, 12, 1)] # -c(15, 4, 12, 1) 等价于 -1 * c(15, 4, 12, 1)
##  [1] 70 68 55 52 80 75 78 54 61 56 53
BMI[-15:-12] # 排除第15到第12个人的身高体重指数
##  [1] 24.39106 24.80159 22.98540 22.03857 19.95936 19.81405 24.41928 24.77210
##  [9] 24.61810 18.46722 20.61925

1.1.8.2 通过名称属性索引向量

向量可以设置一个名称属性,从而可以通过名称来索引向量。

名称属性可以通过 names() 来指定,将一个包含名称的向量,指定给等长度的向量。

例如,我们将这 15 名员工的姓名向量,赋值给身高向量作为名称属性。

这样,身高向量就具有了名称属性,每个身高元素都会对应一个姓名。print(height) 时候也会将元素的名称显示出来。

print(name)
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳" "况天佑" "王珍珍" "马小玲"
print(height)
##  [1] 1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 1.72 1.69 1.74 1.70 1.72
names(height) <- name # 这里将 name 作为 height 的 names 属性
print(height)
## 宋子启 张伯仲 孟轲舆   张伟 王雪梅 陈梦妍 李元礼 杨伯侨 赵蜚廉   蒋欣 沈约度 
##   1.73   1.68   1.72   1.65   1.66   1.62   1.81   1.74   1.78   1.71   1.72 
## 陈淮阳 况天佑 王珍珍 马小玲 
##   1.69   1.74   1.70   1.72

通过名称向量即可索引具体的元素,用法和位置索引类似,但是名称是字符,故而索引的名称要作为字符处理,需要添加字符引号。

通过名称来索引,有个显而易见的好处,记住名字比位置更为方便。

height["马小玲"]
## 马小玲 
##   1.72
height[c("马小玲","蒋欣")]
## 马小玲   蒋欣 
##   1.72   1.71

1.1.8.3 通过逻辑表达式筛选子集

用逻辑向量来筛选向量元素,也是常见的用法。在索引符号中输入逻辑向量,就会筛选出对应的逻辑为真(TRUE)的元素。

比如,在这 15 名员工中,筛选出户口是本地的员工。现在已经有了 islocal 的逻辑向量,可直接筛选出本地户口的员工了。

print(name)
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳" "况天佑" "王珍珍" "马小玲"
print(islocal)
##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
## [13] FALSE  TRUE  TRUE
name[islocal]
## [1] "宋子启" "张伯仲" "张伟"   "杨伯侨" "沈约度" "陈淮阳" "王珍珍" "马小玲"
# 如果把 islocal 原本的逻辑值显示完整等价于
name[c(TRUE, TRUE, FALSE, TRUE, FALSE, FALSE, FALSE, TRUE, FALSE, FALSE, TRUE, TRUE)]
## [1] "宋子启" "张伯仲" "张伟"   "杨伯侨" "沈约度" "陈淮阳" "况天佑" "王珍珍"

如果逻辑向量长度少于被筛选向量, 则会通过向量循环补齐的方式自动补全为与筛选对象等长的逻辑向量在筛选,

name[c(TRUE, FALSE, TRUE, FALSE, FALSE)] # 第1个和第3个元素逻辑值为TRUE
## [1] "宋子启" "孟轲舆" "陈梦妍" "杨伯侨" "沈约度" "况天佑"
# 等价于索引的逻辑向量先循环补齐长度与被筛选对象相同,这里正好是3倍
name[rep(c(TRUE, FALSE, TRUE, FALSE, FALSE), times = 3)] 
## [1] "宋子启" "孟轲舆" "陈梦妍" "杨伯侨" "沈约度" "况天佑"

这种逻辑索引的方式,更为重建的用法是逻辑表达式:一个向量经过逻辑运算后得到相同长度的逻辑向量,然后用此结果逻辑向量来筛选符合逻辑为真的元素。

比如,weight > 60 就是一个逻辑表达式,大于号是一个比较运算符,类似于算术运算,这里也是一个向量与另一个向量的对应的值的比较,返回的结果是一个逻辑向量,也就是每个比较的结果,是否为真,为真就记录为 TRUE,否则就记录为 FALSE。

这里的 60 虽然只是一个数值,但它就是只包含一个元素的向量,这里的运算还是会类似于算术运算中,先对其做向量循环补齐,然后再一一对应做比较运算。关于单个数值的向量循环补齐原则,后续将不再单独说明。

weight > 60 # 逻辑表达式,返回相同长度的逻辑向量
##  [1]  TRUE  TRUE  TRUE FALSE FALSE FALSE  TRUE  TRUE  TRUE FALSE  TRUE  TRUE
## [13] FALSE FALSE  TRUE
height[weight > 60]
## 宋子启 张伯仲 孟轲舆 李元礼 杨伯侨 赵蜚廉 沈约度 陈淮阳 马小玲 
##   1.73   1.68   1.72   1.81   1.74   1.78   1.72   1.69   1.72

如果要对一个逻辑结果做一个转化,真变为假,假变为真,则有一个运算符号来完成,在逻辑表达式前加“!”(英文状态下的惊叹号),也可以将在一个逻辑表达式前面。

weight[!(weight >= 60)] # 这里加入括号,更容易理解其优先级顺序
## [1] 55 52 54 56 53
weight[!weight >= 60] # 这里 !weight >= 60 等价于 !(weight >= 60) 是因为 ! 的优先级比 >= 低
## [1] 55 52 54 56 53
weight[islocal]
## [1] 73 70 60 75 61 62 53 82
weight[!islocal]
## [1] 68 55 52 80 78 54 56
weight[!rep(c(TRUE, FALSE, TRUE, FALSE, FALSE), times = 3)]
## [1] 70 60 55 80 78 54 62 53 82

判断一个值是否等于另一个只,是用 “==” 两个连续等号,因为单个等号有其他两个含义,一个是赋值符号,一个是在函数中指定参数值。

而判断一个值是否不等于另一个值,是否“!=”,一个否定的惊叹号紧接着一个等号。这里也是需要特别注意,因为有很多其他的编程语言用了其他的方式来表示不相等,比如“<>”,小于和大于号连用这种方式在R语言中是不能识别的。

1 == 1 # TRUE
## [1] TRUE
1 != 1 # FALSE
## [1] FALSE
1 == 2 # FALSE
## [1] FALSE
1 != 2 # TRUE
## [1] TRUE
weight[weight == 80]
## [1] 80
weight[weight != 80]
##  [1] 73 70 68 60 55 52 75 78 54 61 62 56 53 82

下表是常用的比较运算符号及其含义:

比较运算符号 含义
> 大于
>= 大于等于
< 小于
<= 小于等于
== 等于
!= 等于

1.1.8.4 通过 subset() 筛选子集

R语言中,有一个专门用来筛选子集的函数 subset(), 其参数为待筛选子集的对象,和一个逻辑表达式。

对向量来说,subset() 的用法和逻辑表达式筛选是类似的,subset()的含义,更多的是使用函数来操作,这在管道操作中比较有效,而不是“[]”这种符号化的操作。

subset(weight, weight >= 60) # 等价于 weight[weight >= 60]
##  [1] 73 70 68 60 80 75 78 61 62 82

1.1.8.5 通过 head() 和 tail() 筛选首尾向量

有时候,我们想要快速的查看某个向量的前几个元素,而不要在屏幕上将所有元素都出来,这样的函数就非常有用。

显示前几个元素函数是 head(),默认显示前6个元素。

对应地,也有一个显示最后几个元素的函数是 tail(),默认显示最后6个元素。

也就是 头(head)尾(tail)

head(height)
## 宋子启 张伯仲 孟轲舆   张伟 王雪梅 陈梦妍 
##   1.73   1.68   1.72   1.65   1.66   1.62
head(name)
## [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍"
head(islocal)
## [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE
tail(height)
##   蒋欣 沈约度 陈淮阳 况天佑 王珍珍 马小玲 
##   1.71   1.72   1.69   1.74   1.70   1.72
tail(name)
## [1] "蒋欣"   "沈约度" "陈淮阳" "况天佑" "王珍珍" "马小玲"
head(islocal)
## [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE

head()tail() 都有一个参数 n 可以指定具体显示元素的个数。

head(height, n = 10)
## 宋子启 张伯仲 孟轲舆   张伟 王雪梅 陈梦妍 李元礼 杨伯侨 赵蜚廉   蒋欣 
##   1.73   1.68   1.72   1.65   1.66   1.62   1.81   1.74   1.78   1.71
head(name, n = 3)
## [1] "宋子启" "张伯仲" "孟轲舆"
head(islocal, n = 6)
## [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE
tail(height, n = 10)
## 陈梦妍 李元礼 杨伯侨 赵蜚廉   蒋欣 沈约度 陈淮阳 况天佑 王珍珍 马小玲 
##   1.62   1.81   1.74   1.78   1.71   1.72   1.69   1.74   1.70   1.72
tail(name, n = 3)
## [1] "况天佑" "王珍珍" "马小玲"
head(islocal, n = 6)
## [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE

1.1.9 因子向量 factor

在这 15 名员工的信息中,现在新添加一列用来存储性别。

在R语言中有一种特殊的数据类型,可以用方便处理类别变量,称其为“因子(factor)”。

因子根据类别是否具有顺序上的意义分为两类:

  • 无序因子:类别变量并未实际的顺序意义,如性别
  • 有序因子:类别变量有实际的顺序意义,如年龄层、收入区间、优良中差等级,类别之间是有大小、高低、好坏等顺序信息的

因子的类别,在R语言中有一个专有的名称,称为“水平(levels)”。

因子是通过 factor() 函数来定义的,跟普通的字符型向量创建类似,但需要指定 levels 有哪些,如果是有序因子则还要指定个水平的顺序。

gender <- factor(x = c("男", "男", "男", "男", "女", "女", "男",  "男", "男", "女", "男", "男", "男", "女", "女"),
                 levels = c("男", "女")) # 一段完整的代码未结束时,其他参数可另起一行书写
print(gender) # 除了显示该向量具体的类别信息外,还是显示所有水平
##  [1] 男 男 男 男 女 女 男 男 男 女 男 男 男 女 女
## Levels: 男 女
class(gender) # 类为 factor
## [1] "factor"

因子是一种向量,可以用 is.atomic(x) 来检验。

检验一个对象是否为因子,可以用 is.factor(x) 函数。

is.atomic(gender) # 因子是一种向量
## [1] TRUE
is.factor(gender) # 检验一个对象是否为因子
## [1] TRUE

从对象的类上来说,因子是一种向量,或者说是一种因子型向量,与逻辑型、数值型、字符型相对。


公司每年在年终结束的时候,需要进行绩效考核,评估员工在过去一年的表现,评级为“优”、“良”、“中”、“差”四种。

而该数据分析团队最终考核是结果只在“优”、“良”、“中”三个等级里,没有人被评级为“差”。在这种情况下,因子的水平就会体现出其作用来。绩效等级因子对应着四个水平,虽然团队考核最终只出现了三种,但从因子水平中可以看到有四种。

“优”、“良”、“中”、“差”四个等级,依次从好到差,是有顺序的,这种情况下因子的顺序也变成了一种有用的信息。

grade <- factor(x = c("中", "良", "良", "中", "优", "良", "中",  "优", "中", "良", "良", "中", "良", "中", "优"),
                 levels = c("差", "中", "良", "优"),
                 ordered = TRUE) # ordered = TRUE 使用排序,具体的排序就是levels中的先后顺序,从小到大(从低到高)
print(grade) # 除了显示该向量具体的类别信息外,还是显示所有水平
##  [1] 中 良 良 中 优 良 中 优 中 良 良 中 良 中 优
## Levels: 差 < 中 < 良 < 优
class(grade) # 有序的因子出了 factor 类之外,还有一个 ordered 的类
## [1] "ordered" "factor"

类似地,可以检验该对象是否为向量,是否为因子,是否是有序的。

is.atomic(grade) # 因子是一种向量
## [1] TRUE
is.factor(grade) # 检验一个对象是否为因子对象
## [1] TRUE
is.ordered(grade) # 检验一个对象是否为有序对象
## [1] TRUE

因子在统计分析中可能会用的比较多,但在我自己的实践中用得很少,多数情况下可以将其作为字符型向量使用即可。

我个人会在 ggplot2 的画图中有时会用到有序的因子,用来指定图例和分面的顺序。

1.1.10 日期向量 Date

还有一种常用的数据格式是日期型向量,比如该数据分析团队15名成员的出生日期,类似“1984-02-28”这样的日期。

最常用的日期格式是“yyyy-mm-dd”这样表示的,分别代表年月日。如果单就这样用引号引起来的格式,那应该算字符,需要将其特殊定义为“日期”类或者说转化为日期类,系统才能真正识别其为日期。

将系统能够识别为日期的字符串,强制转为为日期类 as.Date(x) 是最为常见的定义日期的方法。

birthday <- as.Date(c("1984-02-28", "1988-09-26", "1989-07-28", "1990-01-25", "1987-04-30",
                      "1989-12-20", "1992-06-14", "1991-07-01", "1990-08-08", "1985-05-10",
                      "1993-04-01", "1991-03-05", "1991-09-25", "1992-01-31", "1988-02-14"))
print(birthday) # 屏幕上打印的结果,看起来和字符串没什么分别
##  [1] "1984-02-28" "1988-09-26" "1989-07-28" "1990-01-25" "1987-04-30"
##  [6] "1989-12-20" "1992-06-14" "1991-07-01" "1990-08-08" "1985-05-10"
## [11] "1993-04-01" "1991-03-05" "1991-09-25" "1992-01-31" "1988-02-14"
class(birthday) # 类是 Date 
## [1] "Date"
is.atomic(birthday) # 是否为向量
## [1] TRUE
class(birthday) == "Date" # 系统没有自带的 is.Date 的函数,可以用此方法来检验
## [1] TRUE

日期实际上是一种以“日”为单位的时间,是一维单向的向量,只要定义一个点为 0 点,则可用实数来表示。

在 R语言中,系统定义的 0 点是 “1970-01-01”,这与Unix系统中日期的原点保持一致。

实际上起点定义为哪天是无所谓的,因为时间既没有开始也没有结算,只有相对的差值是有意义的。

日期类向量在存储上是以数字来存储的。

日期的差值有意义,故而两个日期值可以相减,得到结果是数值型。从此可以推演出来,两个日期之间不能直接相减,但一个日期与一个数值相加减可以得到另一个有意义的日期值。

as.Date("1984-01-02") - as.Date("1984-01-01") # 两个日期之间相差的天数
## Time difference of 1 days
as.Date("1984-01-01") + 1 # 一个日期加一个天数可以得到另一个日期
## [1] "1984-01-02"
Sys.Date() # 系统函数,返回当前日期,并且为日期型
## [1] "2022-07-22"
Sys.Date() - 1 # 昨天,定期执行脚本的程序常用系统日期函数引申出来的日期作为变量
## [1] "2022-07-21"

1.2 矩阵 matrix

1.2.1 创建矩阵

矩阵是线性代数上常用的一个概念,是由行和列组成的数据结构。

创建矩阵的方法是通过 matrix() 函数来定义。

# 矩阵的定义函数、参数及参数默认值
matrix(data = NA, nrow = 1, ncol = 1, byrow = FALSE, dimnames = NULL)
# 参数
# data 是原始数据,通常是一个向量
# nrow 是行的数量(行数)
# ncol 是列的数量(两书)
# byrow 是原始数据(向量)是否按行排列填充,默认 FALSE 则默认不按行排列,即默认按列来排列填充
# dimnames 是维度的名称属性,也就是行和列的名称向量,默认是空(不启用名称属性)

通过向量来创建矩阵,可以看做是将原来一维的向量元素,按照行和列重新排列填充,形成一个新结构的对象。

比如我们对原来15名员工的身高进行重排列变成一个3行5列的矩阵:

height <- c(1:15)
print(height) # 先将原来15名员工的身高数据显示出来
##  [1]  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
matrix(data = height, nrow = 3, ncol = 5) # 将身高 height 数据转变成3*5的矩阵
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    4    7   10   13
## [2,]    2    5    8   11   14
## [3,]    3    6    9   12   15
# 对比height向量和新矩阵数据,两者包含的数据和顺序都一样
# 两者只是排列方式从一维数据,变成了3行5列的二维数据
# 可以将员工每连续3个一组分共5组,或者每隔5个分为一组共3组

数据的排列方式默认是按列填充,但可以更改

m_height <- matrix(data = height, nrow = 3, ncol = 5, byrow = TRUE) # 排列方式更为按行填充
print(m_height)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15

1.2.2 矩阵维度

矩阵有一个属性叫做维度(dim),因为矩阵是二维的,所以矩阵的维度分别是行和列,其值就是行数(nrow)列数(ncol)组成的包含两个整数的向量。

dim(m_height) 
## [1] 3 5
# 第一个数值对应的就是 nrow ,第二个数值对应的就是 ncol
# nrow 和 ncol 与 matrix() 矩阵定义中两个参数 nrow 和 ncol 是一一对应的
# nrow 的全称是 Number of Rows
# ncol 的全称是 Number of Columns

如果只需要单独获得矩阵的行数或者列数,可则使用 nrow()ncol().

nrow(m_height) # 行数;等价于 dim(m_height)[1]
## [1] 3
ncol(m_height) # 列数;等价于 dim(m_height)[2]
## [1] 5

R语言中每个对象都有其长度,对矩阵而言,length()返回的是矩阵所有元素的个数,并且等于矩阵的行数与列数乘积。

对矩阵而言,长度并不是一个常用的概念;更多的时候,用行数和列数更实用。

length(m_height) # 矩阵的长度表示矩阵的所有元素个数
## [1] 15
#等价于行数与列数的乘积
nrow(m_height) * ncol(m_height) 
## [1] 15

1.2.3 矩阵索引

类似于向量通过[]来索引定位具体的元素,矩阵也沿用此方法,但需要行和列两部分[,]才能索引定位到具体的元素,他们中间用逗号分隔,前者表示行索引,后者表示列索引。

1.2.3.1 位置整数索引

矩阵位置索引的逻辑与向量是一样的,只是索引需要行和列两部分构成。

  • 通过第n行第m列来定位一个具体的单个元素。

  • 通过某几行(向量)、某几列(向量)来定位某些元素。

  • 如果行或者列没有输入(即缺省),则表示不限制具体的行或者列(也就是所有行或者所有列)。

  • 去掉某几行或者莫几列,用对应的负数。

m_height # 原矩阵,打印到屏幕上,后续的索引解决与此对比验证
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
m_height[3,4, drop = FALSE] # 第3行,第4列的元素
##      [,1]
## [1,]   14
m_height[c(1,3),c(2,5)] # 第1和第3行,第2和第5列的元素
##      [,1] [,2]
## [1,]    2    5
## [2,]   12   15
m_height[c(1,3),] # 第1和第3行,所有列的元素
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]   11   12   13   14   15
m_height[,c(2,5)] # 所有行,第2和第5列的元素
##      [,1] [,2]
## [1,]    2    5
## [2,]    7   10
## [3,]   12   15
m_height[-2,c(2,5)] # 去除第2行,包含第2和第5列的元素
##      [,1] [,2]
## [1,]    2    5
## [2,]   12   15

1.2.3.2 逻辑向量索引

类似于向量,矩阵的逻辑索引由行和列对应的两个逻辑向量完成,索引的结果就是对应为TRUE的行和列。

行和列的逻辑向量的长度,分别等于 nrowncol; 如果逻辑索引向量的长度小于矩阵的行列数则遵循向量循环补齐原则。

m_height # 原矩阵
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
m_height[c(TRUE, FALSE, TRUE), c(TRUE, FALSE, TRUE, FALSE,TRUE)] # 第3行,第4列的元素
##      [,1] [,2] [,3]
## [1,]    1    3    5
## [2,]   11   13   15
m_height[c(TRUE, FALSE), c(TRUE, TRUE, FALSE)] # 行列索引的向量长度小于矩阵的行列数,索引的向量遵循循环补齐原则
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    4    5
## [2,]   11   12   14   15
# 循环补齐后等价于 
m_height[c(TRUE, FALSE, TRUE), c(TRUE, TRUE, FALSE, TRUE, TRUE)] 
##      [,1] [,2] [,3] [,4]
## [1,]    1    2    4    5
## [2,]   11   12   14   15

1.2.3.3 逻辑矩阵

通过逻辑表达式可以获得对应的逻辑矩阵。

m_height > 1.7 # 矩阵中哪些元素大于 1.7
##       [,1] [,2] [,3] [,4] [,5]
## [1,] FALSE TRUE TRUE TRUE TRUE
## [2,]  TRUE TRUE TRUE TRUE TRUE
## [3,]  TRUE TRUE TRUE TRUE TRUE

通过逻辑矩阵来索引矩阵,得到的是矩阵中对应逻辑为真的元素,是一个向量。

m_height[m_height > 1.7] # 索引大于 1.7 的元素
##  [1]  6 11  2  7 12  3  8 13  4  9 14  5 10 15

1.2.3.4 行列名称索引

在矩阵创建时可以定义行和列的名称,但也可以在创建创建后再定义行和列的名称,这种方法更为实用。

通过rownames() 来定义行名称, colnames()来定义列名称。

rownames(m_height) <- c("A", "B", "C") # 行名称
colnames(m_height) <- c("col_1", "col_2", "col_3", "col_4", "col_5") # 列名称
print(m_height)
##   col_1 col_2 col_3 col_4 col_5
## A     1     2     3     4     5
## B     6     7     8     9    10
## C    11    12    13    14    15

在有了矩阵的名称属性之后,就可以通过行和列的名称属性来索引向量。

m_height[c("A", "C"), c("col_1", "col_3", "col_5")]
##   col_1 col_3 col_5
## A     1     3     5
## C    11    13    15

1.2.3.5 subset筛选行和列

subset(x, subset, select)函数可以对矩阵使用,筛选符合条件的行和列。

subset 参数是针对要筛选行的条件,是一个逻辑表达式,长度与函数相同。

select 参数是针对要筛选的列条件,可以是位置(列数)的整数向量,也可以是列名(如果有列名称属性的话)的字符向量。

subset(m_height, m_height[,1] > 1.7, 2:4) # 筛选矩阵第1列中大于1.7的行,并提取第2到第4列
##   col_2 col_3 col_4
## B     7     8     9
## C    12    13    14
subset(m_height, m_height[,5] > 1.7, c("col_1", "col_3", "col_5")) # 筛选矩阵第5列中大于1.7的行,并提取名字为"col_1"、"col_3"、"col_5"的列
##   col_1 col_3 col_5
## A     1     3     5
## B     6     8    10
## C    11    13    15
subset(m_height, m_height[,1] > 1.7, m_height[3,] > 1.7) # 筛选矩阵第1列中大于1.7的行,筛选矩阵第3行中大于1.7的列
##   col_1 col_2 col_3 col_4 col_5
## B     6     7     8     9    10
## C    11    12    13    14    15

subset函数虽然对矩阵有效,但是并不常用。

1.2.3.6 head/tail筛选首尾行

head()tail()同样适用与矩阵,但是参数 n 表示的是行数;也就是只能筛选行,而不能筛选列。

head(m_height, n = 2) # 
##   col_1 col_2 col_3 col_4 col_5
## A     1     2     3     4     5
## B     6     7     8     9    10
tail(m_height, 2)
##   col_1 col_2 col_3 col_4 col_5
## B     6     7     8     9    10
## C    11    12    13    14    15

1.2.4 矩阵合并

类似于向量合并是用c()函数,矩阵的合并分为按行合并 rbind() 和 按列合并 cbind()

按行合并,要求合并的矩阵其列数相同;按列合并,要求合并的矩阵其行数相同;否则会报错。

mc_height <- matrix(data = height, nrow = 3, ncol = 5, byrow = FALSE) # 排列方式更为按列填充
mr_height <- matrix(data = height, nrow = 3, ncol = 5, byrow = TRUE) # 排列方式更为按行填充
print(mc_height)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    4    7   10   13
## [2,]    2    5    8   11   14
## [3,]    3    6    9   12   15
print(mr_height)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
rbind(mc_height, mr_height) # 按行合并
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    4    7   10   13
## [2,]    2    5    8   11   14
## [3,]    3    6    9   12   15
## [4,]    1    2    3    4    5
## [5,]    6    7    8    9   10
## [6,]   11   12   13   14   15
cbind(mc_height, mr_height) # 按列合并
##      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
## [1,]    1    4    7   10   13    1    2    3    4     5
## [2,]    2    5    8   11   14    6    7    8    9    10
## [3,]    3    6    9   12   15   11   12   13   14    15

1.2.5 矩阵计算

1.2.5.1 矩阵算术运算

矩阵算术运算与向量类似,对应元素之间的运算。

m_height <- matrix(data = height, nrow = 3, ncol = 5, byrow = TRUE) # 排列方式更为按行填充
m_weight <- matrix(data = weight, nrow = 3, ncol = 5, byrow = TRUE) # 排列方式更为按行填充
m_weight/(m_height^2) # 身高质量指数;对应元素之间做算术运算
##            [,1]       [,2]      [,3]      [,4]      [,5]
## [1,] 73.0000000 17.5000000 7.5555556 3.7500000 2.2000000
## [2,]  1.4444444  1.6326531 1.1718750 0.9629630 0.5400000
## [3,]  0.5041322  0.4305556 0.3313609 0.2704082 0.3644444

1.2.5.2 矩阵代数运算

矩阵代数运算,有一套自己特有的法则,对应了特殊的运算符号和函数。

print(m_height)
##      [,1] [,2] [,3] [,4] [,5]
## [1,]    1    2    3    4    5
## [2,]    6    7    8    9   10
## [3,]   11   12   13   14   15
t(m_height) # 转置,行列转化
##      [,1] [,2] [,3]
## [1,]    1    6   11
## [2,]    2    7   12
## [3,]    3    8   13
## [4,]    4    9   14
## [5,]    5   10   15
det(m_height[1:3,1:3]) # 求行列式,要求矩阵必须是方阵(正方形矩阵,行数和列数相同)
## [1] 0
diag(m_height[1:3,1:3]) # 求矩阵对角线上的元素
## [1]  1  7 13

线性代数中有非常多的矩阵性质与运算逻辑,这里不一一列出。

1.2.6 矩阵检验与转化

矩阵是一种基于向量上构建的特殊结构,其类属性为 matrix,有一个特殊的维度属性 dim

检验一个对象是否为矩阵,实际上是验证该对象的类是否为 matrix 及是否具有 dim 属性及 dim 的结果是否为包含连个整数值的向量。

类判断函数,本质上是去校验对象是否符合对应类的定义。

class(m_height) # 显示对象的类,如果是矩阵,就显示为 matrix
## [1] "matrix" "array"
is.matrix(m_height) # 对象是否为矩阵
## [1] TRUE
as.matrix(height) # 将对象转化为矩阵
##       [,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
## [13,]   13
## [14,]   14
## [15,]   15
# 这里待转化对象是向量故而结果为列数等于1的矩阵,行数等于向量的长度

1.3 数组 array

数组可以看做是具有多维结构的向量,也就是将原本一维的向量,改变索引结构变为多维表示。

创建数组的方法是通过 array() 函数。

array(data = NA, dim = length(data), dimnames = NULL)
# data 是要创建数组的向量,其元素用于构建数组
# dim 为数组的维数向量(为数值型向 量)
# dimnames 为由各维的名称构成的向量(为字符型向量)

比如,将 1:30 的向量,按照 2*3*5 的3维结构重新排列为数组,则需要三个位置数字向量才能定位到一个具体的元素。

a <- array(1:30, dim = c(2, 3, 5))
print(a) # 因为该数组是3维结构,而屏幕只能显示2维平面,故而按第3个维度每个维数切片显示
## , , 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
## 
## , , 5
## 
##      [,1] [,2] [,3]
## [1,]   25   27   29
## [2,]   26   28   30
dim(a) # 维度向量,按顺序分别代表了每个维度的维数
## [1] 2 3 5
class(a) # 对象的类,如果是数组,则结果为 array
## [1] "array"
is.array(a) # 检验对象是否为数组类:判断元素的模式是否相同,判断dim属性是否不为NULL
## [1] TRUE

矩阵是数组在二维结构上的特殊形式。因为矩阵在数学上常用,且有一套矩阵运算和代数上的意义,故而将其独立作为一个类。

数组的索引,类似矩阵,只是维数不同。不同维度的索引,中间用逗号隔开。

a[2, 1, 4] # 第1为第2个切片,第2为的第1个切片,第3为的第4个切片,三个维度联合定位的元素 
## [1] 20
a[1:2, c(2,3), c(1,4,5)] # 第1为第1到2个切片,第2为的第2和第3个切片,第3为的第1第4和第5个切片
## , , 1
## 
##      [,1] [,2]
## [1,]    3    5
## [2,]    4    6
## 
## , , 2
## 
##      [,1] [,2]
## [1,]   21   23
## [2,]   22   24
## 
## , , 3
## 
##      [,1] [,2]
## [1,]   27   29
## [2,]   28   30

同样可以使用名称索引,如果有名称属性的话。

每个维度的索引也可以使用对应的逻辑向量,其长度与对应的维数相同。

subset()head()/tail()函数,虽然可以对应用在数据上,但并无实际上的意义。

1.4 数据框 data.frame

数据框是R语言中的一个种表格结构,对应于数据库中的表,类似Excel中的数据表。数据框的是由多个向量构成,每个向量的长度相同。

数据框类似于矩阵,也是一个二维表结构。

在统计学术语中,用来表示观测(observations),用来表示变量(variables)

类似于数据库系统,代表数据表的记录(records),代表数据表的字段(fields)

针对数据框来说,可能会在不同的情景下使用观测记录这几个名称,他们指代的含义相同;类似的,也可能会在不同的情景下使用变量字段这几个名称,他们指代的也含义相同;并不会再特别说明,怎么适合表达就怎么用。

1.4.1 创建数据框

创建数据框,最简单的方法就是用同名的定义函数 data.frame(),输入每个变量的名称及对应的向量,每个向量的长度相同。

针对示例过程中创建的15名员工信息的向量,将其组合成一个员工信息表:

# 当参数较多时,可以换行书写,使得函数结构更为清晰
employee <- data.frame(name = name,
                       height = height,
                       weight = weight,
                       islocal = islocal,
                       gender = gender,
                       grade = grade,
                       birthday = birthday
                       )
print(employee) # 打印数据框时,会在屏幕中显示行的序号和变量名称
##      name height weight islocal gender grade   birthday
## 1  宋子启   1.73     73    TRUE     男    中 1984-02-28
## 2  张伯仲   1.68     70    TRUE     男    良 1988-09-26
## 3  孟轲舆   1.72     68   FALSE     男    良 1989-07-28
## 4    张伟   1.65     60    TRUE     男    中 1990-01-25
## 5  王雪梅   1.66     55   FALSE     女    优 1987-04-30
## 6  陈梦妍   1.62     52   FALSE     女    良 1989-12-20
## 7  李元礼   1.81     80   FALSE     男    中 1992-06-14
## 8  杨伯侨   1.74     75    TRUE     男    优 1991-07-01
## 9  赵蜚廉   1.78     78   FALSE     男    中 1990-08-08
## 10   蒋欣   1.71     54   FALSE     女    良 1985-05-10
## 11 沈约度   1.72     61    TRUE     男    良 1993-04-01
## 12 陈淮阳   1.69     62    TRUE     男    中 1991-03-05
## 13 况天佑   1.74     56   FALSE     男    良 1991-09-25
## 14 王珍珍   1.70     53    TRUE     女    中 1992-01-31
## 15 马小玲   1.72     82    TRUE     女    优 1988-02-14
class(employee) # 对象的类,数据框类的名称为 data.frame
## [1] "data.frame"
is.data.frame(employee) # 判断一个对象是否为数据框
## [1] TRUE

变量的名称可以自定义,只要符合R语言对象命名规则即可,上面的自理正好使用和已有向量相同的名字而已。例如下面创建的数据框,变量的名字是任意给定的:

df <- data.frame(a = c("A", "B", "C", "A", "A", "B"), b = c(-0.33, 0.07, -0.40, 0.77, 0.24, 1.07))
print(df)
##   a     b
## 1 A -0.33
## 2 B  0.07
## 3 C -0.40
## 4 A  0.77
## 5 A  0.24
## 6 B  1.07

1.4.2 数据框的属性

数据框是二维的数据表,故而继承了很多矩阵的属性和计算函数。

dim(employee) # 维度属性,行数和列数,也就是观测数和变量数
## [1] 15  7
nrow(employee) # 行数,也就是观测数,记录数
## [1] 15
ncol(employee) # 列数,也就是变量数,字段数
## [1] 7
rownames(employee) # 行名称,如果没有命名则返回行序号向量
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14" "15"
colnames(employee) # 列名称,返回变量名称;数据框中变量名称是必须指定的
## [1] "name"     "height"   "weight"   "islocal"  "gender"   "grade"    "birthday"
row.names(employee) # 行的名称,数据框自己定义的属性,与 rownames 相同
##  [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10" "11" "12" "13" "14" "15"
names(employee) # 变量名称,数据框自己定义的属性,与 colnames 相同
## [1] "name"     "height"   "weight"   "islocal"  "gender"   "grade"    "birthday"
# 数据框中变量名称更为重要,故而直接用 names() 函数返回,更为便捷

1.4.2.1 数据框的合并

df_1 <- data.frame(V1 = 1:2, V2 = c("A","B")) ; print(df_1) # 两个语句之间可以用`;`隔开就可以写在一行中
##   V1 V2
## 1  1  A
## 2  2  B
print(df_2 <- data.frame(V1 = 3:3, V2 = c("C","D"))) # 赋值语句结束后将该表达式的结果打印出来
##   V1 V2
## 1  3  C
## 2  3  D
rbind(df_1, df_2)
##   V1 V2
## 1  1  A
## 2  2  B
## 3  3  C
## 4  3  D
df_3 <- data.frame(V3 = c(95, 88), V4 = c("Actor", "Farmer"))
print(df_3) # 这才是比较合适的书写规范,一行语句执行一个命令;以上两种写法均可,但不推荐
##   V3     V4
## 1 95  Actor
## 2 88 Farmer
cbind(df_1, df_3)
##   V1 V2 V3     V4
## 1  1  A 95  Actor
## 2  2  B 88 Farmer

1.4.3 数据结构与数据汇总

str()可以快速显示一个对象的结构。

对数据框来说, str()返回多个信息,包含:类名称;观测个数和变量个数;每个变量也就是向量的名称,及其类型,和前10个值;如果每个变量是因子向量,则返回其水平,及水平映射的整数值。

str()能显示整个数据框的数据结构,非常实用。

str(employee)
## 'data.frame':    15 obs. of  7 variables:
##  $ name    : chr  "宋子启" "张伯仲" "孟轲舆" "张伟" ...
##  $ height  : num  1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 ...
##  $ weight  : num  73 70 68 60 55 52 80 75 78 54 ...
##  $ islocal : logi  TRUE TRUE FALSE TRUE FALSE FALSE ...
##  $ gender  : Factor w/ 2 levels "男","女": 1 1 1 1 2 2 1 1 1 2 ...
##  $ grade   : Ord.factor w/ 4 levels "差"<"中"<"良"<..: 2 3 3 2 4 3 2 4 2 3 ...
##  $ birthday: Date, format: "1984-02-28" "1988-09-26" ...

summary()函数,可以快速显示一个对象的汇总结果。

对数据框来说,返回每个变量的汇总结果:

  • 对因子向量,返回每个因子的水平及计数结果(个数);只显示前6个,剩下的显示为(Other)
  • 对数值向量,返回5分位数及平均值,分别是
    • Min. :最小值
    • 1st Qu.:四分之一分位数
    • Median :中位数
    • Mean :算术平均值
    • 3rd Qu.:四分之三分位数
    • Max. :最大值
  • 对逻辑向量,返回其模式(mode), TRUE 和 FALSE 的个数,缺失值的个数
  • 对字符向量,返回每个唯一字符的个数,只显示前6个,剩下的显示为(Other)

查看返回数据的结果汇总,就能对数据的概括有个大致的了解。

summary(employee)
##      name               height          weight       islocal        gender 
##  Length:15          Min.   :1.620   Min.   :52.00   Mode :logical   男:10  
##  Class :character   1st Qu.:1.685   1st Qu.:55.50   FALSE:7         女: 5  
##  Mode  :character   Median :1.720   Median :62.00   TRUE :8                
##                     Mean   :1.711   Mean   :65.27                          
##                     3rd Qu.:1.735   3rd Qu.:74.00                          
##                     Max.   :1.810   Max.   :82.00                          
##  grade     birthday         
##  差:0   Min.   :1984-02-28  
##  中:6   1st Qu.:1988-06-05  
##  良:6   Median :1990-01-25  
##  优:3   Mean   :1989-09-27  
##         3rd Qu.:1991-08-13  
##         Max.   :1993-04-01

1.4.4 访问数据框变量

一个数据框可能包含多个变量(向量),有时需要单独提取某个变量,使用$特殊的符号来访问,由数据框$变量名构成。

employee$name # 访问 employee 数据框中名为 name 的变量;结果就是一个向量
##  [1] "宋子启" "张伯仲" "孟轲舆" "张伟"   "王雪梅" "陈梦妍" "李元礼" "杨伯侨"
##  [9] "赵蜚廉" "蒋欣"   "沈约度" "陈淮阳" "况天佑" "王珍珍" "马小玲"
employee$height 
##  [1] 1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 1.72 1.69 1.74 1.70 1.72
employee$weight
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82
employee$islocal
##  [1]  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE  TRUE  TRUE
## [13] FALSE  TRUE  TRUE
employee$gender
##  [1] 男 男 男 男 女 女 男 男 男 女 男 男 男 女 女
## Levels: 男 女

增加一个变量,只需要将一个等长的向量赋值给数据框$新变量名即可

employee$bmi <- employee$weight/(employee$height^2)
str(employee)
## 'data.frame':    15 obs. of  8 variables:
##  $ name    : chr  "宋子启" "张伯仲" "孟轲舆" "张伟" ...
##  $ height  : num  1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 ...
##  $ weight  : num  73 70 68 60 55 52 80 75 78 54 ...
##  $ islocal : logi  TRUE TRUE FALSE TRUE FALSE FALSE ...
##  $ gender  : Factor w/ 2 levels "男","女": 1 1 1 1 2 2 1 1 1 2 ...
##  $ grade   : Ord.factor w/ 4 levels "差"<"中"<"良"<..: 2 3 3 2 4 3 2 4 2 3 ...
##  $ birthday: Date, format: "1984-02-28" "1988-09-26" ...
##  $ bmi     : num  24.4 24.8 23 22 20 ...

1.4.5 数据框的长度与类型

数据框可以由多个不同的向量组成,故而其 长度length()模式mode() 属性没太大的意义。

length(employee)
## [1] 8

增加一个变量,只需要将一个等长的向量赋值给数据框$新变量名即可

employee$bmi <- employee$weight/(employee$height^2) 
str(employee)
## 'data.frame':    15 obs. of  8 variables:
##  $ name    : chr  "宋子启" "张伯仲" "孟轲舆" "张伟" ...
##  $ height  : num  1.73 1.68 1.72 1.65 1.66 1.62 1.81 1.74 1.78 1.71 ...
##  $ weight  : num  73 70 68 60 55 52 80 75 78 54 ...
##  $ islocal : logi  TRUE TRUE FALSE TRUE FALSE FALSE ...
##  $ gender  : Factor w/ 2 levels "男","女": 1 1 1 1 2 2 1 1 1 2 ...
##  $ grade   : Ord.factor w/ 4 levels "差"<"中"<"良"<..: 2 3 3 2 4 3 2 4 2 3 ...
##  $ birthday: Date, format: "1984-02-28" "1988-09-26" ...
##  $ bmi     : num  24.4 24.8 23 22 20 ...

1.4.6 数据框索引与筛选

类似与矩阵,数据框也用类似的方法索引和筛选子集,包括整数位置、名称属性、逻辑向量索引,但最常用的是subset()head()函数。

subset()常用是因为可以复合多个逻辑表达式条件。

head()常用是因为通常数据表的行数很大,直接打印所有的行会使控制台刷屏,多数时候只需看数据库的前几行即可,结合 str() 查看数据结构、summary() 查看数据汇总情况。

# 前3名员工的身高和体重
employee[1:3, c("height", "weight")] 
##   height weight
## 1   1.73     73
## 2   1.68     70
## 3   1.72     68
# employee$islocal 是逻辑向量,第2个维度就是变量,不限制条件就是选中所有变量
employee[employee$islocal,] 
##      name height weight islocal gender grade   birthday      bmi
## 1  宋子启   1.73     73    TRUE     男    中 1984-02-28 24.39106
## 2  张伯仲   1.68     70    TRUE     男    良 1988-09-26 24.80159
## 4    张伟   1.65     60    TRUE     男    中 1990-01-25 22.03857
## 8  杨伯侨   1.74     75    TRUE     男    优 1991-07-01 24.77210
## 11 沈约度   1.72     61    TRUE     男    良 1993-04-01 20.61925
## 12 陈淮阳   1.69     62    TRUE     男    中 1991-03-05 21.70792
## 14 王珍珍   1.70     53    TRUE     女    中 1992-01-31 18.33910
## 15 马小玲   1.72     82    TRUE     女    优 1988-02-14 27.71769
# select 参数不选择就是所有变量
subset(employee, employee$height > 1.7) 
##      name height weight islocal gender grade   birthday      bmi
## 1  宋子启   1.73     73    TRUE     男    中 1984-02-28 24.39106
## 3  孟轲舆   1.72     68   FALSE     男    良 1989-07-28 22.98540
## 7  李元礼   1.81     80   FALSE     男    中 1992-06-14 24.41928
## 8  杨伯侨   1.74     75    TRUE     男    优 1991-07-01 24.77210
## 9  赵蜚廉   1.78     78   FALSE     男    中 1990-08-08 24.61810
## 10   蒋欣   1.71     54   FALSE     女    良 1985-05-10 18.46722
## 11 沈约度   1.72     61    TRUE     男    良 1993-04-01 20.61925
## 13 况天佑   1.74     56   FALSE     男    良 1991-09-25 18.49650
## 15 马小玲   1.72     82    TRUE     女    优 1988-02-14 27.71769
# subset 逻辑表达式可以由多个逻辑表达式的逻辑运算结果构成,所以可以有多个条件的筛选
subset(employee, employee$height > 1.7 & employee$weight > 65, select = c("name", "gender", "bmi")) 
##      name gender      bmi
## 1  宋子启     男 24.39106
## 3  孟轲舆     男 22.98540
## 7  李元礼     男 24.41928
## 8  杨伯侨     男 24.77210
## 9  赵蜚廉     男 24.61810
## 15 马小玲     女 27.71769
head(employee) # 显示前6个元素
##     name height weight islocal gender grade   birthday      bmi
## 1 宋子启   1.73     73    TRUE     男    中 1984-02-28 24.39106
## 2 张伯仲   1.68     70    TRUE     男    良 1988-09-26 24.80159
## 3 孟轲舆   1.72     68   FALSE     男    良 1989-07-28 22.98540
## 4   张伟   1.65     60    TRUE     男    中 1990-01-25 22.03857
## 5 王雪梅   1.66     55   FALSE     女    优 1987-04-30 19.95936
## 6 陈梦妍   1.62     52   FALSE     女    良 1989-12-20 19.81405

1.5 列表 list

列表是R语言基本数据结构中最为复杂的一种,主要作为两种用途:

  • 第一:作为非结构化存储的数据类型,类似于Jason,并且可以同时包含不同的类型的数据,比如字符和数值;

  • 第二:作为对象的集合,可以包含多个不同数据对象,比如向量、矩阵、数据库,甚至还可以包含图像、表达式、函数等对象。

1.5.1 多种类型的复合数据

比如在员工信息表中,我们将每个员工的信息存储在一个对象中,由于该信息包含了多种不同的模式,故而不能用向量。

e_list <- list(name = "宋子启", height = 1.73, weight = 73, islocal = TRUE)
print(e_list)
## $name
## [1] "宋子启"
## 
## $height
## [1] 1.73
## 
## $weight
## [1] 73
## 
## $islocal
## [1] TRUE

在这个列表 e_list 中,list()函数定义了一个列表,有多个对象构成(又称为组件),每个对象代表中包含了一个对应的信息。

虽然看起来和向量的定义有些类似,但最大的区别是列表中各元素的模式是可以不同的。

将这个 e_list 的信息扩充,每个对象可以包含多个信息,且每个对象中的元素个数相同的话,则可以转为数据框。

实际上,数据框是列表的特殊形式。

e_list <- list(name = c("宋子启","张伯仲"), height = c(1.73, 1.68), weight = c(73, 70), islocal = c(TRUE, TRUE))
print(e_list)
## $name
## [1] "宋子启" "张伯仲"
## 
## $height
## [1] 1.73 1.68
## 
## $weight
## [1] 73 70
## 
## $islocal
## [1] TRUE TRUE
# 直接转为数据框
as.data.frame(e_list)
##     name height weight islocal
## 1 宋子启   1.73     73    TRUE
## 2 张伯仲   1.68     70    TRUE

1.5.2 非结构化的数据存储方式

列表还有一个特点是可以嵌套列表对象,则可使得非结构化的数据仓储变得可能。

在员工信息表中,可以存储技能特长这类数据。由于具体的技能数量是未确定的,且是多选项问题,可以用非结构化的数据格式来存储较为方便(实际上非结构化数据是可以转化为结构化数据的,但如果非结构关系较为负责,这种转化的过程和记录各结构化之间关系的问题反而可能更负责,还不如直接存储非结构化关系更为方便)。

e001 <- list(name = "宋子启", height = 1.73, weight = 73, islocal = TRUE, skills = c("SQL", "R"))
e002 <- list(name = "张伯仲", height = 1.68, weight = 70, islocal = TRUE, skills = c("SQL", "R", "Python"))

# list 中包含 list
e_list <- list(e001 = e001, e002 = e002)

# 查看 list 的结构使用 str() 更为方便
str(e_list)
## List of 2
##  $ e001:List of 5
##   ..$ name   : chr "宋子启"
##   ..$ height : num 1.73
##   ..$ weight : num 73
##   ..$ islocal: logi TRUE
##   ..$ skills : chr [1:2] "SQL" "R"
##  $ e002:List of 5
##   ..$ name   : chr "张伯仲"
##   ..$ height : num 1.68
##   ..$ weight : num 70
##   ..$ islocal: logi TRUE
##   ..$ skills : chr [1:3] "SQL" "R" "Python"

1.5.3 对象的集合

列表作为对象的集合,最为典型的场景是线性回归模型的结果存储在列表中。

# 以自变量为 height 因变量为 weight 的简单线性回归模型 lm(y ~ x)
# 将模型的结果存储在 m_list 这个对象中
m_list <- lm(weight ~ height)

# 线性回归模型会返回非常多的信息,会存在不同对象中,故而使用 list 来存储相对合适
summary(m_list)
## 
## Call:
## lm(formula = weight ~ height)
## 
## Residuals:
##      Min       1Q   Median       3Q      Max 
## -13.4003  -4.1905   0.5059   4.3095  15.4836 
## 
## Coefficients:
##             Estimate Std. Error t value Pr(>|t|)   
## (Intercept)  -181.50      76.65  -2.368   0.0341 * 
## height        144.20      44.77   3.221   0.0067 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Residual standard error: 8.13 on 13 degrees of freedom
## Multiple R-squared:  0.4438, Adjusted R-squared:  0.401 
## F-statistic: 10.37 on 1 and 13 DF,  p-value: 0.006697

在日常自定义函数中,如果函数返回的结果包含多种数据(多个对象),将其存储在一个列表中也是最为方便的。

1.5.4 列表子集筛选

如果列表对象是有名称属性的,也就是各个组件是有标签的,则可以直接使用 $ 对象的名字提取该子对象。

将列表作为对象的集合多数属于这种情况,每个子对象通常都有名字便于提取。

# 查看对象的名称属性
names(m_list)
##  [1] "coefficients"  "residuals"     "effects"       "rank"         
##  [5] "fitted.values" "assign"        "qr"            "df.residual"  
##  [9] "xlevels"       "call"          "terms"         "model"
# 查看回归模型的残差
m_list$residuals
##            1            2            3            4            5            6 
##   5.04164982   9.25151638   1.48362313   3.57743631  -2.86453700  -0.09664375 
##            7            8            9           10           11           12 
##   0.50586332   5.59967651   2.83178326 -11.07440356  -5.51637687  -0.19045693 
##           13           14           15 
## -13.40032349 -10.63243025  15.48362313
names(e_list)
## [1] "e001" "e002"
e_list$e002
## $name
## [1] "张伯仲"
## 
## $height
## [1] 1.68
## 
## $weight
## [1] 70
## 
## $islocal
## [1] TRUE
## 
## $skills
## [1] "SQL"    "R"      "Python"

如果对象没有名称属性,可使用位置索引,与向量类使用[]类似,但子对象的提取需要使用[[]]

m_list[[2]] # 等价于 m_list$residuals 因为 residuals 是第2个子对象
##            1            2            3            4            5            6 
##   5.04164982   9.25151638   1.48362313   3.57743631  -2.86453700  -0.09664375 
##            7            8            9           10           11           12 
##   0.50586332   5.59967651   2.83178326 -11.07440356  -5.51637687  -0.19045693 
##           13           14           15 
## -13.40032349 -10.63243025  15.48362313

[]通常用来提取子集,返回的结果与与对象具有相同的类型,是一种子结构。该操作符可以用在列表上,但返回的是列表结构。

m_list[2]
## $residuals
##            1            2            3            4            5            6 
##   5.04164982   9.25151638   1.48362313   3.57743631  -2.86453700  -0.09664375 
##            7            8            9           10           11           12 
##   0.50586332   5.59967651   2.83178326 -11.07440356  -5.51637687  -0.19045693 
##           13           14           15 
## -13.40032349 -10.63243025  15.48362313
str(m_list[[2]]) # 返回第二个子对象,是一个向量
##  Named num [1:15] 5.04 9.25 1.48 3.58 -2.86 ...
##  - attr(*, "names")= chr [1:15] "1" "2" "3" "4" ...
str(m_list[2]) # 返回原列表的第二个子列表,还是一个列表
## List of 1
##  $ residuals: Named num [1:15] 5.04 9.25 1.48 3.58 -2.86 ...
##   ..- attr(*, "names")= chr [1:15] "1" "2" "3" "4" ...

[[]]提取子对象后,如果是向量,还可以继续使用[]来提取子元素来定位结果的元素;如果是矩阵或数据框还可以用[,]定位子集,如果是列表,依然可以用 [[]] 来继续提取子集。

m_list[[2]][2]  # 第二个子对象的第二个元素
##        2 
## 9.251516
str(m_list[[2]]) # 返回第二个子对象,是一个向量
##  Named num [1:15] 5.04 9.25 1.48 3.58 -2.86 ...
##  - attr(*, "names")= chr [1:15] "1" "2" "3" "4" ...
str(m_list[2]) # 返回原列表的第二个子列表,还是一个列表
## List of 1
##  $ residuals: Named num [1:15] 5.04 9.25 1.48 3.58 -2.86 ...
##   ..- attr(*, "names")= chr [1:15] "1" "2" "3" "4" ...

1.6 特殊值 Special Values

为确保所有数据都能被正确识别、计算或统计等,R语言定义了一些特殊值数据:

  • NULL:空值,什么都没有
  • NA:Not Available 的缩写,更多是时候被称作 Missing value,也就是缺失值;有数据值,但具体是什么却不知道
  • NaN:Not a Number 的缩写,表示非数值
  • Inf:positive infinity,正无穷大
  • -Inf:negative infinity,负无穷大

前两者可以包含在任意类型的向量中,后三者是数值型向量。

1.6.1 空值 NULL

NULL 代表一个空对象,通常作为表达式的返回结果(也就是什么都不返回),或者作为函数中未定义的参数值。

as.null(x) 是转化函数,is.null(x) 是判断函数。

NULL 作为向量中的值时,表示什么都没有,在打印过程中的不不会被显示,都不占用一个元素位置。

# 只打印值 1
print(c(1, NULL))
## [1] 1
# 合并后长度只有 1
length(c(1, NULL))
## [1] 1

1.6.2 缺失值 NA

缺失值是R语言中非常重要的概念,且有专门的缺失值处理方法,这里只介绍几本的概念。

缺失值 NA 与 SQL 中的 NULL 概念相似,表示应该有值,但目前缺失。

NA 作为向量中的值,可以包含在任意类型中。不同与 NULL,缺失值是占位一个向量元素的。

NA 作为一个独立的向量时,其类型为逻辑型。

# NA 作为一个值会被打印出来
print(c(1, NA))
## [1]  1 NA
# NA 作为一个向量的元素是包含在长度中的
length(c("test", NA))
## [1] 2
# NA 的模式(类型)为逻辑型
mode(NA)
## [1] "logical"

将向量中的某些值改变为NA,等价于将 NA 赋值给指定的元素。

# 将向量中的某些值改变为NA
test_weight <- weight
test_weight[c(3,9)] <- NA
print(test_weight)
##  [1] 73 70 NA 60 55 52 80 75 NA 54 61 62 56 53 82

在提取向量子集时,如果索引下标超过向量长度,则返回的结果为 NA。

test_weight <- weight
test_weight[c(31,99)]
## [1] NA NA
print(test_weight)
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82

在给向量赋值时,如果赋值的元素超过原本向量的长度,则中间未定义赋值的元素,其值为NA。

用这种向量赋值方式,可以预先定义数据结构,或者增加向量长度。

test_weight <- weight
test_weight[c(30)] <- NA
print(test_weight)
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82 NA NA NA NA NA NA NA NA NA NA
## [26] NA NA NA NA NA
test_weight <- weight
test_weight[c(30)] <- 66
print(test_weight)
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82 NA NA NA NA NA NA NA NA NA NA
## [26] NA NA NA NA 66

is.na(x) 函数用来判断向量中的每个元素是否为 NA,返回长度相同的逻辑向量。

结合元素子集筛选函数可以提取非缺失值的部分,或者将缺失值替换为某个值或者表达式结果。

# 提取非缺失值子集
test_weight <- weight
test_weight[c(30)] <- 66
test_weight[!is.na(test_weight)]
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82 66
# 将缺失值替换为非缺失值的均值
test_weight <- weight
test_weight[c(30)] <- 66
test_weight[!is.na(test_weight)]
##  [1] 73 70 68 60 55 52 80 75 78 54 61 62 56 53 82 66
test_weight[is.na(test_weight)] <- mean(test_weight, na.rm = TRUE)
print(test_weight)
##  [1] 73.0000 70.0000 68.0000 60.0000 55.0000 52.0000 80.0000 75.0000 78.0000
## [10] 54.0000 61.0000 62.0000 56.0000 53.0000 82.0000 65.3125 65.3125 65.3125
## [19] 65.3125 65.3125 65.3125 65.3125 65.3125 65.3125 65.3125 65.3125 65.3125
## [28] 65.3125 65.3125 66.0000

anyNA(x) 函数用来判断对象中是否包含 NA 元素,返回一个逻辑值。

test_weight <- weight
test_weight[c(30)] <- 66
# 返回是否包含缺失值
anyNA(test_weight)
## [1] TRUE

关于缺失值的处理函数中,经常会遇一个名为 na.rm 的参数,用来选择是否排除缺失值。

比如在求均值的函数 mean() 中,参数 na.rm 默认为 FALSE:如果数值型向量包含 NA,则其均值返回 NA;如果 na.rm 设置为 TRUE 则会在求均值中忽略 NA,得到数值结果的平均值。

# 函数中是否移除缺失值
test_weight <- weight
test_weight[c(30)] <- 66
mean(test_weight) # 默认 na.rm = FALSE
## [1] NA
mean(test_weight, na.rm = TRUE)
## [1] 65.3125

还有一个常用的缺失值处理函数 na.omit(),用来移除包含缺失值所在的行记录,多用在 data.frame 中。

# 函数中是否移除缺失值
test_employee <- employee
test_employee$height[6] <- NA
test_employee$weight[8] <- NA

# test_employee 中第6和8行包含有NA元素,先这两行都被移除
na.omit(test_employee) # 结果中不含任何包含 NA 元素的行
##      name height weight islocal gender grade   birthday
## 1  宋子启   1.73     73    TRUE     男    中 1984-02-28
## 2  张伯仲   1.68     70    TRUE     男    良 1988-09-26
## 3  孟轲舆   1.72     68   FALSE     男    良 1989-07-28
## 4    张伟   1.65     60    TRUE     男    中 1990-01-25
## 5  王雪梅   1.66     55   FALSE     女    优 1987-04-30
## 7  李元礼   1.81     80   FALSE     男    中 1992-06-14
## 9  赵蜚廉   1.78     78   FALSE     男    中 1990-08-08
## 10   蒋欣   1.71     54   FALSE     女    良 1985-05-10
## 11 沈约度   1.72     61    TRUE     男    良 1993-04-01
## 12 陈淮阳   1.69     62    TRUE     男    中 1991-03-05
## 13 况天佑   1.74     56   FALSE     男    良 1991-09-25
## 14 王珍珍   1.70     53    TRUE     女    中 1992-01-31
## 15 马小玲   1.72     82    TRUE     女    优 1988-02-14
# 函数中是否移除缺失值
test_weight <- weight
test_weight[c(30)] <- 66
mean(test_weight) # 默认 na.rm = FALSE
## [1] NA
mean(test_weight, na.rm = TRUE)
## [1] 65.3125

1.6.3 非数值 NaN

在数值计算过程中,可能会产生无意义的值,为了使得计算不中断,在 R 中预先定义了 NaN 的特殊值,作为无效数值的表示。

比如 0/0 是没有意义的;还有初等函数中自变量的值不在定义域范围内则函数结果也是无意义的,比如对数函数中自变量小于0也是没有意义的。

当结果产生 NaN 时,控制台会打印一条警告信息“产生了NaNs”(不会中断运行过程,实际上是运行成功后才会显示该消息)。

# 典型会产生 NaN 的情况
0/0 # 分子和分母均为 0 的结果无意义
## [1] NaN
log(-1) # 对数函数在定义域 > 0 的情况下才有意义
## Warning in log(-1): NaNs produced
## [1] NaN

is.nan(x) 是判断非数值的函数,返回对象中元素是否为 NaN 的等长逻辑向量。

is.nan(c(0/0, log(-1)))
## Warning in log(-1): NaNs produced
## [1] TRUE TRUE

NaN 是一种特殊的 NA 值,可以用 is.na() 来验证。

is.na(c(0/0, log(-1)))
## Warning in log(-1): NaNs produced
## [1] TRUE TRUE

1.6.4 无穷大/无穷小 Inf /-Inf

数学中的无穷大和无穷小是一种特殊的数值,不同与 NaN,他们是有意义的数值,故而有特殊的符号表示, Inf 为正无穷大,-Inf 为负无穷小。

# 典型的无穷大和无穷小数值
1/0  # 分子为正,分母为 0 数值为无穷大
## [1] Inf
-1/0  #分子为负,分母为 0 数值为无穷小
## [1] -Inf

Inf 是 infinite 的缩写,表示无穷尽的,无限大的。判读一个数值是否为无穷大或者无穷小,使用 is.infinite() 函数。

# 1/0 和 -1/0 是 Inf  和 -Inf
# 0/0 是 NaN
#  1, -1 是正常数值
is.infinite(c(1/0, -1/0, 0/0, 1, -1))
## [1]  TRUE  TRUE FALSE FALSE FALSE

相对地,判断数值有限大小的函数,就是 is.finite()

# 1/0 和 -1/0 是 Inf  和 -Inf
# 0/0 是 NaN
#  1, -1 是正常数值
is.finite(c(1/0, -1/0, 0/0, 1, -1)) 
## [1] FALSE FALSE FALSE  TRUE  TRUE

Inf 和 -Inf,既不是 NaN,也不是 NA。

# 1/0 和 -1/0 是 Inf  和 -Inf
# 0/0 是 NaN
#  1, -1 是正常数值
is.nan(c(1/0, -1/0, 0/0, 1, -1)) 
## [1] FALSE FALSE  TRUE FALSE FALSE
is.na(c(1/0, -1/0, 0/0, 1, -1)) 
## [1] FALSE FALSE  TRUE FALSE FALSE

既然 Inf 和 -Inf 分别是正无穷大和负无穷小,则说明两者的大小在比较的时候是不同的。

# 无穷大的结果 大于 无穷小的结果
(1/0) > (-1/0) 
## [1] TRUE
# 无穷大 大于 无穷小
Inf > -Inf
## [1] TRUE
# 无穷大 大于 所有有限数值
Inf > 0
## [1] TRUE
# 一个无穷大并不会大于另一个无穷大
(2/0) > (1/0) 
## [1] FALSE
# 两个无穷大的大小比较结果是相等
(2/0) == (1/0) 
## [1] TRUE
# 无穷大与非数值比较结果为 NA
# NaN 也是 NA
# 任何值与 NA 比大小的结果都是 NA
Inf > NaN
## [1] NA
# 无穷大与 NA 比大小的结果都是 NA
Inf > NA
## [1] NA

is.infinite() 函数只是判断一个数值是否为无穷尽的,至于该数值是无穷大还是无穷大,则并未给出对应的函数。

可以结合 Inf 和 -Inf 的大小比较来辅助判断。

# 自定义一个函数
# 先判断对象 x 是否为无穷尽的数值
#    如果是则将 x 值与 0 做比较,
#        x > 0 为 TRUE,则 x 为无穷大
#        x > 0 为 FALSE,则 x 为无穷小
# 如果 x 不是无穷尽的数值,则返回结果为 NA
is.infinite.positive <- function(x) { # 输入的形式参数为 x
  ifelse(is.infinite(x), x > 0, NA) # 返回该表达式的结果
}

# 在函数中输入 x 的值即返回该函数的计算结果
is.infinite.positive(1/0)
## [1] TRUE
is.infinite.positive(-1/0)
## [1] FALSE
is.infinite.positive(0/0)
## [1] NA

1.7 数据模式、类型与对象的类辨析

mode, typeof, class 的区别

知乎:R语言中,mode(模式)和class(类)有何区别?

LFGxiaogang的博客:R语言:mode和class的区别

1.7.1 数据模式、类型

1.7.2 对象的类

1.7.3 基本数据结构的联系与区别

1.7.3.1 向量与矩阵和数组

1.7.3.2 向量与列表

1.7.3.3 列表与数据框

1.7.3.4 矩阵和数据框

1.7.3.5 列表与向量