简体   繁体   中英

Asymmetric expansion of ggplot axis limits

How do you adjust the expansion of limits asymmetrically in ggplot? For example,

library(ggplot2)

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

在此处输入图片说明

I would like the bottom of the bars flush with the bottom of the panel background, but would still like space at the top. I can achieve this with a blank annotation:

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

在此处输入图片说明

In previous versions of ggplot , however, I could use 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
}

and then use scale_y_continuous(expand = list(c(0,0.1), c(0,0))) which would add a consistently addition to the top of the chart. In the current version, however, I get an error

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

Is there an effective solution for ggplot2 2.0?

A solution should include the ability to work flexibly with facets, and free_xy scale options. For example,

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

在此处输入图片说明

A solution should provide something like:

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))

在此处输入图片说明

ggplot2 v3.0.0 released in July 2018 has expand_scale() option (w/ mult argument) to achieve OP's goal.

Edit: expand_scale() will be deprecated in the future release in favor of expansion() . See News for more information.

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))) 

在此处输入图片说明

I have now tried to add code for this to ggplot2 ; see issue #1669 and the corresponding pull request . If it is accepted, the syntax for the expand argument will been changed from c(m, a) to c(m_lower, a_lower, m_uppper, a_upper) , for specifying separate expansion values for the lower and upper range limits. (The old syntax will still continue to work, though, as the first two elements will be reused if elements three and/or four are missing.)

With this new syntax, you can use

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

The result looks like this:

简单的条形图

It also works with facetting:

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))

带分面的条形图

I used Rosen Matev's solution often, and was disappointed when it broke with ggplot version 2.0. I offer a solution, though not nearly as elegant as Rosen's, but will work on plots with no facetting, facet_wrap , and facet_grid , and with one-way and two-way facet_grid . However, it will not work with more complicated facet grids, nor will it work with coord_flip . There are two functions: one for asymmetric expansion along the y-axis, and one for expansion along the x-axis. The functions perform multiplicative and additive expansions.

The functions gather information from the plot, calculate new limits for the y (or x) axis, then use geom_blank to construct new plots with the desired expansion factors.

First, a function to perform asymmetric expansion along the y-axis.

# 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 it out with some facet wrap and facet grid plots.

# 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)))

Second, a function to perform asymmetric expansion along the x-axis.

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 it out.

# 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)))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM