【问题标题】:Asymmetric expansion of ggplot axis limitsggplot轴限制的不对称扩展
【发布时间】:2016-01-06 00:35:21
【问题描述】:

如何在 ggplot 中不对称地调整限制的扩展?例如,

library(ggplot2)

ggplot(mtcars) + 
  geom_bar(aes(x = cyl), width = 1)

我希望条形的底部与面板背景的底部齐平,但仍希望顶部有空间。我可以用空白注释来实现这一点:

ggplot(mtcars) + 
  geom_bar(aes(x = cyl), width = 1) +
  annotate("blank", x = 4, y = 16) +
  scale_y_continuous(expand = c(0.0,0)) 

然而,在以前的ggplot 版本中,我可以使用the solution provided by Rosen Matev

library("scales")
scale_dimension.custom_expand <- function(scale, expand = ggplot2:::scale_expand(scale)) {
  expand_range(ggplot2:::scale_limits(scale), expand[[1]], expand[[2]])
}

scale_y_continuous <- function(...) {
  s <- ggplot2::scale_y_continuous(...)
  class(s) <- c('custom_expand', class(s))
  s
}

然后使用scale_y_continuous(expand = list(c(0,0.1), c(0,0))) 将一致添加到图表顶部。但是,在当前版本中,我收到错误

ggplot(mtcars) + 
  geom_bar(aes(x = cyl), width = 1) +
  scale_y_continuous(expand = list(c(0,0.1), c(0,0)))

# Error in diff(range) * mul : non-numeric argument to binary operator

ggplot2 2.0有有效的解决方案吗?

解决方案应包括灵活处理构面的能力,以及free_xy 缩放选项。例如,

ggplot(mtcars) + 
  geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) + 
  facet_grid(vs ~ ., scales = "free_y")

解决方案应提供如下内容:

ggplot(mtcars) + 
  geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) + 
  facet_grid(vs ~ ., scales = "free_y") + 
  scale_y_continuous(expand = c(0,0)) + 
  geom_blank(data = data.frame(cyl = c(5,5), y = c(12, 16), vs = c(1,0)), aes(x = cyl, y = y))

【问题讨论】:

  • 为了获得最大的灵活性,我建议在您的问题中使用方面的示例。对于无面图,可以聚合您的数据并使用ylim = c(0, 1.1 * max(aggregated_data$y_variable)) - 尽管不如修改后的扩展解决方案好。对于多面图,ylim 解决方案将无法正常工作,因此 需要 类型的解决方案 expand
  • 在不切面的情况下,你不能用coord_cartesian来得到你想要的效果吗?例如:coord_cartesian(ylim=c(0, max(table(mtcars$cyl))+2), xlim=c(min(mtcars$cyl)-0.75, max(mtcars$cyl)+0.75), expand=FALSE)。在 2.0 版之前,没有必要设置 xlim,但是(据我所知)现在默认为 expand=TRUE,如果您想在 x- 上保留填充,则需要明确设置这两个限制轴。
  • @eipi10 说明了我的观点。在非分面的情况下,您可以相当容易地找到解决方法。 感兴趣的答案(我认为 Hugh 也是)是一个适用于有和没有方面的答案。
  • 休,如果您愿意进行编辑以包含方面,我很乐意在该问题符合条件时发布悬赏 - 我非常有兴趣找到解决方案。
  • @Gregor 虽然能够使用刻面设置自定义 y 尺度会很好,但另一种选择是 for 循环(或 lapply)为刻面变量的每个值创建一个图,并且然后使用 grid.arrange (来自 gridExtra 包)布置图。然后,您可以按方面设置 y 范围。例如,如果gear 是分面变量(我们使用i 作为循环虚拟变量),coord_cartesian(ylim=c(0, max(table(mtcars$cyl[mtcars$gear==i]))+2), xlim=c(min(mtcars$cyl)-0.75, max(mtcars$cyl)+0.75), expand=FALSE)

标签: r ggplot2


【解决方案1】:

ggplot2 v3.0.0 于 2018 年 7 月发布,具有 expand_scale() 选项(带有 mult 参数)以实现 OP 的目标。

编辑:expand_scale() 将在未来的版本中被弃用,取而代之的是 expansion()。请参阅News 了解更多信息。

library(ggplot2)

### ggplot <= 3.2.1
ggplot(mtcars) + 
  geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) + 
  facet_grid(vs ~ ., scales = "free_y") + 
  scale_y_continuous(expand = expand_scale(mult = c(0, .2))) 

