Documentation for ggsced
Annotating Phase Changes to ggplot Figures with ggsced
The ggplot package provides various built-in tools for faceting data across multiple panels. In many way, this is largely the desired behavior when preparing data for visual analysis of single-case experimental designs. Although the data presented is largely arranged as desired, the existing defaults are not closely aligned with traditional conventions for single-case design figures.
An example of default behavior is provided below:
ggplot(data, aes(x = Session,
y = Responding,
group = Condition)) +
geom_line() +
geom_point() +
facet_grid(rows = vars(Participant)) +
theme_classic()
Integrating Existing Axis Modifications
We can get a bit closer by applying the axis edits discussed in the prior post (see there for information). However, as indicated in the previous figure, ggplot does not insert x-axis lines or ticks/labels for each panel in the figure. Recent expansions of the existing facet_grid resource can be used to selectively add axes to panels consistent with the single-case design conventions. This involves overriding default settings for the axes and axis.labels fields.
The axes field provides options for selecting which axes to draw. By default, it applies a setting of “margin”, meaning that only axes at the margin (i.e., the border of the overall figure) are drawn. This is the reason why the inner levels of the figure do not have an x-axis. By changing this setting to “all”, axes are drawn for all panels. However, this also results in the default behavior of only drawing axes at the margin, meaning that the inner panels do not have x-axis ticks or labels.
The axis.labels field provides options for similarly selecting which axis labels to draw. By default, it applies a setting of “all”, meaning that axis labels and ticks are drawn for every axis drawn. In this case, adding all axes results in axis labels and ticks being drawn for all panels, which is not consistent with single-case design conventions. By changing this setting to “margins”, we can retain the axes and ticks for all panels, but ensure that axis labels are retained solely for the bottom-most panel.
This is illustrated in code with an accompanying figure below:
ggplot(data, aes(x = Session,
y = Responding,
group = Condition)) +
geom_line() +
geom_point() +
scale_x_continuous(breaks = c(1, 5, 10, 15, 20, 25, 30),
limits = c(1, 30),
guide = guide_axis(cap = "both"),
expand = expansion(mult = c(0.025, 0.025))) +
scale_y_continuous(name = "Percent Accuracy",
breaks = c(0, 25, 50, 75, 100),
limits = c(0, 100),
# Note: see override of default behavior
guide = guide_axis(cap = "both")) +
facet_grid(rows = vars(Participant),
# Note: Draw ALL x-axis lines
axes = "all",
# Note: Only draw axis labels for the margin axes (i.e., bottom-most panel)
axis.labels = "margins") +
theme_classic()
Replacing Facet Labels with Phase/Panel Labels
Default settings for ggplot include facet labels that are generated from the data. In this particular application, the facet labels are the participant IDs, which are drawn vertical at the right-most extreme. Although sufficient to provide information about the participant associated with each panel, the manner of presentation for these labels is not consistent with historical conventions for single-case design.
The strip labels can be removed by overriding the default settings for strip.text in the theme. This allows us to replace the facet labels with custom labels that are more consistent with single-case design conventions. The more ‘tricky’ part of this process involves creating novel data-frames with which to illustrate the desired labels as if they were data objects. Specifically, data frames must be created from the data to uniquely map within (i.e., phase labels for first participant only) as well as between panels (i.e., participant labels).
This can be accomplished in the following manner, see below:
# Note: ggplot doesn't treat geom_text like display theme (i.e., its more like a data element), so we need to update the default settings for text elements to ensure that the labels are in the desired font
update_geom_defaults("text", list(family = "Times New Roman"))
# Note: We need a data frame to uniquely map the panel labels to each panel
panel_labels = data.frame(
Participant = unique(data$Participant),
label = levels(data$Participant),
Responding = 0,
Session = 30,
Condition = unique(data$Condition)[1] # Irrelevant
)
# Note: We need a data frame to uniquely map the phase labels to the first panel (i.e., first participant) only
phase_labels = data.frame(
Participant = rep(unique(data$Participant)[1], 4), # Pull first participant 4x
label = c("Baseline",
"Treatment",
"Generalization",
"Maintenance"), # Phase labels
Responding = rep(100, 4),
Session = c(2.5, 7.5, 19, 26), # Location of phase labels
Condition = rep(unique(data$Condition)[1], 4) # Irrelevant
)
ggplot(data, aes(x = Session,
y = Responding,
group = Condition)) +
geom_line() +
geom_point() +
scale_x_continuous(breaks = c(1, 5, 10, 15, 20, 25, 30),
limits = c(1, 30),
guide = guide_axis(cap = "both"),
expand = expansion(mult = c(0.025, 0.025))) +
scale_y_continuous(name = "Percent Accuracy",
breaks = c(0, 50, 100),
limits = c(0, 100),
guide = guide_axis(cap = "both")) +
geom_text(data = panel_labels,
mapping = aes(label = label),
# Note: Justify to hug the right-most extreme of the panel
hjust = 1,
vjust = 0) +
geom_text(data = phase_labels,
mapping = aes(label = label),
hjust = 0.5,
vjust = 0.25) +
facet_grid(rows = vars(Participant),
axes = "all",
axis.labels = "margins") +
theme_classic() +
theme(
# Note: Override text in non-data elements
text = element_text(family = "Times New Roman", size = 14),
# Note: Get rid of default strip text
strip.text = element_blank()
)
Incorporating ggsced and Phase Change Annotations
The final and most relevant step of this process involves the use of ggsced as a tool for modifying existing gg objects to include phase change annotations. Specifically, ggsced is simply a wrapper around a helper that takes the existing gg object, examines the existing plot levels and dimensions, and adds the relevant annotations globally (i.e., across all panels) per the x-axis locations specified. In this case, we want to add phase change annotations that are staggered across panels, which involves preparing certain instructions for the ggsced helper function.
The ggsced function takes two fields, a required legs parameter and an optional offset parameter. The required legs parameter is simply a list of vectors that specify where each phase line should be drawn across panels. Specifically, each entry in the list represents a cross-panel phase change line, with each ordered entry in the vector representing the participant in the established order (i.e., factor level). Additional phase change lines can similarly be added as additional entries in the list. The x-axis locations for the phase change lines can be determined from the data, or can be estimated based on the desired location of the phase change lines relative to the data points.
The offsets parameter is optional, and provides a means for adjusting lateral connecting points for phase change lines across panels and levels. Occasionally, a small offset is desired in order to prevent overlap between the phase change lines. This is occasionally useful, so it remains an optional setting at this time.
A demonstration of this application is provided in the code snippet below:
update_geom_defaults("text", list(family = "Times New Roman"))
panel_labels = data.frame(
Participant = unique(data$Participant),
label = levels(data$Participant),
Responding = 0,
Session = 30,
Condition = unique(data$Condition)[1] # Irrelevant
)
phase_labels = data.frame(
Participant = rep(unique(data$Participant)[1], 4), # Pull first participant 4x
label = c("Baseline",
"Treatment",
"Generalization",
"Maintenance"), # Phase labels
Responding = rep(100, 4),
Session = c(2.5, 7.5, 19, 26),
Condition = rep(unique(data$Condition)[1], 4) # Irrelevant
)
# Note: The gg object is saved is a variable, which is then passed to the ggsced function for modification
p <- ggplot(data, aes(x = Session,
y = Responding,
group = Condition)) +
geom_line() +
geom_point() +
scale_x_continuous(breaks = c(1, 5, 10, 15, 20, 25, 30),
limits = c(1, 30),
guide = guide_axis(cap = "both"),
expand = expansion(mult = c(0.025, 0.025))) +
scale_y_continuous(name = "Percent Accuracy",
breaks = c(0, 50, 100),
limits = c(0, 100),
guide = guide_axis(cap = "both")) +
geom_text(data = panel_labels,
mapping = aes(label = label),
hjust = 1,
vjust = 0) +
geom_text(data = phase_labels,
mapping = aes(label = label),
hjust = 0.5,
vjust = 0.25) +
facet_grid(rows = vars(Participant),
axes = "all",
axis.labels = "margins") +
theme_classic() +
theme(
text = element_text(family = "Times New Roman", size = 14),
strip.text = element_blank()
)
# Note: There are three phase change lines here, which are staggered across panels
staggered_pls = list(
'1' = c(4.5, 11.5, 18.5),
'2' = c(13.5, 20.5, 23.5),
'3' = c(23.5, 23.5, 23.5)
)
# Note: The print argument is set to FALSE to prevent the default behavior of printing the modified gg object. This let's us save the output to a variable that can be interacted with further (e.g., for saving, or for additional modifications)
final_plot <- ggsced(p, legs = staggered_pls, print = FALSE)
# Note: We draw the final object with grid if we use print = FALSE
grid.draw(final_plot)
Summary and Recap
The ggsced package provides a useful support for adding phase change annotations to existing ggplot objects. The the majority of R users active with single-case design, this is probably the only annotation necessary that isn’t already provided by the ggplot package. The source code provided here, as well as the accompanying package, should be sufficient for the majority of users to effectively visualize the results of multiple baseline designs and others commonly-used in the single-case design world.