5.5 数据框
数据框用于存储类似电子表格的数据。它们既可被看作是每列可存储不同数据类型的矩阵,或是非嵌套的列表,其中每个元素具有相同的长度。
5.5.1 创建数据框
我们用data.frame
函数创建数据框:
(a_data_frame <- data.frame(
x = letters[1:5],
y = rnorm(5),
z = runif(5) > 0.5
))
## x y z
##1 a 2.3772326 FALSE
##2 b -1.2188022 TRUE
##3 c 1.3034011 FALSE
##4 d -0.5001624 TRUE
##5 e -1.5923465 FALSE
class(a_data_frame)
## [1] "data.frame"
请注意,每列的类型可与其他列不同,但在同一列中的元素类型必须相同。还要注意的是,对象的类名是data.frame
,中间有一个点而非空字符。
在此例中,行自动从一到五编号。如果输入的任何向量有名称,那么行名称就取自第一个向量名称。例如,如果y
列有命名,那么数据框中每行的名字将以y
列的向量名命名:
y <- rnorm(5)
names(y) <- month.name[1:5]
data.frame(
x = letters[1:5],
y = y,
z = runif(5) > 0.5
)
## x y z
##January a -0.9373 FALSE
##February b 0.7314 TRUE
##March c -0.3030 TRUE
##April d -1.3307 FALSE
##May e -0.6857 FALSE
这种命名规则可通过给data.frame
函数传入参数row.names = NULL
覆盖掉:
data.frame(
x = letters[1:5],
y = y,
z = runif(5) > 0.5,
row.names = NULL
)
## x y z
## a -0.9373 FALSE
## b 0.7314 TRUE
## c -0.3030 TRUE
## d -1.3307 FALSE
## e -0.6857 FALSE
另外,还可以通过给row.names
传入一个向量来为每行命名。这个向量将被转换为字符character
,如果它不是此类型的话:
data.frame(
x = letters[1:5],
y = y,
z = runif(5) > 0.5,
row.names = c("Jackie", "Tito", "Jermaine", "Marlon", "Michael")
)
## x y z
##Jackie a -0.9373 FALSE
##Tito b 0.7314 TRUE
##Jermaine c -0.3030 TRUE
##Marlon d -1.3307 FALSE
##Michael e -0.6857 FALSE
像矩阵一样,行的名称可通过使用rownames
(或row.names
)在后期进行检索或更改。同样地,通过使用colnames
和dimnames
可分别获取或设置列和维度的名称。事实上,几乎所有可用于矩阵的函数亦可用在数据框上。例如,nrow
、ncol
和dim
函数的使用与它们在矩阵中的用法一样:
rownames(a_data_frame)
## [1] "1" "2" "3" "4" "5"
colnames(a_data_frame)
## [1] "x" "y" "z"
dimnames(a_data_frame)
## [[1]]
## [1] "1" "2" "3" "4" "5"
##
## [[2]]
## [1] "x" "y" "z"
nrow(a_data_frame)
## [1] 5
ncol(a_data_frame)
## [1] 3
dim(a_data_frame)
## [1] 5 3
需注意两个奇怪之处。首先,length
将返回与ncol
相同的值,而不是数据框元素的总数。同样地,names
将返回与colnames
相同的值。为了使代码易于理解,我们建议你避开这两个函数而使用ncol
和colnames
:
length(a_data_frame)
## [1] 3
names(a_data_frame)
## [1] "x" "y" "z"
我们可以使用不同长度的向量来创建数据框,只要长度较短的向量能刚好循环至总长度。从技术上讲,所有向量长度的最小公倍数必须与最长向量的长度相等:
data.frame( # 长度1、2和 4是OK的
x = 1, # 循环4次
y = 2:3, # 循环2次
z = 4:7 # 最长的输入,不需要循环
)
## x y z
## 1 1 2 4
## 2 1 3 5
## 3 1 2 6
## 4 1 3 7
如果长度不兼容,将引发错误:
data.frame( # 长度1、2和3将导致错误
x = 1, # 最小公倍数是6, 比3大
y = 2:3,
z = 4:6
)
创建数据框时的另一个要点为:默认情况下,列名必须是唯一且有效的变量名称。此功能可通过给data.frame
传入check.names = FALSE
关闭:
data.frame(
"A column" = letters[1:5],
"!@#$%^&*()" = rnorm(5),
"..." = runif(5) > 0.5,
check.names = FALSE
)
## A column !@#$%^&*() ...
## 1 a -1.0050744 FALSE
## 2 b -1.3887884 FALSE
## 3 c 1.0953114 TRUE
## 4 d -1.3222524 FALSE
## 5 e -0.1395639 FALSE
一般情况下,使用非标准的列名并不好。复制列名则更糟糕,因为一旦你使用子集,它会导致难以发现的错误。另外,如果关闭列名检查,你将陷于险境。
5.5.2 索引数据框
有很多种不同的方式可用于索引数据框。首先,与矩阵索引的方式一样,可使用四种不同的向量索引(正整数、负整数、逻辑值和字符)。以下的命令将选择数据框中前两列的第二个和第三个元素:
a_data_frame[2:3, -3]
## x y
## 2 b 0.06894
## 3 c 0.74217
a_data_frame[c(FALSE, TRUE, TRUE, FALSE, FALSE), c("x", "y")]
## x y
## 2 b 0.06894
## 3 c 0.74217
因为选择了一个以上的列,所以得到的子集也是一个数据框。如果只选择一个列,其结果将被简化为一个向量:
class(a_data_frame[2:3, -3])
## [1] "data.frame"
class(a_data_frame[2:3, 1])
## [1] "factor"
如果我们只需选择一个列,那么也可以使用列表样式的索引(带有正整数或名称的双方括号,或者带有名称的美元符号运算符)。以下命令将选择第一列中的第二个和第三个元素:
a_data_frame$x[2:3]
## [1] b c
## Levels: a b c d e
a_data_frame[[1]][2:3]
## [1] b c
## Levels: a b c d e
a_data_frame[["x"]][2:3]
## [1] b c
## Levels: a b c d e
如果我们需要给列加上条件来得到一个数据框子集,使用的语法会有点冗长,而subset
函数能做同样的事情且更简洁。subset
需要三个参数:一个数据框,一个行的条件逻辑向量,以及一个需要保留的名字向量(如果最后这个参数被省略了,那么将保留所有列)。subset
的过人之处在于它使用了特殊的估算技巧以避免多余的操作:你无需输入a_data_frame$y
以访问a_data_frame
的第y
列,因为它已经知道要看哪个数据框,所以你只需键入y
。同样地,选择列时,你无需附上引号中列的名称,而是可以直接键入名称。在下例中,记得|
是逻辑or的操作符:
a_data_frame[a_data_frame$y > 0 | a_data_frame$z, "x"]
## [1] a b c d e
## Levels: a b c d e
subset(a_data_frame, y > 0 | z, x)
## x
## 1 a
## 2 b
## 3 c
## 4 d
## 5 e
5.5.3 基本数据框操作
像矩阵一样,数据框可使用t
函数进行转置。但在此过程中,所有的列(它们即将变成行)将被转换为相同的类型,然后将变成一个矩阵:
t(a_data_frame)
## [,1] [,2] [,3] [,4] [,5]
## x "a" "b" "c" "d" "e"
## y " 2.3772326" "-1.2188022" " 1.3034011" "-0.5001624" "-1.5923465"
## z "FALSE" " TRUE" "FALSE" " TRUE" "FALSE"
如果两个数据框的大小一致,也可使用cbind
和rbind
把它们连接(join)起来。rbind
能智能地对列重新排序以匹配。然而,因为cbind
不会对列名作重复性检查,使用时要格外小心:
another_data_frame <- data.frame( # 与a_data_frame有相同的cols,不过次序不同
z = rlnorm(5), # 对数分布的数
y = sample(5), # 1到5随机排列的数
x = letters[3:7]
)
rbind(a_data_frame, another_data_frame)
## x y z
## 1 a 0.17581 1.0000
## 2 b 0.06894 1.0000
## 3 c 0.74217 1.0000
## 4 d 0.72816 1.0000
## 5 e -0.28940 1.0000
## 6 c 1.00000 0.8714
## 7 d 3.00000 0.2432
## 8 e 5.00000 2.3498
## 9 f 4.00000 2.0263
## 10 g 2.00000 1.7145
cbind(a_data_frame, another_data_frame)
## x y z z y x
## 1 a 0.17581 TRUE 0.8714 1 c
## 2 b 0.06894 TRUE 0.2432 3 d
## 3 c 0.74217 TRUE 2.3498 4 e
## 4 d 0.72816 TRUE 2.0263 5 f
## 5 e -0.28940 TRUE 1.7145 2 g
当两个数据框有相同的列时,可使用merge
函数合并。merge
为数据库风格的连接提供了多种选择。当要连接两个数据框时,你需要指定包含键值的列以作匹配。默认情况下,merge
函数会使用两个数据框中所有共同的列,但通常你会想用一个共享ID列。在下例中,我们将通过by
参数指定x
为我们所要包含的ID:
merge(a_data_frame, another_data_frame, by = "x")
## x y.x z.x z.y y.y
## 1 c 0.7422 TRUE 0.8714 1
## 2 d 0.7282 TRUE 0.2432 3
## 3 e -0.2894 TRUE 2.3498 5
merge(a_data_frame, another_data_frame, by = "x", all = TRUE)
## x y.x z.x z.y y.y
## 1 a 0.17581 TRUE NA NA
## 2 b 0.06894 TRUE NA NA
## 3 c 0.74217 TRUE 0.8714 1
## 4 d 0.72816 TRUE 0.2432 3
## 5 e -0.28940 TRUE 2.3498 5
## 6 f NA NA 2.0263 4
## 7 g NA NA 1.7145 2
如果数据框中只包含数值,那么colSums
和colMeans
函数可分别用于计算每列的总和和平均值。同样地,rowSums
和rowMeans
将计算每一行的总和及平均值:
colSums(a_data_frame[, 2:3])
## y z
## 1.426 5.000
colMeans(a_data_frame[, 2:3])
## y z
## 0.2851 1.0000
操作数据框是一个很大的话题,我们将在第13章深入探讨。