英文:
ggplot - how to draw a perfect diagonal line?
问题
考虑以下两种绘制对角线的方法,如下所示在同一图上:
ggplot(iris,
aes(x=Petal.Length,
y=Petal.Width))+
geom_point()+
geom_abline() +
geom_smooth(method = "lm", se = F)
然而,我需要一条线,它从图的左下角(除了边缘)精确到图的右上角(除了边缘)。这里我展示了用画图工具绘制的线,以便与其他两条线进行比较。
如何在不手动计算左下角和右上角的坐标或线的斜率的情况下绘制这条线?
英文:
Consider the following two ways to draw diagonal lines, here shown on the same plot:
ggplot(iris,
aes(x=Petal.Length,
y=Petal.Width))+
geom_point()+
geom_abline() +
geom_smooth(method = "lm", se = F)
However, I need a line which goes exactly from the bottom left corner of the plot (except the margin) to exactly the top right corner of the plot (except the margin). Here I show the line drawn in Paint to compare with the other two lines.
How can I draw this line without having to manually calculate the coordinates of the bottom left and the top right corner or the slope of the line?
答案1
得分: 4
以下是您要翻译的部分:
- The plot corners (green points) - 绘图的角落(绿色点);
- The last breaks (red points) - 最后的间断点(红色点);
- The actual xlim's and ylim's (blue points) - 实际的xlim和ylim(蓝色点)。
"1." Can be achieved with Allan's annotation_custom
answer. For "2." and "3." we must extract the limits and breaks. This can be done by accessing the plot metadata with ggplot_build()
. I created custom functions that save that metadata in a geom_segment
friendly format:
“1.” 可以使用Allan的 annotation_custom
方法实现。对于“2.”和“3.”,我们需要提取限制和间断点。这可以通过使用 ggplot_build()
访问绘图元数据来完成。我创建了自定义函数,将该元数据保存在 geom_segment
友好的格式中:
get_lims <- function(graph){
ggplot_build(graph)$layout$panel_params[[1]][c('x.range', 'y.range')] %>%
flatten() %>%
set_names(c('x', 'xend', 'y', 'yend'))
}
The limits/ranges are on these ...$x.range
and ...$y.range
vectors, we are simply flattening them, and saving with the geom_segment's arguments names.
限制/范围存储在这些 ...$x.range
和 ...$y.range
向量中,我们只是将它们展平,并保存为geom_segment的参数名称。
get_breaks <- function(graph){
ggplot_build(graph)$layout$panel_params[[1]][c('x.sec', 'y.sec')] %>%
map(~ c(.x$breaks, .x$minor_breaks) %>% na.omit() %>% {c(min(.), max(.))}) %>%
flatten() %>%
set_names(c('x', 'xend', 'y', 'yend'))
}
Here, the breaks info are under ...$x.sec$breaks
and ...$x.sec$minor_breaks
vectors (for major and minor breaks). Thus, the map
here is joining major and minor (as any one of them could be the first/last), omitting NA's, and getting the minimal and maximal values. We then flatten and rename.
在这里,间断点信息位于 ...$x.sec$breaks
和 ...$x.sec$minor_breaks
向量下(用于主要和次要间断点)。因此,这里的 map
将主要和次要间断点合并(因为它们中的任何一个都可以是第一个/最后一个),省略NA,并获取最小和最大值。然后我们展平并重命名。
Lastly, you might be interested in expanding the red line of your example, which can be made by manually calculating an abline for it (line in purple).
最后,您可能对扩展示例中的红线感兴趣,这可以通过手动计算abline来实现(紫色线)。
Now to compare the methods:
现在来比较这些方法:
g <- ggplot(iris, aes(x=Petal.Length, y=Petal.Width))+
geom_point() +
geom_smooth(method = "lm", se = F, color = 'black')
breaks <- get_breaks(g)
b <- (breaks$yend - breaks$y)/(breaks$xend - breaks$x)
a <- breaks$y - b*breaks$x
g +
annotation_custom(grid::linesGrob(gp = grid::gpar(col = 'green'))) +
geom_abline(slope = b, intercept = a, color = 'purple') +
geom_segment(do.call(aes, get_breaks(g)), color = 'red') +
geom_segment(do.call(aes, get_lims(g)), color = 'blue')
希望这可以帮助您。
英文:
There are three limits that you might want to use.
- The plot corners (green points);
- The last breaks (red points);
- The actual xlim's and ylim's (blue points).
"1." Can be achieved with Allan's annotation_custom
answer. For "2." and "3." we must extract the limits and breaks. This can be done by acessing the plot metadata with ggplot_build()
. I created custom functions that save that metadata in a geom_segment
friendly format:
get_lims <- function(graph){
ggplot_build(graph)$layout$panel_params[[1]][c('x.range', 'y.range')] %>%
flatten() %>%
set_names(c('x', 'xend', 'y', 'yend'))
}
The limits/ranges are on these ...$x.range
and ...$y.range
vectors, we are simply flattening them, and saving with the geom_segment's arguments names.
get_breaks <- function(graph){
ggplot_build(graph)$layout$panel_params[[1]][c('x.sec', 'y.sec')] %>%
map(~ c(.x$breaks, .x$minor_breaks) %>% na.omit() %>% {c(min(.), max(.))}) %>%
flatten() %>%
set_names(c('x', 'xend', 'y', 'yend'))
}
Here, the breaks info are under ...$x.sec$breaks
and ...$x.sec$minor_breaks
vectors (for major and minor breaks). Thus, the map
here is joining major and minor (as any one of them could be the first/last), omitting NA's, and getting the minimal and maximal values. We then flatten and rename.
Lastly, you might be interested in expanding the red line of your example, which can be made by manually calculating a abline for it (line in purple).
Now to compare the methods:
g <- ggplot(iris, aes(x=Petal.Length, y=Petal.Width))+
geom_point() +
geom_smooth(method = "lm", se = F, color = 'black')
breaks <- get_breaks(g)
b <- (breaks$yend - breaks$y)/(breaks$xend - breaks$x)
a <- breaks$y - b*breaks$x
g +
annotation_custom(grid::linesGrob(gp = grid::gpar(col = 'green'))) +
geom_abline(slope = b, intercept = a, color = 'purple') +
geom_segment(do.call(aes, get_breaks(g)), color = 'red') +
geom_segment(do.call(aes, get_lims(g)), color = 'blue')
Obs: the points were added later.
答案2
得分: 2
以下是代码部分的中文翻译:
使用 annotation_custom
结合 grid::linesGrob
和 grid::gpar
,可以相对容易地在面板的对角线上绘制一条线:
library(ggplot2)
ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
geom_point() +
annotation_custom(
grid::linesGrob(gp = grid::gpar(col = 'red', lty = 2, lwd = 3)))
要绘制不跨越图的“边距”(实际上这些不是边距,而只是图区域的扩展)的对角线会更加困难,因为这些边距是由ggplot在绘制时从数据计算得出的,用户可以通过 scale_*_continuous
进行更改,这时您对“正确”的对角线的概念可能会改变。例如,如果我们更改刻度值,最终会改变所需的对角线的外观:
ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
geom_point() +
annotate('path', color = 'red', x = c(2.4, 6.2), y = c(0.4, 2.2)) +
scale_x_continuous(breaks = c(2.4, 6.2)) +
scale_y_continuous(breaks = c(0.4, 1.3, 2.2))
希望这有助于您理解代码部分的内容。
英文:
It's actually pretty straightforward to get a line going exactly across the diagonal of the panel using annotation_custom
with a grid::linesGrob
and styling it using grid::gpar
library(ggplot2)
ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
geom_point() +
annotation_custom(
grid::linesGrob(gp = grid::gpar(col = 'red', lty = 2, lwd = 3)))
It is more difficult (and less well defined) to draw a diagonal that does not cross the "margins" of the plot, since these are not in fact margins, but just expansions of the plot area proper. The desired line you have drawn on your output is not a perfect diagonal across the panel, but a diagonal across the outermost breaks of your x axis and y axis scales. These are calculated by ggplot from your data at draw time, and can be changed by the user via scale_*_continuous
, at which point your notion of the 'correct' diagonal might change. For example, if we change the breaks we end up changing the apparent desired diagonal:
ggplot(iris, aes(x = Petal.Length, y = Petal.Width)) +
geom_point() +
annotate('path', color = 'red', x = c(2.4, 6.2), y = c(0.4, 2.2)) +
scale_x_continuous(breaks = c(2.4, 6.2)) +
scale_y_continuous(breaks = c(0.4, 1.3, 2.2))
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论