R绘图系列-绘制百分比柱状图以及优化

这篇文章主要是关于对R中的柱状图进行优化的过程,包括堆叠或并列柱状图纵坐标表示百分比添加同一组分在不同分组之间的连线来显示变化对异常值的处理等。

需求

  • 堆叠柱状图
  • 纵坐标表示百分比
  • 添加同一组分在不同分组之间的连线来显示变化

实现

普通柱状图

1
2
3
4
5
6
7
8
9
10
11
cluster_with_cellnum_plot
cell_num ident cluster
1 4317 zebra_mut_3h 0
2 1314 zebra_mut_sham 0
3 297 zebra_wt_3h 0
4 720 zebra_wt_sham 0
5 5129 zebra_mut_3h 1

# 显示的是绝对数值
ggplot(cluster_with_cellnum_plot, aes(fill=cluster, y=cell_num, x=ident)) +
geom_bar(stat="identity")

百分比柱状图

两种方法:

  • 先计算百分比,然后直接画图
  • 直接传递原始数字,ggplot2转化为百分比

计算百分比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
cluster_with_cellnum_plot %>% 
# 数据按ident进行分组
dplyr::group_by (ident) %>%
# mutate() adds new variables and preserves existing ones
# mutate在添加新变量的同时保留原始的变量信息
# 新增的变量名称为frac
dplyr::mutate(frac=round(cell_num/sum(cell_num),4))

# 输出结果:
# A tibble: 44 x 4
# Groups: ident [4]
cell_num ident cluster frac
<int> <chr> <int> <dbl>
1 4317 zebra_mut_3h 0 0.385
2 1314 zebra_mut_sham 0 0.105
3 297 zebra_wt_3h 0 0.0412
4 720 zebra_wt_sham 0 0.190
5 5129 zebra_mut_3h 1 0.457
6 1425 zebra_mut_sham 1 0.114
7 28 zebra_wt_3h 1 0.0039
8 35 zebra_wt_sham 1 0.0092
9 614 zebra_mut_3h 2 0.0547
10 2385 zebra_mut_sham 2 0.191
# … with 34 more rows

直接使用ggplot2转化

1
2
3
ggplot(cluster_with_cellnum_plot, aes(fill=cluster, y=cell_num, x=ident)) + 
# 添加了position="fill"
geom_bar(position="fill", stat="identity")

添加百分比

1
2
3
4
ggplot(cluster_with_cellnum_plot, aes(fill=cluster, y=cell_num, x=ident)) + 
geom_bar(position="fill", stat="identity") +
# 将纵坐标转化为百分比的形式
scale_y_continuous(labels = function(x) paste0(x*100, "%"))

添加分组连线

原始的数值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
# 得到数据框
df=data.frame(
Phylum=c("Ruminococcaceae","Bacteroidaceae","Eubacteriaceae","Lachnospiraceae","Porphyromonadaceae"),
GroupA=c(37.7397,31.34317,222.08827,5.08956,3.7393),
GroupB=c(113.2191,94.02951,66.26481,15.26868,11.2179),
GroupC=c(123.2191,94.02951,46.26481,35.26868,1.2179)
)
df
Phylum GroupA GroupB GroupC
1 Ruminococcaceae 37.73970 113.21910 123.21910
2 Bacteroidaceae 31.34317 94.02951 94.02951
3 Eubacteriaceae 222.08827 66.26481 46.26481
4 Lachnospiraceae 5.08956 15.26868 35.26868
5 Porphyromonadaceae 3.73930 11.21790 1.21790

melt(df)
melt(df)
Using Phylum as id variables
Phylum variable value
1 Ruminococcaceae GroupA 37.73970
2 Bacteroidaceae GroupA 31.34317
3 Eubacteriaceae GroupA 222.08827
4 Lachnospiraceae GroupA 5.08956
5 Porphyromonadaceae GroupA 3.73930
......

# GroupA、GroupB之间的连线情况
data=df %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupA=cumsum(GroupA)) %>%
mutate(GroupB=cumsum(GroupB))
data
Phylum GroupA GroupB GroupC
1 Ruminococcaceae 37.73970 113.2191 123.21910
2 Porphyromonadaceae 41.47900 124.4370 1.21790
3 Lachnospiraceae 46.56856 139.7057 35.26868
4 Eubacteriaceae 268.65683 205.9705 46.26481
5 Bacteroidaceae 300.00000 300.0000 94.02951

# melt将dataframe转化为更适合ggplot2绘图的数据格式
ggplot(melt(df), aes(x=variable, y=value, fill=Phylum)) +
geom_bar(stat = "identity", width=0.5, col='black') + theme_classic()+
# 这里的添加连线的部分,关键点:排序、cumsum
# 第一个是处理A->B
# x=1.25:连线开始的地方,是列名
# xend=1.75:连线结束的地方,是列名
# 上面两个需要和geom_bar的width=0.5参数结合起来
geom_segment(data=df %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupA=cumsum(GroupA)) %>%
mutate(GroupB=cumsum(GroupB)),
aes(x=1.25, xend=1.75, y=GroupA, yend=GroupB))+
# 第二个是处理B->C
geom_segment(data=df %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupB=cumsum(GroupB)) %>%
mutate(GroupC=cumsum(GroupC)),
aes(x=2.25, xend=2.75, y=GroupB, yend=GroupC))

R_bar_plot_optimize.png

转化为百分比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 计算得到百分比
# 不要round,不然加和可能不是0
df= melt(df) %>% group_by(variable) %>%
mutate(percent=value/sum(value)) %>% group_by(variable) %>% mutate(total=sum(percent))
df
# A tibble: 15 x 5
# Groups: variable [3]
Phylum variable value percent total
<fct> <fct> <dbl> <dbl> <dbl>
1 Ruminococcaceae GroupA 37.7 0.126 1
2 Bacteroidaceae GroupA 31.3 0.104 1
3 Eubacteriaceae GroupA 222. 0.740 1
4 Lachnospiraceae GroupA 5.09 0.0170 1
5 Porphyromonadaceae GroupA 3.74 0.0125 1
......

# 还原原始的dataframe结构
# 并且使用percent作为填充值
# 默认使用的是value列
dcast(df,Phylum~variable,value.var="percent")
dcast(df,Phylum~variable,value.var="percent")
Phylum GroupA GroupB GroupC
1 Bacteroidaceae 0.10447723 0.3134317 0.313431700
2 Eubacteriaceae 0.74029423 0.2208827 0.154216033
3 Lachnospiraceae 0.01696520 0.0508956 0.117562267
4 Porphyromonadaceae 0.01246433 0.0373930 0.004059667
5 Ruminococcaceae 0.12579900 0.3773970 0.410730333

# GroupA、GroupB之间的连线情况
data=dcast(df,Phylum~variable,value.var="percent") %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupA=cumsum(GroupA)) %>%
mutate(GroupB=cumsum(GroupB))
data
Phylum GroupA GroupB GroupC
1 Ruminococcaceae 0.1257990 0.3773970 0.410730333
2 Porphyromonadaceae 0.1382633 0.4147900 0.004059667
3 Lachnospiraceae 0.1552285 0.4656856 0.117562267
4 Eubacteriaceae 0.8955228 0.6865683 0.154216033
5 Bacteroidaceae 1.0000000 1.0000000 0.313431700

# 画图
ggplot(df, aes(x=variable, y=percent, fill=Phylum)) +
geom_bar(stat = "identity", width=0.5, col='black') + theme_classic()+
geom_segment(data=dcast(df,Phylum~variable,value.var="percent") %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupA=cumsum(GroupA)) %>%
mutate(GroupB=cumsum(GroupB)),
aes(x=1.25, xend=1.75, y=GroupA, yend=GroupB))+
geom_segment(data=dcast(df,Phylum~variable,value.var="percent") %>%
arrange(by=desc(Phylum)) %>%
mutate(GroupB=cumsum(GroupB)) %>%
mutate(GroupC=cumsum(GroupC)),
aes(x=2.25, xend=2.75, y=GroupB, yend=GroupC))

R_bar_plot_optimize_2.png

多个分组快速绘制

前面在学习的时候都是两个两个来绘制的连线,这种对于分组较少的情况还比较适用,但分组一旦变多就不是很好用了。基于此,简单的做法是重新构建一个数据框,然后将cumsum的结果以及对应的连线坐标都保存,最终直接利用这个数据框就可以得到最后的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
df <- data.frame(
Phylum=c("Ruminococcaceae","Bacteroidaceae","Eubacteriaceae","Lachnospiraceae","Porphyromonadaceae"),
GroupA=c(37.7397,31.34317,222.08827,5.08956,3.7393),
GroupB=c(113.2191,94.02951,66.26481,15.26868,11.2179),
GroupC=c(123.2191,94.02951,46.26481,35.26868,1.2179),
GroupD=c(37.7397,31.34317,222.08827,5.08956,3.7393)
)

