前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >111-R可视化35-结合grid与ggplot输出

111-R可视化35-结合grid与ggplot输出

作者头像
北野茶缸子
发布2022-04-05 15:29:25
7490
发布2022-04-05 15:29:25
举报
文章被收录于专栏:北野茶缸子的专栏
  • 参考:
    • 【R>>>gggrid】ggplot2中实现grid功能 - 简书 (jianshu.com)[1]
    • 《R绘图系统》Paul Murrell

前言

在先前的内容中[[101-R可视化29-底层绘图系统grid学习之使用grid作图]],我们说过,如果可以结合grid 与ggplot 绘图就好了:一方面,通过ggplot 绘图的高级语法,可以省去许多绘图中复杂的代码设置;另一方面,通过grid 底层的调用,我们也可以实现更加灵活的图层设置。

这里有两个思路。

1-通通拆成grob处理

既然ggplot 本质也是grid,那我把ggplot 拆成最底层,再慢慢处理,不也是可以的吗?

比如这里我心血来潮,想要在本来的ggplot 图形侧边放一个的棒棒糖图:

代码语言:javascript
复制
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))

其实这里都不要转换ggplot 对象,直接在原有图层上叠加即可:

代码语言:javascript
复制
p1 <- ggplot(iris) + geom_point(
  aes(Sepal.Length, Sepal.Width)
) + theme_void()
p1

candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))

md_inset <- viewport(x = 0, y = 0, 
                     just = c("left", "bottom"),
                     width = 0.35, height = 0.35)
pushViewport(md_inset)
grid.draw(rectGrob(gp = gpar(alpha = 0.5, col = "white")))
grid.draw(rectGrob(gp = gpar(fill = NA, col = "black")))
grid.draw(lollipop)
# popViewport()

但如果,我们想要在md_inset 视图下,结合ggplot 对象呢?

就需要我们将其拆解成grid 对象了:

代码语言:javascript
复制
p1 <- ggplot(iris) + geom_point(
  aes(Sepal.Length, Sepal.Width)
) + theme_void()

p2 <- ggplot(iris) + geom_bar(
  aes(Species)
)

p3 <- ggplotGrob(p1)

p2
md_inset <- viewport(x = 0, y = 0, 
                     just = c("left", "bottom"),
                     width = 0.35, height = 0.35)
pushViewport(md_inset)
grid.draw(rectGrob(gp = gpar(alpha = 0.5, col = "white")))
grid.draw(rectGrob(gp = gpar(fill = NA, col = "black")))
grid.draw(ggplotGrob(p1))

而这个转换后的ggplot 对象,我已经认不得了:

代码语言:javascript
复制
> names(p3)
 [1] "grobs"         "layout"        "widths"        "heights"       "respect"      
 [6] "rownames"      "colnames"      "name"          "gp"            "vp"           
[11] "children"      "childrenOrder"
> class(p3)
[1] "gtable" "gTree"  "grob"   "gDesc" 

如果我们想要进入指定的ggplot 相关的图层绘图,那就需要更加暴力的手段,grid.force,将ggplot 的视图可以获取使用。接着通过grid.grep获得名称,以进入对应viewport。

2-打印并不开启新页面

上面的例子中,当我们想要实现两个ggplot 结果的叠加显示时,使用的方法是,将被叠加的ggplot 对象转为grid,从而实现视图上的控制:

我们也可以在打印时声明ggplot 不创建newpage,从而实现类似的效果。

代码语言:javascript
复制
md_inset <- viewport(x = 0, y = 0, 
                     just = c("left", "bottom"),
                     width = 0.35, height = 0.35)
pushViewport(md_inset)
grid.draw(rectGrob(gp = gpar(alpha = 0.5, col = "white")))
grid.draw(rectGrob(gp = gpar(fill = NA, col = "black")))
print(p1, newpage = F)

3-ggplot原生方法annotation_custom

其实哈德雷也非常有远见的提供了这个方法annotation_custom

★This is a special geom intended for use as static annotations that are the same in every panel. These annotations will not affect scales (i.e. the x and y axes will not grow to cover the range of the grob, and the grob will not be modified by any ggplot settings or mappings). ”

参数如下:

  • grob:grob to display
  • xmin, xmax:x location (in data coordinates) giving horizontal location of raster
  • ymin, ymax:y location (in data coordinates) giving vertical location of raster

使用起来也非常简单,以上面的为例:

代码语言:javascript
复制
candy <- circleGrob(r = 0.1, x = 0.5, y = 0.6,
                    gp = gpar(col = "pink",
                              lty = 3,
                              lwd = 2))
stick <- segmentsGrob(x0 = 0.5, x1 = 0.5, y0 = 0, y1 = 0.5, gp = gpar(lwd = 2))
lollipop <- gTree(children = gList(candy, stick))
rect1 <- rectGrob(gp = gpar(alpha = 0.5, col = "white"))
rect2 <- rectGrob(gp = gpar(fill = NA, col = "black"))
rect_tree <- gTree(children = gList(rect1, rect2))

p2 + annotation_custom(rect_tree, 
                       xmin=0,xmax=1.5,ymin=0,ymax=10) + 
  annotation_custom(lollipop, 
                    xmin=0,xmax=1.5,ymin=0,ymax=10)

