Introduction to ggseqplot

Prelude & Setup

Following Fasang and Liao (2014), we distinguish between sequence representation and summarization graphs. Summarization plots aggregate information from the data without displaying individual sequences. Given the complexity of sequence data, these plots focus on one or two dimensions of information stored in sequence data (Brzinsky-Fay, 2014). Common examples include transition plots, Kaplan-Meier curves, modal state plots, mean time plots, state distribution plots, and entropy plots (Fasang & Liao, 2014; Raab & Struffolino, 2022).

{ggseqplot} includes five types of summarization plots:

In contrast to summarization plots, representation plots visualize actually observed sequences. The most basic version is the sequence index plot, which displays all sequences in the dataset. However, this approach becomes impractical with large datasets due to overplotting—individual sequences may overlap, making the visualization misleading. To address this, alternative representations that display only a subset of sequences have been proposed.

{ggseqplot} supports both full and subset-based representation plots, offering the following plot types:

For a deeper discussion of sequence data visualization, see Brzinsky-Fay (2014), Fasang and Liao (2014) and Chapter 2 of Raab & Struffolino (2022).

With the exception of transition rate plots, all of the plots listed above can also be produced with {TraMineR}. In fact, {TraMineR} typically offers more customization options—but only through base R’s plotting system. If you are comfortable using base R graphics, there may be little reason to switch to {ggseqplot}.

However, {ggseqplot} was developed precisely because many R users today prefer {ggplot2} to base R’s plot environment for visualizing data. {TraMineR} (Gabadinho et al., 2011) was developed before {ggplot2} (Wickham, 2016) was as popular as it is today and back then many users were more familiar with coding base R plots. Today, most researchers and students are more familiar with {ggplot2} and would rather build on existing skills instead of learning how to refine base R plots just for sequence visualizations.

This vignette demonstrates how sequence objects created via TraMineR::seqdef are reshaped to plot them as ggplot2-typed figures using {ggseqplot}.

It outlines which functions from {TraMineR} and the {ggplot2} universe are involved in generating the visualizations.

The vignette further illustrates how the appearance of plots produced with {ggseqplot} can be customized using {ggplot2} functions and extensions.

Setup example

We begin by loading the required libraries and preparing the sequence data to be visualized. The examples are based on data sets and examples provided by the {TraMineR} package.


Click to show code for installing and loading required packages
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Load and download (if necessary) required packages ----
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


## Save package names as a vector of strings
pkgs <- c("colorspace", # for using colors palettes
          "forcats", # for dropping unused factor levels with `fct_drop`
          "ggh4x", # for proportional panel sized with `force_panelsizes`
          "ggplot2", # for using all the ggplot2 functions 
          "ggthemes", # for getting access to the canva_palettes
          "hrbrthemes", # for using the ggplot2 theme `theme_ipsum` 
          "patchwork", # for working with plot types built with patchwork
          "purrr", # used in the grouped rplot example
          "TraMineR") # the ultimate sequence analysis suite


## Install uninstalled packages
lapply(pkgs[!(pkgs %in% installed.packages())], 
       install.packages, repos = getOption("repos")["CRAN"])


## Load all packages to library and adjust options
lapply(pkgs, library, character.only = TRUE)

## Don't forget to load ggseqplot
library(ggseqplot)


## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
## Creating state sequence objects from example data sets ----
## ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

## biofam data
data(biofam)

biofam.lab <- c("Parent", "Left", "Married", "Left+Marr",
                "Child", "Left+Child", "Left+Marr+Child", "Divorced")

biofam.seq <- seqdef(biofam[501:600, ], 10:25, # we only use a subsample
                     labels = biofam.lab,
                     weights = biofam$wp00tbgs[501:600])

## actcal data
data(actcal)

actcal.lab <- c("> 37 hours", "19-36 hours", "1-18 hours", "no work")

actcal.seq <- seqdef(actcal,13:24,
                     labels=actcal.lab)

## ex1 data
data(ex1)
ex1.seq <- seqdef(ex1, 1:13, 
                  weights=ex1$weights)

Note: The default figure size in this document is fig.width=8, fig.height=4.94


Technicalities

All functions in {ggseqplot} follow a similar structure: they take a state sequence object—created with TraMineR::seqdef—as their starting point. The functions either operate directly on the sequence data or use other {TraMineR} functions, such as TraMineR::seqstatd, to derive the information to be visualized.

