Как я могу поместить преобразованную шкалу в правую сторону ggplot2?

Я создаю график, показывающий изменение уровня озера с течением времени. Ниже приведен простой пример. Я хотел бы добавить шкалу (отметки и аннотации) в правой части графика, который показывает высоту в футах. Я знаю, что ggplot2 не допустит двух разных масштабов (см. Участок с осью 2 y, одна ось y слева и другая ось y справа), но потому, что это трансформация того же масштаба, есть ли способ сделать это? Я предпочел бы продолжать использовать ggplot2 и не возвращаться к функции plot().

library(ggplot2)
LakeLevels<-data.frame(Day=c(1:365),Elevation=sin(seq(0,2*pi,2*pi/364))*10+100)
p <- ggplot(data=LakeLevels) + geom_line(aes(x=Day,y=Elevation)) + 
  scale_y_continuous(name="Elevation (m)",limits=c(75,125)) 
p

Ответ 1

Вы должны посмотреть на эту ссылку http://rpubs.com/kohske/dual_axis_in_ggplot2.

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

    library(ggplot2)
    library(gtable)
    library(grid)
    LakeLevels<-data.frame(Day=c(1:365),Elevation=sin(seq(0,2*pi,2*pi/364))*10+100)
    p1 <- ggplot(data=LakeLevels) + geom_line(aes(x=Day,y=Elevation)) + 
          scale_y_continuous(name="Elevation (m)",limits=c(75,125))

    p2<-ggplot(data=LakeLevels)+geom_line(aes(x=Day, y=Elevation))+
        scale_y_continuous(name="Elevation (ft)", limits=c(75,125),           
        breaks=c(80,90,100,110,120),
                 labels=c("262", "295", "328", "361", "394"))

    #extract gtable
    g1<-ggplot_gtable(ggplot_build(p1))
    g2<-ggplot_gtable(ggplot_build(p2))

    #overlap the panel of the 2nd plot on that of the 1st plot

    pp<-c(subset(g1$layout, name=="panel", se=t:r))
    g<-gtable_add_grob(g1, g2$grobs[[which(g2$layout$name=="panel")]], pp$t, pp$l, pp$b, 
                       pp$l)

   ia <- which(g2$layout$name == "axis-l")
   ga <- g2$grobs[[ia]]
   ax <- ga$children[[2]]
   ax$widths <- rev(ax$widths)
   ax$grobs <- rev(ax$grobs)
   ax$grobs[[1]]$x <- ax$grobs[[1]]$x - unit(1, "npc") + unit(0.15, "cm")
   g <- gtable_add_cols(g, g2$widths[g2$layout[ia, ]$l], length(g$widths) - 1)
   g <- gtable_add_grob(g, ax, pp$t, length(g$widths) - 1, pp$b)

   # draw it
   grid.draw(g)

enter image description here

Ответ 2

Я мог бы найти решение для размещения названия оси, с некоторыми ответами от ответа Nate Pope, которые можно найти здесь:
ggplot2: добавление вторичной трансформированной оси x поверх графика
И обсуждение о доступе к grobs в gtable здесь: https://groups.google.com/forum/m/#!topic/ggplot2-dev/AVHHcYqc5uU

В конце концов, я просто добавил строку

g <- gtable_add_grob(g, g2$grob[[7]], pp$t, length(g$widths), pp$b)

перед вызовом grid.draw(g), который, казалось, сделал трюк.
Насколько я понимаю, он принимает заголовок оси y g2$grob[[7]] и помещает его в верхнюю правую сторону. Это может быть не решение для pretties, но это сработало для меня.

Последнее. Было бы неплохо найти способ поворота названия оси.

Привет,

Тим

Ответ 3

На этот вопрос был дан ответ, но общая проблема добавления вторичной оси и вторичной шкалы в правую часть объекта ggplot - это все, что возникает постоянно. Я хотел бы сообщить ниже свою собственную настройку проблемы, основанную на нескольких элементах, заданных различными ответами в этом потоке, а также в нескольких других потоках (см. Неполный список ссылок ниже).

