如何控制 `facet_wrap()` 中的轴刻度数?

Posted

技术标签:

【中文标题】如何控制 `facet_wrap()` 中的轴刻度数?【英文标题】:How can one control the number of axis ticks within `facet_wrap()`? 【发布时间】:2022-01-08 05:43:42 【问题描述】:

我有一个用 facet_wrap 创建的图形,可视化了许多组的估计密度。一些组的方差比其他组小得多。这导致某些面板无法读取 x 轴。最小可重现示例:

library(tidyverse)
x1 <- rnorm(1e4)
x2 <- rnorm(1e4,mean=2,sd=0.00001)

data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group,scales="free")

这个问题的明显解决方案是增加图形大小,使所有内容都变得可读。但是,面板太多,无法使其成为有用的解决方案。我最喜欢的解决方案是控制轴刻度的数量,例如在所有 x 轴上只允许两个刻度。有没有办法做到这一点?


建议后编辑:

添加+ scale_x_continuous(n.breaks = 2) 看起来应该完全符合我的要求,但实际上并没有:

按照建议的问题Change the number of breaks using facet_grid in ggplot2 中的答案,我最终得到了两个轴刻度,但小数点太多了:

equal_breaks <- function(n = 3, s = 0.5, ...)
  function(x)
    # rescaling
    d <- s * diff(range(x)) / (1+2*s)
    seq(min(x)+d, max(x)-d, length=n)
  


data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group,scales="free")  + scale_x_continuous(breaks=equal_breaks(n=3, s=0.05), expand = c(0.05, 0))

【问题讨论】:

您在寻找... + scale_x_continuous(n.breaks = 2)吗? 我认为他们是,虽然3 可能是最好的! @nniloc 从第二个图表中删除刻度 这能回答你的问题吗? Change the number of breaks using facet_grid in ggplot2 这能回答你的问题吗? Increase number of axis ticks 【参考方案1】:

您可以将if(seq[2]-seq[1] &lt; 10^(-r)) seq else round(seq, r)添加到函数equal_breaks开发的here中。

通过这样做,只有当标签之间的差异高于阈值 10^(-r) 时,您才会将 x 轴上的标签四舍五入。

equal_breaks <- function(n = 3, s = 0.05, r = 0,...)
  function(x)
    d <- s * diff(range(x)) / (1+2*s)
    seq = seq(min(x)+d, max(x)-d, length=n)
    if(seq[2]-seq[1] < 10^(-r)) seq else round(seq, r)
  


data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group, scales="free") +
  scale_x_continuous(breaks=equal_breaks(n=3, s=0.05, r=0)) 

正如您正确指出的那样,此答案仅提供了两种位数的选择;所以另一种可能性是返回round(seq, -floor(log10(abs(seq[2]-seq[1])))),它会得到每个方面的“最佳”位数。

equal_breaks <- function(n = 3, s = 0.1,...)
  function(x)
    d <- s * diff(range(x)) / (1+2*s)
    seq = seq(min(x)+d, max(x)-d, length=n)
    round(seq, -floor(log10(abs(seq[2]-seq[1]))))
  


