Используя ggplot2, могу ли я вставить разрыв в оси?

Я хочу сделать гистограмму, где одно из значений намного больше, чем все другие значения. Есть ли способ иметь прерывистую ось Y? Мои данные таковы:

df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))

p <- ggplot(data = df, aes(x = b, y = a)) + geom_bar() 
p <- p + opts(axis.text.x=theme_text(angle= 90, hjust=1))  + coord_flip()
p

Есть ли способ заставить мою ось работать от 1 до 10, а затем от 490 до 500? Я не могу придумать какой-либо другой способ построения данных (кроме преобразования, которое я не хочу делать)

11 ответов

Решение

Как отмечалось в другом месте, это не то, что ggplot2 хорошо справится, так как сломанные топоры обычно считаются сомнительными.

Другие стратегии часто считаются лучшими решениями этой проблемы. Брайан упомянул несколько (огранка, два графика, ориентированные на разные наборы значений). Еще один вариант, который люди слишком часто упускают из виду, особенно для гистограммы, состоит в том, чтобы составить таблицу:

Глядя на фактические значения, 500 не затеняет различия в других значениях! По какой-то причине таблицы не получают достаточного уважения в качестве метода визуализации данных. Вы можете возразить, что ваши данные имеют много, много категорий, которые становятся громоздкими в таблице. Если это так, то, скорее всего, на вашем гистограмме будет слишком много баров, чтобы быть разумными.

И я не спорю о таблицах все время. Но им определенно стоит подумать, если вы делаете гистограммы с относительно небольшим количеством баров. И если вы делаете гирлянды с тоннами слитков, вам, возможно, придется все равно переосмыслить.

Наконец, есть также axis.break функция в plotrix пакет, который реализует сломанные оси. Однако из того, что я понял, вам придется самим определять метки и положения осей вручную.

Восемь лет спустя ggforce пакет предлагает facet_zoom() расширение, являющееся реализацией предложения Хэдли Уикхем показать два сюжета (как указано в ответе Брайана Диггса).

Увеличить грань

library(ggforce)
ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() +
  facet_zoom(ylim = c(0, 10))

К сожалению, текущая версия 0.2.2 ggforce выдает ошибку с coord_flip() поэтому могут быть показаны только вертикальные полосы.

Увеличенный фасет показывает изменения малых значений, но по-прежнему содержит большие - теперь обрезанные - a4 бар. zoom.data Параметр контролирует, какие значения появляются в увеличенном фасете:

library(ggforce)
ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() +
  facet_zoom(ylim = c(0, 10), zoom.data = ifelse(a <= 10, NA, FALSE))

Два участка

Хэдли Уикхем предложил

Я думаю, что гораздо более уместно показать два графика - один из всех данных, а другой - только небольшие значения.

Этот код создает два участка

library(ggplot2)
g1 <- ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() +
  coord_flip()
g2 <- ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() +
  coord_flip() +
  ylim(NA, 10)

которые могут быть объединены в один сюжет

cowplot::plot_grid(g1, g2) # or ggpubr::ggarrange(g1, g2)

или же

gridExtra::grid.arrange(g1, g2) # or egg::ggarrange(g1, g2)

Две грани

Это было предложено в комментарии Чейза, а также Брайана Диггса в его ответе, который интерпретировал предложение Хэдли использовать

граненые участки, один со всеми данными, один увеличенный в определенном регионе

но пока не было предоставлено никакого кода для этого подхода.

Поскольку не существует простого способа отдельно масштабировать фасеты (см., Например, связанный вопрос), необходимо манипулировать данными:

library(dplyr)
library(ggplot2)
ggplot() + 
  aes(x = b, y = a) +
  geom_col(data = df %>% mutate(subset = "all")) +
  geom_col(data = df %>% filter(a <= 10) %>% mutate(subset = "small")) +
  coord_flip() + 
  facet_wrap(~ subset, scales = "free_x")

Нет, не используя ggplot. См. Обсуждение в теме по адресу http://groups.google.com/group/ggplot2/browse_thread/thread/8d2acbfc59d2f247 где Хэдли объясняет, почему это невозможно, но предлагает предложенную альтернативу (граненые графики, один со всеми данными, один увеличено в конкретном регионе).

Не с ggplot, а с plotrix, вы можете легко сделать это:

