简介

好的数据分析都应该具有灵活性这一优点。如果数据发生变化,或者出现一些很不利于基本假设的信息时,这时候应该能够快速、便捷地更改之前地图形。重复地代码时灵活性地主要障碍。如果代码中很多都是重复、冗余的,那么当发生变化时就需要一处一处地去修改。这些繁杂地修改工作往往是很令人抓狂、沮丧地。通过展示如何用ggplot2编程,这一章将教导如何解决这些问题
为了让代码更具灵活性,需要通过编写函数来减少重复地代码。当发现自己在不断地做同样的事情的时候,就需要归纳相同的功能,把它写成一个函数。如果不是很熟悉R里面函数的运作方式

这一章会展示如何编写能创建以下对象的函数的方法

  • 单个ggplot2组件
  • 多个ggplot2组件
  • 一幅完整的图像

单个组件

每一个ggplot2图像的组件都是一个对象。通常,创建完一个组件之后就会直接把它添加到图像里面,但是着并不是必要要求。相反,可以(通过命名)把任意组件保存成一个变量,然后把它添加到不同的图像当中

bestfit <- geom_smooth(method = "lm",se = FALSE,color = alpha("steelblue", 0.5),size = 2)
ggplot(mpg, aes(cty, hwy)) + geom_point() + bestfit
ggplot(mpg, aes(displ, hwy)) + geom_point() + bestfit

这是一种很好的减少简单代码重复的方法(比“复制粘贴”要好多了!),不过这种方法要求每次被添加的组件都完全一样。如果想要更加灵活,也可以把可复用的代码片段封装成一个函数。比如,可以把bestfit对象扩展成一个更普适的函数,新函数用于向图像添加拟合度最好的线条。以下代码创建了geom_lm()函数,这个函数有三个参数:模型formula、线条颜色color和线条粗细size

geom_lm <- function(formula = y ~ x, color = alpha("steelblue", 0.5), size = 2, ...){geom_smooth(formula = formula, se = FALSE,method = "lm", color = color, size = size, ...)}
ggplot(mpg, aes(displ, 1 / hwy)) + geom_point() + geom_lm()
ggplot(mpg, aes(displ, 1 / hwy)) + geom_point() +geom_lm(y ~ poly(x, 2), size = 1, color = "red")

请注意…参数。函数定义里面的…参数意味着这个函数可以接收不定数目的附加参数。在函数内部,可以使用…把那些不确定的附加参数传递到另外的函数里面。这里把…传到geom_smooth()里面,那么用户就依然可以修改没有显式覆盖到的参数。编写自己组件函数的时候,推荐这样使用…
最后,请注意,只可以向图像添加组件,而不可以修改或者移除已有的对象

多个组件

单个组件不一定总是足够的。幸运的是,ggplot2能够使用列表来方便地向图像一次性添加多个组件。以下函数会添加两个图层:一个用于展示平均值,另一个用于展开标准误

geom_mean <- function() {list(stat_summary(fun.y = "mean", geom = "bar", fill = "grey70"),if(se)stat_summary(fun.data = "mean_cl_normal",geom = "errorbar", width = 0.4))}
ggplot(mpg, aes(drv, cty)) + geom_mean()
ggplot(mpg, aes(drv, cty)) + geom_mean(se = FALSE)

1. 绘图组件

以上方法不仅仅可以用来添加图层,也可以在列表中添加以下类型的对象

  • 一个数据框,它用于覆盖图像原有的数据集(如果只添加数据框,需要用到 %+%,但是当数据框在列表里面的时候,就不需要这个特别的运算符了)
  • 一个aes()对象,它会和已有的默认图形属性结合起来
  • 标度,它会覆盖已有的标度,如果用户已经设置了标度,添加新标度对象的时候会有警告
  • 坐标系和分面设置,它们会覆盖已有的设置
  • 主题组件,它会覆盖已有的相关组件

2. 注解

向图像添加标准注解通常是有好处。这里,函数也会在图层函数里面设定数据,而不是从图像中继承过来。这时,还需要设置另外两个选项,它们保证了图层是独立的

  • inherit.aes = FALSE 阻止了图层从原图像中继承图形属性。这个设置保证了无论图像中还有什么东西注解都能正常工作
  • show.legend = FALSE 保证了注解不会出现在图例区域里
    ggplot2内置的borders()函数运用了这些技术。它用于向maps包里面的数据集添加地图边界