也不难发现这个方法的缺点,一个是对于分类数据的位置设定,即使不是分类数据,其位置也是按照坐标轴来确定,而非一个grid 舒舒服服的units 系统。

即使我们直接对grob 对象设置:

代码语言:javascript
复制
rect1 <- rectGrob(gp = gpar(alpha = 0.5, col = "white"),
                  x = 0, y = 0, 
                  just = c("left", "bottom"),
                  width = 0.35, height = 0.35)
rect2 <- rectGrob(gp = gpar(fill = NA, col = "black"),
                  x = 0, y = 0, 
                  just = c("left", "bottom"),
                  width = 0.35, height = 0.35)
rect_tree <- gTree(children = gList(rect1, rect2))

p2 + annotation_custom(rect_tree)

反正我挺不喜欢的。

4-使用包gggrid

gggrid 也就是R 绘图系统作者Paul Murrel 写的用于grid 融入ggplot 体系的R 包,其一共只有两个函数:

  • grid_panel()
  • grid_group()
代码语言:javascript
复制
p2 + gggrid::grid_panel(rect_tree)

这不是跟annotation_custom 差不多吗?

虽然grid_panel 也限定在了坐标轴的范围内,但其厉害之处在于可以接受函数作为grob 输入。

代码语言:javascript
复制
rectFun <- function(data,coords){
  left=min(coords$x)
  bottom=min(coords$y)
  width=diff(range(coords$x))
  height=diff(range(coords$y))
  rectGrob(left,bottom,width,height,
           just=c("left","bottom"),
           gp=gpar(fill=NA,lwd=1))
}
ggplot(mtcars,aes(disp,mpg))+
  geom_point()+
  grid_panel(rectFun)

比如我们可以根据对应坐标的结果进行绘图:

有意思的是,在[[106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象1]]中,coords 并不是直接获取的:

代码语言:javascript
复制
coords <- coord$transform(data, panel_scales)

原来文档早已说明:

代码语言:javascript
复制
`grob`:Either a grid grob or a function. The function must accept two arguments (`data` and `coords`) and must return a grid grob.

我们还可以通过函数的调试,以了解ggplot 的值:

代码语言:javascript
复制
debugHead <- function(data,coords){
  print(head(data))
  print(head(coords))
}
ggplot(mtcars,aes(disp,mpg))+
  geom_point()+
  grid_panel(debug=debugHead)
  
    x    y PANEL group xmin xmax
1 160 21.0     1    -1 -Inf -Inf
2 160 21.0     1    -1 -Inf -Inf
3 108 22.8     1    -1 -Inf -Inf
4 258 21.4     1    -1 -Inf -Inf
5 360 18.7     1    -1 -Inf -Inf
6 225 18.1     1    -1 -Inf -Inf
          x         y PANEL group xmin xmax
1 0.2470464 0.4555126     1    -1    0    0
2 0.2470464 0.4555126     1    -1    0    0
3 0.1291299 0.5251451     1    -1    0    0
4 0.4692737 0.4709865     1    -1    0    0
5 0.7005714 0.3665377     1    -1    0    0
6 0.3944421 0.3433269     1    -1    0    0

总结

其实这个总结真不好写。

如果是更加自由地使用,还是选择拆成grob 再各自处理的方案。

但如果你的grid 使用并不熟练,且需要的功能并不复杂,那么直接打印,也不失为一个对策。

而如果对于ggplot 的元素有所互动,比如通过其坐标判断以增加额外元素,那么gggrid 包,必然是不二选择了。

比如:

代码语言:javascript
复制
rectFun <- function(data,coords){
 left=min(coords$x)
 bottom=min(coords$y)
 width=diff(range(coords$x))
 height=diff(range(coords$y))
 rectGrob(left,bottom,width,height,
          just=c("left","bottom"),
          gp=gpar(fill=NA,lwd=1))
}
ggplot(mtcars,aes(disp,mpg))+
 geom_point()+
 grid_panel(rectFun)

就有个更成熟的R包 ggforce 可以替代。

它的底层代码,也是依靠gggrid吗?还是自己从[[106-R可视化30-底层绘图系统grid学习之重头创建ggplot对象1]] 这样更加底层的方式实现的吗?

其实无非就是获得coords 的结果,再结合[[110-R可视化34-通过seurat包中的LabelClusters给散点图中心添加文本]] 的一些思路实现的吧。

此外,如果是简单的绘图,其实利用图层的叠加关系,也可以实现,并不需要你有任何的grid 基础。

比如[[87-R可视化19-利用其他图层映射自由的控制背景的颜色]]:

随着R 包越来越多,我们还需不需要底层呢?甚至如此之多的shiny 可视化工具,我们还需不需要编程呢?

参考资料

[1]

【R>>>gggrid】ggplot2中实现grid功能 - 简书 (jianshu.com): https://www.jianshu.com/p/eb5a2f7299ff

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-03-07,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 北野茶缸子 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 1-通通拆成grob处理
  • 2-打印并不开启新页面
  • 3-ggplot原生方法annotation_custom
  • 4-使用包gggrid
  • 总结
    • 参考资料
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档