我喜欢的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可以搞定。

参考这个教程学习一下

group_by和summrise连用后,分组计算就很方便

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个琴键的钢琴,我们一定无所适从。 弦有数,律无穷。 被限定在方寸之间,反倒是激发了无限创意,我们的人生就是在用有限的资源去创造无限的可能性。

好了,写完了这个帖子,也完成了我一个心愿。从此,宽长转换,不是难题,好记也好用!

# tidyr 

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×