参考:李东风老师的R 语言实战
假设数据以 tibble 格式保存。数据集如果用于统计与绘图,需要满足一定的格式要求,(Wickham, 2014) 称之为 整洁数据 (tidy data),基本要求是每行一个观测,每列一个变量,每个单元格恰好有一个数据值。这些变量应该是真正的属性,而不是同一属性在不同年、月等时间的值分别放到单独的列。
我们可以使用tidyverse 系统来操作,其中包括了magrittr 包,readr 包,dplyr 包和 tidyr 包等。
数据集使用:
> set.seed(1234)
> d.class <- data.frame(sex = sample(c("M", "F"), 10, replace=T), age = runif(10,10,19))
> knitr::kable(d.class)
|sex | age|
|:---|--------:|
|F | 16.24232|
|F | 14.90477|
|F | 12.54460|
|F | 18.31090|
|M | 12.63084|
|F | 17.53566|
|M | 12.57601|
|M | 12.40139|
|M | 11.68051|
|F | 12.09003|
使用逻辑条件对行筛选。
filter(test, Species == "setosa")
filter(test, Species == "setosa"&Sepal.Length > 5 )
filter(test, Species %in% c("setosa","versicolor"))
filter() 函数第一个参数是要选择的数据框,后续的参数是条件,这些条件是需要同时满足的,另外,条件中取 缺失值的观测自动放弃,这一点与直接在数据框的行下标中用逻辑下标有所不同,逻辑下标中有缺失值会在结果中 产生缺失值。filter() 会自动舍弃行名,如果需要行名只能将其转换成数据框的一列。
dplyr 包的 sample_n(tbl, size) 函数可以从数据集 tbl 中随机无放回抽取 size 行,如:
> d.class %>% sample_n(size = 3) %>% knitr::kable()
|sex | age|
|:---|--------:|
|F | 11.40572|
|F | 16.50834|
|M | 16.90388|
ps: 可以用 weight 选项指定数据框中的一列作为抽样 权重,进行不等概抽样。感觉还挺有意思,有机会研究研究。
用来去除重复行,有时我们希望得到一个或若干个变量组合的所有不同值。dplyr 包的 distinct() 函数可以对数据框指定若干变 量,然后筛选出所有不同值,每组不同值仅保留一行。指定变量名时不是写成字符串形式而是直接写变量名:
d.class %>% distinct(sex, age) %>% knitr::kable()
如果希望保留数据框中其它列,可以加选项 keep_all=TRUE,默认distinct 后只会返回选定的列。
效果和na.omit 一样,但是高级之处在于,其可以指定列,对数据框某列存在NA 的行直接删除:
> library(tidyr)
> drop_na(X,X1)
X1 X2
1 A 1
2 B NA
3 C 3
4 D 4
5 E 5
如果不特定指定列,则和na.omit 效果一样。
对行或列筛选,比较有用的是其一些专属函数:
select(test, starts_with("Petal")) #选中..开头的列
select(test, ends_with("Width")) #选中..结尾的列
select(test, contains("etal")) #选中包含..的列
select(test, matches(".t.")) #选中符合某正则表达的列
select(test, everything()) #选中所有列,可以使指定的列先提前
select(test, last_col()) #选中最后一列
select(test, last_col(offset = 1)) #选中倒数第二列。offset 表示忽略n个。忽略最后一个即表示选择倒数第二个。
按照数据框里的某列或某几列,对所有行进行排序。可以使用 desc 产生倒序,或写入多个列使其按照多个列进行排序。
arrange(test, Sepal.Length)#默认从小到大排序
arrange(test, desc(Sepal.Length))#用desc从大到小
arrange(test, desc(Sepal.Width),Sepal.Length) #先按照一列排,再按照一列排
排序时不论升序还是降序,所有的缺失值都自动排到末尾。
修改变量名。在 dplyr 包的 rename() 中用 “新名字 = 旧名字” 格式修改变量名,如:
d2.class <- d.class %>% dplyr::rename(h=height, w=weight)
rename() 这个函数可能出现在其它包中,保险起见写成 dplyr::rename()。
可以为数据框计算新变量,返回含有新变量以及原变量的新数据框:
mutate(test, new = Sepal.Length * Sepal.Width)
> head(test,3)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 7.0 3.2 4.7 1.4 versicolor
new
1 17.85
2 14.70
3 22.40
另外,mutate 也可以用来添加新列,结合我先前说过的新增列的种种方法,并且支持多个语句组成的复合语句:
> d.class %>% mutate(sexc = {
+ x <- rep(" 男", length(sex))
+ x[sex == "F"] <- " 女"
+ x
+ })
其中复合语句中也可以简化的调用数据框的列。
select(test,1)
select(test,c(1,5))
如果想要用向量来存放希望筛选的列名,需要使用函数 one_of
来存放该向量。
select(test,Sepal.Length)
select(test, Petal.Length, Petal.Width)
vars <- c("Petal.Length", "Petal.Width")
select(test, one_of(vars))
可以使用,冒号表示列的范围,向列号一样的切片操作:
d.class %>% select(age:weight) %>% head(n=3) %>% knitr::kable()
参数中前面写负号表示扣除,如:
d.class %>% select(-name, -age) %>% head(n=3) %>% knitr::kable()
可以将本来扁平的数据框变为宽长的数据框。扁平(两个维度对应一个数据)。
# 先生成一个原始的数据
> test <- data.frame(geneid = paste0("gene",1:4),
+ sample1 = c(1,4,7,10),
+ sample2 = c(2,5,0.8,11),
+ sample3 = c(0.3,6,9,12))
> test
geneid sample1 sample2 sample3
1 gene1 1 2.0 0.3
2 gene2 4 5.0 6.0
3 gene3 7 0.8 9.0
4 gene4 10 11.0 12.0
很显然, data.frame
生成的是“扁平”的数据框。
> test_gather <- gather(data = test,
+ key = sample_nm,
+ value = exp,
+ - geneid)
> head(test_gather)
geneid sample_nm exp
1 gene1 sample1 1
2 gene2 sample1 4
3 gene3 sample1 7
4 gene4 sample1 10
5 gene1 sample2 2
6 gene2 sample2 5
通过 gather
,并设定key(原先的列),与value(原先的数据),并通过 -
(原先的行),对数据框进行转换。
宽长(一个维度对应一个数据)。
> test_re <- spread(data = test_gather,
+ key = sample_nm,
+ value = exp)
> test_re
geneid sample1 sample2 sample3
1 gene1 1 2.0 0.3
2 gene2 4 5.0 6.0
3 gene3 7 0.8 9.0
4 gene4 10 11.0 12.0
通过spread ,设定key(希望转换的列),value(希望转换的数据)。也就回到了开始创建的数据框test。
大部分功能是类似的,这里主要说下pivot_longer 针对下面情况的功能:
我们需要 指定切分变量名和随访号的模式,以解决一行中有多个属性的多次观测的情形,在对应的 names_to 中用特殊的".value" 名字表示切分出来的那一部分实际是变量名,这 时不需要 values_to 选项:
dwide4 %>% pivot_longer(
-id,
names_pattern = "(x|y)([[:digit:]])", names_to = c(".value", "time")
) %>% knitr::kable()
对应的长变宽的函数有pivot_wider:
dlong4 %>% pivot_wider(
names_from = c("sex", "type"),
values_from = "count" ) %>%
knitr::kable()
有时候,需要将数据框先转换为宽列表,再转换回长列表,比如:
这个数据的问题是 x, y 应该放在两列中却合并成一个了,2018 和 2019 应该放在一列中却分成了两列。先合并 2018 和 2019 这两列,然后再拆分 x 和 y:
dlong6 %>% pivot_longer(
`2018`:`2019`, names_to = "year", values_to = "value" ) %>%
pivot_wider(
names_from = "variable", values_from = "value"
) %>%
knitr::kable()
关于更详细用法,参见李东风的R 教程。
将同一列中的内容分为两列内容。或将两列内容合并为同一列内容。
首先还是可以创建一个数据框。
> test <- data.frame(x = c( "a,b", "a,d", "b,c"));test
x
1 a,b
2 a,d
3 b,c
使用separate,便可以对一列中的数据达到“分离”的效果。对于待分离的对象(col),不必加上引号;但对于即将创建的新列(into),需要使用引号,由于是两列,这里使用向量创建。sep参数设定读取表格信息时以何符号作为分隔符。
> test_seprate <- separate(test,col = x, into = c("X", "Y"),sep = ",");test_seprate
X Y
1 a b
2 a d
3 b c
使用unite,可以将两列“合并”为一列。对于即将合并的新列,需要使用引号;但对于想要合并的多个列名,可以不用使用引号。sep 参数设定多列合并后不同数据分隔使用的分割符。
> test_re <- unite(test_seprate,col = "x",X,Y,sep = ",");test_re
x
1 a,b
2 a,d
3 b,c
其实也可以用paste 替代。
到底需不需要引号,对于要处理的列(无论分离还是合并)不用;对于待生成的列则需要。
除了seperate 外,函数 extract() 可以按照某种正则表达式表示的模式从指定列拆分出对应于正则表达式中捕获组的一列或多列内容。
参见:中的join 函数介绍部分
参见:34. R 数据整理(六:根据分类新增列的种种方法 1.0)
dplyr 包的函数 slice(.data, ...) 可以用来选择指定序号的行子集,正的序号表示保留,负的序号表示排除。如:
> d.class %>% slice(3:5) %>% knitr::kable()
|sex | age|
|:---|--------:|
|F | 17.63344|
|F | 11.40572|
|F | 16.50834|
函数 transmute() 用法与 mutate() 类似,但是仅保留新定义的变量,不保留原来的所有变量。如:
d.class %>% transmute(
height_cm = round(height*2.54),
weight_kg = round(weight*0.4535924),
bmi = weight_kg / (height_cm / 100)^2) %>%
head(n=3) %>% knitr::kable()
汇总。使用统计相关参数计算列表内相关内容。如sum, mean, median, min, max。
summarize(test, mean(Sepal.Length), sd(Sepal.Length))
在有多个变量需要汇总时,summarise 的格式就会比较罗嗦。比如,需要对 cancer 数据集中 v0 和 v1 两个变量同时计算平均值和标准差:
显然,如果有许多变量要计算不止一个统计量,就需要人为地将每一个变量的每一个统计量单独命名。dplyr 包的 summarse_at()
函数可以指定一批变量名与一批统计函数,自动命名结果变量,如:
d.cancer %>% summarise_at(
c("v0", "v1"),
list(avg = ~mean(.), std = ~sd(.)), na.rm=TRUE) %>%
knitr::kable()
其中的变量子集也可以用序号范围表示,或者用 vars() 函数写成不加撇号的格式,比如vars(v0, v1)。
其他还有几个变形:
对列筛选,进行汇总:
d.cancer %>% summarise_if( is.numeric,
list(avg = ~mean(.), std = ~sd(.)),
na.rm=TRUE) %>% knitr::kable()
直接对所有变量进行计算:
d.cancer %>% select(v0, v1) %>% summarise_all(
list(avg = ~mean(.), std = ~sd(.)),
na.rm=TRUE) %>% knitr::kable()
image.png
进行计数:
> CO2 %>% group_by(Type) %>% summarise(
+ count=dplyr::n(),
+ mean.uptake=mean(uptake, na.rm=TRUE)) %>% knitr::kable()
|Type | count| mean.uptake|
|:-----------|-----:|-----------:|
|Quebec | 42| 33.54286|
|Mississippi | 42| 20.88333|
group_by 按照某列对数据框进行分组,非常适合联合summarize 使用,获取指定组别不同类型内容的统计数值。
group_by(test, Species)
tmp = summarise(group_by(test, Species),mean(Sepal.Length), sd(Sepal.Length))
> tmp
# A tibble: 3 x 3
Species `mean(Sepal.Length)` `sd(Sepal.Length)`
<fct> <dbl> <dbl>
1 setosa 5 0.141
2 versicolor 6.7 0.424
3 virginica 6.05 0.354
我们可以输入多行给group_by,并且传递给summarise 进行统计:
> CO2 %>% group_by(Type, Plant) %>% summarise(
+ count=dplyr::n(),
+ mean.uptake=mean(uptake, na.rm=TRUE)) %>% knitr::kable()
`summarise()` has grouped output by 'Type'. You can override using the `.groups` argument.
|Type |Plant | count| mean.uptake|
|:-----------|:-----|-----:|-----------:|
|Quebec |Qn1 | 7| 33.22857|
|Quebec |Qn2 | 7| 35.15714|
|Quebec |Qn3 | 7| 37.61429|
|Quebec |Qc1 | 7| 29.97143|
|Quebec |Qc3 | 7| 32.58571|
|Quebec |Qc2 | 7| 32.70000|
|Mississippi |Mn3 | 7| 24.11429|
|Mississippi |Mn2 | 7| 27.34286|
|Mississippi |Mn1 | 7| 26.40000|
|Mississippi |Mc2 | 7| 12.14286|
|Mississippi |Mc3 | 7| 17.30000|
|Mississippi |Mc1 | 7| 18.00000|
如果只是想获得计数的统计量,可以直接用count 函数:
> head(CO2 %>% count(Type, Plant))
Type Plant n
1 Quebec Qn1 7
2 Quebec Qn2 7
3 Quebec Qn3 7
4 Quebec Qc1 7
5 Quebec Qc3 7
6 Quebec Qc2 7
这里有个小问题,交叉分组计算频数后的结果仍按照外层分类变量 Type 分组。比如:
CO2 %>% group_by(Type, Plant) %>% summarise(freq=n()) %>%
summarise(ntotal=sum(freq))
`summarise()` has grouped output by 'Type'. You can override using the `.groups` argument.
# A tibble: 2 x 2
Type ntotal
<fct> <int>
1 Quebec 42
2 Mississippi 42
如果想要摆脱分组限制,计算总共的和,加入ungroup
即可:
CO2 %>% group_by(Type, Plant) %>% summarise(freq=n()) %>% ungroup() %>%
summarise(ntotal=sum(freq))
ntotal
<int>
1 84
对于数据框,我们可以使用split 将数据框按某列拆分为多个数据框,并储存在列表中。nest 和 unnest 函数,可以将子数据框保存在 tibble 中,可以将保存在 tibble 中的子数据框合并为一个大数据 框。实际上,tibble 允许存在数据类型是列表 (list) 的列,子数据框就是以列表数据类型保存在 tibble 的一列中的。
tmp <- CO2 %>% group_by(Type) %>% nest()
> tmp
# A tibble: 2 x 2
# Groups: Type [2]
Type data
<fct> <list>
1 Quebec <tibble[,4] [42 × 4]>
2 Mississippi <tibble[,4] [42 × 4]>
由于tibble 类型数据相较数据框来说其元素类型可以是列表,因此相比split 拆分为列表来说,其保存后数据更加直观。
我们还可以借助unlist 将tibble 元素提取出来: