R语言教程-R数据类型与运算2

11. R矩阵和数组

矩阵用matrix函数定义,实际存储成一个向量,根据保存的行数和列数对应到矩阵的元素, 存储次序为按列存储。

A <- matrix(11:16,nrow=3, ncol=2)
A
# 取第1、3行
A[c(1,3), 1:2]
# 取第2列
A[1:3,2]
# 其他相等的写法,取第1、3行
A[c(1,3),c(1,2)]
# 取第2列
A[c(1,2,3),c(2)]

用colnames()函数可以给矩阵每列命名, 也可以访问矩阵列名, 用rownames()函数可以给矩阵每行命名, 也可以访问矩阵行名。

colnames(A) <- c('第一列','第二列')
rownames(A) <- c("第一行","第二行","第三行")
A
# 矩阵A的属性
attributes(A)

dim(A)
dimnames(A)
# 可以用列名和行名代替矩阵下标
A[,"第二列"]
A[,2]

注意在对矩阵取子集时, 如果取出的子集仅有一行或仅有一列, 结果就不再是矩阵而是变成了R向量, R向量既不是行向量也不是列向量。 如果想避免这样的规则起作用, 需要在方括号下标中加选项drop=FALSE

A[,1,drop=FALSE]
# 用逻辑下标取子集
A[,1]>=12
A[A[,1]>=12,1:2]

矩阵本质上是一个向量添加了dim属性, 实际保存还是保存成一个向量, 其中元素的保存次序是按列填入, 所以, 也可以向对一个向量取子集那样, 仅用一个正整数对向量的矩阵取子集。

A
A[c(1,3,5)]
A[c(2,4,6)]
matrix(c(1,1, 2,2, 3,2), ncol=2,byrow = T)

为了挑选矩阵的任意元素组成的子集而不是子矩阵, 可以用一个两列的矩阵作为下标, 矩阵的每行的两个元素分别指定一个元素的行号和列号。

rod <- matrix(c(1,1, 2,2, 3,2), ncol=2,byrow = T)
A
# 取矩阵中坐标为1,1, 2,2, 3,2的值
A[rod]
  • 用c(A)或A[]返回矩阵A的所有元素。
  • 如果要修改矩阵A的所有元素, 可以对A[]赋值。
  • diag(A)访问A的主对角线元素组成的向量。
  • 若x为长度大于1的向量, diag(x)返回以x的元素为主对角线元素的对角矩阵。
  • 若x为正整数值标量,diag(x)返回x阶单位阵
c(A)
A[]
diag(A)
diag(6)
diag(c(1,2,3,4,5,6))

cbind()和rbind()函数

x是向量,cbind(x)x变成列向量, 即列数为1的矩阵, rbind(x)x变成行向量。

若x1, x2, x3是等长的向量, cbind(x1, x2, x3)把它们看成列向量并在一起组成一个矩阵。 cbind()的自变量可以同时包含向量与矩阵,向量的长度必须与矩阵行数相等。

cbind(c(1,2), c(3,4), c(5,6))
A
A1 <- cbind(A,c(-3,6,-9))
colnames(A1) <- c("第一列","第二列","新增列")
A1

rbind()同理,向量的长度必须与矩阵列数相同(因为次函数下数字是按row来排列矩阵的)

rbind(c(1,2,3), c(4,5,6))

四则运算

  1. 矩阵可以与标量作四则运算,结果为每个元素进行相应运算
# 加法
A
C1 <- A + 2; C1
# 减法
c2 <- A - 3; c2

# 乘法
c3 <- A * 2; c3

# 除法
c4 <- A / 2; c4

  • 两个同形状的矩阵进行加、减运算, 即对应元素相加、相减, 用A + B,A - B表示

  • 对两个同形状的矩阵, 用*表示两个矩阵对应元素相乘(注意这不是线性代数中的矩阵乘法), 用/表示两个矩阵对应元素相除。

