引言
一直以来都听闻tidy-r是使R语言起死回生的存在,尽管没有系统学习过,但已经在coding中潜移默化的使用了许多tidy语法,例如管道符%>%
、ggplot2
等等。最近在处理bed文件时遇到了很多base-r解决起来非常复杂的问题,网上一查都是用dplyr包解决的。因此本文记录一下dplyr的常用语法,希望以后逐渐由base-r向tidy-r过渡。
dplyr函数特征
- 第一个参数是一个数据框。
- 随后的参数描述了如何处理第一个参数中指定的数据框,你可以直接引用数据框中的列,而无需使用 $ 运算符(只需使用列名)。
- 函数的返回结果是一个新的数据框
- 数据框必须经过正确格式化和注释才能发挥作用。尤其是数据必须整齐。简而言之,每一行应该有一个样本,每一列应该代表那个样本的特征。
筛选行:filter()
- 例如,筛选出 1 月 1 日的所有航班
filter(flights, month == 1, day == 1)
这里,flights是数据框,“month == 1, day == 1”是筛选条件,默认是且的关系,还可以用其他条件进行筛选:
filter(flights, month == 1 | day == 1)
排序行:arrange()
- 按year, month, day的优先级排列数据框flights
arrange(flights, year, month, day)
- 如果有多个列名,那么就先按前面的列名排,然后在此基础上排后面的列名
- 默认按升序排(从小到大)
- 用 desc() 降序排,如
arrange(flights, desc(arr_delay))
# 按arr_delay降序,对观测进行重排
- 缺失值总是排在最后
筛选列:select()
- 选择数据框flights中的year, month, day这几列
select(flights, year, month, day)
- 选择“year”和“day”之间的所有列(包括“year”和“day”)
select(flights, year:day)
- 选择不在“year”和“day”之间的所有列,记得带括号
select(flights, -(year:day))
可以在 select () 函数中使用一些辅助函数,这些跟Excel中选择名称的规则类似
starts_with("abc")
:匹配 开头是“abc” 的名称。
ends_with("xyz")
:匹配 结尾是“xyz” 的名称。
contains("ijk")
:匹配 包含“ijk” 的名称。
matches("(.)\\1")
:选择匹配正则表达式的那些变量。这个正则表达式会匹配名称中有重复字符的变量。
num_range("x", 1:3)
:匹配 x1、 x2 和 x3。把 time_hour 和 air_time 移到数据框的开头,其余按原来的顺序呈现
select(flights, time_hour, air_time, everything())
重命名列:rename()
- 将变量tailnum重命名为tail_num
rename(flights, tail_num = tailnum)
添加新列:mutate()
- 添加新列:gain 和 speed,并排在数据集的最后
mutate(flights,gain = arr_delay - dep_delay,speed = distance / air_time * 60)
- 新列一旦创建,就可立即使用。如在创建新列 gain_per_hour 的时候,用到了刚创建的新列 gain 和 hours
mutate(flights_sml,gain = arr_delay - dep_delay,hours = air_time / 60,gain_per_hour = gain / hours)
- 如果只想保留新变量,可以使用 transmute() 函数,如下代码输出结果中只有3列:gain 、 hours 和 gain_per_hour
transmute(flights,gain = arr_delay - dep_delay,hours = air_time / 60,gain_per_hour = gain / hours)
- 辅助运算符
算术运算符:+、 -、 *、 /、 ^
模运算符:%/%(求整) 和 %%(求余),可以拆分整数。
对数函数:log()、 log2() 和 log10()。
偏移函数:lead() 返回序列领先值、 lag() 返回序列滞后值。
累加和滚动聚合:cumsum() 累加和、 cumprod() 累加积、commin() 累加最小值、 cummax() 累加最大值、cummean() 累加均值,这几个函数对于绘图非常重要
逻辑比较:<、 <=、 >、 >= 和 !=
排秩:最常用的是min_rank() ,升序排,输出结果是名次,如1 2 3 ... n。
分组分析:group_by() 和 summarize()
- 可以将数据框折叠成一行,如下代码输出为一个值,即所有航班的平均起飞延误时间
summarize(flights, delay = mean(dep_delay, na.rm = TRUE))
- 与 group_by() 联用,即在分组基础上进行摘要统计。group_by() 和 summarize() 联用是 dplyr 包最常用的操作之一。
- 如:将所有结果按method和benchmark分组,计算新分组的每组平均值,并用 n() 函数计数,返回当前分组的大小
> group_by(fig2a,method,benchmark) %>%
+ summarise(mean = mean(value),
+ num = n())
# 输出:
`summarise()` has grouped output by 'method'. You can override using the `.groups` argument.
# A tibble: 28 × 4
# Groups: method [7]
method benchmark mean num
<fct> <fct> <dbl> <int>
1 scMAGIC-atac Accuracy 0.806 3
2 scMAGIC-atac Average Recall 0.783 3
3 scMAGIC-atac Average Precision 0.754 3
4 scMAGIC-atac Mean F1 0.752 3
5 GLUE Accuracy 0.822 3
6 GLUE Average Recall 0.727 3
7 GLUE Average Precision 0.765 3
8 GLUE Mean F1 0.725 3
9 Seurat Accuracy 0.776 3
10 Seurat Average Recall 0.666 3
# … with 18 more rows
-
只使用均值、计数和求和是远远不够的, dplyr还提供了常见的摘要函数,如:
- 位置度量:median(x) 中位数
- 分散程度度量:sd(x) 标准误差、 IQR(x) 四分位距 和 mad(x) 绝对中位差
- 秩的度量:min(x)、max(x) 和 quantile(x, 0.25) 找出 x 中按从小到大顺序大于前25% 而小于后 75% 的值
- 定位度量:first(x)、 nth(x, 2) 和 last(x)
- 计数:n() 返回当前分组的大小, sum(!is.na(x)) 计算出非缺失值的数量, n_distinct(x) 计算出唯一值的数量,count()返回指定组合的计数
- 逻辑值的计数和比例:sum(x > 10) 找出 x 中 TRUE 的数量, mean(y == 0) 找出x 中 TRUE 的比例。
用 ungroup() 函数取消分组,并回到未分组的数据继续操作
处理双表格
- 左链接
left_join()
:以左边的表的by变量为准合并,如果有数据缺失则显示NA。 - 右链接
right_join()
:以右边的表的by变量为准合并,如果有数据缺失则显示NA。 - 内链接
inner_join()
:返回两个表中的交集部分。 - 外链接
full_join()
:返回两表中的并集部分。
其他函数
- add_count():给最后一列添加某列的counts数
- distinct(col, .keep_all = T):只保留col不重复的行
实战
- 计算单细胞矩阵的TPM
mm10_gene <- read.table('/mdshare/node10/xyx/projects/RNA-editing/reference/genome_bed/mm10/mm10_gene.bed')
mm10_gene %>%
as_tibble() %>% # df转tibble
transmute(gene_name = V6, # 新建一个tibble包含列gene_name
length = V3 - V2) %>% # 包含列length为end - start
group_by(gene_name) %>% # 按gene_name分组
summarise(meanlen = round(mean(length))) %>% # 计算每组的平均长度
filter(gene_name %in% rownames(counts_mtx)) -> # 筛选在query_mtx中的基因名
counts_mtx_gene_len
counts_mtx_gene_len
counts_mtx <- counts_mtx[counts_mtx_gene_len$gene_name,]
counts_mtx_TPM <- apply(counts_mtx,2,function(col){col/counts_mtx_gene_len$meanlen}) %>%
NormalizeData()
总结
初次接触tidyr会感觉函数的形参比较随意,除了第一个参数必须是数据框本身,后续参数都没有严格要求,有的参数还是函数名(例如之前一直不太理解的ggplot2中的aes()
)。但实际上,这是因为tidyr的语法更多的是面向操作,而我之前使用的基于which()的筛选方法更多的是面向结果。想想网上的tidyr代码头几行为什么通常使用的是df %>% filter() %>% ...
,而不是filter(df, ...)
?如果使用管道符,就可以感受到最初的df只是一个输入的对象,而后续则是对数据框内的元素进行花式操作。如果这样理解的话就会发现tidyr代码的优雅和易读了。
参考
http://www.360doc.com/content/21/0805/12/73394596_989613319.shtml
https://bookdown.org/rdpeng/rprogdatascience/managing-data-frames-with-the-dplyr-package.html#dplyr-grammar
https://blog.csdn.net/m0_52406014/article/details/123823476