borders <- function(database = "world", regions = ".", fill = NA,color = "grey50", ...){df <- map_data(database, regions)geom_polygon(geom_(~lat, ~long, group = ~group),data = df, fill = fill, color = color, ...,inherit.aes = FALSE, show.legend = FALSE)
}

3. 附加参数

使用…向函数内部组件传递附加参数并不是一个好的选择:它不能把不同的参数传递到不同的组件当中。相反,需要思考函数应该如何工作,把握好“拥有可以做所有事情的函数”和“拥有一个难以理解的复杂函数”之间的平衡
为了帮助上手,以下方法使用了modiyList()和do.call()

geom_mean <- function(...,bar.params = list(), errorbar.params = list()) {params <- list(...)bar.params <- modifyList(params, errorbar.params)bar <- do.call("stat_summary", modifyList(list(fun.y = "mean", geom = "bar", fill = "grey70"),bar.params))errorbar <- do.call("stat_summary", modifyList(list(fun.data = "mean_cl_normal",geom = "errorbar", width = 0.4,errorbar.params))list(bar, errorbar)}ggplot(mpg, aes(class, cty)) +geom_mean(color = "steelblue",errorbar.params = list(width = 0.5, size = 1))
ggplot(mpg, aes(class, cty)) +geom_mean(bar.params = list(fill = "steelblue"),errorbar.params = list(color = "blue"))

如果需要更复杂的行为,创建自定义的几何对象或统计变换或许更加容易。ggplot2包内置的说明Extending ggplot2介绍了对应的技术。可以通过运行vignette(“extending-ggplot2”)来阅读它

绘图函数

创建小型的可复用组件非常符合ggplot2的精神:灵活重组组件来绘制任意图像。但是某些时候只需要重复地创建相同地图像,而不需要那些灵活的性质。这样的话,也许不想编写组件,而是向编写一个接受数据和参数返回一个完整图像的函数

比如说,可以把所有相关代码都放到一个函数里面,这个函数用来绘制一幅饼图

piechart <- function(data, mapping){ggplot(data, mapping) +geom_bar(width = 1) +coord_polar(theta = "y") +xlab(NULL) + ylab(NULL)
}
piechart(mpg, aes(factor(1), fill = class))

和组合组件的方法相比,以上方法丧失不少灵活性,但是相应地,它获得了简洁性。需要注意的是,这里返回了绘图对象,而不是直接在函数里面把图像画出来。这样的话,可以向这个对下个添加其它的ggplot2组件

类似的方法可用于绘制平行坐标图(parallel coordnates plot, PCP)。平行坐标图要求对数据进行变换,因此简易编写两个函数:一个用于数据变换,另一个用于创建图像。如果想在不同的可视化任务中采取同样的变换方式,把这两个功能切分成两个函数能大大地简化以后的工作量

pcp_data <- function(df) {is_numeric <- vapply(df, is.numeric, logical(1))# 每一列的数值调整到相同的范围rescale01 <- function(x){rng <- range(x, na.rm = TRUE)(x - rng[1]) / (rng[2] - rng[1])}df[is_numeric] <- lappy(df[is_numeric], rescale01)# 行名作为行识别信息df$.row <- rownames(df)# 把数值变量变成value(即所测量到的)变量# gather_ 是gather函数的标准求值(standard-evaluation)版本,# 一般来说更加容易使用tidyr::gather_(df, "variable", "value", names(df)[is_numeric])}pcp <- function(df, ...){df <- pcp_data(df)ggplot(df, aes(variable, value, group = .row)) + geom_line(...)}
pcp(mpg)
pcp(mpg, aes(color - drv))

qplot()完整地探索了这个思想,它对常见的ggplot()选项提供了相对深入的封装。可以通过学习qplot()的源代码,来观察这些基本的技术能够提供怎样深入的用途

1. 间接地引用变量

上述的piechart()函数有一点不好:它要求用户明确地知道如何使用aes()函数来创建饼图。如果只要用户指定所需的变量名,就可以把图像绘制出来的话,这就更加方便了。因而需要学习一些有关aes()如何运作的知识

aes()使用了非标准求值(non-standard evaluation):它关注参数的表达式,而不是参数的值。这就使得变成更加困难,因为没有办法存储对象里的变量名,接着在之后的代码引用它

x_var <- "displ"
aes(x_var)
#> * x -> x_var

相反,下面使用aes_():它使用常规的求值方式。用aes_()创建一个映射规则有两种基本方法:

  • 使用quote()、substitute()、as.name()或parse()所创建的被引用调用(quoted call)
aes_(quote(displ))
#> * x -> displ
aes_(as.name(x_var))
#> * x -> displ
aes_(parse(text = x_var)[[1]])
#> * x -> displf <- function(x_var){aes_(substitute(x_var))}
f(displ)
#> * x -> displ

as.name()和parse()有很细微的差异。如果x_var是”a + b“的话,as.name()会把它变成一个名为`a + b`的变量,而parse()会把它变成一个函数调用 a + b。(如果对此感到很异或,参考http://adv-r.had.co.nz/Expressions.html也许会有些帮助)

  • 使用~所创建的公式
aes_(~ displ)
#> * x -> displ

对于用户提供变量的方法,aes_()给了三个选项:提供字符串、提供公式、提供纯表示式。以下是三个选项的用法

piechart1 <- function(data, var, ...){piechart(data, aes_(~factor(1), fill = as.name(var)))}
piechart1(mpg, "class") + theme(legend.position = "none")piechart2 <- function(data, var, ...){piechart(data, aes_(~factor(1), fill = var))}
piechart2(mpg, ~class) + theme(legend.position = "none")piechart3 <- function(data, var, ...){piechart(data, aes_(~factor(1), fill = substitute(var)))}
piechart3(mpg, class) + theme(legend.position = "none")

如果在一个软件包中编写与ggplot2图像相关的函数,aes_()相对于aes()还有一个有点:使用aes_(~x, ~y)取代aes(x, y)后能避免R CMD check里出现的NOTE全局变量的事项

2. 绘图环境

随着创建越来越复杂的绘图函数,将要理解更多的ggplot2的作用域(scoping)规则。在还没有完全理解非标准求值的复杂之处之前,作者就开始创造ggplot2了,因此ggplot2有一个非常简单的作用域系统。如果在data里面找不到某个变量,就在所对应的那个绘图环境里面寻找它。一个图像只有一个环境(而不是一个图层有一个环境),就是调用ggplot()函数的那个环境(比如说,parent.frame())
这意味着以下函数不能运行,因为n没有被存储在aes()的环境当中

f <- function(){n <- 10geom_line(aes(x / n))
}
df <- data.frame(x = 1:3, y = 1:3)
ggplot(df, aes(x, y)) + f()
#> Error in x/n: 二进列运算符中有非数值参数

注意,这个问题只出现在mapping参数有关的代码当中。所有其它的参数都会立刻被求值,所以它们的值(而不是只想名字的引用)会被存储在绘图对象内部。这意味着以下代码是可以工作的

f <- function(){color <- "blue"geom_line(color = color)}
ggplot(df, aes(x, y)) + f()

如果需要不同的绘图环境,可以用ggplot()里的environment参数来指定它。在创建接受用户所提供的数据的绘图函数的时候,都需要这样做。qplot()函数可以作为一个例子

函数式编程

因为ggplot2对象知识普通的R对象,所以可以把它们放到列表当中。着意味着可以使用R里面所有的好用的函数式编程工具。比如说,如果想向同一个基础图像添加不同的几何对象,可以把这些几何对象放到列表当中,然后使用lappy()

geoms <- list(geom_point(),geom_boxplot(aes(group = cut_width(displ, 1))),list(geom_point(), geom_smooth())
)p <- ggplot(mpg, aes(displ, hwy))
lapply(geoms, function(g) p + g)

如果不是很熟悉函数式编程,可以阅读http://adv-r.had.co.nz/Functional-programming.html,然后思考一下如何使用函数式编程技术来优化重复的图像代码

ggplot2-用ggplot2编程相关推荐

  1. 如何用ggplot2绘制漂亮的统计图形

    导言 ggplot2 是由 Hadley Wickham 开发的一个功能十分强大的 R 语言绘图程序包,它能够非常轻松的画出各种好看的统计图形.关于 ggplot2 的教程有很多,Cédric Sch ...

  2. r语言ggplot合并图形_R中带有ggplot2的图形

    r语言ggplot合并图形 介绍 (Introduction) R is known to be a really powerful programming language when it come ...

  3. R语言入门——ggplot2

    常用可视化R包 image.png #作图分三类#1.基础包 略显陈旧 了解一下plot(iris[,1],iris[,3],col = iris[,5]) # text(6.5,4, labels ...

  4. ggplot2设置坐标轴范围_R可视化03|ggplot2图层-几何对象图层(geom layer)

    前面简单介绍ggplot2是基于图层图形语法(the Grammar of Graphics),一张完整图由不同图层叠加而成,本文介绍几何对象图层(geom layer),续前篇: R可视化01|gg ...

  5. r语言ggplot2一夜多图_ggplot2绘图:多张图合并为一张

    以下内容来自教程 R语言中多张图画到同一个页面上常用的函数为par()和layout() par()函数详解 layout()函数的简单使用 但是这两个函数不适用于ggplot2:ggplot2作图如 ...

  6. 语言 标签倾斜 绘图_一文搞懂ggplot2:老板再也不用担心我的科研绘图

    本文主引用:R可视化19|ggplot2绘制常用30+个靓图(附R code) 作者:pythonic生物人 参考文献:R语言 ggplot2 绘图入门,看完你就理解ggplot2的绘图逻辑了 202 ...

  7. R语言ggplot2移除图例_读书笔记:R语言绘图—ggplot2

    这次给大家带来的是R语言绘图神器-ggplot2绘图包,根据Hadley的说法,这个包的核心思想,是来源于 Leland Wilkinson<The Grammar of Graphics> ...

  8. r语言导入ggplot2_【ggplot2】R语言:ggplot2包

    原标题:[ggplot2]R语言:ggplot2包 1 如何使用ggplot2包绘制高质量图画? 我们将学习R中最受欢迎的软件包之一,它是ggplot2:图形语法的实现. 我正在使用R中提供的iris ...

  9. 【R语言】ggplot2:初次见面,请多多关照!

    目录 一.序 二.ggplot2是什么? 三.ggplot2能画出什么样的图? 四.组装机器 五.设计图纸 六.机器的零件 1. 零件--散点图 1) 变换颜色 2) 拟合曲线 3) 变换大小 4) ...

  10. r语言做绘制精美pcoa图_科学网—R语言 PCA PCoA ggplot2 - 靳泽星的博文

    这一篇是衔接上一篇的,就是要用ggplot2程序包对PCA和PCoA进行可视化.代码我直接照搬过来了,只是绘图的时候用ggplot函数.ggplot2包实现了一个在R中基于全面一致的语法创建图形时的系 ...