df
Phylum GroupA GroupB GroupC GroupD
1 Ruminococcaceae 37.73970 113.21910 123.21910 37.73970
2 Bacteroidaceae 31.34317 94.02951 94.02951 31.34317
3 Eubacteriaceae 222.08827 66.26481 46.26481 222.08827
4 Lachnospiraceae 5.08956 15.26868 35.26868 5.08956
5 Porphyromonadaceae 3.73930 11.21790 1.21790 3.73930

df.long <- df %>% gather(group, abundance, -Phylum)
# 等同于
# df.long = melt(df,id.vars = "Phylum")
Phylum variable value
1 Ruminococcaceae GroupA 37.73970
2 Bacteroidaceae GroupA 31.34317
3 Eubacteriaceae GroupA 222.08827
4 Lachnospiraceae GroupA 5.08956
5 Porphyromonadaceae GroupA 3.73930
6 Ruminococcaceae GroupB 113.21910
......

# 分组求和
link_dat <- df %>%
arrange(by=desc(Phylum)) %>%
mutate_if(is.numeric, cumsum)

link_dat
Phylum GroupA GroupB GroupC GroupD
1 Ruminococcaceae 37.73970 113.2191 123.2191 37.73970
2 Porphyromonadaceae 41.47900 124.4370 124.4370 41.47900
3 Lachnospiraceae 46.56856 139.7057 159.7057 46.56856
4 Eubacteriaceae 268.65683 205.9705 205.9705 268.65683
5 Bacteroidaceae 300.00000 300.0000 300.0000 300.00000

# 设置柱状图柱子的宽度
bar.width <- 0.7
# 将除GroupA、GroupD之外的所有列重复2遍
link_dat <- link_dat[, c(1,2,rep(3:(ncol(link_dat)-1),each=2), ncol(link_dat))]
link_dat
Phylum GroupA GroupB GroupB.1 GroupC GroupC.1 GroupD
1 Ruminococcaceae 37.73970 113.2191 113.2191 123.2191 123.2191 37.73970
2 Porphyromonadaceae 41.47900 124.4370 124.4370 124.4370 124.4370 41.47900
3 Lachnospiraceae 46.56856 139.7057 139.7057 159.7057 159.7057 46.56856
4 Eubacteriaceae 268.65683 205.9705 205.9705 205.9705 205.9705 268.65683
5 Bacteroidaceae 300.00000 300.0000 300.0000 300.0000 300.0000 300.00000

# matrix在不指定ncol和nrow的时候会返回一列数据,按列连接
# 指定了nrow的话就是将默认的一列数据转化为指定的行的数据
link_dat <- data.frame(y=t(matrix(t(link_dat[,-1]), nrow=2)))
link_dat
y.1 y.2
1 37.73970 113.21910
2 113.21910 123.21910
3 123.21910 37.73970
4 41.47900 124.43700
5 124.43700 124.43700
6 124.43700 41.47900
7 46.56856 139.70568
8 139.70568 159.70568
9 159.70568 46.56856
10 268.65683 205.97049
11 205.97049 205.97049
12 205.97049 268.65683
13 300.00000 300.00000
14 300.00000 300.00000
15 300.00000 300.00000

# 添加线的连接点
# 结合前面的width
link_dat$x.1 <- 1:(ncol(df)-2)+bar.width/2
link_dat$x.2 <- 1:(ncol(df)-2)+(1-bar.width/2)
link_dat
y.1 y.2 x.1 x.2
1 37.73970 113.21910 1.35 1.65
2 113.21910 123.21910 2.35 2.65
3 123.21910 37.73970 3.35 3.65
4 41.47900 124.43700 1.35 1.65
5 124.43700 124.43700 2.35 2.65
6 124.43700 41.47900 3.35 3.65
7 46.56856 139.70568 1.35 1.65
8 139.70568 159.70568 2.35 2.65
9 159.70568 46.56856 3.35 3.65
10 268.65683 205.97049 1.35 1.65
11 205.97049 205.97049 2.35 2.65
12 205.97049 268.65683 3.35 3.65
13 300.00000 300.00000 1.35 1.65
14 300.00000 300.00000 2.35 2.65
15 300.00000 300.00000 3.35 3.65