Мне нужно массовое производство графиков с двумя у-оси, поэтому я построил функцию ggplot_dual_axis(). Вот потенциальные возможности:

  • Код отображает линии сетки для осей y-left и y-right (это мой основной вклад, хотя это тривиально)

  • Код печатает символ евро и вставляет его в pdf (что-то я увидел там: Построение символа евро в ggplot2?)

  • Код пытается избежать печати некоторых элементов дважды ( "попытки" предполагают, что я сомневаюсь, что он полностью преуспеет)

Неотвеченные вопросы:

  • Есть ли способ изменить функцию ggplot_dual_axis(), чтобы удалить один из geom_line() или geom_point(), или что бы это ни было, без ошибок бросания, если такие элементы геометрии отсутствуют. В псевдокоде что-то вроде if has(geom_line) ...

  • Как я могу назвать g2$grobs[[7]] по ключевому слову, а не по индексу? Это то, что он возвращает: text[axis.title.y.text.232] Мой интерес к вопросу связан с моими неудачными попытками захватить линии сетки, применив подобный трюк. Я думаю, что линии сетки скрыты где-то внутри g2$grobs[[4]], но я не уверен, как их получить.

Edit. Вопрос: Я смог ответить сам себе: как я могу увеличить границу участка с правой стороны, где есть этикетка "Евро"? Ответ: theme(plot.margin = unit(c(1,3,0.5,0.8), "lines")) выполнит трюк, например.

Просьба указать на очевидные проблемы или предложить улучшения.

Теперь код: надеюсь, кому-то это будет полезно. Как я уже сказал, я не претендую на оригинальность, это сочетание вещей, которые уже показали другие.

##' function named ggplot_dual_axis()
##' Takes 2 ggplot plots and makes a dual y-axis plot
##' function takes 2 compulsory arguments and 1 optional argument
##' arg lhs is the ggplot whose y-axis is to be displayed on the left
##' arg rhs is the ggplot whose y-axis is to be displayed on the right
##' arg 'axis.title.y.rhs' takes value "rotate" to rotate right y-axis label
##' The function does as little as possible, namely:
##'  # display the lhs plot without minor grid lines and with a
##'  transparent background to allow grid lines to show
##'  # display the rhs plot without minor grid lines and with a
##'  secondary y axis, a rotated axis label, without minor grid lines
##'  # justify the y-axis label by setting 'hjust = 0' in 'axis.text.y'
##'  # rotate the right plot 'axis.title.y' by 270 degrees, for symmetry
##'  # rotation can be turned off with 'axis.title.y.rhs' option
##'  

ggplot_dual_axis <- function(lhs, rhs, axis.title.y.rhs = "rotate") {
  # 1. Fix the right y-axis label justification
    rhs <- rhs + theme(axis.text.y = element_text(hjust = 0))
  # 2. Rotate the right y-axis label by 270 degrees by default
    if (missing(axis.title.y.rhs) | 
        axis.title.y.rhs %in% c("rotate", "rotated")) {
        rhs <- rhs + theme(axis.title.y = element_text(angle = 270)) 
    }
  # 3a. Use only major grid lines for the left axis
    lhs <- lhs + theme(panel.grid.minor = element_blank())
  # 3b. Use only major grid lines for the right axis
  #     force transparency of the backgrounds to allow grid lines to show
    rhs <- rhs + theme(panel.grid.minor = element_blank(), 
        panel.background = element_rect(fill = "transparent", colour = NA), 
        plot.background = element_rect(fill = "transparent", colour = NA))
# Process gtable objects
  # 4. Extract gtable
    library("gtable") # loads the grid package
    g1 <- ggplot_gtable(ggplot_build(lhs))
    g2 <- ggplot_gtable(ggplot_build(rhs))
  # 5. Overlap the panel of the rhs plot on that of the lhs plot
    pp <- c(subset(g1$layout, name == "panel", se = t:r))
    g <- gtable_add_grob(g1, 
        g2$grobs[[which(g2$layout$name == "panel")]], pp$t, pp$l, pp$b, pp$l)
  # Tweak axis position and labels
    ia <- which(g2$layout$name == "axis-l")
    ga <- g2$grobs[[ia]]
    ax <- ga$children[["axis"]]  # ga$children[[2]]
    ax$widths <- rev(ax$widths)
    ax$grobs <- rev(ax$grobs)
    ax$grobs[[1]]$x <- ax$grobs[[1]]$x - unit(1, "npc") + unit(0.15, "cm")
    g <- gtable_add_cols(g, g2$widths[g2$layout[ia, ]$l], length(g$widths) - 1)
    g <- gtable_add_grob(g, ax, pp$t, length(g$widths) - 1, pp$b)
    g <- gtable_add_grob(g, g2$grobs[[7]], pp$t, length(g$widths), pp$b)
  # Display plot with arrangeGrob wrapper arrangeGrob(g)
    library("gridExtra")
    grid.newpage()
    return(arrangeGrob(g))
}