library(plotrix)
gap.barplot(df$a, gap=c(5,495),horiz=T)

Вариантом может быть использование пакета ggbreak с использованием функции scale_y_cut() или scale_x_cut() . Эта функция позволяет вырезатьggplotобъект на части с возможностью указать, какую часть увеличивать или уменьшать. Вот воспроизводимый пример с нормальным левым графиком и правым графиком с используемой функцией:

      df <- data.frame(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))

library(ggplot2)
library(ggbreak)
library(patchwork)
p1 <- ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() 
p2 <- ggplot(df) + 
  aes(x = b, y = a) +
  geom_col() +
  scale_y_cut(breaks=c(4, 30), which=c(1, 3), scales=c(0.5, 3)) 
  
p1 + p2

Создано 22 августа 2022 г. с репрексом v2.0.2

Как видно из примера, некоторые части увеличены и уменьшены. Это можно изменить, используя различные аргументы.

Используемые аргументы:

  • перерывы:

числовой или числовой вектор, точки для разделения

  • который:

целое число, положение подграфиков на шкале, начиная слева направо или сверху вниз.

  • Весы:

числовая, относительная ширина или высота подграфиков.

Чтобы изменить расстояние между сюжетами, вы можете использовать аргументspace.

Дополнительную информацию и примеры можно найти в этом руководстве .

нет, к сожалению нет

Опасение заключается в том, что допущение прерывистых осей приведет к обману аудитории. Однако существуют случаи, когда отсутствие прерывистой оси приводит к искажению.

Например, если ось усечена, но обычно лежит в некотором интервале (скажем, [0,1]), аудитория может не заметить усечение и сделать искаженные выводы о данных. В этом случае явная разрывная ось была бы более подходящей и прозрачной.

Для сравнения:

Пример хорошего использования непрерывной и прерывистой оси

По состоянию на 01.06.2022 у нас есть элегантныйggbreakpackage, который, кажется, отвечает на вопрос ОП. Хотя я не пробовал его на своих собственных данных, похоже, он совместим со многими или со всеми другимиggplot2функциональность. Также предлагает дифференциальное масштабирование, возможно, полезное для OP и подобных целей.

      library(ggplot2)
library(ggbreak) 

set.seed(2019-01-19)
d <- data.frame(x = 1:20,
   y = c(rnorm(5) + 4, rnorm(5) + 20, rnorm(5) + 5, rnorm(5) + 22))
 
p1 <- ggplot(d, aes(y, x)) + geom_col(orientation="y") + 
theme_minimal()
p1 + scale_x_break(c(7, 17), scales = 1.5) + scale_x_break(c(18, 21), scales=2)

Умное решение ggplot обеспечивается Йорг Steinkamp, используя facet_grid. В упрощенном виде это примерно так:

      library("tidyverse")
df <- data.frame(myLetter=LETTERS[1:4], myValue=runif(12) + rep(c(4,0,0),2))  # cluster a few values well above 1
df$myFacet <- df$myValue > 3
(ggplot(df, aes(y=myLetter, x=myValue)) 
  + geom_point() 
  + facet_grid(. ~ myFacet, scales="free", space="free")
  + scale_x_continuous(breaks = seq(0, 5, .25)) # this gives both facets equal interval spacing.
  + theme(strip.text.x = element_blank()) # get rid of the facet labels
)

Я сомневаюсь, что в R есть что-нибудь готовое, но вы можете показать данные в виде серии трехмерных частичных кубов. 500 - это всего лишь 5*10*10, поэтому он будет хорошо масштабироваться. Точное значение может быть меткой.

Это, вероятно, следует использовать, только если по какой-то причине у вас должно быть графическое представление.

Одна стратегия состоит в том, чтобы изменить ось для построения Log Scale. Таким образом, вы можете уменьшить экспоненциально более высокое значение в 10 раз

      library(data.table)
dt <- data.table(a = c(1,2,3,500), b = c('a1', 'a2','a3', 'a4'))
dt[,ggplot(.SD)+
      aes(x = b, y = a) +
      geom_col(data = subset(.SD,TRUE)[,subset:="all"])+
      geom_col(data = subset(.SD ,a <= 10)[,subset:= "small"]) +
     coord_flip() + 
     facet_wrap(~ subset, scales = "free_x")]
Другие вопросы по тегам