Пользовательский масштаб оси Y и вторичные метки оси Y в ggplot2 3.1.0 - PullRequest
0 голосов
/ 31 октября 2018

Редактировать 2

Текущая версия ggplot2-пакета для разработки действительно решает проблему, упомянутую в моем вопросе ниже. Установите версию dev, используя

devtools::install_github("tidyverse/ggplot2")

Редактировать

Кажется, что некорректное поведение sec_axis в ggplot2 3.1.0 является ошибкой. Это было признано разработчиками, и они работают над исправлением (см. thread на GitHub).


Цель

У меня есть график, где ось Y колеблется от 0 до 1. Я хотел бы добавить вторичную ось Y, которая варьируется от 0 до 0,5 (так что ровно половина значений первичной оси Y). Пока проблем нет.

Что усложняет дело, так это то, что у меня есть пользовательское преобразование для оси Y, где часть оси Y отображается линейно, а остальные - логарифмически (см. Пример кода ниже). Для справки см. этот пост или этот пост .

Проблема

Это прекрасно работает при использовании ggplot2 версии 3.0.0, но больше не работает при использовании новейшей версии (3.1.0). Смотрите пример ниже. Я не знаю, как это исправить в последней версии.

Из журнала изменений :

sec_axis () и dup_axis () теперь возвращают соответствующие разрывы для вторичная ось применительно к логарифмически преобразованным шкалам

Эта новая функциональность нарушается в случае смешанно-преобразованных осей Y.

Воспроизводимый пример

Вот пример использования самой последней версии (3.1.0) ggplot2:

library(ggplot2)
library(scales)

#-------------------------------------------------------------------------------------------------------
# Custom y-axis
#-------------------------------------------------------------------------------------------------------

magnify_trans_log <- function(interval_low = 0.05, interval_high = 1,  reducer = 0.05, reducer2 = 8) {

  trans <- Vectorize(function(x, i_low = interval_low, i_high = interval_high, r = reducer, r2 = reducer2) {
    if(is.na(x) || (x >= i_low & x <= i_high)) {
      x
    } else if(x < i_low & !is.na(x)) {
      (log10(x / r)/r2 + i_low)
    } else {
      log10((x - i_high) / r + i_high)/r2
    }
  })

  inv <- Vectorize(function(x, i_low = interval_low, i_high = interval_high, r = reducer, r2 = reducer2) {
    if(is.na(x) || (x >= i_low & x <= i_high)) {
      x
    } else if(x < i_low & !is.na(x)) {
      10^(-(i_low - x)*r2)*r
    } else {
      i_high + 10^(x*r2)*r - i_high*r
    }
  })

  trans_new(name = 'customlog', transform = trans, inverse = inv, domain = c(1e-16, Inf))
}

#-------------------------------------------------------------------------------------------------------
# Create data
#-------------------------------------------------------------------------------------------------------

x <- seq(-1, 1, length.out = 1000)
y <- c(x[x<0] + 1, -x[x>0] + 1)

dat <- data.frame(
  x = x
  , y = y
)

#-------------------------------------------------------------------------------------------------------
# Plot using ggplot2
#-------------------------------------------------------------------------------------------------------

theme_set(theme_bw())
ggplot(dat, aes(x = x, y = y)) +
  geom_line(size = 1) +
  scale_y_continuous(
    , trans = magnify_trans_log(interval_low = 0.5, interval_high = 1, reducer = 0.5, reducer2 = 8)
    , breaks = c(0.001, 0.01, 0.1, 0.5, 0.6, 0.7, 0.8, 0.9, 1)
    , sec.axis = sec_axis(
      trans = ~.*(1/2)
      , breaks = c(0.001, 0.01, 0.1, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5)
    )
  ) + theme(
    axis.text.y=element_text(colour = "black", size=15)
  )

Это дает следующий сюжет:

ggplot_new

Маркировка вторичной оси Y является правильной для логарифмической части оси (ниже 0,5), но неправильной для линейной части оси.

Если я установлю ggplot2 3.0.0, используя

require(devtools)
install_version("ggplot2", version = "3.0.0", repos = "http://cran.us.r-project.org")

и запустить тот же код, что и выше, я получаю следующий график, который я хочу:

ggplot_old