И ниже некоторые поддельные данные и два графика, которые предназначены для долларовых и евро-единиц. Было бы здорово иметь пакет, который позволил бы вам сделать один сюжет и обернуть вокруг него вызов графика с двойной осью, подобный ggplot_dual_axis_er(ggplot_object, currency = c("dollar", "euro")), и он автоматически выберет обменные курсы для вас!: -)

# Set directory:
if(.Platform$OS.type == "windows"){
  setwd("c:/R/plots")
} else { 
  setwd("~/R/plots")
}

# Load libraries
library("ggplot2")
library("scales")

# Create euro currency symbol in plot labels, simple version
# avoids loading multiple libraries
# avoids problems with rounding of small numbers, e.g. .0001
labels_euro <- function(x) {# no rounding
paste0("€", format(x, big.mark = ",", decimal.mark = ".", trim = TRUE,
    scientific = FALSE))
} 

labels_dollar <- function(x) {# no rounding: overwrites dollar() of library scales
paste0("$", format(x, big.mark = ",", decimal.mark = ".", trim = TRUE,
    scientific = FALSE))
} 

# Create data
df <- data.frame(
  Year = as.Date(c("2001", "2002", "2003", "2004", "2005", "2006", "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014", "2015", "2016", "2017", "2018"),
    "%Y"), 
  Dollar = c(0, 9000000, 1000000, 8000000, 2000000, 7000000, 3000000, 6000000, 4000000, 5000000, 5000000, 6000000, 4000000, 7000000, 300000, 8000000, 2000000, 9000000))
# set Euro/Dollar exchange rate at 0.8 euros = 1 dollar
df <- cbind(df, Euro = 0.8 * df$Dollar)
# Left y-axis
p1 <- ggplot(data = df, aes(x = Year, y = Dollar)) + 
    geom_line(linestyle = "blank") + # manually remove the line
    theme_bw(20) +                   # make sure font sizes match in both plots
    scale_x_date(labels = date_format("%Y"), breaks = date_breaks("2 years")) + 
    scale_y_continuous(labels = labels_dollar, 
        breaks = seq(from = 0, to = 8000000, by = 2000000))
# Right y-axis
p2 <- ggplot(data = df, aes(x = Year, y = Euro)) + 
    geom_line(color = "blue", linestyle = "dotted", size = 1) + 
    xlab(NULL) +                     # manually remove the label
    theme_bw(20) +                   # make sure font sizes match in both plots
    scale_x_date(labels = date_format("%Y"), breaks = date_breaks("2 years")) +
    scale_y_continuous(labels = labels_euro, 
        breaks = seq(from = 0, to = 7000000, by = 2000000))

# Combine left y-axis with right y-axis
p <- ggplot_dual_axis(lhs = p1, rhs = p2)
p

# Save to PDF
pdf("ggplot-dual-axis-function-test.pdf", 
  encoding = "ISOLatin9.enc", width = 12, height = 8)
p
dev.off()

embedFonts(file = "ggplot-dual-axis-function-test.pdf", 
           outfile = "ggplot-dual-axis-function-test-embedded.pdf")

enter image description here

Частичный список ссылок:

Ответ 4

Чтобы повернуть заголовок оси, добавьте следующее к графику p2:

p2 <- p2 + theme(axis.title.y=element_text(angle=270))