library(dplyr) library(magrittr) library(purrr) library(looplot)
This vignette contains example annotated use-cases of the looplot
package.
We introduce several of the available options and briefly explain their effect.
For an in-depth introduction into the goals of this package and the general
workflow, see the vignette "looplot: A package for creating nested loop plots".
For many options to further customize the plots, please refer to the documentation of the underlying ggplot2 package.
This example uses the same artifically created dataset as the other vignette.
set.seed(14) params = list( samplesize = c(100, 200, 500), param1 = c(1, 2), param2 = c(1, 2, 3), param3 = c(1, 2, 3, 4) ) design = expand.grid(params) # add some "results" design %<>% mutate(method1 = rnorm(n = n(), mean = param1 * (param2 * param3 + 1000 / samplesize), sd = 2), method2 = rnorm(n = n(), mean = param1 * (param2 + param3 + 2000 / samplesize), sd = 2), method3 = rnorm(n = n(), mean = param1 * (param2 + param3 + 3000 / samplesize), sd = 2)) knitr::kable(head(design, n = 10))
x
, grid_rows
,
grid_cols
, steps
). steps_y_base
, steps_y_height
).x_name
, y_name
).spu_x_shift
).colors
).steps_values_annotate
, steps_annotation_size
).hline_intercept
).y_expand_add
).add_custom_theme
).p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
grid_rows
argument, move other parameters into steps
.p = nested_loop_plot(resdf = design, x = "samplesize", steps = c("param2", "param3"), grid_rows = "param1", steps_y_base = -10, steps_y_height = 3, steps_y_shift = 3, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
grid_cols
argument, move other parameters into steps
.p = nested_loop_plot(resdf = design, x = "samplesize", steps = c("param2", "param3"), grid_cols = "param1", steps_y_base = -5, steps_y_height = 1, steps_y_shift = 3, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
steps
.draw
to
c("add_points", "add_steps")
to display results via points and steps, then
set connect_spus
to TRUE
.p = nested_loop_plot(resdf = design, x = "samplesize", steps = c("param1", "param2", "param3"), draw = "add_steps", steps_y_base = -5, steps_y_height = 1, steps_y_shift = 3, x_name = "Samplesize", y_name = "Error", spu_x_shift = 200, connect_spus = TRUE, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 5) ) )) print(p)
So far the x-axis has been treated as numeric scale with spacing of the breaks according to the values of the design parameter which defines the x-axis. If the design parameter is actually a factor, or converted to be one, then the spacing on the x-axis is equal.
nested_loop_plot
now uses a factor as x-axis
(converted via dplyr::mutate
).spu_x_shift
. Note that the gap between any two datapoints on the x-axis is
now exactly one unit.x_factor_design = design %>% mutate(samplesize = as.factor(samplesize)) p = nested_loop_plot(resdf = x_factor_design, x = "samplesize", steps = c("param1", "param2", "param3"), draw = "add_steps", steps_y_base = -5, steps_y_height = 1, steps_y_shift = 3, x_name = "Samplesize", y_name = "Error", spu_x_shift = 1, connect_spus = TRUE, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 5) ) )) print(p)
Data can be re-labelled to streamline the display of parameters on the x-axis or give meaningful names to panels.
replace_labels
.p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), replace_labels = list( samplesize = c("100$" = "100", "200$" = "", "500$" = "500"), param2 = c("1" = "low", "2" = "mid", "3" = "high"), param3 = c("1" = "very low", # partial replacement "4" = "very high") ), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
Elements of the plot can be customized. Note that these parameters
(sizes
, line_linetypes
, point_shapes
, colors
) all at least take either
a single numeric value, a vector of numeric values or a named vector of
numeric values specifying which method should have which value.
line_linetypes
).point_shapes
).sizes
and setting point_size
to NULL
due to the underlying
implementation in the ggplot2
package.line_size
).add_annotation
).p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), line_linetypes = c(1, 2, 3), point_shapes = c("method1" = 2, "method2" = 3, "method3" = 1), sizes = c(1, 1.5, 2), point_size = NULL, line_size = 1, steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( # add ribbon via an annotation add_annotation = list( geom = "rect", xmin = -Inf, xmax = Inf, # stretch whole x-axis ymin = 0, ymax = 10, alpha = 0.1, fill = "black" ), add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
In a partial design, not every possible combination of values for the design parameters is present in the results from the experiment. In our example, we simply remove parts corresponding to two design parameters from the data.frame containing the data.
design_type
can be set to "partial" to remove the parameter step
functions for the missing design parts. If left at default ("full"), then
the missing parts are added. partial_design = design %>% filter(!(param2 == 2 & param3 %in% c(3, 4))) p = nested_loop_plot(resdf = partial_design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", design_type = "partial", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
An alternative parameter to use in case of missing data due to partial designs determines how "holes" in the data are handled.
na_rm
argument can be set to TRUE (default) to ignore missing data
when connecting result values. (Note that in the middle panel no data is
available for samplesize 200.)partial_design = design %>% filter(!(param2 == 2 & samplesize == 200)) p = nested_loop_plot(resdf = partial_design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", na_rm = TRUE, spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
na_rm
argument can be set to FALSE make it clear that data is missing
by not connecting the result values accross missing data.partial_design = design %>% filter(!(param2 == 2 & samplesize == 200)) p = nested_loop_plot(resdf = partial_design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", na_rm = FALSE, spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
Axes of the facets can have different scales. However, currently it is only
possible to have different y- and x-scales for each row and column, respectively.
This is made possible thanks to the facet_grid_sc
extension of ggplot2
. Because of this, it is also possible to adjust the
axes individually (i.e. per row / per column).
grid_scales
argument to "free_y" to free the y-scale. The x-axis remains
the same, even if set to be free.y_expand_add
(entries
of this list are named by the values of the design parameter which defines
the grid_rows
).p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", grid_scales = "free_y", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = list( "2" = c(10, NULL) ), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
The package supports adding steps for meta-information. These do not influence the layout of the plot.
steps_add
(note that additional column
added to the design data.frame).meta_design = design %>% mutate(Difficulty = if_else(param3 == 4, "Hard", "Easy")) p = nested_loop_plot(resdf = meta_design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_add = "Difficulty", steps_y_base = -10, steps_y_height = 5, steps_y_shift = 10, x_name = "Samplesize", y_name = "Error", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
Further geometries can be added to the plot via post-processing. For several
simple geometries convenience wrapper functions are implented in this package
(all starting with add_
) so that only the parameters of the geometry need
to be passed to post-processing. However, arbitrary geometries are supported
via the add_geom_at_position
wrapper function, which allows the user to
directly pass any geometry object.
Here we add text to annotate the results, but also additional points or lines could be drawn in the same way. By passing a new data.frame, even new data can be added in this step. Examples for such advanced usage are shown below for adding panel specific geometries or using this package in a modularl fashion.
add_text
).add_geom_at_position
(note that inherit.aes
is set to FALSE so that the rest
of the plot does not impact the ribbons).p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", draw = "add_lines", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( add_text = list( decimals = 1, size = 2, vjust = 0, hjust = 0 ), add_geom_at_position = list( g = geom_rect( data = NULL, xmin = -Inf, xmax = Inf, ymin = 0, ymax = 10, alpha = 0.005, fill = "blue", linetype = "blank", inherit.aes = FALSE ), position = "bottom" # place below other geometries ), add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
The normal axis transformations of ggplot2
can not be used in general as the
steps added for simulation parameters are also on the same y-scale as the
simulation results. E.g. if a logarithmic scale is to be used for results, but
the parameter steps have negative y-coordinates, then the approach using ggplot2
will not work.
Hence, the functionality is implemented by transformation of the data.
This can be done by the user outside the looplot
package or passed to the
functions implemented in this package using the trans
argument.
Note that this is a transformation of the data. Hence, the y-axis labels are
also on the transformed scale. If that is not wanted, the user can easily
reverse this transformation for the labels by passing a function to the
y_labels
argument. See documentation. Another possibility is to simply
rename the y-axis as e.g. 'log2 of results' to indicate the scaling.
A further consequence is that all additions to the plot such as lines or parameter steps are now also on the transformed scale and may require re-adjustment.
trans
. ylim
, y_breaks
and y_labels
arguments.p = nested_loop_plot(resdf = design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", steps_y_base = -0.5, steps_y_height = 0.5, x_name = "Samplesize", y_name = "Error", trans = log2, ylim = c(0, 6), y_breaks = 0:6, y_labels = function(x) ifelse(x > 0, 2^x, 0), spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(3, 0.5), post_processing = list( add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
We expand the design parameters by another layer of parameters. Overall the setup contains 600 unique parameter combinations.
set.seed(82356) params = list( samplesize = c(10, 50, 100, 200, 500), param1 = c(1, 2), param2 = c(1, 2, 3), param3 = c(1, 2, 3, 4), param4 = c(1, 2, 3, 4, 5) ) design = expand.grid(params) design %<>% mutate(method1 = rnorm(n = n(), mean = param1 * (param2 * param3 * param4 + 50 / samplesize), sd = 2), method2 = rnorm(n = n(), mean = param1 * (param2 + param3 + param4 + 100 / samplesize), sd = 2), method3 = rnorm(n = n(), mean = param1 * (param2 + param3 * param4 + 150 / samplesize), sd = 2)) knitr::kable(head(design, n = 10))
x_labels
). Add
values on x-axis to name of axis.p = nested_loop_plot(resdf = design, x = "samplesize", steps = c("param3", "param4"), grid_rows = "param1", grid_cols = "param2", steps_y_base = -10, steps_y_height = 2.5, steps_y_shift = 5, x_name = "Samplesize (100, 200, 500)", y_name = "Error", draw = "add_lines", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), x_labels = NULL, post_processing = list( add_custom_theme = list(legend.position = "top") )) print(p)
add_custom_theme
).p = nested_loop_plot(resdf = design, x = "samplesize", steps = c("param1", "param2", "param3", "param4"), steps_y_base = -10, steps_y_height = 2.5, steps_y_shift = 7.5, x_name = "Samplesize (100, 200, 500)", y_name = "Error", draw = "add_lines", spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), x_labels = NULL, post_processing = list( add_custom_theme = list( legend.position = "top", panel.grid.major = element_line( color = "grey95", size = 0.1 ) ) )) print(p)
Annotations may depend on the design factors of the simulation study. For
example, variability or highlighted regions in the plot may be indicated by
ribbons, which in turn may depend on the column variable of the facets. To add
such panel parameter specific decorations we can use the following
approach. We demonstrate them by recreating panel specific ribbons.
Data is created in the same way as in the first example.
set.seed(14) params = list( samplesize = c(100, 200, 500), param1 = c(1, 2), param2 = c(1, 2, 3), param3 = c(1, 2, 3, 4) ) design = expand.grid(params) # add some "results" design %<>% mutate(method1 = rnorm(n = n(), mean = param1 * (param2 * param3 + 1000 / samplesize), sd = 2), method2 = rnorm(n = n(), mean = param1 * (param2 + param3 + 2000 / samplesize), sd = 2), method3 = rnorm(n = n(), mean = param1 * (param2 + param3 + 3000 / samplesize), sd = 2))
dplyr::case_when
. pass_through
argument of the nested_loop_plot
function to pass the
augmented columns untouched to post-processing. They leave the rest of the plot
completely unaffected.add_geom_at_position
function in post-processing and make
sure to set inherit.aes
to FALSE (otherwise the legend may be affected by the
new geometries).# augment design data augmented_design = design %>% mutate( ymin = case_when(param2 == 1 ~ 0, param2 == 2 ~ 10), ymax = case_when(param2 == 1 ~ 10, param2 == 2 ~ 20), note = case_when(param2 == 3 & param1 == 1 ~ "This is a special case.") ) p = nested_loop_plot(resdf = augmented_design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", # pass data to post-processing pass_through = c("ymin", "ymax", "note"), steps_y_base = -10, steps_y_height = 5, x_name = "Samplesize", y_name = "Error", draw = c("add_points", "add_lines"), spu_x_shift = 75, colors = scales::brewer_pal(palette = "Dark2"), steps_values_annotate = TRUE, steps_annotation_size = 2.5, hline_intercept = 0, y_expand_add = c(10, NULL), post_processing = list( # add geometries that use the passed through data # add panel specific text add_geom_at_position = list( geom = geom_text( aes(x = 50, y = 60, label = note), size = 4, color = "black", hjust = 0, inherit.aes = FALSE) ), # add panel specific ribbons add_geom_at_position = list( geom = geom_rect( aes(ymin = ymin, ymax = ymax), xmin = -Inf, xmax = Inf, alpha = 0.005, fill = "blue", linetype = "blank", inherit.aes = FALSE ), position = "bottom" # place below other geometries ), add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) )) print(p)
Similar results can also be obtained when working with the package in a modular fashion, as demonstrated below.
While the main interface to this package is a single function, the creation of nested loop plots is actually implemented via modular functions. This is useful if the user wants more control over the design of the plot and especially if additional meta-data should be added.
We recreate the basic facetted nested loop plot using the same data step by step. These are
nested_loop_base_data
. This data.frame
contains all information to create the nested loop plots from this package,
except for the data for drawing design parameter step functions. It
can be used to add further information for plotting.ggplot2
object via nested_loop_base_plot
. It does not
contain design parameter step functions.nested_loop_paramsteps_data
.ggplot2
object for drawing parameter step functions.ggplot2
object to adjust scales and axes.set.seed(14) params = list( samplesize = c(100, 200, 500), param1 = c(1, 2), param2 = c(1, 2, 3), param3 = c(1, 2, 3, 4) ) design = expand.grid(params) # add some "results" design %<>% mutate(method1 = rnorm(n = n(), mean = param1 * (param2 * param3 + 1000 / samplesize), sd = 2), method2 = rnorm(n = n(), mean = param1 * (param2 + param3 + 2000 / samplesize), sd = 2), method3 = rnorm(n = n(), mean = param1 * (param2 + param3 + 3000 / samplesize), sd = 2))
plot_data = nested_loop_base_data( design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", spu_x_shift = 75 ) p = nested_loop_base_plot( plot_data, x_name = "Samplesize", y_name = "Error", colors = scales::brewer_pal(palette = "Dark2") ) print(p) plot_data = nested_loop_paramsteps_data( plot_data, steps_y_base = -10, steps_y_height = 5 ) p = nested_loop_paramsteps_plot( p, plot_data, steps_values_annotate = TRUE, steps_annotation_size = 2.5 ) print(p) p = add_processing( p, list( # set limits adjust_ylim = list( y_expand_add = c(10, NULL) ), # adjust theme add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ), # add horizontal lines add_abline = list( intercept = 0 ) ) ) print(p)
Using the package functions modularly opens up many more options for
customization. These are all based on modification of the data.frames which
are returned by nested_loop_base_data
and nested_loop_paramsteps_data
that
provide the basis data for the plotting functions of this package.
We demonstrate an exemplary customization here. First, we obtain the
necessary data. The data.frame of interest is stored in the plotdf
entry of
the resulting list, which can be modified to add meta-information to the
plots. We do this using the dplyr::mutate
and dplyr::case_when
functionality.
plot_data = nested_loop_base_data( design, x = "samplesize", steps = "param3", grid_rows = "param1", grid_cols = "param2", spu_x_shift = 75 ) %>% nested_loop_paramsteps_data( steps_y_base = -10, steps_y_height = 5 ) # create annotation data plot_data$plotdf %<>% mutate( annotate_scenario = case_when( samplesize %in% c(100, 200) & param3 == 1 & param2 %in% c(2, 3) ~ 0 # height at which a point should be drawn on y-axis ), annotate_value = case_when(y_coord > 60 ~ y_coord) %>% round(2) )
We add the additional plot elements to the post-processing list via add_points
and add_text
. Note that we set inherit.aes
to FALSE so that the points
do not show up in the legend.
p = nested_loop_base_plot( plot_data, x_name = "Samplesize", y_name = "Error", colors = scales::brewer_pal(palette = "Dark2") ) %>% nested_loop_paramsteps_plot( plot_data, steps_values_annotate = TRUE, steps_annotation_size = 2.5 ) %>% add_processing( list( # annotate interesting scenarios add_points = list( mapping = aes(x = samplesize, y = annotate_scenario), shape = 8, color = "red", inherit.aes = FALSE ), # annotate high values add_text = list( mapping = aes(label = annotate_value), color = "black", size = 3, hjust = 0, vjust = 0, nudge_x = 20, nudge_y = 1 ), # set limits adjust_ylim = list( y_expand_add = c(10, NULL) ), # adjust theme add_custom_theme = list( axis.text.x = element_text(angle = -90, vjust = 0.5, size = 8) ) ) ) print(p)
sessionInfo()
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.