我需要在一个图表中绘制一个显示计数的柱状图和一个显示率的折线图,我可以分别做这两个,但当我把它们放在一起时,我的第一层(即geom_bar)的比例被第二层(即geom_line)重叠。
我可以将geom_line的轴向右移动吗?
我需要在一个图表中绘制一个显示计数的柱状图和一个显示率的折线图,我可以分别做这两个,但当我把它们放在一起时,我的第一层(即geom_bar)的比例被第二层(即geom_line)重叠。
我可以将geom_line的轴向右移动吗?
当前回答
这是我对如何做二次轴变换的两种看法。首先,您希望将主数据和辅助数据的范围耦合起来。这通常是混乱的,因为您不想要的变量污染了全局环境。
为了简化这一点,我们将创建一个生成两个函数的函数工厂,其中scales::rescale()完成所有繁重的工作。因为这些是闭包,所以它们知道创建它们的环境,所以它们“有”创建之前生成的to和from参数的“内存”。
一个函数进行正向转换:将辅助数据转换为主要尺度。 第二个函数进行反向转换:将主要单位中的数据转换为次要单位。
library(ggplot2)
library(scales)
# Function factory for secondary axis transforms
train_sec <- function(primary, secondary, na.rm = TRUE) {
# Thanks Henry Holm for including the na.rm argument!
from <- range(secondary, na.rm = na.rm)
to <- range(primary, na.rm = na.rm)
# Forward transform for the data
forward <- function(x) {
rescale(x, from = from, to = to)
}
# Reverse transform for the secondary axis
reverse <- function(x) {
rescale(x, from = to, to = from)
}
list(fwd = forward, rev = reverse)
}
这看起来相当复杂,但是创建函数工厂会使其余的一切变得更简单。现在,在绘制图形之前,我们将通过向工厂显示主要和次要数据来生成相关函数。我们将使用经济学数据集,它的失业列和pasavert列的范围非常不同。
sec <- with(economics, train_sec(unemploy, psavert))
然后我们使用y = sec$fwd(psavert)将辅助数据重新缩放到主轴,并指定~ sec$rev(.)作为辅助轴的转换参数。这给了我们一个主要范围和次要范围在图上占据相同空间的图。
ggplot(economics, aes(date)) +
geom_line(aes(y = unemploy), colour = "blue") +
geom_line(aes(y = sec$fwd(psavert)), colour = "red") +
scale_y_continuous(sec.axis = sec_axis(~sec$rev(.), name = "psavert"))
工厂比这稍微灵活一些,因为如果您只是想重新调整最大值,您可以传入下限为0的数据。
# Rescaling the maximum
sec <- with(economics, train_sec(c(0, max(unemploy)),
c(0, max(psavert))))
ggplot(economics, aes(date)) +
geom_line(aes(y = unemploy), colour = "blue") +
geom_line(aes(y = sec$fwd(psavert)), colour = "red") +
scale_y_continuous(sec.axis = sec_axis(~sec$rev(.), name = "psavert"))
由reprex包于2021-02-05创建(v0.3.0)
我承认这个例子中的区别不是很明显,但如果你仔细观察,你会发现最大值是相同的,红线比蓝色的线低。
编辑:
这种方法现在已经在ggh4x包中的help_secondary()函数中被捕获和扩展。声明:我是ggh4x的作者。
其他回答
有时客户想要两个y刻度。给他们“有缺陷”的演讲通常是毫无意义的。但是我喜欢ggplot2坚持以正确的方式做事。我确信ggplot实际上是在向普通用户传授正确的可视化技术。
也许你可以使用面形和无比例来比较两个数据序列?看这里:https://github.com/hadley/ggplot2/wiki/Align-two-plots-on-a-page
总有办法的。
这里有一个解决方案,允许完全任意轴而不重新缩放。其思想是生成两个除了轴以外完全相同的图,并使用cowplot包中的insert_yaxis_grob和get_y_axis函数将它们组合在一起。
library(ggplot2)
library(cowplot)
## first plot
p1 <- ggplot(mtcars,aes(disp,hp,color=as.factor(am))) +
geom_point() + theme_bw() + theme(legend.position='top', text=element_text(size=16)) +
ylab("Horse points" )+ xlab("Display size") + scale_color_discrete(name='Transmitter') +
stat_smooth(se=F)
## same plot with different, arbitrary scale
p2 <- p1 +
scale_y_continuous(position='right',breaks=seq(120,173,length.out = 3),
labels=c('little','medium little','medium hefty'))
ggdraw(insert_yaxis_grob(p1,get_y_axis(p2,position='right')))
您可以创建一个缩放因子,应用于第二个geom和右y轴。这是从塞巴斯蒂安的解推导出来的。
library(ggplot2)
scaleFactor <- max(mtcars$cyl) / max(mtcars$hp)
ggplot(mtcars, aes(x=disp)) +
geom_smooth(aes(y=cyl), method="loess", col="blue") +
geom_smooth(aes(y=hp * scaleFactor), method="loess", col="red") +
scale_y_continuous(name="cyl", sec.axis=sec_axis(~./scaleFactor, name="hp")) +
theme(
axis.title.y.left=element_text(color="blue"),
axis.text.y.left=element_text(color="blue"),
axis.title.y.right=element_text(color="red"),
axis.text.y.right=element_text(color="red")
)
注意:使用ggplot2 v3.0.0
这在ggplot2中是不可能的,因为我认为具有单独y尺度的图(不是相互转换的y尺度)从根本上是有缺陷的。一些问题:
The are not invertible: given a point on the plot space, you can not uniquely map it back to a point in the data space. They are relatively hard to read correctly compared to other options. See A Study on Dual-Scale Data Charts by Petra Isenberg, Anastasia Bezerianos, Pierre Dragicevic, and Jean-Daniel Fekete for details. They are easily manipulated to mislead: there is no unique way to specify the relative scales of the axes, leaving them open to manipulation. Two examples from the Junkcharts blog: one, two They are arbitrary: why have only 2 scales, not 3, 4 or ten?
你也可能想要阅读Stephen Few关于双缩放轴在图形中的主题的冗长讨论,它们是最好的解决方案吗?
根据上面的答案和一些微调(无论它有什么价值),这里有一种通过sec_axis实现两个尺度的方法:
假设有一个简单的(完全虚构的)数据集dt:在五天的时间里,它追踪了被打断的次数VS工作效率:
when numinter prod
1 2018-03-20 1 0.95
2 2018-03-21 5 0.50
3 2018-03-23 4 0.70
4 2018-03-24 3 0.75
5 2018-03-25 4 0.60
(两列的范围相差大约5倍)。
下面的代码将画出它们占用整个y轴的两个级数:
ggplot() +
geom_bar(mapping = aes(x = dt$when, y = dt$numinter), stat = "identity", fill = "grey") +
geom_line(mapping = aes(x = dt$when, y = dt$prod*5), size = 2, color = "blue") +
scale_x_date(name = "Day", labels = NULL) +
scale_y_continuous(name = "Interruptions/day",
sec.axis = sec_axis(~./5, name = "Productivity % of best",
labels = function(b) { paste0(round(b * 100, 0), "%")})) +
theme(
axis.title.y = element_text(color = "grey"),
axis.title.y.right = element_text(color = "blue"))
下面是结果(上面的代码+一些颜色调整):
重点(除了在指定y_scale时使用sec_axis之外)是在指定系列时将第二个数据系列的每个值与5相乘。为了在sec_axis定义中获得正确的标签,它需要除以5(并格式化)。因此,上述代码中的关键部分实际上是geom_line和~中的*5。sec_axis中的/5(一个除当前值的公式。5)。
相比之下(我不想在这里判断方法),这是两个图表叠加在一起的样子:
你可以自己判断哪一个能更好地传递信息(“不要打扰别人工作!”)。我想这是一个公平的决定方式。
这两个图像的完整代码(实际上并没有比上面更多,只是完成并准备运行)在这里:https://gist.github.com/sebastianrothbucher/de847063f32fdff02c83b75f59c36a7d更详细的解释在这里:https://sebastianrothbucher.github.io/datascience/r/visualization/ggplot/2018/03/24/two-scales-ggplot-r.html