2  stack layout

stack_layout() arranges plots either horizontally or vertically. Based on whether we want to align the observations, there are two types of stack layouts:

Several aliases are available for convenience:

Code
library(ggalign)
set.seed(123)
small_mat <- matrix(rnorm(56), nrow = 7)
rownames(small_mat) <- paste0("row", seq_len(nrow(small_mat)))
colnames(small_mat) <- paste0("column", seq_len(ncol(small_mat)))

2.1 Input data

When aligning observations, we typically use a matrix, as it is easy to melt the matrix into a long-formatted data frame. Additionally, matrices are used to fit the observation concept, as they can be transposed (rows to columns, columns to rows), which is necessary for use in functions like quad_layout() and ggheatmap(), where observations may be aligned in both directions simultaneously.

  • For stack_free(), a data frame is required, and the input will be automatically converted using fortify_data_frame() if needed.
  • For stack_align(), a matrix is required, and the input will be automatically converted using fortify_matrix() if needed.

By default, fortify_data_frame() will invoke the ggplot2::fortify() function for conversion. Note, for matrix, it will be converted to a long-formatted data frame which is different from the ggplot2::fortify().

stack_align()/stack_free() will set up the layout, but no plot will be drawn until you add a plot element:

stack_alignh(small_mat) +
    layout_annotation(
        theme = theme(plot.background = element_rect(color = "red"))
    )
# the same for `stack_free()`
1
initialize a vertical stack layout.
2
Add a plot background in for the entire layout.

In this example, we use layout_annotation() to insert a plot background in the entire layout, it can be also used to control the theme of title, subtitle, caption (layout_title()), guides, margins, panel.border, panel.spacing.

2.2 Layout Customize

When we use stack_align(), it aligns the observations across multiple plots along the specified direction:

  • For stack_alignh(): Alignment occurs along the horizontal direction (y-axis).
  • For stack_alignv(): Alignment occurs along the vertical direction (x-axis).

The package offers a suite of align_* functions designed to give you precise control over the observations. These functions enable you to reorder the observations or partition the observations into multiple groups. Instead of detailing each align_* function individually, we will focus on the general usage and how to combine them with stack_align().

Here, we remain take align_dendro() as an example, it can reorder the observations, split them into groups, and can add a plot for visualization.

When used for stack_alignh(), the observations are aligned along the y-axis:

stack_alignh(small_mat) +
    align_dendro()
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.

When used for stack_alignv(), the observations are aligned along the x-axis:

stack_alignv(small_mat) +
    align_dendro()
1
initialize a vertical stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.

When align_dendro() is added to the layout, it performs following actions:

  1. reorder the observations.
  2. set the active plot to the dendrogram.

The active plot refers to the plot that subsequent ggplot2 components will target. In this case, the active plot is the dendrogram, and any new layers added will be applied to it. For instance, we can add additional layers to visualize the dendrogram’s structure or data. The default data underlying the ggplot object of align_dendro() consists of the dendrogram node data. It is also possible to use the dendrogram’s edge data for customization, which I will introduce in Section 5.5.

stack_alignh(small_mat) +
    align_dendro() +
    geom_point()
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.
3
add a point layer to the dendrogram

The active argument controls whether a plot should be set as the active plot. It accepts an active() object with the use argument to specify if the plot should be active when added.

stack_alignh(small_mat) +
    align_dendro(active = active(use = FALSE)) +
    geom_point()
#> Error in `stack_layout_add()`:
#> ! Cannot add `geom_point()` to `stack_align()`
#> ℹ No active plot component
#> ℹ Did you forget to initialize a <ggplot> object with `ggalign()` or
#>   `ggfree()`?
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, but don’t set the active plot to this plot.
3
try to add a point layer to the dendrogram, should fail due to no active plot

Usually, you don’t need to set this manually, as the active context is automatically applied only for functions that add plot areas. You can inspect whether a align_* function will add a plot by print it:

align_dendro()
#> `align_dendro()` object:
#>   plot: yes
#>   reorder: yes
#>   split: no

You might find it confusing that we mentioned align_dendro() will split observations into groups, while the print output shows split = "no". This happens because we haven’t specified the k/h argument in align_dendro().

align_dendro(k = 3L)
#> `align_dendro()` object:
#>   plot: yes
#>   reorder: yes
#>   split: yes

You don’t need to explicitly provide data to align_dendro(). By default, it inherits data from the layout. However, you can always provide another data source, but note that this package uses the concept of number of observations (NROW()). When aligning the observations, you must ensure the number of observations is consistent across all plots.

set.seed(123)
stack_alignh(small_mat) +
    align_dendro(data = matrix(rnorm(56), nrow = 8)) +
    theme(axis.text.y = element_text())
#> Error in `layout()`:
#> ! `align_dendro(data = matrix(rnorm(56), nrow = 8))` (nobs: 8) is not
#>   compatible with the `stack_align()` (nobs: 7)
1
initialize a horizontal stack layout.
2
reorder the observations based on hierarchical clustering, add a dendrogram tree, and set the active plot to this one, using self-provided data. This should fail because the number of observations is inconsistent.
3
try to add y-axis text to the dendrogram.
set.seed(123)
stack_alignh(small_mat) +
    align_dendro(data = matrix(rnorm(70), nrow = 7)) +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot, using self-provided data
