ggplot2:如何有条件地更改 geom_text 的 vjust 当低栏使文本超过栏的底部时

ggplot2: How to conditionally change geom_text's vjust when low bars make text exceed bar's bottom

绘制条形图时,我经常给条形图添加标签以表示每个条形图的 y 值。然而,我 运行 遇到了麻烦,当标准变得太低时,标签变得不可读或简直丑陋。

例子


library(ggplot2)

df_blood <- data.frame(blood_type = c("O-", "O+",   "A-",   "A+",   "B-",   "B+",   "AB-",  "AB+"),
                       frequency  = c(0.13, 0.35, 0.08, 0.3, 0.02, 0.08, 0.01, 0.02))

ggplot(df_blood, aes(x = blood_type, y = frequency, fill = blood_type)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = frequency), color = "blue", vjust = 1, size = 7)

reprex package (v0.3.0) 于 2021-01-25 创建
查看 AB- 的栏,我们可以看到 0.01 文本超过了栏的高度(在栏的底部)。在这种情况下,我想将 geom_text()vjust 更改为 0


具有不同 y 比例的另一个例子

这里我使用与上面相同的 size = 7 geom_text():

library(ggplot2)

df_something <- data.frame(something = c("a", "b", "c"),
                   quantity = c(10000, 7800, 500))

ggplot(df_something, aes(x = something, y = quantity)) +
  geom_bar(stat = "identity", fill = "black") +
  geom_text(aes(label = quantity), color = "red", vjust = 1, size = 7)

reprex package (v0.3.0) 于 2021-01-25 创建
在这里,我们看到 c 栏的 500 文本超出了栏的底部。因此,在这种情况下,我还想将 geom_text()vjust 更改为 0,仅适用于小节 c


总结

尽管有一些解决方案可以根据 y 值使用简单的 ifelse(请参阅 )有条件地更改 vjust,但我正在尝试弄清楚如何设置条件vjust 这样无论 y 尺度上的值如何,它都可以工作。相反,规则应该是,如果栏的高度低于 geom_text() 的大小,文本位置将移动到顶部。谢谢!


编辑


根据下面与@Paul 的讨论,我想知道根据 geom_text() 位置是否覆盖 y = 0 来确定 vjust 是否更容易,如果覆盖,则更改 vjust0.


编辑 2


(credit to 寻找)似乎足够接近我的要求。它动态更改 geom_text()size 以适合条形宽度,并且即使在调整绘图大小时也能正常工作。所以我认为这为我所追求的提供了基础,而不是调整 size 我需要调整 vjust,而不是根据条宽度调节它我需要根据条高度调节它。不幸的是,它对于我对 ggproto 等的理解来说太复杂了,所以我不知道如何使它适应我的情况。

您可以不将标签放在列中,而是将它们放在最上面就可以解决您的问题吗?它适用于所有列,您无需担心选择个别列。将所有标签都放在上面可能更干净,而不是在内部和上面放置一些标签。

library(ggplot2)
df_something <- data.frame(something = c("a", "b", "c"),
                           quantity = c(10000, 7800, 500))
ggplot(data = df_something, aes(x = something, y = quantity)) +
  geom_col(position = "dodge", fill = "black") +
  geom_text(
    aes(label = quantity, y = quantity + 50),
    position = position_dodge(0.9),
    vjust = 0,
    color = "red",
    size = 7
  )

vjust 也可以采用等于 x 轴大小的输入向量。 vjust(我放 0 的地方)的顺序基于数据集的顺序,而不是 ggplot 中显示的顺序。您可以考虑 blood_type 以非常具体地确定每个条形图的位置,并更好地控制 v。

library(ggplot2)

df_blood <- data.frame(blood_type = c("O-", "O+",   "A-",   "A+",   "B-",   "B+",   "AB-",  "AB+"),
                       frequency  = c(0.13, 0.35, 0.08, 0.3, 0.02, 0.08, 0.01, 0.02))

ggplot(df_blood, aes(x = blood_type, y = frequency, fill = blood_type)) +
  geom_bar(stat = "identity") +
  geom_text(aes(label = frequency), color = "blue", vjust = c(1,1,1,1,1,1,0,1), size = 7)

刚在 googling around 时发现: 尝试使用 vjust = "inward"。然而,这可能会导致其他问题(如前所述 )...

ggplot(df_something, aes(x = something, y = quantity)) +
  geom_bar(stat = "identity", fill = "black") +
  geom_text(aes(label = quantity), color = "red", vjust = "inward", size = 7)

[旧 post]

我post这是一个“答案”,只是为了说明我的意思。将通过反馈进行改进。

here 所述,“文本的大小以毫米为单位”。然后无论你的情节的最终大小是什么,它总是有相同的大小。

我想我们需要知道您是否必须为情节保留最终维度。 例如:

ggsave(filename = "test_small.png", height = 5, units = "cm")

ggsave(filename = "test_big.png", height = 20, units = "cm")

作为一个开箱即用的选项来达到你想要的结果,我建议看看 ggfittext 包,它有一些选项可以将标签放在条形之外,如果他们不'适合内部或缩小标签。此外,还有一些选项可以在标签周围添加一些填充。但是,它使用非默认大小调整策略,因此您必须将默认单位乘以 ggplot2::.pt:

library(ggplot2)
library(ggfittext)

df_something <- data.frame(something = c("a", "b", "c"),
                           quantity = c(10000, 7800, 500))

ggplot(df_something, aes(x = something, y = quantity)) +
  geom_bar(stat = "identity", fill = "black") +
  geom_bar_text(aes(label = quantity), 
                color = "red", 
                vjust = 1, 
                size = 7 * ggplot2::.pt, 
                min.size = 7 * ggplot2::.pt,
                padding.x = grid::unit(0, "pt"),
                padding.y = grid::unit(0, "pt"),
                outside = TRUE)
#> Warning: Ignoring unknown aesthetics: label

df_blood <- data.frame(blood_type = c("O-", "O+",   "A-",   "A+",   "B-",   "B+",   "AB-",  "AB+"),
                       frequency  = c(0.13, 0.35, 0.08, 0.3, 0.02, 0.08, 0.01, 0.02))

ggplot(df_blood, aes(x = blood_type, y = frequency, fill = blood_type)) +
  geom_bar(stat = "identity") +
  geom_bar_text(aes(label = frequency), 
                color = "blue", 
                vjust = 1, 
                size = 7 * ggplot2::.pt, 
                min.size = 7 * ggplot2::.pt,
                padding.x = grid::unit(0, "pt"),
                padding.y = grid::unit(0, "pt"),
                outside = TRUE)
#> Warning: Ignoring unknown aesthetics: label