Internally, {ggseqplot} reshapes the data into a tidy (long) format before plotting them with {ggplot2} functions. This reshaping step is typically necessary for creating layered and faceted graphics in the grammar of graphics framework.

The following example demonstrates this process using a state distribution plot. First, we compute the cross-sectional state distributions across the sequence positions:

seqstatd(actcal.seq)
#>   [State frequencies]
#>   jan00 feb00 mar00 apr00 may00 jun00 jul00 aug00 sep00 oct00 nov00 dec00
#> A 0.421 0.420 0.421 0.417 0.419  0.42  0.42  0.42  0.42 0.418 0.414 0.415
#> B 0.162 0.162 0.162 0.162 0.162  0.16  0.16  0.17  0.16 0.169 0.170 0.171
#> C 0.098 0.098 0.098 0.097 0.097  0.10  0.10  0.10  0.10 0.096 0.096 0.098
#> D 0.320 0.321 0.320 0.324 0.322  0.32  0.32  0.31  0.31 0.318 0.320 0.316
#> 
#>    [Valid states]
#>   jan00 feb00 mar00 apr00 may00 jun00 jul00 aug00 sep00 oct00 nov00 dec00
#> N  2000  2000  2000  2000  2000  2000  2000  2000  2000  2000  2000  2000
#> 
#>    [Entropy index]
#>   jan00 feb00 mar00 apr00 may00 jun00 jul00 aug00 sep00 oct00 nov00 dec00
#> H   0.9   0.9   0.9   0.9   0.9   0.9   0.9  0.91  0.91   0.9  0.91  0.91

When calling ggseqdplot, these distributional data are internally reshaped into a long-format dataset, where each row represents the (weighted) relative frequency of a specific state at a specific sequence position.

For example, the actcal.seq data contain sequences of length 12 with an alphabet of 4 states. The reshaped dataset used for plotting will therefore consist of \(12 \times 4 = 48\) rows. If a grouping variable is provided, the resulting dataset will contain 48 rows per group. To inspect this internal data structure, assign the plot output to an object. The resulting list object stores the data as its first element (named data).

dplot <- ggseqdplot(actcal.seq)
dplot$data
#> # A tibble: 48 × 6
#>    group state      k     x     value grouplab           
#>    <fct> <fct>      <fct> <fct> <dbl> <fct>              
#>  1 1     > 37 hours jan00 1     0.421 Rel. Freq. (n=2000)
#>  2 1     > 37 hours feb00 2     0.420 Rel. Freq. (n=2000)
#>  3 1     > 37 hours mar00 3     0.422 Rel. Freq. (n=2000)
#>  4 1     > 37 hours apr00 4     0.418 Rel. Freq. (n=2000)
#>  5 1     > 37 hours may00 5     0.420 Rel. Freq. (n=2000)
#>  6 1     > 37 hours jun00 6     0.42  Rel. Freq. (n=2000)
#>  7 1     > 37 hours jul00 7     0.422 Rel. Freq. (n=2000)
#>  8 1     > 37 hours aug00 8     0.420 Rel. Freq. (n=2000)
#>  9 1     > 37 hours sep00 9     0.418 Rel. Freq. (n=2000)
#> 10 1     > 37 hours oct00 10    0.418 Rel. Freq. (n=2000)
#> # ℹ 38 more rows

Once the data are in the correct shape, {ggseqplot} uses standard {ggplot2} functions to create the visualizations. In the case of state distribution plots, for instance, ggseqdplot renders stacked bar charts for each sequence position using ggplot2::geom_bar.

The following table summarizes the core internal function calls for the various plot types available in {ggseqplot}:

ggseqplot function TraMineR function ggplot2 geoms and extensions
ggseqdplot TraMineR::seqstatd ggplot2::geom_bar
optional: geom_line
ggseqeplot TraMineR::seqstatd ggplot2::geom_line
ggseqmsplot TraMineR::seqmodst ggplot2::geom_bar
ggseqmtplot TraMineR::seqmeant ggplot2::geom_bar
ggseqtrplot TraMineR::seqtrate ggplot2::geom_tile
ggseqiplot TraMineR::seqformat ggplot2::geom_rect
ggseqfplot TraMineR::seqtab ggplot2::geom_rect
{ggh4x} (for the axis labeling if group has been specified)
ggseqrplot TraMineR::seqrep ggplot2::geom_rect
ggrepel::geom_text_repel
{ggtext} (for optional colored axis labels)
{patchwork} (to combine plots)
ggseqrfplot TraMineR::seqrfplot ggplot2::geom_rect
ggplot2::geom_boxplot
{patchwork} (to combine plots)

