我喜欢的gather快要被淘汰了,好在来了更好的宽长转换工具!
果子唠嗑
不断地有人问我,你的简书文章哪里去了。
很难受,我的简书被封掉了。而我一直把简书当作写作工具在使用,也一直在蹭他的图床。
今天在熊的帮助下,我用上了新的markdown工具,用上了自己的图床,工作又可以开展了。具体的过程在明天的帖子里面。
(在发这个帖子的过程中,又获得了熊的极其骚的操作,我现在跪在地上找下巴)
假如要办法生信界十大感动人物,我觉得评语可以这样写
他以一己之力,结束了生信界效率工具使用长年累月的混乱局面,帮码农们节约了日渐稀疏的毛发。
回到今天的主题,我们要讲讲数据调整中的宽长转换
我以前只知道转换,但是并不知道什么叫长数据,什么叫宽数据。最近在学习的过程中,看到了定义。
如果一个表格数据被处理过后,行数增多,就叫长数据,如果列数增多就叫宽数据。
以下展示的是就是长宽数据的转换。
数据变成,就是数据的多个列的数值变成一列,而其对应的列名也变成新的一列。
什么是长数据,什么是宽数据
那以前是怎么转换的呢?
有很多种方法,包括dcast, melt这些看到名字就头疼的函数,当年就是因为这两个函数,导致我每次看R语言实战的时候都折戟而返,直到后来遇到了gather 和spread。
我们来看一下gather如何把宽数据变成成数据。
先加载一个数据,1208行,6列的数据框。
load("gathe_data.Rdata")
他是一个清洁数据,行是观察,列是变量,用来作图是真真极好的。
第一列是亚型,第二列是癌症和癌旁信息,3-6列是四个基因的表达量。
宽长是相对的,取决于你要把他变成什么数据。如果我们要把他变成长数据(行数更多),那么此刻这个数据就是宽数据。
我们用gather来试一下,这个函数再tidyr这个包中
library(tidyr)
data <- exprSet %>%
gather(gene,expression,3:6)
exprSet就是刚才的数据,我们现在要把3:6列变成两列,数值那一列的列名是expression,3:6 列的名称所形成的列名是gene。我们看看现在的数据,他变成了4832行,4列的数据框。行数变多,就是长数据。
第一列,第二列名称都不变,第三列叫gene,是之前3:6列数据的列名,第四列是expression,是这四列的数值。
数据变成就用spread,也很简单,这里不讲了,因为我们有了更好的选择。
pivot_longer和pivot_wider
自从我理解了gather,我的作图也跟着有了很大的进步(原因后面解释),很长一段时间,我都是跟gather相依为命。但是有个问题,就是大部分人的生活中都没有gather这个词,也不基本上没有spread这个词,用的不多的时候甚至会忘记。就连发明者Hadley Wickham也说,要用这两个函数的时候需要看文档复习才行。
所以,他就连夜(夸张)创建了两个新的函数实现同样的功能,但是名称更好记,使用更符合人性。
他们就是
pivot_longer和pivot_wider,顾名可以思义,pivot_longer是数据变得长,pivot_longer是数据变得宽。
我们先来看看pivot_longer如何使数据变长,把多列变成两列,减少列数增加行数。
library(tidyr)
data <- exprSet %>%
pivot_longer(cols=3:6,
names_to= "gene",
values_to = "expression")
有三个参数要注意,
- cols 指定哪几列是要合并在一起的,数值,字符串都可以。这里用的是3:6列
- names_to 指定这几列的列名变成一列后新的名字,我没用gene来表示
- values_to 指定3:6列合并后其内容形成新的一列,他的列名是什么
数据变长究竟有什么用?
我觉得数据变长主要是为了作图方便,把不可能变成可能,不困难变成方便。
如果用原来的数据作图,展示一个基因在癌和癌旁的表达,毫无压力,此处以ESR1为例
library(ggplot2)
ggplot(exprSet,aes(x=sample,y=ESR1,fill=sample))+
geom_boxplot()
但是如果我想要同时展示多个基因在癌和癌旁的表达量呢,比如这里的四个基因。
实在不知道也没关系啊,我们做四个图拼接起来就可以,有点麻烦对吧,我看过有人打开AI搞过。
但是有了长数据,这个就变得很简单,上述的代码几乎不变,只是换个数据就行。
library(ggplot2)
ggplot(data,aes(x=gene,y=expression,fill=sample))+
geom_boxplot()
如果用分面还可以实现另外的效果
library(ggplot2)
ggplot(data,aes(x=sample,y=expression,fill=sample))+
geom_boxplot()+
facet_grid(.~gene)
pivot_longer的用法就是这么简单,现在我们来探索一下,如何方便地选择多个列。
直接设置cols参数就行,数值和字符串都可以,下面四种方法得到的结果是一样的。
data1 <- exprSet %>%
pivot_longer(cols=3:6,
names_to= "gene",
values_to = "expression")
data2 <- exprSet %>%
pivot_longer(cols=-c(1,2),
names_to= "gene",
values_to = "expression")
data3 <- exprSet %>%
pivot_longer(cols=c("BRCA1","TP53","FOXA1","ESR1"),
names_to= "gene",
values_to = "expression")
data4 <- exprSet %>%
pivot_longer(cols=-c("subgroup","sample"),
names_to= "gene",
values_to = "expression")
数据变宽的意义在哪啊?
首先数据变宽是数据变成的逆向操作,刚才的数据可以被方便地变回去, 这个函数就是pivot_wider,用起来更加简单
library(tidyr)
data_w <- data1 %>%
pivot_wider( names_from = gene,
values_from = expression)
注意两个参数
- values_from,指定那一列数据需要变成更多的列,很明显,现在是expression这一列
- names_from, 指定变成的更多列用什么来指定名称,用gene这一列包含的名称去命名多出来的列
这么一搞,理论上没有问题,但是出现了报错。
Warning message:
Values in `expression` are not uniquely identified; output will contain list-cols.
* Use `values_fn = list(expression = list)` to suppress this warning.
* Use `values_fn = list(expression = length)` to identify where the duplicates arise
* Use `values_fn = list(expression = summary_fun)` to summarise duplicates
原因就是当我们在宽边长的时候,会出现表达值一模一样的行,比如
pivot_wider不允许这种情况出现,解决方案就是在一开始宽变成的之前增加一列序号作为标识
data1 <- exprSet %>%
## 增加一列,序号来表示
mutate(num=seq(1:nrow(.))) %>%
pivot_longer(cols=3:6,
names_to= "gene",
values_to = "expression")
形成的数据也多出来一列num
这时候用pivot_wider就没有问题了,代码一抹一样呢。
data_w <- data1 %>%
pivot_wider( names_from = gene,
values_from = expression)
除了逆向操作,这个长变宽还有什么意义,我觉得是让不可能变成可能。
比如现在有个数据, 是个突变数据,只有两列,第一列是样本名称,第二列表示该样本中发生突变的基因名称。
load(file = "BRCAmuts.Rdata")
如果想要能够做出那种突变作图的清洁数据,也就是行是样本,列是基因名称,该怎么做呢?
首先要统计一下,每个样本中,每个基因突变的次数。
这个使用group_by联合summarise可以搞定。
参考这个教程学习一下
dd <- BRCAmuts %>%
group_by(TCGA_id,Hugo_Symbol)%>%
summarise(num=n())
这时候的数据是这样的
现在就简单了哈,只要把Hugo_Symbol这一列变成多个列就可以
然后再见招拆招,看到有很多NA,把他变成0
dd1[is.na(dd1)] =0
当然这个把NA变成0的操作,pivot_wider函数也想到了,可以用values_fill参数来设定,把num这一列最后是NA直接填充为0
dd2 <- dd %>%
pivot_wider(names_from =Hugo_Symbol,
values_from = num,
values_fill = list(num = 0)) %>%
ungroup()
这样我们用管道符号(%>%)把他们串联起来就可以一步实现
dd3 <- BRCAmuts %>%
group_by(TCGA_id,Hugo_Symbol)%>%
summarise(num=n()) %>%
pivot_wider(names_from =Hugo_Symbol,
values_from = num,
values_fill = list(num = 0)) %>%
ungroup()
我们看到数据,然后分析自己的需求,最终根据这些需求编写代码实现,每一步都很清晰明了,是不是很神奇呢?
当然更神奇的还在后面。
在reshape包里面有个函数dcast,可以一步实现这个操作
dd4 <- reshape2::dcast(BRCAmuts,TCGA_id~Hugo_Symbol)
那我们是不是就不需要pivot_wider函数,直接学习简单的dcast呢。
我不这么认为,作为一名曾经焦虑的讲师,我觉得编程应该带来的是内心的平和,而不是额外的焦虑。我们应该去学习那些最基本的函数,然后用这些函数搭建自己的编程宫殿。
不知道dcast,没有关系,只要我们了解了需求,我们一样可以实现同样的效果。
钢琴只有88个键,但是可以弹出不计其数的曲子,如果给你一把1000个琴键的钢琴,我们一定无所适从。
弦有数,律无穷。
被限定在方寸之间,反倒是激发了无限创意,我们的人生就是在用有限的资源去创造无限的可能性。
好了,写完了这个帖子,也完成了我一个心愿。从此,宽长转换,不是难题,好记也好用!