文章和代码已经归档至【Github仓库:https://github.com/timerring/dive-into-AI 】或者公众号【AIShareLab】回复 R语言 也可获取。
这个包以一种统一的规范更高效地处理数据框。dplyr
包里处理数据框的所有函数的第一个参数都是数据框名。
下面以 MASS 包里的 birthwt 数据集为例,介绍 dplyr 包里常用函数的用法。该数据集来自一项关于新生儿低体重危险因素的病例对照研究。首先加载该数据集并查看其相关信息。
library(dplyr)
data(birthwt, package = "MASS")
# ??birthwt
数据集 birthwt 里一共包含 189 个研究对象、10 个变量。其中结果变量 bwt 是新生儿的体重(单位:g),变量 low 是将 bwt 的取值以 2500g 为分点转换成的一个二分类变量。其余 8 个变量均为预测变量,包括孕妇的年龄(age)、种族(race)、吸烟状况(smoke)、高血压史(ht)等。
1.使用 filter( ) 和 slice( ) 筛选行
函数 filter()
可以基于观测值筛选数据框的一个子集。第一个参数是数据框名,第二个参数以及随后的参数是用来筛选数据框的表达式。
例如,筛选数据框里年龄大于 35 岁的对象的所有记录:
filter(birthwt, age > 35)
函数 filter ( ) 里可以用逗号分隔多个条件。使用下面的命令将会选择选择年龄大于 35 岁,并且出生体重小于 2500g 或者大于 4000g 的所有记录,因为记录较多,这里只显示了前 10 行。
head(filter(birthwt, age > 35, bwt < 2500 | bwt > 4000),10)
函数 slice( )
可以按照行号选择指定的行。例如,下面的命令选择数据集里面的第 2 行到第 5 行。
slice(birthwt, 2:5)
2.使用 arrange( ) 排列行
有时候我们想要将数据框的记录按照某个变量进行排序,函数 arrange()
可以实现这个功能。下面的命令将数据框按照变量 bwt 的值从小到大进行排序后显示:
arrange(birthwt, bwt) # 默认升序
在上面的输出中,第 6 行和第 7 行的变量 bwt 的值都是 1588,在这种情况下如果还想将数据框按照第二个变量排序,只需要在函数 arrange( )
里加上第二个变量即可。例如,下面的命令将数据框按照变量 bwt 的值从小到大排序,在 bwt 取值相等的情况下再按照第二个变量 age 的值从小到大排序。
arrange(birthwt, bwt, age)
如果想把数据框按照某个变量的值从大到小进行排序,可以借助函数 desc( )
实现。
arrange(birthwt, desc(bwt))
# 等价于
arrange(birthwt, - bwt)
3. 使用 select( ) 选择列
函数 select( )
用于选择数据框中的列(变量)。
# 下面的命令选择数据框里面的 bwt、age、race 和 smoke 这 4 个变量组成新的数据框。
select(birthwt, bwt, age, race, smoke)
请注意,MASS 包里有一个同名函数 select( ),如果同时加载了 dplyr 包和 MASS 包,R 会默认使用较后加载的包里的函数。为了避免混淆,我们可以使用符号 ::
特别指明使用某一个包里的函数,例如 dplyr::select( )
。之后我们将会对函数 select( ) 作进一步介绍。
4.使用 mutate( ) 添加新变量
函数 mutate( )
用于在数据框中创建新的变量。下面的命令将数据集 birthwt 里的变量 lwt(单位:lb)乘以系数 0.4536 后生成新的变量 lwt.kg(1lb ≈ 0.4536kg)。
# 当然如果想要用新变量替换原来的变量,只需把新变量命名为原来的变量名:
mutate(birthwt, lwt.kg = lwt*0.4536)
5.使用 summarise( ) 计算统计量
函数 summarise( ) 可以用于计算数据框中某个变量的指定统计量。
例如,计算变量 bwt 的样本均值和样本标准差:
summarise(birthwt, Mean.bwt = mean(bwt), Sd.bwt = sd(bwt))
6. 使用 group_by( ) 拆分数据框
函数 group_by( )
可以将数据框按照某一个或某几个分类变量拆分成多个数据框。例如:
group_by(birthwt, race)
str(group_by(birthwt, race))
# ============ 输出 =============
grouped_df [189 × 10] (S3: grouped_df/tbl_df/tbl/data.frame)
$ low : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ age : int [1:189] 19 33 20 21 18 21 22 17 29 26 ...
$ lwt : int [1:189] 182 155 105 108 107 124 118 103 123 113 ...
$ race : int [1:189] 2 3 1 1 1 3 1 3 1 1 ...
$ smoke: int [1:189] 0 0 1 1 1 0 0 0 1 1 ...
$ ptl : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ ht : int [1:189] 0 0 0 0 0 0 0 0 0 0 ...
$ ui : int [1:189] 1 0 0 1 1 0 0 0 0 0 ...
$ ftv : int [1:189] 0 3 1 2 0 0 1 1 1 0 ...
$ bwt : int [1:189] 2523 2551 2557 2594 2600 2622 2637 2637 2663 2665 ...
- attr(*, "groups")= tibble [3 × 2] (S3: tbl_df/tbl/data.frame)
..$ race : int [1:3] 1 2 3
..$ .rows: list<int> [1:3]
.. ..$ : int [1:96] 3 4 5 7 9 10 15 16 18 20 ...
.. ..$ : int [1:26] 1 17 29 30 31 33 35 41 43 70 ...
.. ..$ : int [1:67] 2 6 8 11 12 13 14 19 21 24 ...
.. ..@ ptype: int(0)
..- attr(*, ".drop")= logi TRUE
函数 group_by( ) 不会改变数据框的外观,而会改变它与其他 dplyr 动词函数的作用方式 。因此,上面的输出结果看上去和原来的数据框没有什么差别,但实质上是不同的。最本质的差别是多了一个分组属性(Groups),即上面的结果包含了 3 个数据框,分别对应于变量 race 的 3 个类别。
你还可能注意到上面输出对象的格式(grouped_df [189 × 10] (S3: grouped_df/tbl_df/tbl/data.frame)
)。与 R/Rstudio 上不同,notebook 这里把它显示成了 A grouped_df: 189 × 10
(而非 # A tibble: 189 x 10
),实际它仍然包含 tibble(注意其中的 - attr(*, "groups")= tibble [3 × 2] (S3: tbl_df/tbl/data.frame)
)。另外,它没有显示 Groups
属性信息,实际应为 # Groups: race [3]
。
tibble 是 tidyverse 系列包(包括 dplyr 包)提供的一种类似数据框的格式。相对于传统的数据框,tibble 在很多方面具有优势,感兴趣的读者可以参阅函数 tibble( ) 的帮助文档。我们可以用函数 as_tibble( )
将传统的数据框转换为 tibble,也可以用函数 as.data.frame( )
将 tibble 转换成传统的数据框。
as_tibble(birthwt)
下面我们将会看到,把函数 group_by( ) 和 summarise( ) 联合使用能方便地对变量进行分组统计。
7. 使用传递符 %>% 组合多个操作
我们经常需要对一个数据框做一系列的操作,后面一个操作的输入需要用前一个操作的输出结果。
# 第一步把数据框 birthwt 里面的变量 race 转换成因子并给各个水平添加标签,把新的数据框命名为 birthwt1
birthwt1 <- mutate(birthwt,
race = factor(race, labels = c("white", "black", "other")))
# 第二步把数据框 birthwt1 按照变量 race 分组,把分组后的对象命名为 birthwt.group;
birthwt.group <- group_by(birthwt1, race)
# 第三步对于分组对象 birthwt.group 计算各组中变量 bwt 的平均值。
summarise(birthwt.group, mean(bwt))
这种方法的最大缺点是需要为每个中间结果建立一个变量。在很多情况下,比如在上面的示例中,这些中间变量其实是没有什么实际意义的。我们需要给这些中间变量命名,而且这些中间变量会保存在工作空间中占用内存。传递操作符 %>%
将该符号之前的对象传递给符号后面的函数并作为函数的第一个参数值。例如:
c(2, 4, 6, 8) %>% matrix(nrow = 2)
因为 dplyr 包里面的函数第一个参数总是数据框,所以这些函数配合传递操作符处理数据框非常方便。下面用传递操作符改写上面的命令:
birthwt %>%
mutate(race = factor(race, labels = c("white", "black", "other"))) %>%
group_by(race) %>%
summarise(mean(bwt))
上述代码的重点在于动词函数,而不是函数中的参数。在阅读这一串代码组合时,可以将它们当成一系列的规定动作。
项目实战
epiDisplay
包里的数据集 Planning
来自 20 世纪 80 年代中期泰国的一项计划生育调查研究,请通过其帮助文件查看数据信息并整理该数据集。
library(epiDisplay)
data(Planning)
print(des(Planning))
names(Planning) <- tolower(names(Planning)) # 把变量名变为小写字母
summary(Planning)
table(duplicated(Planning$id)) # 查看是否有重复id;
# FALSE TRUE
# 250 1
which(duplicated(Planning$id)) # 找出重复id的行号;把 XXXXXX 替换成正确的代码
# 216
Planning$id # 验证下
Planning$id[216] <- 216 # 修正重复id;
library(dplyr)
Planning <- mutate(
Planning,
relig = ifelse(relig == 9, NA, relig), # 将变量relig中的9变成NA
ped = ifelse(ped == 0 | ped == 9, NA, ped), # 将变量ped中的0和9变成NA
income = ifelse(income == 9, NA, income), # 将变量income中的9变成NA
am = ifelse(am == 99, NA, am), # 将变量am中的99变成NA
reason = ifelse(reason == 9, NA, reason), # 将变量reason中的9变成NA
bps = ifelse(bps == 0 | bps == 999, NA, bps), # 将变量bps中的0和999变成NA
bpd = ifelse(bpd == 0 | bpd == 999, NA, bpd), # 将变量bpd中的0和999变成NA
wt = ifelse(wt == 0 | wt > 99, NA, wt), # 将变量wt中的0和大于99的值变成NA
ht = ifelse(ht == 0 | ht > 300, NA, ht) # 将变量ht中的0和大于300的值变成NA;
)