3
add y-axis text to the dendrogram.

Alternatively, you can provide a function (or purrr-lambda) that will be applied to the layout’s matrix. For layouts that align observations, a matrix is always required, so the data input must be in matrix form.

set.seed(123)
stack_alignh(small_mat) +
    align_dendro(data = ~ .x[sample(nrow(.x)), ]) +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot, using self-provided data function
3
add y-axis text to the dendrogram.

Without adding another plot, it’s difficult to appreciate the benefits. Let’s now explore how to incorporate a plot.

2.3 Plot initialize

There are two primary functions for adding plots:

  • align_gg()/ggalign(): Create a ggplot object and align with the layout.
  • free_gg()/ggfree(): Create a ggplot object without aligning.

Both functions initialize a ggplot object and, by default, set the active plot when added to the layout.

For stack_align(), plots can be added regardless of whether they need to align observations.

stack_alignh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text())
1
initialize a horizontal stack layout.
2
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.
3
initialize a ggplot object, and set the active plot to this plot, using self-provided data function
4
add a bar to the plot
5
add y-axis text

You can build the plot separately and then add it to the layout:

my_bar <- ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text())
stack_alignh(small_mat) +
    align_dendro() +
    my_bar
1
Create the bar plot.
2
initialize a horizontal stack layout.
3
reorder the observations based on the hierarchical clustering, add a dendrogram tree, and set the active plot to this plot.

The active argument can also control the place of the plot area to be added. It accepts an active() object with the order argument to specify the order of the plot area.

stack_alignh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums, active = active(order = 1)) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    theme(axis.text.y = element_text()) 

You can also stack plots vertically using stack_alignv():

stack_alignv(small_mat) + 
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(.discrete_x, value), stat = "identity") +
    theme(axis.text.y = element_text()) 

stack_align() can also add plot without aligning observations. free_gg() focuses on layout integration without enforcing strict axis alignment. ggfree() is an alias for free_gg.

stack_alignv() +
    ggfree(mpg, aes(displ, hwy, colour = class)) +
    geom_point(size = 2) +
    ggfree(mpg, aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

The & operator applies the added element to all plots in the layout, similar to its functionality in the patchwork package.

For stack_free(), only free plots (ggfree()) can be added. This layout arranges plots in one row or column without enforcing axis alignment:

stack_freev(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

By default, ggfree() will also inherit data from the layout and call fortify_data_frame() to convert the data to a data frame. So, note that if the layout data is a matrix, it will be converted into a long-formatted data frame.

2.4 Plot Size

Both ggalign() and ggfree() functions have a size argument to control the relative width (for horizontal stack layout) or height (for vertical stack layout) of the plot’s panel area.

stack_freev(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = 2) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = 1) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

Alternatively, you can define an absolute size by using a unit() object:

stack_freev(mpg) +
    ggfree(mapping = aes(displ, hwy, colour = class), size = unit(1, "cm")) +
    geom_point(size = 2) +
    ggfree(mapping = aes(displ, hwy, colour = class)) +
    geom_point(size = 2) &
    scale_color_brewer(palette = "Dark2") &
    theme_bw()

2.5 active plot

As mentioned earlier, the active plot refers to the plot that subsequent ggplot2 components will target. The package provide two functions to work with active plot for stack_layout.

  • stack_switch(): switch the active context
  • stack_active(): An alias for stack_switch(), which sets what = NULL

The stack_switch() function accepts the what argument, which can either be the index of the plot added (based on its adding order) or the plot name specified via the active() object using the name argument.

Note that the what argument must be explicitly named, as it is placed second in the function signature. This is because, in most cases, we don’t need to switch the active plot manually—adjusting the order of plot additions typically suffices.

stack_alignh(small_mat) +
    align_dendro() +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    stack_switch(what = 1) +
    geom_point() +
    theme(axis.text.y = element_text()) +
    layout_title(title = "switch by integer")

stack_alignh(small_mat) +
    align_dendro(active = active(name = "tree")) +
    ggalign(data = rowSums) +
    geom_bar(aes(value, .discrete_y), stat = "identity") +
    stack_switch(what = "tree") +
    geom_point() +
    theme(axis.text.y = element_text()) +
    layout_title(title = "switch by string")

In the example, we use layout_title() to insert a title for the entire layout. Alternatively, you can add a title to a single plot with ggtitle().

By setting what = NULL (or alias stack_active()), we remove the active plot. This is particularly useful when the active plot is a nested Layout object, as any additions would otherwise be directed to that nested Layout. By removing the active plot, you can continue adding components directly to the StackLayout. We’ll introduce the nested layout of StackLayout in Chapter 7.

In the next chapter, we will dive into the HeatmapLayout, which can take the StackLayout as input. Heatmap layouts offer additional features for aligning observations in both directions. Let’s move ahead and explore how heatmaps can be seamlessly integrated into your layout workflows.