首页 > 科研绘图 > R|ggplot2(四)|stat_ geom_ 和position
2022
10-07

R|ggplot2(四)|stat_ geom_ 和position

作者:dwzbR语言中文社区专栏作者知乎专栏:https://zhuanlan.zhihu.com/Data-AnalysisR

第一篇戳:

第二篇戳:R|ggplot2(二)|覆盖柱状图各种需求

引用一句Hadley在ggplot2网站上的一句话

A layer combines data, aesthetic mapping, a geom (geometric object), a stat (statistical transformation), and a position adjustment. Typically, you will create layers using a geom_ function, overriding the default position and stat if needed.

一个图层需要指定数据集、使用数据集中的哪些内容(aes部分),做什么样的图形(geom_ 指定是制作点图还是柱状图)、数据的统计转换(stat部分)、图形位置调整(position)。

本节主要针对后三者进行介绍,我们分为以下几个内容

  • geom_和stat_之间的关系

  • 一些需要注意的点

  • stat的定义及使用

  • position的使用

1. geom_和stat_之间的关系

  • 相互替代的关系,比如geom_bar和stat_count是可以相互替代的

  • 默认和改变。比如geom_bar默认stat是"count",但是可以转化为"identity",从而使用其他类型的数据

library(ggplot2)

ggplot(mpg,aes(x=class)) + geom_bar() # 使用一个变量做柱状图

ggplot(mpg,aes(x=class)) + stat_count() # 和上面一样

ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity") # 使用两个变量作柱状图

ggplot(mpg,aes(x=class,y=displ)) + geom_col() # 与上面相同

ggplot(mpg,aes(x=class,y=displ)) + stat_identity() # 散点图

ggplot(mpg,aes(x=class,y=displ)) + geom_point() # 等价于上一条

上面的代码显示出了如下内容

  • geom_bar和stat_count的相互替代,即geom_bar默认使用stat="count",stat_count默认使用geom="bar",即这种统计变换默认画出的是柱状图

  • 在geom_bar中更改默认的"count"为"identity"就可以接受两个变量作图

  • geom_col也是画柱状图,但是默认stat="identity"

  • geom_point和stat_identity 互相默认

  • 所以ggplot2包中geom与stat经常成对出现,如果不特意指定更改就可以相互替代

我们可以查看函数的帮助文档来获知默认参数,比如

?geom_boxplot # 输入这条命令查看函数定义,截取如下内容

geom_boxplot(mapping = NULL, data = NULL, stat = "boxplot",

position = "dodge", ..., na.rm= FALSE, show.legend= NA,

inherit.aes= TRUE)

stat_boxplot(mapping = NULL, data = NULL, geom = "boxplot",

position = "dodge", ..., coef = 1.5, na.rm= FALSE, show.legend= NA,

inherit.aes= TRUE)

我们可以看到geom_boxplot里面参数stat默认为"boxplot",stat_boxplot也有一个参数geom默认是"boxplot"。

2. 一些需要注意的点

我们先来看一看如下代码

ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity")

ggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="bar") # 调换顺序图形不一样了

ggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="col") # 和上面一样

ggplot(mpg,aes(x=class,y=displ)) + stat_identity() # 看看散点图的样子

ggplot(mtcars, aes(wt, mpg)) + stat_identity(geom="bar") # 换一个数据集试一试

ggplot(mtcars, aes(wt, mpg)) + stat_identity() # 和它对应的散点图做对比

# ggplot(mpg,aes(x=class)) + geom_col(stat="count") # 报错

从上面结果我们可以看出如下信息

(1)看到geom和stat的相互替换现象,一个很自然的想法是,geom_bar修改stat为"identity"作图结果,和stat_identity修改geom为"bar"应该是一样的。但是实际上却不一样

后者作图结果是什么呢?我们可以从散点图中得到启发。比如第一根柱子最高是7,我们可以看到散点图中2seater对应的点纵坐标最大也是7.所以我们猜想这样做的结果是将点换成一个有相同高度的柱子来表示,而因为这个数据的横坐标是离散的,很多柱子重叠在一起,无法分辨,所以我们考虑换一个横坐标是连续的数据再试一试。mtcars数据集作图结果正好证实了我们的猜想