矩阵乘法

%*%表示矩阵乘法而不是用*表示, 注意矩阵乘法要求左边的矩阵的列数等于右边的矩阵的行数。

12. 数据框

数据框类似于一个矩阵,有行、列, 但各列允许有不同类型:数值型向量、因子、字符型向量、日期时间向量。 同一列的数据类型相同。

d <- data.frame(
姓名=c("李明", "张聪", "王建"),
年龄=c(30, 35, 28),
身高=c(180, 162, 175),
stringsAsFactors=FALSE)
d

data.frame()函数会将字符型列转换成因子, 加选项stringsAsFactors=FALSE可以避免这样的转换。

数据框每列叫做一个变量, 每列都有名字,称为列名或变量名, 可以用names()函数和colnames()函数访问。

names(d)
colnames(d)
# 修改列名
names(d)[1] <- "names"
names(d)

as.data.frame(x)可以把x转换成数据框。 如果x是一个向量, 转换结果是以x为唯一一列的数据框。 如果x是一个列表并且列表元素都是长度相同的向量, 转换结果中每个列表变成数据框的一列。 如果x是一个矩阵,转换结果把矩阵的每列变成数据框的一列。

数据框是一个随着R语言前身S语言继承下来的概念, 现在已经有一些不足之处, tibble包提供了tibble类, 这是数据框的一个改进版本。

数据框内容访问

# 数据框可以用矩阵格式访问
print(d)
# 访问单个元素
d[2,3]
# 访问第二列
d[2]
# 范文第二列,使其结果为向量
d[[2]]
# 或者d[,2],也访问第二列,但是这种作法与tibble不兼容, 所以应避免使用。

# 三种结果相同的访问列方法,其中第二种做法与tibble不兼容,应避免使用。
d[["names"]]
d[,"names"]
d$names

因为数据框的一行不一定是相同数据类型, 所以数据框的一行作为子集, 结果还是数据框,而不是向量。

# 取数据框的第一行
d[1,]
is.data.frame(d[1,])
# 可以同时取行子集和列子集
d[1:2,"names"]
d[1:2, c('names', '年龄')]
# 取年龄大于30的所有行和列,即前两行
d[d[['年龄']]>=30,]

与矩阵类似地是,用如d[,‘age’], d[,2]这样的方法取出的数据框的单个列是向量而不再是数据框。但是,如果取出两列或者两列以上, 结果则是数据框。
对一般的数据框, 可以在取子集的方括号内加上drop=FALSE选项, 确保取列子集的结果总是数据框。 数据框的改进类型tibble在取出列子集时保持为tibble格式。

t1 <- d[,"names"]
is.data.frame(t1)
t2 <- d[,"names",drop=F]
is.data.frame(t2)

数据框每一行可以有行名, 这在原始的S语言和传统的R语言中是重要的技术, 但是在改进类型tibble中则取消了行名, 需要用列名实现功能一般改用left_join()函数实现。
可以用数据中的某一列的每一行作为行名,如d数据康中可以用names(没有重复值)作为行名。

rownames(d) <- d$names
# d$name <- NULL
d

输出结果

     names 年龄 身高
李明 李明 30 180
张聪 张聪 35 162
王建 王建 28 175
rownames(d) <- d$names
d$names <- NULL
d

输出结果

     年龄 身高
李明 30 180
张聪 35 162
王建 28 175

用数据框的行名可以建立一个值到多个值的对应表。

dm <- data.frame(
"年级"=1:6,
'出游'=c(0, 2, 2, 2, 2, 1),
'疫苗'=c(T, F, F, F, T, F)
)

把年级变成行名,可以建立年级到出游次数与疫苗注射的对应表:

rownames(dm) <- dm[['年级']]
dm[["年级"]] <- NULL

假设某个社区的小学中抽取的4个班的年级为 c(2,1,1,3), 其对应的出游和疫苗注射信息可查询如下:

