【问题标题】:R: place geom_text() relative to plot borders rather than fixed position on the plotR:相对于绘图边界放置 geom_text() 而不是绘图上的固定位置
【发布时间】:2018-03-04 09:53:42
【问题描述】:

我在 R 中使用 ggplot2 创建了许多图,并且想要一种方法来标准化截止线的实现。我有四个城市在大约 10 年的时间段内的许多不同衡量标准的数据。我将它们绘制为折线图,每个城市在给定的图表中都有不同的颜色。我将为我拥有的每个不同的措施(大约 20 个)创建一个图。

在这些图表中的每一个上,我都需要放置两条分界线(旁边有一个词)来表示某些政策的实施,以便阅读图表的人可以轻松识别实施前后的性能差异。以下是我目前正在使用的大致代码。

gg_plot1<- ggplot(data=ggdata, aes(x=Year, y=measure1, group=Area, color=Area)) +
  geom_vline(xintercept=2011, color="#EE0000") +
  geom_text(aes(x=2011, label="City1\n", y=0.855), color="#EE0000", angle=90, hjust=0, family="serif") +
  geom_vline(xintercept=2007, color="#000099") +
  geom_text(aes(x=2007, label="City2", y=0.855), color="#000099", angle=0, hjust=1, family="serif") +
  geom_line(size=.75) +
  geom_point(size=1.5) +
  scale_y_continuous(breaks=round(seq(min(ggdata$measure1, na.rm=T), max(ggdata$measure1, na.rm=T), by=0.01), 2)) +
  scale_x_continuous(breaks=min(ggdata$Year):max(ggdata$Year)) +
  scale_color_manual(values=c("#EE0000", "#00DDFF", "#009900", "#000099")) +
  theme(axis.text.x = element_text(angle=90, vjust=1),
        panel.background = element_rect(fill="white", color="white"),
        panel.grid.major = element_line(color="grey95"),
        text = element_text(size=11, family="serif"))

这个实现的问题在于它依赖于将两个geom_text() 放在特定图表上的特定位置。这些不同的措施都有不同的范围,所以为了做到这一点,我需要一个接一个地去寻找一个地方来放置它们。我更愿意做的是将每个绘图的范围缩小 X% 并将 geom_text() 与范围的底部对齐。线条不需要调整(每个情节都在同一年),只需要调整文本的位置。我在这里发现了一些类似的问题,但与在不同范围的不同图表上放置相同位置的特定问题无关。

有没有办法做我正在寻找的东西?如果我不得不猜测,它就像使用相对定位而不是绝对定位,但我无法在 ggplot 中找到这样做。作为记录,我知道这两个geom_text()s 的方向不同。我这样做是为了比较我们更喜欢哪个,但留给大家。我们最终将使用文本旋转 90 度的那个。此外,其中一些将被刻面在一起,这样可能会增加一层难度。还没到那个地步。

侧边栏:另一种可视化方式是在截止年份将线从实线更改为虚线。这可能吗?我不确定客户是否希望这样做,但如果有人能指出我在哪里学习如何做到这一点,我很乐意将其作为一种选择。

编辑添加:

显示使用不同 y 范围运行时会发生什么的示例数据

ggdata <- data.frame(Area=rep(c("City1", "City2", "City3", "City4"), times=7),
                     Year=c(rep(2006,4), rep(2007,4), rep(2008,4), rep(2009,4), rep(2010,4), rep(2011,4), rep(2012,4)),
                     measure1=rnorm(28,10,2),
                     measure2=rnorm(28,50,10))

geom_text()s 位于正确位置的示例图,但这是使用上面的代码在图中固定位置完成的。当我使用具有不同 y 范围的不同度量来复制代码时,它最终会拉伸绘图窗口。