### ggplot >= 3.2.1.9000
ggplot(mtcars) + 
  geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) + 
  facet_grid(vs ~ ., scales = "free_y") + 
  scale_y_continuous(expand = expansion(mult = c(0, .2))) 

【讨论】:

  • +1 为我节省了将虚拟数据框添加到我的绘图以手动更改所有轴所花费的时间......想想我过去花费的所有时间! !
【解决方案2】:

我现在尝试将此代码添加到ggplot2;参见issue #1669corresponding pull request。如果它被接受,expand 参数的语法将从c(m, a) 更改为c(m_lower, a_lower, m_uppper, a_upper),用于为范围下限和上限指定单独的扩展值。 (不过,旧语法仍将继续工作,因为如果缺少元素 3 和/或 4,前两个元素将被重用。)

有了这个新语法,你可以使用

ggplot(mtcars) +
  geom_bar(aes(x = cyl), width = 1) +
  scale_y_continuous(expand = c(0, 0, 0.05, 0))

结果如下:

它也适用于刻面:

ggplot(mtcars) +
  geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
  facet_grid(vs ~ ., scales = "free_y") +
  scale_y_continuous(expand = c(0, 0, 0.05, 0))

【讨论】:

  • 实施发生了什么?
【解决方案3】:

我经常使用 Rosen Matev 的解决方案,当它与 ggplot 版本 2.0 中断时,我感到很失望。我提供了一个解决方案,虽然不如 Rosen 的优雅,但可以处理没有分面的图,facet_wrapfacet_grid,以及单向和双向 facet_grid。但是,它不适用于更复杂的分面网格,也不适用于coord_flip。有两种功能:一种用于沿 y 轴的非对称扩展,另一种用于沿 x 轴的扩展。这些函数执行乘法和加法扩展。

函数从绘图收集信息,计算 y(或 x)轴的新限制,然后使用 geom_blank 构建具有所需扩展因子的新绘图。

首先,一个沿 y 轴执行不对称扩展的函数。

# Function takes two parameters
#   'p' is the plot
#   'expand' is a list of two vectors:
#     First vector contains the multiplicative factors;
#     Second vector contains the additive parts.
#       First element in each vector refers to the lower boundary;
#       Second element refers to the upper boundary.

asymmY = function(p, expand = list(mult = c(0, .2), add = c(0, 0))) {

  np = p + coord_cartesian(expand = FALSE)  # No expand
  gb <- ggplot_build(np)

  limits <- sapply(gb$panel$ranges, "[[", "y.range")
  range = apply(limits, 2, function(x) max(x) - min(x))
  rangeU = range*expand[[1]][2]
  rangeL = range*expand[[1]][1]
  limits <- limits + rbind(-rangeL, rangeU)  # Multiplicative expand

  limits[1,] = limits[1,] - expand[[2]][1]   # Additive expand
  limits[2,] = limits[2,] + expand[[2]][2]   

  limits = as.vector(limits)

  df = facet_type(np, gb, "y", limits)  # df with new limits - depends on facet type

  np = np + geom_blank(data = df, inherit.aes = FALSE, aes(x = Inf, y = y)) # new plot

  # But the x axis expansions were set to false. Put back the default expand
  gb <- ggplot_build(np)

 if(any(grepl("Discrete", class(gb$panel$x_scale[[1]])))) {
    limits <- sapply(gb$panel$ranges, "[[", "x.range")
    limits[1,] = ceiling(limits[1,]) - .6
    limits[2,] = trunc(limits[2,]) + .6
    limits = as.vector(limits)
 } else {
    limits <- sapply(gb$panel$ranges, "[[", "x.range")
    range = apply(limits, 2, function(x) max(x) - min(x))
    rangeU = range*.05
    rangeL = range*.05
    limits <- limits + rbind(-rangeL, rangeU)
    limits = as.vector(limits)
 }

  df = facet_type(np, gb, "x", limits)

  np + geom_blank(data = df, inherit.aes = FALSE, aes(x = x, y = Inf))
}

# Function to determine type of facetting  
# and to get data frame of new limits.
facet_type = function(np, gb, axis, limits) {
    if(class(np$facet)[1] == "null") { 
      setNames(data.frame(y = limits), axis)
  } else 
    if(class(np$facet)[1] == "wrap") {
      facetvar <- as.character(np$facet$facets)
      facetlev <- gb$panel$layout[[facetvar]]
      setNames(data.frame(rep(facetlev, each = 2), limits), c(facetvar, axis))
  } else {
      facetvar <- as.character(np$facet$cols)
      if(length(facetvar) == 0) facetvar <- as.character(np$facet$rows)
      facetlev <- gb$panel$layout[[facetvar]]
      setNames(data.frame(rep(facetlev, each = 2), limits), c(facetvar, axis))
  }
}