Most plots created with {ggseqplot} behave like regular ggplots. You can customize them by adding themes, adjusting scales, or using other standard {ggplot2} syntax via +.

Representative sequence plots and relative frequency sequence plots, however, behave differently because they are composed of multiple subplots which are arranged using the {patchwork} library. The following section illustrates how the appearance of the plots can be changed.


Changing the appearance of plots

The default case

As mentioned above, most plots rendered by {ggseqplot} are of class c("gg", "ggplot") and can be customized just like any other plot created with {ggplot2}

Example 1: State distribution plot

We begin with a simple state distribution plot using the sequence object actcal.seq, without modifying the default settings.

ggseqdplot(actcal.seq)

Next, we demonstrate how to refine the plot using a variety of {ggplot2} functions and extensions. As with any other {ggplot2} plot, the appearance of plots generated with {ggseqplot} functions can be extensively customized:

ggseqdplot(actcal.seq) +
  scale_fill_discrete_sequential("heat") +
  scale_x_discrete(labels = month.abb) +
  labs(title = "State distribution plot",
       x = "Month") +
  guides(fill=guide_legend(title="Alphabet")) +
  theme_ipsum(base_family = "") +
  theme(plot.title = element_text(size = 30, 
                                  margin=margin(0,0,20,0)),
        plot.title.position = "plot")

In the following example, we use two ggseqdplot calls to create plots of the same data, one with and one without weights. We also display entropy lines (geom_line) and combine the two plots using the {patchwork} package (Pedersen, 2020).

# Save plot using weights
p1 <- ggseqdplot(ex1.seq, 
                 with.entropy = TRUE) + 
  ggtitle("Weighted data")

# Save same plot without using weights
p2 <- ggseqdplot(ex1.seq, 
                 with.entropy = TRUE,
                 weighted = FALSE) + 
  ggtitle("Unweighted data")

# Arrange and refine plots using patchwork
p1 + p2 + 
  plot_layout(guides = "collect") &
  scale_fill_manual(values= canva_palettes$`Fun and tropical`[1:4]) &
  theme_ipsum(base_family = "") &
  theme(plot.title = element_text(size = 20,
                                  hjust = 0.5),
        legend.position = "bottom",
        legend.title = element_blank())

Example 2: Transition rate plot

In the next set of examples, we refine plots generated by ggseqtrplot, which internally calls TraMineR::seqtrate to compute the transition rates between states. These rates are returned in a symmetrical matrix and then reshaped into long format within ggseqtrplot to be plotted with ggplot2::geom_tile.

We begin with a basic example that visualizes transition rate matrices for two groups defined by the sex variable The output is a faceted plot visualizing two transition rate matrices of DSS sequence data.

ggseqtrplot(actcal.seq, 
            group = actcal$sex)
#>  [>] computing transition probabilities for states A/B/C/D ...
#>  [>] computing transition probabilities for states A/B/C/D ...

In the second example, we add customizations and utilize once again the {patchwork} library to compose a figure that compares the transition matrices of the same sequence data in STS and DSS format.

We set x_n.dodge = 2 to prevent overlapping x-axis labels, reduce the font size of the value labels displayed within the tiles (labsize = 3), and set dss = FALSE to switch to the STS format.


p1 <- ggseqtrplot(biofam.seq, 
                  dss = FALSE, 
                  x_n.dodge = 2,
                  labsize = 3) +
  ggtitle("STS Sequences") +
  theme(plot.margin = unit(c(5,10,5,5), "points"))
#>  [>] computing transition probabilities for states 0/1/2/3/4/5/6/7 ...

p2 <- ggseqtrplot(biofam.seq, 
                  x_n.dodge = 2,
                  labsize = 3) +
    ggtitle("DSS Sequences") +
  theme(plot.margin = unit(c(5,5,5,10), "points"))
#>  [>] computing transition probabilities for states 0/1/2/3/4/5/6/7 ...

p1 + p2 &
  theme(plot.title = element_text(size = 20,
                                  hjust = 0.5))

In contrast to the grouped plot above, the combined figure contains duplicated y-axis labels and titles. These can be removed using standard ggplot2::theme() arguments:


p2 <- p2 +
  theme(axis.text.y = element_blank(), 
        axis.title.y = element_blank())

p1 + p2 &
  theme(plot.title = element_text(size = 20,
                                  hjust = 0.5))

