--- title: "User Guide: 2 Autoplot Methods" subtitle: "'ggspectra' `r packageVersion('ggspectra')`" author: "Pedro J. Aphalo" date: "`r Sys.Date()`" output: rmarkdown::html_vignette: toc: yes vignette: > %\VignetteIndexEntry{User Guide: 2 Autoplot Methods} %\VignetteEngine{knitr::rmarkdown} %\VignetteEncoding{UTF-8} --- ## Introduction Package `ggspectra` extends `ggplot2` with stats, geoms and annotations suitable for light and radiation spectra. It also defines `ggplot()` and `autoplot()` methods specialized for the classes defined in package `photobiology` for storing different types of spectral data. This vignette describes the use of these `autoplot()` methods. The package uses 'ggplot2', 'photobiology' and 'photobiologyWavebands'. More importantly it defines specializations of methods defined in 'ggplot2' and exports methods specialized for classes defined in package 'photobiology', consequently both 'ggplot2' and 'photobiology' are loaded and attached automatically when package 'ggspectra' is loaded. (When using earlier versions earlier than 0.9.26 these packages had to be loaded and attached explicitly.) ```{r} library(ggplot2) library(photobiologyWavebands) library(ggspectra) library(ggrepel) # ensure all labels are plotted options(ggrepel.max.overlaps = Inf) ``` ```{r, include=FALSE, echo=FALSE} library(knitr) opts_chunk$set(fig.align = 'center', fig.show = 'hold', fig.width = 7, fig.height = 4, cache = FALSE) options(warnPartialMatchArgs = FALSE) ``` We will use an individual sunlight spectrum `sun.spct` and a short time series of spectra `sun_evening.spct` and `sun_evening.mspct', from 'photobiology' for examples. ```{r} summary(sun.spct) ``` ```{r} summary(sun_evening.spct) ``` ```{r} summary(sun_evening.mspct) ``` We change the default theme for ggplots. ```{r} theme_set(theme_bw(10)) ``` ## Using the `autoplot()` methods The most automatic way of plotting spectral data stored in one of the classes defined in package `photobiology` is to use the `autoplot()` methods (for compatibility with versions < 0.9.25 `plot()` remains as a deprecated name for the same methods). As `autoplot()` methods defined by 'ggplot2' they take advantage of all the data and metadata stored in objects of the special classes used to store spectra. This allows the automatic construction of axis labels as quantities and units are well defined. ### The basics Here we use the same example `source_spct` object from package 'photobiology' to demonstrate parameters common to all `autoplot()` methods for spectra. Methods for plotting of objects of most classes defined in package 'photobiology' are available. Function `spct_classes()` lists these classes. ```{r} # the classes spct_classes() ``` ```{r} # autoplot methods for spectra grep("_spct*$", methods("autoplot"), value = TRUE) ``` The simplest possible call to `autoplot()` needs only one argument, the object to plot. ```{r} autoplot(sun.spct) ``` In contrast to the examples in the _User Guide 1_, here we obtain directly an annotated plot of the solar spectrum at ground level. This would be of limited use without the possibility to adjust the design and components of the _ggplot_ object created. One approach is to use the grammar of graphics to add to the plot. However, it is also possible to control many features of the plot by passing additional arguments in the call to `autoplot()`. User-provided arguments override the defaults. When plotting `source_spct` and `response_spct` objects we can change the basis of expression of spectral irradiance and spectral response from `"energy"` to `"photon"`. Be aware that by design, within one plot all spectral values and derived summaries will always use the same base of expression. ```{r} autoplot(sun.spct, unit.out = "photon") ``` We can build and area (`"spct"`) plot instead the default line plot. ```{r} autoplot(sun.spct, geom = "spct") ``` We can build and area + line (`c("spct", "line")`) plot instead the default line plot. ```{r} autoplot(sun.spct, geom = c("spct", "line")) ``` If a single object contains multiple spectra in long form, they are all plotted. In this case no summary values are displayed as they would overlap. ```{r} autoplot(sun_evening.spct) ``` We can rename the factor that identifies the spectra, changing the title of the key for the lines. ```{r} autoplot(sun_evening.spct, idfactor = "Time") ``` In addition to the `autoplot()` methods for individual spectra and spectra in stored long form (`_spct` classes), there are `autoplot()` methods available for collections of spectra (`_mspct` classes). Collections of spectra like `sun_evening.mspct` can be plotted also automatically, as they are combined on-the-fly and plotted as shown above in the example for `sun_evening.spct`. The methods for collections of spectra and for multiple spectra in long form behave almost identically. The parameters are the same as those in the `autoplot()` methods for multiple spectra in long form described above. We use in the next examples a simple collection of five spectra. These are the same five spectra as above (`sun_evening.spct`), but stored in an object of a different class. The plotting of objects of all the classes for collections of spectra defined in package 'photobiology' is similar. Function `mspct_classes()` lists all the classes for collections of spectra defined in package 'photobiology'. ```{r} # the classes mspct_classes() ``` ```{r} # autoplot methods for collections of spectra grep("_mspct*$", methods("autoplot"), value = TRUE) ``` The plot using defaults is identical to that above. ```{r} autoplot(sun_evening.mspct) ``` We can choose a different name for the factor identifying the spectra in the collection. ```{r} autoplot(sun_evening.mspct, idfactor = "Time") ``` With facets, there is only one spectrum per panel. Summaries per waveband are shown. ```{r} #| fig.asp: 0.67 autoplot(sun_evening.spct, facets = TRUE) ``` Above we showed the default faceting by passing `facets = TRUE`. We can set the number of columns by passing a number. For panels in one column we pass `facets = 1`, and for two columns we pass `facets = 2`, and so on. ```{r} #| fig.asp: 1 autoplot(sun_evening.mspct, facets = 2) ``` ```{r} #| fig.asp: 1 autoplot(sun_evening.mspct, geom = "spct", facets = 2) ``` --- Contrary to `"gg"` objects created with package 'ggplot2', the data embedded in the `"gg"` objects created with these `autoplot()` methods retains the attributes used by package 'photobiology', including the class. ```{r} p1 <- autoplot(sun.spct) summary(p1$data) ``` This makes it possible to trace the origin of the data, and also to apply specialised methods to them. **Note:** When using 'ggplot2' (>= 4.0.0) `@` can be used instead of `$` to extract components like data `data`, and should be preferred for new scripts. --- ### Wavelengths We can zoom into the spectrum, by providing a wavelength range in nanometres. Here we pass a vector of two numbers as argument, but any object for which a `range()` method is available can also be used, including longer numeric vectors, objects of one the spectrum classes, such as class `source_spct`, or a `waveband` definition. In the case of vectors of length two, `NA` can be used to signal the use of the default limit. ```{r} autoplot(sun.spct, range = c(400, 700)) ``` *The range can be also expanded in a similar way, for example to ensure consistent limits across plots of spectra of different wavelength ranges.* We can show summaries for different wavebands, by passing them to parameter `w.band`. We can pass either individual wavebands or lists of wavebands. First a list of wavebands created with a constructor, here showing the limits based on the ISO standard. ```{r} autoplot(sun.spct, w.band = photobiologyWavebands::VIS_bands("ISO")) ``` A single waveband can also be created with a generic constructor. ```{r} autoplot(sun.spct, w.band = waveband(c(380, 760))) ``` `NULL` as argument for `w.band` is replaced by a waveband covering the full range of the spectral data. The _whole range_ is the range plotted, which is controlled by the argument passed to `range`. ```{r} autoplot(sun.spct, w.band = NULL) ``` Summarizing the examples above, arguments passed to `range` and `w.band` play very different roles. Parameter `range`, gives the wavelengths to include in the plot, and the default for `range` is always the range of wavelengths in the spectrum being plotted. The argument to `w.band` is only used for the _annotations_ and decorations, described in a later section in more detail. The effect of `range` is different to the effect of `ggplot2::xlim()` as `range` is used to _trim_ ($\approx$ subset) the spectral data **before** passing it to `ggplot`, using interpolation when needed (see `photobiology::trim_wl()`). In contrast `xlim` changes the limits of the scale, and discards data for all wavelengths not within the range (equivalent to action of `photobiology::clip_wl()`). The difference is subtle when the spectrum has high wavelength resolution but can result in inconsistencies among plots. --- The default argument to parameter `w.band` can be changed by setting the R option `"photobiology.plot.bands"` to a single waveband object or to a list waveband objects. Function `set_w.band_default()` allows to set this option using the same syntax as described for parameter `w.band`. See package 'photobiologyWavebands' for waveband constructors for ISO and other waveband definitions in common use, and package 'photobiology' for defining your own. ```{r, eval=FALSE} set_w.band_default(w.band = Plant_bands()) ``` --- ### Normalization _How already normalized spectral data are plotted by these `autoplot()` methods has changed during the history of the package. Currently, the normalization can be selected by passing an argument to `norm` as explained in this section._ We first consider the case of spectral data that has not been previously normalized. These data are by default plotted _as is_, except for the possible conversion into a different quantity or base of expression. If the argument passed to `norm` is one of `"max"` or `"min"` or a numeric value for a wavelength in nanometres, the normalization is applied after the unit or quantity conversion and the data subsequently plotted. If the argument passed to `norm` is one of `"skip"`, `"update"` (the default) or `"undo"` the spectral data are plotted as is. We next consider the case of spectral data that has not been previously normalized. If the argument passed to `norm` is one of `"max"` or `"min"` or a numeric value for a wavelength in nanometres, the normalization is applied after the unit or quantity conversion and the data subsequently plotted. In other words, the previous normalization is replaced by the new one. If the argument passed to `norm` is one of `"skip"` or `"update"` (the default) a new normalization is applied after the unit or quantity conversion reusing the criterion previsouly used to normalize the data passed as argument to `"data"`. The difference between `"skip"` and `"update"` is that the former issues a warning if an update is needed and the later does not. If the argument passed to `norm` is `"undo"` the existing normalization is reverted _if possible_ before plotting. Whether "undoing" a normalization is possible depends on full metadata stored in the object attributes, a feature implemented in 'photobiology' (>= 0.14.0). ```{r} autoplot(sun.spct, geom = "spct", norm = "max") ``` Normalization is reflected in the _y_-scale tick labels and axis title. In addition, summaries by waveband are no longer expressed as irradiances but instead as a contribution to the total, i.e., the area under the curve. To emphasize this `geom = "spct"` is used instead of the default. ### "Parallel" summaries All plotting of multiple spectra shown above were done with the default `plot.data = "as.is"`. We can also plot a row-wise summary (wavelength by wavelength, or _parallel_ summaries) of the spectra in the collection, here the `mean` of the spectra. Currently implemented only for spectra with identical wavelength vectors and of the same class. Of the different supported functions, `"mean"` and `"median"` are likely the most useful. ```{r} autoplot(sun_evening.mspct, plot.data = "mean") ``` ### Summary quantities by waveband Although the defaults result in the addition of frequently useful annotations, the `autoplot()` methods accepts arguments for several parameters that make possible flexible control of the annotations. Currently, numerical summaries can be added automatically to plots only when a single spectrum is plotted per plot panel. In this first example we pass `"mean"` as argument to `label.qty`, to print means instead of integrals in the labels. Note that the units and quantity labels for the waveband summaries have also changed. ```{r} autoplot(sun.spct, label.qty = "mean") ``` Two `label.qty` values need explanation. The first one, `"relative"` displays in labels the relative contribution of the integral of each waveband, to the sum of the integrals of all wavebands displayed in the plot. The second one, `"contribution"` displays in labels the integral of each waveband divided by the integral of the whole spectrum displayed in the plot. Consequently, this second option should be interpreted with caution, as the spectral data is unlikely in many cases to include the whole emission or absorption spectrum of a source. Adding `.pc` to the arguments, that is, using `"relative.pc"` or `"contribution.pc"`, results in the corresponding values being displayed as percentages. When using `"contribution.pc"` unless the summarized wavebands cover the whole range of wavelengths in the spectrum, the sum of the summary values is less than 100%. ```{r} autoplot(sun.spct, label.qty = "contribution.pc") ``` When using `"relative.pc"` the sum adds to 100. ```{r} autoplot(sun.spct, label.qty = "relative.pc") ``` ### Automatic annotations Which _annotations_ are included can be controlled through parameter `annotations`. It accepts a `character` vector, or a list of `character` vectors as argument. Three values have special meaning if at the head of the vector (with index = 1): `"="` means override the current defaults with the annotations that follow in the vector, `"+"` means add to the current default the annotations that follow, and `"-"` means remove from the current default the annotations that follow. A `NULL` value means use package-defined defaults as is, `""` means no annotations, and `"reserve.space"` means no annotations, but expand axis limits and set identity scales ready for manually adding annotations. Used together with `"-"`, `"title*"`, `"peaks*"` and `"valleys*"` are _wildcards_ that remove all flavours of each of these annotations. In cases when the intention is to both add and remove annotations from the default, the argument to parameter `annotations` can be a list of character vectors, which are interpreted as above but operated upon sequentially. Which annotations are included by default can be changed by setting R option `"photobiology.plot.annotations"` to a character vector with the names of the desired default annotations. Function `set_annotations_default()` allows to set or modify this option using the same syntax as described for parameter `annotations`. | annotation | default for classes | overrides | |:-----------|:------------------------------------------|:-------------| | "boxes" | all | "segments" | | "segments" | none | "boxes" | | "color.guide" | all | | | "peaks" | all | "peak.labels"| | "peak.labels" | none | "peaks" | | "valleys" | none | "valley.labels" | | "valley.labels" | none | "valleys" | | "wls" | none | "wls.labels" | | "wls.labels" | none | "wls" | | "labels" | all | | | "summaries" | `source_spct`, `response_spct`, `filter_spct`, `reflector_spct` | | | "boundaries" | `raw_spct` | | | "title" | none | | | "title:_type_" | none | | | "title:_type_:_type_" | none | | | "title:_type_:_type_:_type_" | none | | Titles come in different flavours. Package 'ggplot2' supports titles, subtitles and captions. The `"title"` as argument to parameter `annotation` of `autoplot()` methods and of function `autotitle()` takes up to three optional modifiers separated by colons that can be used to specify the contents of the _automatic_ title, subtitle and caption. Currently supported modifiers are shown in the table below. If the metadata item is not stored in the spectral object, the title or subtitle will show `NA`. See the documentation of packages 'photobiology' and 'ooacquire' for information on how to set and unset attributes of spectral objects. To add an arbitrary title and/or subtitle to a plot, use either function `ggtitle()` or function `labs()` from package 'ggplot2'. | Modifier | text source used | | -------- | ------------- | | `objt` | name of the object plotted | | `class` | class of the plotted object | | `what` | what.measured attribute | | `when` | when.measured attribute | | `where` | where.measured attribute | | `how` | how.measured attribute | | `inst.name` | spectrometer name | | `inst.sn` | spectrometer serial number | | `comment`| comment attribute | | `none` | _no title_, _no subtitle_ or _no caption_ | For example `"title:objt"`, the default for `"title"`, adds a title with the name of the object being plotted. `what`, `when`, `where` and `how` use the `what.measured`, `when.measured`, `where.measured` and `how.measured` attributes if available. For example `"title:what:when"` will use the `what.measured` attribute for the title and the `when.measured` attribute for the subtitle. We add a title, subtitle and caption. ```{r} autoplot(sun.spct, annotations = c("+", "title:objt:when:where")) ``` We use "none" as a _filler_ so that only subtitle and caption are added to the plot. ```{r} autoplot(sun.spct, annotations = c("+", "title:none:what:where")) ``` With `"boundaries"`, we add one or two horizontal dashed lines showing the valid range of values. It does not override any other annotation. (The boundary line is shown in red when a plot displays out-of-range spectral data.) ```{r} autoplot(sun.spct, annotations = c("+", "boundaries")) ``` We can list all the annotations to be included in a plot, in which case `"="` is optional so as to maintain compatibility with earlier versions. ```{r} autoplot(sun.spct, annotations = c("=", "labels", "summaries", "color.guide", "peaks", "boundaries")) ``` As indicated in the table above, some annotations override other annotations which fulfil a similar role. Here `"segments"` overrides the `"boxes"`, included in the default annotations. ```{r} autoplot(sun.spct, annotations = c("+", "segments")) ``` We can also _remove_ some of the default annotations on a case by case basis. ```{r} autoplot(sun.spct, annotations = c("-", "summaries", "peaks")) ``` The behaviour of some annotations can be tweaked. Below we add `"valleys"` as annotations, and control with `span` how close to each other are the peaks and valleys found. ```{r} autoplot(sun.spct, annotations = c("+", "valleys"), span = 41) ``` The annotations `"peak.labels"` and `"valley.labels"` override `"peaks"` and `"valleys"`. They use the _repulsive_ geometry `geom_label_repel` from package 'ggrepel'. ```{r} autoplot(sun.spct, annotations = list(c("+", "peak.labels"), c("-", "boxes", "summaries", "labels")), span = 21) ``` ```{r} autoplot(sun.spct, annotations = list(c("+", "valley.labels"), c("-", "peaks")), span = 31) ``` ```{r} autoplot(sun.spct, annotations = c("+", "peak.labels", "valley.labels"), span = 31) ``` Passing `""` as argument to `annotations` results in a plot with no annotations, and no extra expansion of scale limits. ```{r} autoplot(sun.spct, annotations = "") ``` Passing `"reserve.space"` as argument to `annotations` results in a plot with no annotations, but with scale limits expanded so as to receive annotations. ```{r} autoplot(sun.spct, annotations = "reserve.space") ``` The size of the font used for the annotations is controlled by argument `text.size`. ```{r} autoplot(sun.spct, annotations = c("=", "segments", "labels", "color.guide"), text.size = 3.5) ``` Argument `ylim` allows to manually set the limits of the $y$ axis using the same syntax as in package 'ggplot2'. The annotations are still automatically positioned, and the range extended to make space for them. In other words the values passed to `ylim` still give the "space" available for plotting data. ```{r} autoplot(sun.spct, ylim = c(NA, 1)) ``` The time base of the spectral unit or the duration of the exposure is stored as metadata. As demonstrated here using spectral data integrated over 24 h, a one-day-long exposure, the units in the axis labels change according to the value stored in the metadata. ```{r} getTimeUnit(sun.daily.spct) autoplot(sun.daily.spct) ``` Even though the `autoplot()` methods can return a finished plot, the returned object is a `ggplot` object and can be built upon by adding additional elements like _facets_, _aesthetics_ and even additional _layers_. We pass `idfactor = NA` to suppress the mapping of the spectra to `linetype`. ```{r} #| fig.asp: 1 autoplot(sun_evening.spct, facets = 2) + geom_vline(xintercept = c(400, 700), linetype = "dashed") ``` It is possible to construct and bind the spectra on-the-fly, and to use arbitrary variable names for the index factor. This works automatically as long as row binding is done with function `rbindspct()` which saves the name of the factor. ```{r} filter_no_yes.spct <- rbindspct(list(sun = sun.spct, filtered = yellow_gel.spct * sun.spct), idfactor = "Light source") autoplot(filter_no_yes.spct) ``` In the examples above the `source_spct` object `sun.spct` was used. The `autoplot()` methods for other spectral classes have only slight differences. We show some examples for `filter_spct` objects. For a long-pass filter the wavelength at half maximum is more interesting than peaks or valleys. ```{r} autoplot(yellow_gel.spct, annotations = list(c("-", "peaks"), c("+", "wls"))) ``` In many cases it is possible to convert on-the-fly the quantity plotted. In this case, given that the data are clipped as absorbance, a fixed target of A = 2 for the cut-off to be labelled with the wavelength is appropriate. ```{r} autoplot(yellow_gel.spct, plot.qty = "absorbance", wls.target = 2, annotations = list(c("-", "peaks"), c("+", "wls"))) ``` ```{r, eval=FALSE, echo=FALSE} yellow_gel.spct$Rfr <- 1 - max(yellow_gel.spct$Tfr) autoplot(yellow_gel.spct, plot.qty = "absorptance", annotations = c("-", "peaks")) ``` If one needs to, one can add a suitable layer function, _geom_ or _stat_, using 'local' data or add an annotation as shown here. ```{r} autoplot(sun.spct, w.band = UV_bands("CIE"), geom = c("spct", "line"), range = c(280, 400), unit.out = "photon") + geom_vline(xintercept = c(280, 315, 340, 400), linetype = "dotted") ``` In the case of quantities like transmittance which have a certain range of valid values, both upper and lower boundaries are highlighted, but in other cases only one, or even none depending on the possible valid ranges for the spectral quantities. ```{r} autoplot(yellow_gel.spct, annotations = c("+", "boundaries")) ``` Differently from other classes `raw_spct` and `cps_spct` objects can contain multiple columns of data, normally measured different integration times, and meant to be combined before conversion into physical quantities. In the case of raw instrument counts data, if the spectral object contains an instrument descriptor as metadata, the upper boundary is set to the maximum counts of the detector. ```{r} autoplot(white_led.raw_spct, annotations = list(c("+", "boundaries"), c("-", "peaks"))) ``` Both `raw_spct` and `cps_spct` objects contain multiple data columns when integration time bracketing has been used during data acquisition. In such cases, if one wants to plot only one of the raw spectra, one can extract the columns as usual in R using the extraction operator (`[ ]`). The single remaining column of spectral data is automatically renamed. ```{r, eval=FALSE, message = FALSE} autoplot(white_led.raw_spct[ , c("w.length", "counts_1")], annotations = c("+", "boundaries")) ``` ```{r, eval=FALSE} autoplot(white_led.raw_spct[ , c("w.length", "counts_1", "counts_3")], annotations = list(c("+", "boundaries"), c("-", "peaks"))) ``` ### Handling of off-range data If the supplied data include off-range values such as negative irradiance or fractional transmittance, reflectance, or absorptance outside the zero to one range the exceeded boundary is highlighted in red and a warning issued. ```{r, eval=FALSE} # Not run so as to pass CRAN checks!! autoplot(yellow_gel.spct - 0.01, annotations = c("+", "boundaries")) ``` ## Editing plots and adding layers Using colors when adding to a plot generated by the `autoplot()` methods is more involved than usual, one has to take into account that the identity scale is in use for color in annotations, and a ggplot can make use of only one scale for a given aesthetic. For this same reason no color key in generated automatically. ```{r} autoplot(sun_evening.mspct, annotations = c("-", "peaks")) + aes(color = ifelse(spct.idx == "time.05", "black", "darkred")) + theme(legend.position = "none") ``` Plots created with `autoplot()` methods are `ggplot` objects and can be customized, bearing in mind that any added layers will be plotted on top or existing layers (that is unless we make use of methods from package 'gginnards' that allow insertion of layers at any position in a `ggplot` object). Here we replace the default peaks annotations with a custom one, but still take advantage of other defaults like nice axis labels and other annotations. ```{r} autoplot(sun_evening.mspct, annotations = c("-", "peaks"), facets = TRUE) + stat_peaks(span = NULL, color = "red") + stat_peaks(span = NULL, geom = "text", label.fmt = "max at %3.1f nm", vjust = -0.5, hjust = 0, color = "red", size = 3) ``` ## Ploting wavebands An `autoplot()` method for `waveband` objects is also provided. ```{r} autoplot(VIS()) ``` ```{r} autoplot(PAR(), range = c(200, 1000), geom = "spct", unit.in = "photon", unit.out = "energy") ``` ```{r} autoplot(CIE(), geom = "spct", annotations = c("-", "color.guide")) ``` ```{r} autoplot(DNA_N(), geom = "spct", annotations = c("-", "color.guide")) ```