Dual axes are not easy to interpret and can be easily manipulated to follow an agenda.

Please read this linked comment by Hadley Wickham on the issue: http://stackoverflow.com/a/3101876

ggplot2 dual axes support

Despite the problems with dual axes mentioned above, some support for them became available in ggplot2 version 2.2.0, November 2016: See https://cran.r-project.org/web/packages/ggplot2/news.html for details on version 2.2.0

“Continuous scales

scale_x_continuous() and scale_y_continuous() can now display a secondary axis that is a one-to-one transformation of the primary axis (e.g. degrees Celcius to degrees Fahrenheit). The secondary axis will be positioned opposite to the primary axis and can be controlled with the sec.axis argument to the scale constructor."

Example using weather data

ggplot2 supports direct, 1:1 transformation of data to be displayed on the secondary axis. This example shows how this can be exploited to show two parameters, each with its unique data range, to make a plot that can be frowned upon.

The parameter shown on the seconday, right hand axis is re-calculated and added to the plot. While the secondary axis is created, the transformation function reverts the re-calculation to show the correct axis ticks for the secondary axis in units of the secondary parameter.

Using weather observations from Melbourne airport, provided by BOM.

library(ggplot2) # must be version > 2.2.0
library(ReadAxfBOM) # load library for axf data

# import data
obs <- ReadAxfBOM("http://www.bom.gov.au/fwo/IDV60901/IDV60901.94866.axf")

# show first observations
head(obs)
##             Timestamp   wmo              name history_product
## 1 2016-11-15 12:30:00 94866 Melbourne Airport        IDV60901
## 2 2016-11-15 12:00:00 94866 Melbourne Airport        IDV60901
## 3 2016-11-15 11:30:00 94866 Melbourne Airport        IDV60901
## 4 2016-11-15 11:00:00 94866 Melbourne Airport        IDV60901
## 5 2016-11-15 10:30:00 94866 Melbourne Airport        IDV60901
## 6 2016-11-15 10:00:00 94866 Melbourne Airport        IDV60901
##   local_date_time local_date_time_full aifstime_utc   lat   lon apparent_t
## 1      15/12:30pm       20161115123000 2.016112e+13 -37.7 144.8        9.9
## 2      15/12:00pm       20161115120000 2.016112e+13 -37.7 144.8       10.6
## 3      15/11:30am       20161115113000 2.016112e+13 -37.7 144.8        9.9
## 4      15/11:00am       20161115110000 2.016112e+13 -37.7 144.8        9.8
## 5      15/10:30am       20161115103000 2.016111e+13 -37.7 144.8       10.1
## 6      15/10:00am       20161115100000 2.016111e+13 -37.7 144.8        9.3
##           cloud cloud_base_m cloud_oktas cloud_type_id cloud_type delta_t
## 1  Mostly clear          660           1             7    Stratus     3.4
## 2 Mostly cloudy          600           7            35       <NA>     3.5
## 3  Mostly clear          570           1             7    Stratus     3.1
## 4  Mostly clear          570           1             7    Stratus     2.9
## 5  Mostly clear          630           1             7    Stratus     3.2
## 6  Mostly clear          630           2             7    Stratus     2.5
##   gust_kmh gust_kt air_temp dewpt  press press_qnh press_msl press_tend
## 1       32      17     14.9   8.1 1020.1    1020.3    1020.1       <NA>
## 2       32      17     15.2   8.3 1020.2    1020.4    1020.2          R
## 3       28      15     14.2   8.0 1020.1    1020.3    1020.1       <NA>
## 4       30      16     14.0   8.2 1020.1    1020.3    1020.1       <NA>
## 5       26      14     14.4   8.0 1020.0    1020.2    1020.0       <NA>
## 6       26      14     13.3   8.3 1020.0    1020.2    1020.0       <NA>
##   rain_trace rel_hum sea_state swell_dir_worded swell_height swell_period
## 1          0      64        NA               NA        -9999        -9999
## 2          0      63        NA               NA        -9999        -9999
## 3          0      66        NA               NA        -9999        -9999
## 4          0      68        NA               NA        -9999        -9999
## 5          0      65        NA               NA        -9999        -9999
## 6          0      72        NA               NA        -9999        -9999
##   vis_km weather wind_dir wind_spd_kmh wind_spd_kt extreme_event
## 1     10    <NA>       SW           24          13         FALSE
## 2     30    Fine       SW           22          12         FALSE
## 3     10    <NA>       SW           20          11         FALSE
## 4     10    <NA>       SW           20          11         FALSE
## 5     10    <NA>      SSW           20          11         FALSE
## 6     10    <NA>       SW           19          10         FALSE
# plot showing air temperature 
p <- ggplot(obs, aes(x = Timestamp))
  p <- p + geom_line(aes(y = air_temp))
p

# add humidity
p <- p + geom_line(aes(y = rel_hum))
p

Now using the sec.axis argument

As the secondary axis can only show a one-to-one transformation of the right y-axis, we’ll have to transform the the data that are shown on the secondary axis. In this case, relative humidity is divided by 5.

p <- ggplot(obs, aes(x = Timestamp))
  p <- p + geom_line(aes(y = air_temp, colour = "Temperature"))
  
  # adding the relative humidity data, transformed to match roughly the range of the temperature
  p <- p + geom_line(aes(y = rel_hum/5, colour = "Humidity"))
  
  # now adding the secondary axis, following the example in the help file ?scale_y_continuous
  # and, very important, reverting the above transformation
  p <- p + scale_y_continuous(sec.axis = sec_axis(~.*5, name = "Relative humidity [%]"))
  
  # modifying colours and theme options
  p <- p + scale_colour_manual(values = c("blue", "red"))
  p <- p + labs(y = "Air temperature [°C]",
                x = "Date and time",
                colour = "Parameter")
  p <- p + theme(legend.position = c(0.8, 0.9))
p

Axis tweaking is, to my knowledge, not implemented and not desired. I.e. it is not easily possible to change the range of the secondary y-axis. It’s a 1:1 transformation after all!

However, the “scales” package provides a convenient rescale function, scales::rescale(). It allows to transform the secondary parameter into any desired range outside of ggplot2. Matching the primary y-axis tick marks with the secondary tick marks is then a manual process, but possible.

In this example, it can be argued that relative humidity and air temperature are related to each other via well known water vapour pressure and saturating vapour pressure functions. Therefore, showing them in the same figure on a transformed scale should be ok in this context, even though it’s not a 1:1, linear relationship across all temperatures.

Good luck.