Example 3: Sequence index plots of illustrative (very few) sequences

{TraMineR} offers two types of sequence index plots: TraMineR::seqiplot for displaying a small selection of sequences (typically no more than 10), and TraMineR::seqIplot for plotting the full set of sequences. The former adds white space between the displayed sequences, making it well suited for illustrative purposes—for example, when showcasing a few hand-picked sequences in publications or teaching materials. While {ggseqplot} does not provide built-in spacing between sequences, a similar effect can be achieved with a small trick: by assigning each sequence to its own group via the group argument, ggseqiplot uses ggplot2::facet_wrap to place each sequence in a separate panel, thereby visually separating them.

In the example below, we display five sequences (biofam.seq[2:6,]) as a faceted plot, where each sequence forms its own group (group = 1:5) and is shown in a separate panel arranged in a single column (facet_ncol = 1). The remainder of the code applies visual fine-tuning: group labels are moved to the left (strip.position = "left") and rotated (strip.text.y.left = element_text(angle = 0)); superfluous axis elements—such as y-axis labels (axis.text.y = element_blank()), ticks (axis.ticks.y = element_blank()), and the axis title (labs(y = NULL))—are removed; and the white space between sequences is slightly reduced by adjusting vertical panel spacing (panel.spacing.y = unit(1.5, "lines")).

# plot a subset of 5 sequences from biofam.seq
ggseqiplot(biofam.seq[2:6,],
           group = 1:5,
           facet_ncol = 1,
           strip.position = "left",
           no.n = TRUE,
           border = TRUE, 
           weighted = FALSE) +
  labs(y = NULL) +
  theme(strip.text.y.left = element_text(angle = 0),
        panel.spacing.y = unit(1.5, "lines"),
        axis.text.y =  element_blank(),
        axis.ticks.y = element_blank())

Example 4: Flipping coordinates

Another way to adjust the appearance of plots rendered by {ggseqplot} is to flip their coordinates—an approach commonly used in the {ggplot2} ecosystem.

In the example below, we illustrate coordinate flipping for both a mean time plot and a sequence index plot. For each plot type, we show the default version followed by its flipped counterpart:

## default plot
ggseqmtplot(actcal.seq, no.n = TRUE, error.bar = "SE") 


## flipped version
ggseqmtplot(actcal.seq, no.n = TRUE, error.bar = "SE") +
 coord_flip() +
 theme(axis.text.y=element_blank(),
       axis.ticks.y = element_blank(),
       panel.grid.major.y = element_blank(),
       legend.position = "top")

While the flipped version above may be more in line with some users’ aesthetic preferences, flipping coordinates for sequence index plots is a more opinionated design choice. Most researchers display time along the horizontal axis. However, if you prefer time to run vertically—bottom to top, as in Piccarreta and Lior (Piccarreta & Lior, 2010)—this preference is easily accommodated:

## default plot
ggseqiplot(actcal.seq, sortv = "from.end") + 
  scale_x_discrete(labels = month.abb) 
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.


## flipped version
ggseqiplot(actcal.seq, sortv = "from.end") + 
  scale_x_discrete(labels = month.abb) +
  coord_flip()
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.

Example 5: Proportional panel sizes for grouped sequence plots

A common use case—especially in the context of cluster analyses—is to display grouped sequence plots with panel sizes that reflect the relative size of each group. This makes the visualization more informative, as it visually communicates the prevalence of each group.

In the following example, we illustrate this approach using actcal$sex as the grouping variable. As before, the group argument is used to split the data into separate panels arranged in a single column (facet_ncol = 1). To scale the height of each panel proportionally to the group size, we compute the relative frequencies using:

hghts <- table(fct_drop(actcal$sex)) / nrow(actcal.seq)

The fct_drop() function ensures that only those factor levels actually present in the data are considered when tabulating group sizes—this avoids reserving space for empty panels corresponding to unused levels. We then pass the resulting proportions to ggh4x::force_panelsizes() via the rows argument. This function adjusts the panel heights accordingly, so that larger groups occupy more vertical space in the plot. Finally, we reduce the spacing between panels using theme(panel.spacing = unit(1, "lines")) for a more compact appearance.

## use ggh4x::force_panelsizes to get proportional panels
ggseqiplot(actcal.seq, 
           sortv = "from.end", 
           group = actcal$sex,
           facet_ncol = 1) +
  force_panelsizes(rows = hghts) +
  theme(panel.spacing = unit(1, "lines"))

The special case of combined plots

Two types of plots differ from the standard behavior of {ggseqplot} functions: they are composed by two subplots, which are arranged into a single figure using the {patchwork} library. Because of this composition, their appearance cannot be adjusted in exactly the same way as plots returned directly as ggplot objects. For more details on the functionality and syntax of {patchwork},
we refer readers to the to the package documentation.

Example 6: Annotation and themes

Despite their special structure, many aspects of combined {patchwork} plots can still be customized in ways familiar from {ggplot2}. For example, themes and plot annotations can be modified using the & operator (instead of +, which is used for standard ggplot objects).

## compute dissimilarity matrix required for plot
diss <- seqdist(biofam.seq, method = "LCS")
#>  [>] 100 sequences with 8 distinct states
#>  [>] creating a 'sm' with a substitution cost of 2
#>  [>] creating 8x8 substitution-cost matrix using 2 as constant value
#>  [>] 76 distinct  sequences
#>  [>] min/max sequence lengths: 16/16
#>  [>] computing distances using the LCS metric
#>  [>] elapsed time: 0.03 secs

## Relative Frequency Sequence Plot
## default version
ggseqrfplot(biofam.seq, diss = diss, k = 12) 
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.367766
#>  [>] Pseudo/medoid-based-F statistic: 5.268049, p-value: 1.655783e-06


## adjusted version
ggseqrfplot(biofam.seq, diss = diss, k = 12) &
  theme_ipsum(base_family = "") &
  theme(legend.position = "bottom",
        legend.title = element_blank(),
        plot.title = element_text(size = 12)) &
  plot_annotation(title = "Relative Frequency Sequence Plot")
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.367766
#>  [>] Pseudo/medoid-based-F statistic: 5.268049, p-value: 1.655783e-06

If you want to manipulate the appearance of a specific subplot, however, the default approach might not work as expected. For example, if you aim to change the x-axis labels of the index plot, the following code will not yield the intended result:

ggseqrfplot(biofam.seq, diss = diss, k = 12) +
  scale_x_discrete(labels = 15:30)
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.367766
#>  [>] Pseudo/medoid-based-F statistic: 5.268049, p-value: 1.655783e-06

In this case, scale_x_discrete() will be applied to the boxplot (i.e., the last plot constructed in the combined object), not to the index plot. This behavior arises because the + operator affects the final ggplot object passed into the patchwork layout.

To adjust the appearance of individual subplots within a combined plot, you need to first save the composite plot object, access its components, and then modify them individually before reassembling the full plot.

## save & view original plot
p <- ggseqrfplot(biofam.seq, diss = diss, k = 12)
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.367766
#>  [>] Pseudo/medoid-based-F statistic: 5.268049, p-value: 1.655783e-06
p


## change appearance of sub-plots

## first component: index plot
p[[1]] <- p[[1]] +
  scale_x_discrete(labels = 15:30)
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.

## second component: boxplot
p[[2]] <- p[[2]] + labs(title = "Changed title")

## adjusted plot
p

Example 7: The complex case of grouped rplots

Things become a bit more complex when working with grouped representative sequence plots. In such plots, each group contributes two subplots: one showing information on the “quality” of the representative sequences (e.g., frequency, coverage, or centrality), and another displaying the corresponding index plots.

If you want to adjust the x-axis labels, for example, you must specifically target the index plots, which appear in the second row of the combined layout. The subplots are arranged by row, so for a two-group example, the index plots correspond to subplots 3 and 4.

To apply the desired changes, you need to extract these components, modify them as needed, and then re-plot the final figure.

## Compute a pairwise dissimilarity matrix
diss <- seqdist(actcal.seq, method = "LCS")
#>  [>] 2000 sequences with 4 distinct states
#>  [>] creating a 'sm' with a substitution cost of 2
#>  [>] creating 4x4 substitution-cost matrix using 2 as constant value
#>  [>] 186 distinct  sequences
#>  [>] min/max sequence lengths: 12/12
#>  [>] computing distances using the LCS metric
#>  [>] elapsed time: 0.32 secs

## original plot
p <- ggseqrplot(actcal.seq, 
                diss = diss,
                nrep = 3,
                group = actcal$sex)
#>  [>] number of objects (sum of weights): 884
#>  [>] max. distance: 24
#>  [>] neighborhood radius: 2.4
#>  [>] 3 representative(s) selected
#>  [>] 80 distinct sequence(s)
#>  [>] number of objects (sum of weights): 1116
#>  [>] max. distance: 24
#>  [>] neighborhood radius: 2.4
#>  [>] 3 representative(s) selected
#>  [>] 144 distinct sequence(s)
p