# 出图
ggplot(df.long, aes(x=group, y=abundance, fill=Phylum)) +
geom_bar(stat = "identity", width=bar.width, col='black') +
# 这里直接指定x、xend、y、yend
geom_segment(data=link_dat,
aes(x=x.1, xend=x.2, y=y.1, yend=y.2), inherit.aes = F)

R_bar_plot_optimize_3.png


其他小技巧

并列或者堆叠柱状图

默认柱状图是堆叠柱状图,如果想画出并列柱状图可以使用如下方法:

1
2
3
4
# fill:控制颜色和堆叠效果
# position ="dodge":并列柱状图,默认是堆叠
ggplot(final_stat, aes(x=ST, y=frac,fill=Sample_name)) +
geom_bar( stat="identity",position ="dodge")


存在异常值

如果柱状图的某些结果异常高,那么就会导致其他结果的可视化效果变差(增大了scale,压缩了其他bar),为了解决这种情况就可以设置坐标轴的显示范围,或者进行坐标轴截断

控制坐标轴范围

错误的做法,使用scale_y_continuousylim

1
2
3
4
5
6
7
8
9
10
11
12
13
# 直接使用scale_y_continuous(limits=c(0,0.03))会出问题
# 会直接将超过设置范围的bar去掉
ggplot(final_stat, aes(x=ST, y=frac,fill=Sample_name)) +
geom_bar( stat="identity",position ="dodge") +
scale_y_continuous(limits=c(0,0.03))

Warning message:
Removed 22 rows containing missing values (geom_bar).

# 和scale_y_continuous(limits=c(0,0.03))相同,直接使用ylim(0,0.03)也是一样的效果
ggplot(final_stat, aes(x=ST, y=frac,fill=Sample_name)) +
geom_bar( stat="identity",position ="dodge") +
ylim(0,0.03)

正确的做法,使用coord_cartesiancoord_flip

1
2
3
4
5
6
7
8
9
# 如果想让bar是垂直显示的,那就使用coord_cartesian
ggplot(final_stat, aes(x=ST, y=frac,fill=Sample_name)) +
geom_bar( stat="identity",position ="dodge") +
coord_cartesian(ylim=c(0,0.03))

# 如果想让bar水平显示,那就使用coord_flip
ggplot(final_stat, aes(x=ST, y=frac,fill=Sample_name)) +
geom_bar( stat="identity",position ="dodge") +
coord_flip(ylim=c(0,0.03))


坐标轴截断

ggplot2本身没有坐标轴截断的功能,所以一些文献中通过软件实现的截断图用ggplot2难以实现,标准的方案是使用分面达到类似的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
set.seed(2019-01-19)
d <- data.frame(
x = 1:20,
y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22)
)

d
x y
1 1 4.732672
2 2 3.962170
3 3 5.203009
4 4 5.469802
......

# 原始图形
ggplot(d, aes(x, y)) + geom_col()

# 绘制分面
library(dplyr)
# 设置分面位置
breaks = c(7, 17)
d$.type <- NA
d$.type[d$y < breaks[1]] = "small"
d$.type[d$y > breaks[2]] = "big"
d
x y .type
1 1 4.732672 small
2 2 3.962170 small
3 3 5.203009 small
4 4 5.469802 small
5 5 4.133690 small
6 6 20.519827 big
......
# 将大于break的值设置为break,并且将这些信息和原有的数据concat
d <- filter(d, .type == 'big') %>%
mutate(.type = "small", y = breaks[1]) %>%
bind_rows(d)
d
x y .type
1 6 7.000000 small
2 7 7.000000 small
3 8 7.000000 small
4 9 7.000000 small
5 10 7.000000 small
6 16 7.000000 small
7 17 7.000000 small
8 18 7.000000 small
9 19 7.000000 small
10 20 7.000000 small
11 1 4.732672 small
......


mymin = function(y) ifelse(y <= breaks[1], 0, breaks[2])
p <- ggplot(d, aes(x, y)) +
geom_rect(aes(xmin = x - .4, xmax = x + .4, ymin = mymin(y), ymax = y)) +
facet_grid(.type ~ ., scales = "free") +
theme(strip.text=element_blank())
p


参考链接



-----本文结束感谢您的阅读-----

本文标题:R绘图系列-绘制百分比柱状图以及优化

文章作者:showteeth

发布时间:2019年11月20日 - 22:47

最后更新:2019年12月12日 - 00:03

原始链接:http://showteeth.tech/posts/18071.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

0%