Вопросы

  1. Есть ли способ исправить эту проблему в новейшей версии ggplot2 (3.1.0)? В идеале я хотел бы воздержаться от использования более старой версии ggplot2 (т.е. 3.0.0).
  2. Есть ли альтернативы sec_axis, которые бы работали в этом случае?

Ответы [ 2 ]

0 голосов
/ 05 ноября 2018

Вот решение, которое работает с ggplot2 версией 3.1.0 с использованием sec_axis() и требует только создания одного графика. Мы по-прежнему используем sec_axis(), как и раньше, но вместо масштабирования преобразования на 1/2 для вторичной оси, мы инвертируем масштабирование разрывов на вторичной оси вместо .

В этом конкретном случае у нас это довольно просто, так как нам просто нужно умножить желаемые позиции точек останова на 2. Затем полученные точки останова будут правильно размещены как для логарифмической, так и для линейной частей вашего графика. После этого все, что нам нужно сделать, - это сопоставить разрывы с их желаемыми значениями. Это обходит проблему того, что ggplot2 может запутаться из-за размещения разрыва, когда оно должно масштабировать смешанное преобразование, как мы делаем масштабирование самостоятельно. Грубый, но эффективный.

К сожалению, в настоящий момент, похоже, нет никаких других альтернатив sec_axis() (кроме dup_axis(), которые здесь мало помогут). Однако я был бы рад исправить это! Удачи, и я надеюсь, что это решение окажется полезным для вас!

Вот код:

# Vector of desired breakpoints for secondary axis
sec_breaks <- c(0.001, 0.01, 0.1, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5)
# Vector of scaled breakpoints that we will actually add to the plot
scaled_breaks <- 2 * sec_breaks

ggplot(data = dat, aes(x = x, y = y)) +
  geom_line(size = 1) +
  scale_y_continuous(trans = magnify_trans_log(interval_low = 0.5,
                                               interval_high = 1,
                                               reducer = 0.5,
                                               reducer2 = 8),
                     breaks = c(0.001, 0.01, 0.1, 0.5, 0.6, 0.7, 0.8, 0.9, 1),
                     sec.axis = sec_axis(trans = ~.,
                                         breaks = scaled_breaks,
                                         labels = sprintf("%.3f", sec_breaks))) +
  theme_bw() +
  theme(axis.text.y=element_text(colour = "black", size=15))

И получившийся участок:

enter image description here

0 голосов
/ 02 ноября 2018

Можете ли вы создать два отдельных графика для разных диапазонов оси Y и сложить их вместе? У меня на ggplot2 3.1.0 работает следующее:

library(cowplot)

theme_set(theme_bw())

p.bottom <- ggplot(dat, aes(x = x, y = y)) +
  geom_line(size = 1) +
  scale_y_log10(breaks = c(0.001, 0.01, 0.1, 0.5),
                expand = c(0, 0),
                sec.axis = sec_axis(trans = ~ . * (1/2),
                                    breaks = c(0.001, 0.01, 0.1, 0.25))) +
  coord_cartesian(ylim = c(0.001, 0.5)) + # limit y-axis range
  theme(axis.text.y=element_text(colour = "black", size=15),
        axis.title.y = element_blank(),
        axis.ticks.length = unit(0, "pt"),
        plot.margin = unit(c(0, 5.5, 5.5, 5.5), "pt")) #remove any space above plot panel

p.top <- ggplot(dat, aes(x = x, y = y)) +
  geom_line(size = 1) +
  scale_y_continuous(breaks = c(0.6, 0.7, 0.8, 0.9, 1),
                     labels = function(y) sprintf("%.3f", y), #ensure same label format as p.bottom
                expand = c(0, 0),
                sec.axis = sec_axis(trans = ~ . * (1/2),
                                    breaks = c(0.3, 0.35, 0.4, 0.45, 0.5),
                                    labels = function(y) sprintf("%.3f", y))) +
  coord_cartesian(ylim = c(0.5, 1)) + # limit y-axis range
  theme(axis.text.y=element_text(colour = "black", size=15),
        axis.text.x = element_blank(),       # remove x-axis labels / ticks / title &
        axis.ticks.x = element_blank(),      # any space below the plot panel
        axis.title.x = element_blank(),
        axis.ticks.length = unit(0, "pt"),
        plot.margin = unit(c(5.5, 5.5, 0, 5.5), "pt"))

plot_grid(p.top, p.bottom, 
          align = "v", ncol = 1)

plot

...