# 相当于将年级的那一项当做行来取对应的列值
dm[as.character(c(2,1,1,3)),]

输出结果包含了两个1

    出游  疫苗
2 2 FALSE
1 0 TRUE
1.1 0 TRUE
3 2 FALSE

可以去掉,以上程序改成:

x <- dm[as.character(c(2,1,1,3)),]
rownames(x) <- NULL
x

输出结果:

  出游  疫苗
1 2 FALSE
2 0 TRUE
3 0 TRUE
4 2 FALSE

对于代替数据框的tibble类型, 如果要实现行名的功能, 可以将行名作为单独的一列

数据框与矩阵的区别

数据框不能作为矩阵参加矩阵运算。 需要时,可以用as.matrix()函数转换数据框或数据框的子集为矩阵。

d2 <- as.matrix(d[,c("年龄", "身高")])
# 矩阵运算
d3 <- crossprod(d2); d3
d4 <- c(1,2,3) %*% d2;d4

gl()函数

d4 <- data.frame(
group=gl(3, 10, length=30),
subgroup=gl(5,2,length=30),
obs=gl(2,1,length=30))
print(d4)

结果的数据框d有三个变量: group是大组,共分3个大组,每组10个观测; subgroup是子组,在每个大组内分为5个子组,每个子组2个观测。 共有3x5x2=30个观测(行)。

gl()函数第一个参数是因子水平个数, 第二个参数是同一因子水平连续重复次数, 第三个参数是总共需要的元素个数, 所有水平都出现后则重复整个模式直到长度满足要求。

tibble类型

tibble类型是一种改进的数据框。 readr包的read_csv()函数是read.csv()函数的一个改进版本, 它将CSV文件读入为tibble类型,如文件class.csv的读入:

library(tibble)
library(readr)
t.class <- read_csv('class.csv')
t.class
class(t.class)

用as_tibble()可以将一个数据框转换为tibble.
可以用tibble()函数生成小的tibble,和生成data.frame的格式一样。如

t.b <- tibble(
"序号"=c(1,3,5,4,6,7),
`收缩压`=c(145, 110, "未测", 150, "拒绝", 115)
)
t.b

用tribble可以按类似于CSV格式输入一个tibble,

t.bp <- tribble(
~`序号`,~`收缩压`,
1,145,
5,110,
6,"未测",
9,150,
10,"拒绝",
15,115
)
t.bp

注意tribble()中数据每行末尾也需要有逗号, 最后一行末尾没有逗号。 这比较适用于在程序中输入小的数据集。

tibble与数据框的一大区别是在显示时不自动显示所有内容, 这样可以避免显示很大的数据框将命令行的所有显示都充满。 可以在print()用n=和width=选项指定要显示的行数和列数。

另外,用单重的方括号取列子集时, 即使仅取一列, 从tibble取出的一列结果仍是tibble而不是向量, 这时应使用双方括号格式或$格式。 因为这个原因有些原来的程序输入tibble会出错, 这时可以用as.data.frame()转换成数据框。

# tibble
t.bp[,"收缩压"]
# 向量
t.bp[["收缩压"]]
t.bp$"收缩压"
# 数据框
as.data.frame(t.bp$'收缩压')

tibble不使用行名, 需要行名时, 将其保存为tibble的一列。

练习

假设class.csv已经读入为R数据框d.class, 其中的sex列已经自动转换为因子。

library(tibble)
library(readr)
d.class <- read_csv('class.csv')
  1. 显示d.class中年龄至少为15的行子集;
x1 <- d.class[d.class["age"]>=15,]
x1
  1. 显示女生且年龄至少为15的学生姓名和年龄;
    注意:此处取sex=F时,表达式为x1$sex==“F”,两个等于号
x2 <- x1[x1$sex=="F",c("name","age")]
x2
  1. 取出数据框中的age变量赋给变量x。
x <- d.class$age
x