## adjusted sequence index subplots
p[[3]] <- p[[3]] +
  scale_x_discrete(labels = month.abb) 
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.

p[[4]] <- p[[4]] +
  scale_x_discrete(labels = month.abb) 
#> Scale for x is already present.
#> Adding another scale for x, which will replace the existing scale.

p

Example 8: What about grouped rfplots?

Grouped representative frequency plots are currently not implemented in ggseqrfplot and must be assembled manually using the {patchwork} library.

In the following example, we:

  1. Create rfplots for each group (here: “Men” and “Women”),
  2. Slightly misuse {patchwork}’s tag annotation to assign group-specific labels to the first plot in each row,
  3. Remove the legend from the upper plot panel (p$man), and
  4. Combine the two plots in a final stacked figure using a nested patchwork layout.

Technically, the resulting plot is a nested patchwork plot. According to the {patchwork} documentation,

[i]t is important to note that plot annotations only have an effect on the top-level patchwork. Any annotation added to nested patchworks are (currently) lost. If you need to have annotations for a nested patchwork you’ll need to wrap it in wrap_elements() with the side-effect that alignment no longer works.1

For this reason, we refrained from using patchwork::plot_annotation or ggplot2::ggtitle to add group titles and used plot tags instead.


diss <- seqdist(biofam.seq, method = "LCS")
#>  [>] 100 sequences with 8 distinct states
#>  [>] creating a 'sm' with a substitution cost of 2
#>  [>] creating 8x8 substitution-cost matrix using 2 as constant value
#>  [>] 76 distinct  sequences
#>  [>] min/max sequence lengths: 16/16
#>  [>] computing distances using the LCS metric
#>  [>] elapsed time: 0.02 secs

sex <- biofam[501:600, "sex"]

p <- map2(
  levels(sex), # input x
  c("Men", "Women"), # input y
  function(x, y) {
    p <- ggseqrfplot(biofam.seq[sex == x,],
                     diss = diss[sex == x,sex == x],
                     k = 12)
    p[[1]] <- p[[1]] + labs(tag = y)
    return(p)
    }
  )
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.5448432
#>  [>] Pseudo/medoid-based-F statistic: 3.408139, p-value: 0.003407164
#>  [>] Using k=12 frequency groups with grp.meth='prop'
#>  [>] Pseudo/medoid-based-R2: 0.5263291
#>  [>] Pseudo/medoid-based-F statistic: 5.687386, p-value: 4.537004e-06

names(p) <- levels(sex)

(p$man & theme(legend.position = "none")) / p$woman

This approach gives you full control over the layout and design of grouped rfplots, even in the absence of direct support in the ggseqrfplot function.


References

Brzinsky-Fay, C. (2014). Graphical Representation of Transitions and Sequences (P. Blanchard, F. Bühlmann, & J.-A. Gauthier, Eds.; pp. 265–284). Springer. https://doi.org/10.1007/978-3-319-04969-4_14
Fasang, A. E., & Liao, T. F. (2014). Visualizing Sequences in the Social Sciences: Relative Frequency Sequence Plots. Sociological Methods & Research, 43(4), 643–676. https://doi.org/10.1177/0049124113506563
Gabadinho, A., Ritschard, G., Müller, N. S., & Studer, M. (2011). Analyzing and Visualizing State Sequences in R with TraMineR. Journal of Statistical Software, 40(4), 1–37. https://doi.org/10.18637/jss.v040.i04
Pedersen, T. L. (2020). Patchwork: The composer of plots. https://CRAN.R-project.org/package=patchwork
Piccarreta, R., & Lior, O. (2010). Exploring sequences: A graphical tool based on multi-dimensional scaling: Exploring Sequences. Journal of the Royal Statistical Society: Series A (Statistics in Society), 173(1), 165–184. https://doi.org/10.1111/j.1467-985X.2009.00606.x
Raab, M., & Struffolino, E. (2022). Sequence analysis (Vol. 190). SAGE. https://sa-book.github.io/
Wickham, H. (2016). ggplot2: Elegant graphics for data analysis (2nd ed. 2016). Springer. https://doi.org/10.1007/978-3-319-24277-4

  1. https://patchwork.data-imaginist.com/articles/guides/annotation.html#titles-subtitles-and-captions↩︎