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.

Example 1: general use-case

This example uses the same artifically created dataset as the other vignette.

Data

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))
samplesize param1 param2 param3 method1 method2 method3
100 1 1 1 9.676300 22.984443 33.793024
200 1 1 1 9.437908 12.710978 17.636887
500 1 1 1 7.243334 6.225153 6.659465
100 2 1 1 24.994307 46.274059 62.983457
200 2 1 1 11.927719 22.866110 36.833710
500 2 1 1 8.463890 13.105232 14.311812
100 1 2 1 11.870238 22.974996 35.277654
200 1 2 1 9.137987 11.857579 18.394986
500 1 2 1 3.246069 5.631446 9.701592
100 2 2 1 26.086366 48.338618 65.184775

Basic facetted nested loop plot

  • Define x-axis, grid facets and parameter step functions (x, grid_rows, grid_cols, steps).
  • Adjust step positions (steps_y_base, steps_y_height).
  • Set axis names (x_name, y_name).
  • Adjust spacing between connected areas on the x-axis (spu_x_shift).
  • Set color specifications via a function (colors).
  • Adjust step annotations (steps_values_annotate, steps_annotation_size).
  • Add a horizontal line with intercept 0 (hline_intercept).
  • Expand the y-axis for proper display of the parameter step functions (y_expand_add).
  • Rotate x-axis labels and adjust their position and size (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)

Only rows

  • Only set grid_rows argument, move other parameters into steps.
  • Re-adjust steps positions.
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)

Only columns

  • Only set grid_cols argument, move other parameters into steps.
  • Re-ajust steps positions.
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)

“Classic” nested loop plots

  • Move all parameters except one into steps.
  • Connect all the results via step functions: set argument draw to c("add_points", "add_steps") to display results via points and steps, then set connect_spus to TRUE.
  • Re-ajust steps positions.
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)

Spacing on x-axis

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.

  • Input data.frame for nested_loop_plot now uses a factor as x-axis (converted via dplyr::mutate).
  • Adapt gap between different connected areas of the plot (spu) via 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)

Re-labelling data

Data can be re-labelled to streamline the display of parameters on the x-axis or give meaningful names to panels.

  • Relabel data using 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)

Changing and adding design elements

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.

  • Provide different linetpyes per method (line_linetypes).
  • Provide different point shapes per method (point_shapes).
  • Provide different point sizes per method. This requires passing a numeric vector to sizes and setting point_size to NULL due to the underlying implementation in the ggplot2 package.
  • Fixing linewidth to 1 (line_size).
  • Add a ribbon around zero, .e.g to indicate confidence regions, simulation monte carlo error or a target region the methods should reach (added to post-processing list via 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)

Partial designs

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.

  • The 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.

  • The 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)

  • The 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)

Free axes and axis adjustments

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).

  • Set grid_scales argument to “free_y” to free the y-scale. The x-axis remains the same, even if set to be free.
  • Adjust y-axis only in second row by passing a list to 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)

Addtional data display

The package supports adding steps for meta-information. These do not influence the layout of the plot.

  • Additional steps are added via 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)

Adding geometries to the plot

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.

  • Annotate results by adding labels to them in post-processing (add_text).
  • Add ribbons behind the other geometries to highlight areas of the plot via 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)

Axis transformations

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.

  • Define transformation by setting trans.
  • Set ylim, y_breaks and y_labels arguments.
  • Adjust parameter step functions and y-axis expansion for proper display.
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)

Example 2: many design parameter

Data

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))
samplesize param1 param2 param3 param4 method1 method2 method3
10 1 1 1 1 4.4014514 16.411023 17.6199471
50 1 1 1 1 0.2362822 5.111114 6.6717133
100 1 1 1 1 -2.5726311 2.753999 3.5378787
200 1 1 1 1 3.1213877 4.181095 0.1538609
500 1 1 1 1 2.6420683 5.497084 3.5507153
10 2 1 1 1 11.8447059 27.180114 35.4790589
50 2 1 1 1 3.6258356 10.845427 9.3207731
100 2 1 1 1 5.0769028 8.175888 9.4723016
200 2 1 1 1 3.8329818 7.640888 5.8487234
500 2 1 1 1 2.1029934 5.666710 5.9034683

Plots

  • Set x-axis labels to NULL to avoid cluttering display (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 grid lines as in Rücker and Schwarzer (2014) (adjust theme in 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)

Advanced usage

Panel specific decorations

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.

  • Augment the initial data matrix by additional columns which specify panel-wise parameters for the geometries to add by using dplyr::case_when.
  • Use 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 geometries that work with the columns that were passed through. For that we use the 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.

Modular use of the package

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

  • Creation of basic plotting data via 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.
  • Creation of a basic ggplot2 object via nested_loop_base_plot. It does not contain design parameter step functions.
  • Addition of data for drawing parameter step functions via nested_loop_paramsteps_data.
  • Updating the ggplot2 object for drawing parameter step functions.
  • Add post-processing to the ggplot2 object to adjust scales and axes.
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)

Customizing plots

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.

  • Annotate scenarios of special interest, e.g. due especially interesting combination of design parameters. Here we indicate scenarios with samplesize of 100 or 200, param3 of 1 and param2 either 2 or 3.
  • Annotate unusually high values by their numeric value.
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)

R session information

## R version 3.6.3 (2020-02-29)
## Platform: x86_64-w64-mingw32/x64 (64-bit)
## Running under: Windows 8.1 x64 (build 9600)
## 
## Matrix products: default
## 
## locale:
## [1] LC_COLLATE=German_Austria.1252  LC_CTYPE=German_Austria.1252   
## [3] LC_MONETARY=German_Austria.1252 LC_NUMERIC=C                   
## [5] LC_TIME=German_Austria.1252    
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
## [1] looplot_0.5.0.9001 ggplot2_3.3.0.9000 purrr_0.3.3       
## [4] magrittr_1.5       dplyr_0.8.4       
## 
## loaded via a namespace (and not attached):
##  [1] Rcpp_1.0.1         RColorBrewer_1.1-2 pillar_1.4.3      
##  [4] compiler_3.6.3     highr_0.8          plyr_1.8.4        
##  [7] forcats_0.4.0      tools_3.6.3        digest_0.6.19     
## [10] packrat_0.5.0      evaluate_0.14      tibble_2.1.3      
## [13] gtable_0.3.0       viridisLite_0.3.0  pkgconfig_2.0.2   
## [16] rlang_0.4.5        rstudioapi_0.11    yaml_2.2.0        
## [19] xfun_0.8           withr_2.1.2        stringr_1.4.0     
## [22] knitr_1.23         vctrs_0.2.4        grid_3.6.3        
## [25] tidyselect_1.0.0   glue_1.3.1         R6_2.4.0          
## [28] rmarkdown_1.13     reshape2_1.4.3     scales_1.0.0      
## [31] htmltools_0.3.6    ellipsis_0.3.0     assertthat_0.2.1  
## [34] colorspace_1.4-1   labeling_0.3       stringi_1.4.3     
## [37] munsell_0.5.0      crayon_1.3.4

References

Rücker, Gerta, and Guido Schwarzer. 2014. “Presenting Simulation Results in a Nested Loop Plot.” Journal Article. BMC Medical Research Methodology 14.