Documentation for fxl

Reviewing the fxl Package: Drawing Figures in R for Single-case Design

Written by Shawn P. Gilroy (Last Updated: 2024-05-27)ProgrammingData StructuresFigure Design

Rationale and Purpose for the fxl R Package

R supports a robust range of packages and tools for visualizing and analyzing data. That said, users versed in R have many options for constructing figures (e.g., base R graphics, ggplot2). The fxl R package is designed largely to <span class="bold">provide features relevant to single-case design figures not supported elsewhere in R</span>. Many of these features are seldom provided because they are specific to visual analysis and not statistical analysis. Specifically, practices that are related to visually emphasizing experimental manipulations (e.g., phase changes) help to reveal potential differences within (e.g., over time) and across individuals (e.g., in the presence/absense of some environmental arrangement).

The range of conventions and figure construction practices in single-case design is considerable, but for convenience, a simple prototypical example is presented below:

This figure provides a convenient means of visualizing (1) a distinction of contextually-related performances (i.e., Baseline vs. Intervention) and (2) contrasts between those specific contexts (e.g., via phase separations). Within-facet (i.e., rows/columns) annotations such as phase lines assist with consistent interpretation of data presented in this manner.

Highlighting Relevant Plot Features and Elements

A core plotting engine, derived from base R methods, was necessary to organize and support a number of very specific plotting practices. An entire package was designed to facilitate a more coherent and structured ways to express data from single-case experimental design research in purely R code.

In nearly all R plotting practices, including base R, output outside of the inner plot region is ‘clipped’ and reserved for features such as axis ticks and labels. This is problematic because many questions tested with single-case design summarize changes across different plots using shared plot features (e.g., lagged introduction of independent variable across figures).

The figure illustrated above shows a prototypical reversal design, with phase contrasts distinguished in a single plot with phase change lines demarcating data reflective of specific phases. Phase line separations may be necessary across one or more plots (e.g., multiple individuals) as in the case for multiple baseline designs.

An example using fxl with the Multiple Baseline Design is provided below:

The figure above illustrates a feature that is virtually non-existent in most plotting engines, as it is generally uncommon for users to annotate some plot feature that cuts across different plots. That was historically made possible by manually (i.e., some combination of post hoc automation or manually editing) editing figures to make such distinctions clear.

The fxl package provides support for these practices to make up for the lack of tools in this regard. Additionally, various other annotations relevant to single-case research (e.g., brackets for demand fading) are also provided and demonstrated in the package.

Designing Data Structures for fxl Drawing

Most single case figures are designed to visualize data that is contextually related across individuals (e.g., a row/facet in a broader figure), conditions (e.g., “Baseline” vs. “Intervention”), and time (e.g., performance across Sessions). The fxl plotting (i.e., the scr_plot function) provides an understandable means of ‘mapping’ data to relevant aspects of figures.

A brief snippet of relevant code is illustrated below and discussed in greater detail in the space that follows.


reversal_data_frame = data.frame(
  Sessions = c(1:20),
  Phases = c(rep("Baseline", 5),
             rep("Intervention", 5),
             # Note: Phases are labeled differently so as *not to be connected*
             rep("Baseline2", 5),
             # Same here (i.e., "Intervention" vs. "Intervention2")
             rep("Intervention2", 5)),
  BehaviorRates = c(23, 25, 30, 35, 30,
                    15, 10, 12, 0, 5,
                    16, 18, 30, 35, 32,
                    12, 6, 4, 2, 1)
)

scr_plot(reversal_data_frame,
         aesthetics = var_map(x = Sessions,
                              y = BehaviorRates,
                              p = Phases)) |>
  ... # More code would follow regarding the drawing of points/lines/etc.

Plot Construction and Design

The chain of calls necessary to draw a figure with fxl begins with using the scr_plot function read in relevant data, mappings of data to plot features (e.g., x/y coordinates, phase distinctions, participant distinctions), and other optional stylistic features (e.g., padding along sides of figures). Data is provided as the first required argument (e.g., ‘reversal_data_frame’) and the aesthetics argument is second required argument. The aesthetics argument maps features of the figure to the data supplied in order to arrange the data in some meaningful way.

The var_map function is used to map the data columns to the appropriate aesthetics. The x and y arguments are required and others are optional. The p argument is used to map the data to a phase or condition (i.e., p = ‘Phase’). The facet argument is used to map the data to a facet or cluster (i.e., facet = ‘Item’ or ‘Participant’ or ‘Setting’). These arguments are optional as not all designs require them to achieve the desired result. For example, in the Reversal Design, no facet option is necessary because there is only one plot necessary to create the overall figure.

As a brief note, the chain of calls following the scr_plot function is relevant because the order in which calls are supplied informs how those draws are layered in the final figure (i.e., later calls are drawn later/last).

Drawing Markers and Lines

The aesthetics supplied to the scr_plot object specify the required defaults for the figure (e.g., the X/Y coordinates in one or more facets/plots). For plots with just one type of data to represent (i.e., one pair of X/Y coordinates), one would simply pipe the scr_plot object to the desired functions (e.g., to draw lines, points) to build the desired layers. For example, the code below illustrates how to pipe the previous code to the scr_lines and scr_points functions, which draws points and lines grouped by Phase in the data supplied.

The relevant extensions of the prior code are illustrated below:


