rggplot2yaxisinverse

Two y axes in R Studio where one of them is inverse and the other is not (precipitation and water depth hydrology graph)


Very new programmer here. I'm struggling with this graph; I am trying to have one y axis reversed (for precipitation) and one y axis not reversed (for depth). I finally got both axes to work, but I can't figure out how to invert just one of them and the data too. The precipitation should be coming down from the top of the graph while the depth should stay how it is on the graph I shared here.

enter image description here

Here is what I tried:

ggp1 <-ggplot(BeachSensors) + geom_bar(aes(x=Date, y=Precip),stat="identity", fill="cyan",colour="#006000") + scale_y_reverse()
ggp1
ggp2 <- ggp1 + geom_line(aes(x=Date, y=CorrDepth, color=Sensor),stat="identity",size=1)+
labs(title= "GLSM Precipitation and Depth",
x="Date",y="Precipitation (in.)")+
scale_y_continuous(sec.axis=sec_axis(~.*2,name="Depth (ft.)")) + theme_minimal()

But I kept either inverting both of the y axes at the same time, which is not what I want.


Solution

  • The "trick" (and often nightmares of inverting algebraic equations in high school) is knowing how to change the transforms for sec_axis, and knowing that you need to change the raw data itself.

    I'm going to assume your data is in a "wide" format, looking somewhat like this:

    set.seed(42)
    dat <- data.frame(precip = runif(50, 0, 1.5)) |>
      transform(
        time = 1:50,
        precip = ifelse(runif(50) < 0.8, 0, precip),
        sensor = sample(c("inside", "outside"), 50, replace = TRUE)
      ) |>
      transform(depth = 3.6 + cumsum(precip) - time/10)
    head(dat)
    #   precip time  sensor depth
    # 1      0    1 outside   3.5
    # 2      0    2 outside   3.4
    # 3      0    3  inside   3.3
    # 4      0    4 outside   3.2
    # 5      0    5  inside   3.1
    # 6      0    6 outside   3.0
    

    From here, we need to know how to algebraically convert depth to the correct location and scale of the precip data.

    The conversion is a "simple" scaling, meaning we need to adjust it to be completely within [0,1] and then adjust to the new scale. Using some prep objects,

    rngdepth <- range(dat$depth)
    diffdepth <- diff(rngdepth)
    maxprecip <- max(dat$precip)
    diffprecip <- diff(range(dat$precip))
    

    We now have a simple relationship and the inversion. The two formulas we're interested in are the first and last:

    y = maxprecip - diffprecip * (depth - rngdepth[1]) / diffdepth
    maxprecip - y = diffprecip * (depth - rngdepth[1]) / diffdepth
    diffdepth * (maxprecip - y) = diffprecip * (depth - rngdepth[1])
    diffdepth * (maxprecip - y) / diffprecip = depth - rngdepth[1]
    diffdepth * (maxprecip - y) / diffprecip + rngdepth[1] = depth
    

    I prefer to use ~-function transforms in the data, so that the original frame is not encumbered (confusingly, sometimes) with the variables. Here's the plotting code:

    ggplot(dat, aes(x = time)) +
      geom_col(aes(y = precip)) +
      geom_line(
        aes(x = time, y = depth2, color = sensor, group = sensor),
        data = ~ transform(., depth2 = maxprecip - diffprecip * (depth - rngdepth[1]) / diffdepth)
      ) +
      scale_y_reverse(
        name = "Precipitation",
        sec.axis = sec_axis(
          ~ diffdepth * (maxprecip - .) / diffprecip + rngdepth[1],
          name = "Depth"
        )
      )
    

    ggplot2 with primary axis inverted, secondary axis upright and scaled