最新文章

  1. c++ char数组和char*
  2. python爬虫吧-Python爬虫案例集合
  3. tomcat向weblogic移植需要注意的问题
  4. 重磅!2K图像90FPS,中科院开源轻量级通用人脸检测器
  5. sed教程(七)之特殊字符
  6. Java中使用ArrayList的10个示例–教程
  7. Oracle数据库物理存储结构管理遇到的问题与解决
  8. Java枚举(Enum)类型的基本介绍与原理探求
  9. 良好的XHTML编写习惯
  10. autocoder自动代码生成器_Spring Boot 集成MyBatis Plus代码生成器
  11. 【老生谈算法】matlab实现图像复原算法源码——图像复原
  12. 数据结构基础知识——非线性数据结构(二叉树、二叉排序树、优先队列、散列表)
  13. PostgreSQL 逻辑复制插件 UDR,可以愉快的玩类似MySQL的binlog复制了。
  14. 密码箱忘记密码解决方法
  15. unia-app第三方app调用
  16. 有哪些运动耳机比较好用,推荐六款值得入手的运动耳机
  17. 如何在微信小程序里实现聊天室功能?
  18. Pandas含中文表格对齐输出
  19. 【Python】【Java】【面试】【WordPress】【深度学习】【开源软件】| Chat · 预告
  20. 问题七:vue+ts The left-hand side of an assignment expression may not be an optional property?

热门文章

  1. xlwings : 从此可以 VBA 调用 Python 代码啦
  2. C++ Primer 学习笔记 第十章 泛型算法
  3. 收藏这些vue项目性能优化方式,总有一天能用上
  4. python ggplot画等值线图_用Python画漂亮的专业插图 ?So easy!
  5. 概率函数P(x)、概率分布函数F(x)、概率密度函数f(x)
  6. 【长难句分析精讲】定语从句
  7. linux 性能测试命令
  8. unbound域名解析
  9. 第3章-Java NIO编程
  10. 树莓派3B连接wifi