Подтвердить что ты не робот

Асимметричное расширение границ оси ggplot

Как настроить асимметрию расширения пределов в ggplot? Например,

library(ggplot2)

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

enter image description here

Я хотел бы, чтобы дно баров заподлицо с нижней частью фона панели, но все равно хотелось бы место наверху. Я могу добиться этого с помощью пустой аннотации:

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

enter image description here

Однако в предыдущих версиях ggplot я мог использовать решение, предоставленное Розеном Матевым:

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 и free_xy. Например,

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

enter image description here

Решение должно предоставить что-то вроде:

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

enter image description here

4b9b3361

Ответ 1

ggplot2 v3.0.0 выпущенный в июле 2018 года, имеет параметр expand_scale() (w/mult аргумент) для достижения цели OP

library(ggplot2)

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

enter image description here

Ответ 2

Теперь я попытался добавить код для этого в ggplot2; см. issue # 1669 и соответствующий запрос на перенос. Если это принято, синтаксис аргумента expand будет изменен с c(m, a) на c(m_lower, a_lower, m_uppper, a_upper), для указания отдельных значений расширения для нижнего и верхнего пределов диапазона. (Старый синтаксис все равно будет продолжать работать, поскольку первые два элемента будут повторно использоваться, если отсутствуют элементы три и/или четыре.)

С помощью этого нового синтаксиса вы можете использовать

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. Я предлагаю решение, хотя и не столь же изящное, как у Розена, но будет работать на графиках без фацетинга, facet_wrap и facet_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)))