查看原文
其他

可别再说base plot不能图形语法!

Y叔叔 YuLabSMU 2022-09-24

经常有人说ggplot2牛逼,图形语法,base plot不行,这两货根本就不在一个level,怎么能这么比呢?我在《👉树变图,图变树?》一文中已经做了解释。base plot应该和grid比,而ggplot2是构建在grid之上的,这主要是因为有大神hadley wickham。很多东西所谓行与不行,多半是因为还没有大神来插手,比如说python机器学习牛逼,其实就一张皮,算法都是C++,难道perl, R就不配拥有一套马甲?原因还是因为没有大神染指。想当年ruby火起来的时候,就是因为有ruby on rails。所以啊,A语言行,B语言不行,说到底多半不是行不行的问题,而是缺乏唐长老来缝虎皮裙子。

当初在推特上被AT了,《👉ggfree:试图让你摆脱ggplot2》,我看完之后,一顿吐槽,吐槽之后,我又想,语言是苍白的,所谓you can you up, no can no bb。

于是我就撸起袖子,搞了几天,写了一个叫plotbb的包,正好是ggplot反过来,bb和gg也像是反过来似的。取名brammar of braphics的基于base plot的图形语法。

自2019年12月写了一些功能,疫情来了之后,就一直没空再写,毕竟很忙,再者要真写出来一整套图形语法,可不简单,ggplot2可是hadley wickham大神的博士论文,而且又经历了这么多年的完善。但先能用,以后的再说。

美学映射

基本上所有的东西都是以bb_打头,这样不会和ggplot2撞名,也方便大家把plotbb包的功能用tab键给列出来。

图形语法怎么能没有美学映射,这也是我吐槽ggfree包的重点之一。废话不说,直接看代码:

library(plotbb)

p <- bbplot(mtcars, bb_aes(mpg, disp, col=factor(cyl)))
p + bb_grid(col='grey50', lty='dashed') + bb_point(pch=19

和你的ggplot2代码是不是几乎一毛一样,出图如下:

几何图层

上面的图,我可以加不同的图层,也可以叠加不同的图层:

p2 <- p + bb_point() + bb_lm(bb_aes(group=cyl), lwd=2)
p3 <- p2 + bb_lm(col="red", lwd=3, lty='dotted')
p4 <- p + bb_text(bb_aes(label=cyl), cex=2)
par(mfrow=c(1,3))
p2; p3; p4

画个热图试试

用base plot,除了用现成的函数,你是不是不会画热图?现在我就要来教你画了,而且是辣么的简单,映射xy轴,然后一个bb_tile的图层搞定。

df <- data.frame(x = rep(1:1012),
                 y = rep(1:12, each = 10),
                 values = rnorm(120, mean = 10, sd = 5),
                 type = sample(LETTERS[1:5], 120, replace=TRUE),
                 stringsAsFactors = FALSE)
par(mfrow=c(2,2))
bbplot(df, bb_aes(x,y, col=values)) + bb_tile() + 
  bb_title("heatmap for continuous numerical values"
bbplot(df, bb_aes(x,y, col=values)) + bb_tile() + bb_scale_col_palette("YlOrRd") +
  bb_title("applying a color palette"
bbplot(df, bb_aes(x,y, col=type)) + bb_tile() +
  bb_title("heatmap for discrete categorical values"
bbplot(df, bb_aes(x,y, col=values)) + bb_tile() + bb_text(col='black') +
  bb_title("heatmap with text labels") + bb_theme_expand()

此处画了几个,连续型、离散型,还有变换颜色标尺,和加文本图层。

设置label

title, xlab, ylab, subtitle一应具全。

p2 + bb_labs(title = "hello", sub = "just for demo",
              xlab="this is xlab", ylab = "this is ylab") +
  bb_title("hello world"# last one rules

主题

怎么可以没有主题,对吧?在画热图的时候,大家已经主题初印象了。

g <- p2 +
     bb_theme(col.main="red", cex.main=2,
             mar = c(4431)) +
     bb_title("applying graphics::par")
par(mfrow=c(1,2))
print(g) 
p2 + bb_title("theme has no side effect")

看到没有,应用了主题画g,然后这个主题对p2没有影响。这就是牛逼的地方,因为base plot要设置主题啊,得经过par(),一搞就是全局的。但我就是可以搞成没有side effect的。

平时用base plot画,会不会觉得图的margin太大?我反正就有这感觉,所以啊,我给你写了一个放大画图区域的主题。

par(mfrow=c(1,2)) 
p3 + bb_theme_expand()
print(p3)

对比一下,是不是左图舒服多了?

再来一个仿ggplot2的灰色背景主题吧。

p + bb_grid(col='grey50', lty='dashed') +
  bb_point(pch=19) +
  bb_theme_expand() +
  bb_theme_grey() 

此处我主题再加主题的用法,没加主题之前,一切是你的环境设置,加了主题1,就会用主题1指定的东西去改变默认的。再加主题2就又叠加了设置。比如说主题1设置了A和B,主题2设置了B和C,那么主题1+主题2就设置了A,B和C,而且B是主题2设置的,因为它在后面加的。

最后再来一个深蓝的主题:

p + bb_point(pch=19, cex=2) +
  bb_theme_expand() +
  bb_theme_deepblue() 

你也会写主题

我一不小心,就写了一个最容易写的主题系统,小白菜鸟都会写主题了。

我们来看深蓝的主题是怎么定义的:

bb_theme_deepblue <- function(...) {
    bb_theme(
        bg = "#002E49",
        fg = "#CCCCCC",
        col.axis = "#BEBEBE",
        col.lab = "#FFFFFF"
    ) <= bb_theme(...)
}

一开始是bb_theme()定义了你默认的东西,然后是bb_theme(...)允许用户再传递额外的设置,或者覆盖掉默认设置,比如你能深蓝主题传个bg的参数,设置背景颜色不是深蓝,它也OK。你给它传别的参数,比如mar设置margin,同样也OK。你只要仿照这个写法,把第一个bb_theme()中的东西,改成你想要的一些默认设置,你就定义了一个新的主题。至于后面那个<= bb_theme(...),你照抄就可以了。没错,我这里自己定义了一个<=的操作符,它不再是比大小,不是小于等于的意思了。

和现有代码衔接

做为一套图形语法,还有很多东西没有实现,所以啊,要让plotbb在一开始就可以被大家所使用,它就必须能够衔接现有的代码。这里包括了几个方面:

  • 代码出图,我是有的,我就想用用主题,可行?
  • 我有代码去画图(包括各种包的各种函数),然后我想用图形语法加个图层,行不?
  • 我用图形语法出图,想再加点什么,当前还不支持,能否用我熟悉的老代码去加上去?

做为一个成年人,我啥都想要,然后对于上面的问题,答案全部是行行行。

首先假如我有以下代码:

plot(mtcars$mpg, mtcars$disp)
abline(lm(disp ~ mpg, data=mtcars), col='red')

这时候能我把它封装成函数:

f <- function() { 
  plot(mtcars$mpg, mtcars$disp)
  abline(lm(disp ~ mpg, data=mtcars), col='red')
}

plotbb包呢,提供了一个函数as.bbplot()就可以把函数里的代码,转成了一个bbplot对象,然后就可以愉快地用图形语法去加图层,和应用主题了。

library(dplyr) 
d <- group_by(mtcars, cyl) %>%
  summarize(xm=mean(mpg), ym=mean(disp))

pp <- as.bbplot(f) +
   bb_theme_expand() +
   bb_theme_grey() +
   bb_lm(bb_aes(mpg, disp, group=cyl, col=factor(cyl)), data=mtcars, lwd=2, lty='dashed') +
   bb_point(bb_aes(xm, ym, col=factor(cyl)), data=d, pch=19, cex=2) +
   bb_title("hello plotbb") +
   bb_grid(col='grey30', lty='dashed'## grid lines were plotted as background by default

这时候啊,假设我还想干点什么,我能不能用base plot的代码来搞?可以可以都可以!

pp + (~points(30400, pch=19, col="red", cex=3)) + 
   ~text(30420, label="hae fun :)", col="blue", cex=1.2)

只需要在你的代码前加一个波浪线,plotbb就会去处理它,变成图层给你加上去。

就是最后这一些兼容的功能,让这个包在当前还是BB(baby)状态的时候,已经可以用了,就像在梦里一样,想干什么干什么!

往期精彩

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存