尝试使用一些分面环绕和分面网格图。

# Try asymmetric expand along y-axis
library(ggplot2)

p1 <- ggplot(mtcars) + 
  geom_bar(aes(x = factor(cyl))) + 
  facet_grid(am  ~ vs , scales = "free_y")

p2 <- ggplot(mtcars) + 
  geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) + 
  facet_grid(vs ~ ., scales = "free_y") 

p3 <- ggplot(mtcars) + 
  geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) + 
  facet_grid(. ~ vs)

p4 <- ggplot(mtcars) + 
  geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) + 
  facet_wrap(~vs, scales = "free_y") 

asymmY(p1, list(c(0, 0.1), c(0, 0)))
asymmY(p2, list(c(0, 0.1), c(0, 0)))
asymmY(p3, list(c(0, 0.1), c(0, 0)))
asymmY(p4, list(c(0, 0.1), c(0, 0)))

第二,沿x轴进行非对称扩展的函数。

asymmX = function(p, expand = list(mult = c(0, .2), add = c(0, 0))) {

  np = p + coord_cartesian(expand = FALSE)  # No expand
  gb <- ggplot_build(np)

  limits <- sapply(gb$panel$ranges, "[[", "x.range")
  range = apply(limits, 2, function(x) max(x) - min(x))
  rangeU = range*expand[[1]][2]
  rangeL = range*expand[[1]][1]
  limits <- limits + rbind(-rangeL, rangeU)  # Mult expand

  limits[1,] = limits[1,] - expand[[2]][1]
  limits[2,] = limits[2,] + expand[[2]][2]   # Add expand

  limits = as.vector(limits)

  df = facet_type(np, gb, "x", limits)  # df with new limits - depends on facet type

  np = np + geom_blank(data = df, inherit.aes = FALSE, aes(x = x, y = Inf)) # new plot

  # But the y axis expansions were set to false. Put back the default expand
  gb <- ggplot_build(np)

 if(any(grepl("Discrete", class(gb$panel$y_scale[[1]])))) {
    limits <- sapply(gb$panel$ranges, "[[", "y.range")
    limits[1,] = ceiling(limits[1,]) - .6
    limits[2,] = trunc(limits[2,]) + .6
    limits = as.vector(limits)
 } else {
    limits <- sapply(gb$panel$ranges, "[[", "y.range")
    range = apply(limits, 2, function(x) max(x) - min(x))
    rangeU = range*.05
    rangeL = range*.05
    limits <- limits + rbind(-rangeL, rangeU)
    limits = as.vector(limits)
 }

  df = facet_type(np, gb, "y", limits)

  np + geom_blank(data = df, inherit.aes = FALSE, aes(x = Inf, y = y))
}

试试看。

# Try asymmetric expand along x-axis
df = data.frame(x = c(20, 15, 25, 23, 12, 14), 
                y = rep(c("a", "b", "c"), 2),
                z = rep(c("aaa", "bbb"), each = 3),
                w = rep(c("ccc", "ddd", "eee"), each = 2))

p1 = ggplot(df[,-4]) + geom_point(aes(x, y)) +
   geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
   geom_text(aes(x = x, y = y, label = x), hjust = -1) +
   facet_grid(. ~ z, scales = "free_x") 

p2 = ggplot(df[, -4]) + geom_point(aes(x, y)) +
   geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
   geom_text(aes(x = x, y = y, label = x), hjust = -1) +
   facet_grid(z ~ .)

p3 = ggplot(df) + geom_point(aes(x, y)) +
   geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
   geom_text(aes(x = x, y = y, label = x), hjust = -1) +
   facet_grid(w ~ z)

p4 = ggplot(df[,-4]) + geom_point(aes(x, y)) +
   geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
   geom_text(aes(x = x, y = y, label = x), hjust = -1) +
   facet_wrap(~ z)

asymmX(p1, list(c(0, .15), c(0, 0)))
asymmX(p2, list(c(0, 0), c(0, 5)))
asymmX(p3, list(c(0, .2), c(0, 0)))
asymmX(p4, list(c(0, 0), c(9, 5)))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-03-06
    • 1970-01-01
    • 2023-03-27
    • 1970-01-01
    • 2015-03-27
    相关资源
    最近更新 更多