data.frame(x=c(x1,x2,x3),group=c(rep("1",length(x1)),rep("2",length(x2)),rep("3",length(x3)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group, scales="free") +
  scale_x_continuous(breaks=equal_breaks(n=3, s=0.1)) 

【讨论】:

+1 很好的答案,谢谢!对于示例数据集,它工作得非常好。但是,对于我的实际数据,它仍然会产生太多的小数点。我不明白为什么......(当然,你也不能说,因为你不知道我的数据)。 谢谢。你有哪些序列有问题? 您的答案仅定义了两种舍入的可能性,即最大和最小小数位。与其他组一起尝试:x3 &lt;- rnorm(1e4,mean=2,sd=0.01) 没错!我进行了编辑以获得一些“最佳”舍入值。让我知道它是否适合您;) floor(log10()) 是一个聪明的把戏!为了我自己的使用,我在舍入中添加了+1,以避免不对称的轴刻度(在你的图中,你可以看到这种情况发生在第二密度,但在我的实际数据中,有更多极端的情况)。现在它就像一个魅力!【参考方案2】:

实现您想要的结果的一个选项是使用基于scales::breaks_extended 的自定义中断限制函数首先获得范围的漂亮中断,然后使用seq获得所需的休息次数。但是,根据所需的休息次数,这种简单的方法并不能确保我们最终得到漂亮的休息:

library(ggplot2)

set.seed(123)
x1 <- rnorm(1e4)
x2 <- rnorm(1e4,mean=2,sd=0.00001)

mylimits <- function(x) range(scales::breaks_extended()(x))

mybreaks <- function(n = 3) 
  function(x) 
    breaks <- mylimits(x)
    seq(breaks[1], breaks[2], length.out = n)  
  


d <- data.frame(x=c(x1,x2),group=c(rep("1",length(x1)),rep("2",length(x2))))
ggplot(d) + 
  geom_density(aes(x=x)) + 
  scale_x_continuous(breaks = mybreaks(n = 3), limits = mylimits) +
  facet_wrap(~group,scales="free")

【讨论】:

这是迄今为止最好的建议!它工作得非常好,但并不完美。对于某些面板,出现的刻度比其他面板少。 breaks_extended() 的帮助中也提到了这一点:“您可能会得到更多或更少的休息时间。” 没错。但我使用scales::breaks_extended 只是为了在该范围内获得漂亮的休息时间。应该提到我的回答并不是设置休息或滴答数的一般方法。只是一种实现所需结果的方法,即每个面板有两个刻度。这就是我的方法正在做的事情,它会给你两个漂亮的区间,如果需要的话,中间点。但这个想法可能会扩展到处理更一般的情况。【参考方案3】:

非常感谢您提供这么多有用的建议和出色的答案!通过修改@Maël 的方法并从Count leading zeros between the decimal point and first nonzero digit 借用RHertel 的强大功能,我想出了一个适用于任意复杂数据集的解决方案(至少我希望如此)。

在某些情况下,舍入到第一个有效小数点会导致高度不对称的刻度,因此我舍入到第二个有效小数点。

library(tidyverse)
x1 <- rnorm(1e4)
x2 <- rnorm(1e4,mean=2,sd=0.000001)
x3 <- rnorm(1e4,mean=2,sd=0.01)

zeros_after_period <- function(x) 
  if (isTRUE(all.equal(round(x),x))) return (0) # y would be -Inf for integer values
  y <- log10(abs(x)-floor(abs(x)))   
  ifelse(isTRUE(all.equal(round(y),y)), -y-1, -ceiling(y)) # corrects case ending with ..01

equal_breaks <- function(n,s)
  function(x)
    x=x*10000
    d <- s * diff(range(x)) / (1+2*s)
    seq = seq(min(x)+d, max(x)-d, length=n) / 10000
    round(seq,zeros_after_period(seq[2]-seq[1])+2)
  


data.frame(x=c(x1,x2,x3),group=c(rep("1",length(x1)),rep("2",length(x2)),rep("3",length(x3)))) %>%
  ggplot(.) + geom_density(aes(x=x)) + facet_wrap(~group, scales="free") +
  scale_x_continuous(breaks=equal_breaks(n=2, s=0.1)) 
 

很抱歉回答我自己的问题...但如果没有社区的大力帮助,这将是不可能的 :-)

【讨论】:

以上是关于如何控制 `facet_wrap()` 中的轴刻度数?的主要内容,如果未能解决你的问题,请参考以下文章

如何解决重叠的轴刻度标签

如何更改 matplotlib 中绘图的轴、刻度和标签的颜色

如何更改三元图的轴刻度顺序?

使用多个连接的箱线图更改 Matplotlib 中的轴刻度

如何分别格式化每个 ggplot 面板的轴刻度标签?

R语言ggplot2可视化分面图(faceting): ggplot2可视化分面图(facet_wrap)并设置不同的分面使用不同的坐标轴数值范围以及不同的轴标签断点间隔breaks