【问题讨论】:

  • 请发布示例数据和想要的输出示例。
  • 刚刚更新了
  • 您的数据框代码会产生错误,而绘图代码也会产生错误,即使数据框错误已解决。请更新示例以使代码正常工作。
  • 抱歉,刚刚又更新了。嵌入图像在使用原始数据运行时显示代码。示例数据与示例代码一起运行,并显示了我正在尝试修复的问题。
  • 试试:geom_text(aes(x=2011, label="City1\n", y=min(measure1)*1.1)

标签: r ggplot2 geom-text


【解决方案1】:

您可以使用数据的 y 范围定位到文本标签。我在下面的示例中明确设置了 y 限制,但这不是绝对必要的,除非您想将它们从默认值更改。您还可以使用数据的 x 范围调整文本标签的 x 位置。无论数据的 y 范围如何,下面的代码都会将标签放置在绘图的底部。

我也从geom_text 切换到annotategeom_text 多次重叠文本标签,数据中的每一行一次。 annotate 绘制一次标签。

ypos = min(ggdata$measure1) + 0.005*diff(range(ggdata$measure1))
xv = 0.02
xh = 0.01
xadj = diff(range(ggdata$Year))

ggplot(data=ggdata, aes(x=Year, y=measure1, group=Area, color=Area)) +
  geom_vline(xintercept=2011, color="#EE0000") +
  geom_vline(xintercept=2007, color="#000099") +
  geom_line(size=.75) +
  geom_point(size=1.5) +
  annotate(geom="text", x=2011 - xv*xadj, label="City1", y=ypos, color="#EE0000", angle=90, hjust=0, family="serif") +
  annotate(geom="text", x=2007 - xh*xadj, label="City2", y=ypos, color="#000099", angle=0, hjust=1, family="serif") +
  scale_y_continuous(limits=range(ggdata$measure1),
                     breaks=round(seq(min(ggdata$measure1, na.rm=T), max(ggdata$measure1, na.rm=T), by=1), 0)) +
  scale_x_continuous(breaks=min(ggdata$Year):max(ggdata$Year)) +
  scale_color_manual(values=c("#EE0000", "#00DDFF", "#009900", "#000099")) +
  theme(axis.text.x = element_text(angle=90, vjust=1),
        panel.background = element_rect(fill="white", color="white"),
        panel.grid.major = element_line(color="grey95"),
        text = element_text(size=11, family="serif"))

更新:为了回复您的评论,您可以按照以下方法为数据框中的每个“度量”列创建单独的图。

首先,我们使用三个度量列创建可重现的数据:

library(ggplot2)
library(gridExtra)
library(scales)

set.seed(4)
ggdata <- data.frame(Year=rep(2006:2012,each=4),
                     Area=rep(paste0("City",1:4), 7),
                     measure1=rnorm(28,10,2),
                     measure2=rnorm(28,50,10),
                     measure3=rnorm(28,-50,5))

现在,我们将上面的代码打包成一个函数。该函数接受一个名为measure_var 的参数。这是作为字符字符串提供的数据列,它将为绘图提供 y 值。请注意,我们现在在ggplot 中使用aes_string 而不是aes

plot_func = function(measure_var) {

  ypos = min(ggdata[ , measure_var]) + 0.005*diff(range(ggdata[ , measure_var]))
  xv = 0.02
  xh = 0.01
  xadj = diff(range(ggdata$Year))

  ggplot(data=ggdata, aes_string(x="Year", y=measure_var, group="Area", color="Area")) +
    geom_vline(xintercept=2011, color="#EE0000") +
    geom_vline(xintercept=2007, color="#000099") +
    geom_line(size=.75) +
    geom_point(size=1.5) +
    annotate(geom="text", x=2011 - xv*xadj, label="City1", y=ypos, 
             color="#EE0000", angle=90, hjust=0, family="serif") +
    annotate(geom="text", x=2007 - xh*xadj, label="City2", y=ypos, 
             color="#000099", angle=0, hjust=1, family="serif") +
    scale_y_continuous(limits=range(ggdata[ , measure_var]),
                       breaks=pretty_breaks(5)) +
    scale_x_continuous(breaks=min(ggdata$Year):max(ggdata$Year)) +
    scale_color_manual(values=c("#EE0000", "#00DDFF", "#009900", "#000099")) +
    theme(axis.text.x = element_text(angle=90, vjust=1),
          panel.background = element_rect(fill="white", color="white"),
          panel.grid.major = element_line(color="grey95"),
          text = element_text(size=11, family="serif")) +
    ggtitle(paste("Plot of", measure_var))
}

我们现在可以像这样运行函数一次:plot_func("measure1")。但是,让我们使用lapply 一次性在所有度量列上运行它。我们给 lapply 一个带有度量列名称的向量 (names(ggdata)[grepl("measure", names(ggdata))]),它依次在每个列上运行 plot_func,将结果图存储在列表 plot_list 中。

plot_list = lapply(names(ggdata)[grepl("measure", names(ggdata))], plot_func)

现在,如果我们愿意,我们可以使用grid.arrange 将它们全部放在一起。在这种情况下,我们只需要一个图例,而不是每个图都有一个单独的图例,因此我们将图例提取为一个单独的图形对象并将其放在三个图旁边。

# Function to get legend from a ggplot as a separate graphical object
# Source: https://github.com/tidyverse/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs/047381b48b0f0ef51a174286a595817f01a0dfad
g_legend<-function(a.gplot){
  tmp <- ggplot_gtable(ggplot_build(a.gplot))
  leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
  legend <- tmp$grobs[[leg]]
  return(legend)
}

# Get legend
leg = g_legend(plot_list[[1]])

# Lay out all of the plots together with a single legend
grid.arrange(arrangeGrob(grobs=lapply(plot_list, function(x) x + guides(colour=FALSE))),
             leg,
             ncol=2, widths=c(10,1))

【讨论】:

  • 我认为这会很完美。用两种不同的方法进行了尝试,到目前为止它确实如此,现在只需再复制 20 次!感谢您的帮助,尤其是关于 geom_text()annotate(geom()) 的提示。我一直想知道为什么文字看起来这么难看。
  • 如果你想一遍又一遍地做这个,把它打包成一个接受数据框的函数。然后为所有数据框运行该函数。如果将数据框加载到列表中,这将更加容易:lapply(data_frame_list, my_plot_function)
  • 我以前从未写过合适的函数,但我会做一些搜索并试一试。您是否知道可以提供帮助的特定教程?
  • 查看R for Data Science,作者是 Hadley Wickham(ggplot2 和其他几个广泛使用的软件包的作者)。它可以在网上免费获得,也可以购买硬拷贝。第 19 章讨论函数。
  • 所有数据都在一个 data.frame 中,其中包含两个参考变量(Area、Year)和大约 20 列不同的度量值。我尝试了plot_graphs &lt;- function(x) { code_from_above_with_cols_replaced_with_x },然后尝试了lapply(ggdata$measure1, plot_graphs),但收到的错误消息似乎围绕着一个问题“未知或未初始化的列:'测量'”。我在正确的轨道上吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-24
  • 2017-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多