出现这个问题的原因后文会进行说明

(2) 接下来看,最后一行报错的代码我们原来认为geom_col默认stat是"identity",所以想如果将其换成geom_bar对应的"count"是不是就可以只接受一个变量作柱状图,结果竟然报错。

查找原因发现,其实geom_col并没有stat这个参数。直接在控制台输入geom_col查看源码,可以看出在调用layer函数的时候,stat参数直接指定的是"identity",如果查看geom_bar函数源代码的话,可以看到,调用layer函数的时候stat参数接的是stat即我们指定的参数对应的内容。所以geom_col是无法更改stat的

3. stat与geom的定义及使用

上面我们看到一些和我们预期不相符的结果,我们要想弄懂它们,就要理解stat和geom内部的运行机制,我们可以看ggplot2包中的User guides, package vignettes and other documentation.里面的文章extending-ggplot2,这也可以在ggplot2的网站上找到。这盘文章介绍了如何自己创建一个新的geom和新的stat,创建新的函数需要我们遇到具体问题时才要去做的事情,在这里我只想通过理解它的创建过程,来理解原有函数之间的关系。

(1) 首先看stat的创建过程

一个完整的创建样例代码如下

StatChull <- ggproto("StatChull", Stat,

compute_group = function(data, scales){

data[chull(data$x, data$y), , drop = FALSE]

},

required_aes = c("x", "y")

)

stat_chull <- function(mapping = NULL, data = NULL, geom = "polygon",

position = "identity", na.rm = FALSE, show.legend = NA,

inherit.aes = TRUE, ...){

layer(

stat = StatChull, data = data, mapping = mapping, geom = geom,

position = position, show.legend = show.legend, inherit.aes = inherit.aes,

params = list(na.rm = na.rm, ...)

)

}

# 使用新的函数

ggplot(mpg, aes(displ, hwy)) +

geom_point() +

stat_chull(fill = NA, colour = "black")

解释如下

  • 使用ggproto定义了一个StatChull对象,compute_group指定这个函数对数据进行了什么样的操作,required_aes表示使用这样一个stat时aes中需要使用几个变量作为参数。

  • 下面定义了一个stat_chull函数,它的形式和参数都很像我们之前遇到的stat_count等函数,所以这就是我们想要创建的最终函数。

  • 这个函数默认geom = "polygon"。函数调用了layer函数,这和我们之前查看的stat_boxplot源代码格式相同。stat参数使用了上面创建的StatChull。

  • 所以调用stat_chull函数的内在逻辑是:画geom_polygon这个类型的图,使用的数据这样得到:输入data中的x和y值,进行StatChull中的compute_group函数中的变换,使用变换之后的数据。在这里,变换就是data[]取子集,拿取出来的点做多边形

(2) 我们看看geom的创建过程

geom创建代码较长,就不在这里贴出,读者可以自行到网站上查看。过程和stat的创建大同小异。

  • 先创建GeomSimplePoint对象,再创建geom_simple_point函数(最终想要的),其中调用layer函数,默认指定geom=GeomSimplePoint

  • 其中GeomSimplePoint对象中也是定义了接受的aes,以及用于绘图的函数。我们可以从中看出ggplot2中绘图函数调用的是grid包中的相应函数

(3) 查看已有geom和stat细节的方法

比如stat_count,我们进行如下几个步骤

  • 我们直接输入命令stat_count,找到默认参数stat = StatCount,知道了存储着统计变换函数的对象名字叫做StatCount

  • 输入StatCount列出了可以查看的元素,我们要看的是compute_group这个函数,所以输入StatCount$compute_group

  • 找到<Inner function (f)> 部分,最后生成的data.frame就是这个stat变换之后会使用的数据

  • 对于stat_identity来说,找到StatIdentity,结果<Inner function (f)>内容没有什么变换,意味着这是使用原始数据本身

同理,查看geom_point也是先找到GeomPoint,再看GeomPoint$draw_key 查看作图函数