reversal_data_frame = data.frame(
  Sessions = c(1:20),
  Phases = c(rep("Baseline", 5),
             rep("Intervention", 5),
             # Note: Phases are labeled differently so as not to be connected
             rep("Baseline2", 5),
             # Same here
             rep("Intervention2", 5)),
  BehaviorRates = c(23, 25, 30, 35, 30,
                    15, 10, 12, 0, 5,
                    16, 18, 30, 35, 32,
                    12, 6, 4, 2, 1)
)

scr_plot(reversal_data_frame,
         aesthetics = var_map(x = Sessions,
                              y = BehaviorRates,
                              p = Phases)) |>
  scr_lines() |>
  # Note: cex is an argument that represents size/expansion (i.e., base R interprets cex = 2 as 200% the size of normal)
  scr_points(cex = 2)
  ... # More code would follow

Labels and Annotations

Another common convention for single-case design figures is prioritizing text labels over legends. This is presumably to avoid overly complex legends when multiple phase contrasts are featured. The fxl package supports a flexible means of annotating figures across or within facets using either the scr_label_facet or scr_label_phase functions, respectively.

Annotations within a Facet (e.g., Phases)

The scr_label_phase function is used to add one of more labels on a specific facet (e.g., only writing atop the first plot in a series of facets).

Drawing individual labels with the scr_label_phase function is designed to use a list, and by default, draw the text used as the ‘key’ in the manner/location supplied to the key.

There is often some redundant information here (e.g., all are the same size, at the same height) and arguments specified outside of the list are interpreted as general defaults unless specified otherwise. Each named entry will use the key supplied to represent the label drawn (each key should be unique, but isn’t required); however, when this is not tenable this can be overridden in the data supplied by manually specifying the label argument in the keyed values.

An example of this syntax is provided below:


reversal_data_frame = data.frame(
  Sessions = c(1:20),
  Phases = c(rep("Baseline", 5),
             rep("Intervention", 5),
             # Note: Phases are labeled differently so as not to be connected
             rep("Baseline2", 5),
             # Same here
             rep("Intervention2", 5)),
  BehaviorRates = c(23, 25, 30, 35, 30,
                    15, 10, 12, 0, 5,
                    16, 18, 30, 35, 32,
                    12, 6, 4, 2, 1)
)

scr_plot(reversal_data_frame,
         aesthetics = var_map(x = Sessions,
                              y = BehaviorRates,
                              p = Phases)) |>
  scr_lines() |>
  scr_points() |>
  scr_label_phase(
    # Note: adj = 0.5 means the x/y positioning is in the 'center' of the text
    adj = 0.5,
    # Note: a common y value means all labels at the same height
    y = 37.5
    labels = list(
      "Baseline" = list(
        x = 3
      ),
      "Intervention" = list(
        x = 8,
      ),
      "Baseline" = list(
        x = 13
      ),
      "Intervention" = list(
        x = 18
      )
    )
  ) |>
  ... # More code would follow

Annotations across Facets (e.g., Participants, Settings)

The scr_label_facet function is functionally similar to that of the scr_label_phase function, but it differs on how the keyed text is organized (i.e., it is often easier to work with one or the other). Specifically, the scr_label_facet uses a listed keyed by facets (i.e., across plots) and this is often useful when assigning labels specific to Participants, Items, Settings, etc. (i.e., they are often larger/bolded)

An example using the Multiple Baseline Design plot illustrated above is shown in abbreviated example code below:


... # Note: mbd_data would have to be created to show relevant data

scr_plot(mbd_data,
         aesthetics = var_map(x = Sessions,
                              y = Responding,
                              p = Phase,
                              facet = Participant)) |>
  ... # Relevant code omitted for brevity
  scr_lines() |>
  scr_points(cex = 2) |>
  # Note: facet argument specifies 'which plot' to draw labels upon
  # Note: facet = "A" means to draw only on "Participant A"
  scr_label_phase(
    facet = "A",
    adj = 0.5,
    y = 21,
    cex = 1.25,
    labels = list(
      "Baseline" = list(
        x = 3
      ),
      "Intervention" = list(
        x = 8
      )
    )
  ) |>
  # Note: We use the same x/y across each plot to draw names
  scr_label_facet(
    x = 20,
    y = 17,
    adj = 1,
    cex = 1.5,
    labels = list(
      # Each key represents the facets that need to be drawn upon
      "A" = list(
        label = "Participant A"
      ),
      "B" = list(
        label = "Participant B"
      ),
      "C" = list(
        label = "Participant C"
      )
    )
  ) |>
  ... # Note remaining code omitted

Suggestions for Drawing Figures with fxl

As a general principle, the package does not have any strong opinions on data are to be presented or organized. However, certain conventions seem to make good sense given that most figures are derived from a fairly consistent and limited number of research designs (e.g., reversal, multiple baseline). For example, it makes good sense to consider the following:

  • Consistent Data Management: Data structures make the most sense when they use informative and predictable conventions (e.g., common column names). Common data management practices maximize code re-usability.

  • DRY: Don’t Repeat Yourself. If a figure template is useful/effective, write your figure drawing code with fxl and re-use that script with different data (e.g., preference assessment, concurrent chains, etc.).

  • Comment/Document your Code Well: Use informative column/variable names and document how and why your script is structured as it was. This is relevant both prior to and during drawing, especially if your figure ultimate displays and outcome in a different scale that originally supplied (e.g., converted from a continuous value to some proportion, such as 0 out of 10 being reflected as percentage correct)