(4) 分析之前遗留下来的问题

geom_bar(stat="identity")和stat_identity(geom="bar")结果不一样的问题。

通过上面的学习,我们知道了stat和geom函数其实就是相互调用的关系,都是调用的layer函数,指定geom和stat,以上两者的这两个参数都应该是一样的,所以问题出现在其他参数的选择上。

在控制台上分别输入stat_identity和geom_bar,对比默认的参数,发现不一样在于position参数,前者是"identity",而后者是"stack",所以我们更改默认参数就可以使作图结果相同。

ggplot(mpg,aes(x=class,y=displ)) + geom_bar(stat="identity")

ggplot(mpg,aes(x=class,y=displ)) + stat_identity(geom="bar",position="stack")

即geom_bar默认使用stack堆叠的方式,将所有柱子堆积成一根柱子,而stat_identity默认原地放置,全部重叠在一起,只能显示出最长的那根柱子

下面我们系统地介绍position

4. position的使用

在rstudio的控制台中输入position_就会提示出所有类型的position,我们以position_dodge为例进行解释

position_dodge对应PositionDodge,可以在这里查看内部计算函数

下面我们说一说position参数设置,主要是width参数

这里的width要和geom_bar的参数width进行区分

df <- data.frame(x = 1,

y = 1,

grp = c("A", "B"))

p <- ggplot(data = df, aes(x = x, y = y, fill = grp)) + theme_minimal()

p + geom_bar(stat = "identity", position = "dodge")

# 这里width是两个柱子单独算宽度再相加

p + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)

p + geom_bar(stat = "identity", width = 1.5, position = position_dodge(), alpha = 0.8)

# 多一组数来看

df1 <- data.frame(x = c(1,1,2,2),

y = rep(1,4),

grp = c("A", "B"))

pp <- ggplot(data = df1, aes(x = x, y = y, fill = grp)) + theme_minimal()

# width为1正好两组挨在一起,再增大就会出现重叠

pp + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)

pp + geom_bar(stat = "identity", width = 1.5, position = position_dodge(), alpha = 0.8)#接下来还是使用df来看position中的width

# 这个width配合之前的width调整一组内两根柱子的重叠与分离

# 这里width是两根柱子中心距离的两倍

# 所以当这个width大于bar中的width,柱子就会分离,小于就会有重叠

p + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 1.5), alpha = 0.8)

p + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 0.5), alpha = 0.8)

# 给每组的空间还是1的长度,当position中的width比较大,拉开内部两根柱子的距离,就会和另外一组的柱子发生重叠

pp + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 1.5), alpha = 0.8)

pp + geom_bar(stat = "identity", width = 1, position = position_dodge(width = 0.5), alpha = 0.8)

# 在柱子上增加文字

# 查看帮助可知geom_text对应stat是identity,所以当不用position调整时,加的标签全堆在点(1,1)处

p <- ggplot(data = df, aes(x = x, y = y, fill = grp, label = grp)) + theme_minimal()

p + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)+

geom_text(size = 10)

# geom_text中指定position内width参数,调整文字间距离

p + geom_bar(stat = "identity", width = 1, position = position_dodge(), alpha = 0.8)+

geom_text(position = position_dodge(width = 0.3), size = 10)

# 两个position_dodge中的width应该相同,文字才会在柱子中心

p + geom_bar(stat = "identity", width = 2, position = position_dodge(), alpha = 0.8)+

geom_text(position = position_dodge(width = 2), size = 10) # bar中position的width自动等于前面的width

p + geom_bar(stat = "identity", width = 1, position = position_dodge(width=1.2), alpha = 0.8)+

geom_text(position = position_dodge(width = 1), size = 10) # bar中position指定另外一个width

p + geom_bar(stat = "identity", width = 1, position = position_dodge(width=1.2), alpha = 0.8)+

geom_text(position = position_dodge(width = 1.2), size = 10) # 两个position中width保持相同又回到中心



最后编辑:
作者:萌小白
一个热爱网络的青年!

发布评论

表情