Documentation for fxl

Compound Figures in R: Integrating Integrity and Behavior Rates in FA Figures

Written by Shawn P. Gilroy (Last Updated: 2024-06-02)Functional AnalysisBarsHybrid Figures

The purpose of this post is to cover a unique case for the Multielement Design–the need to have some type of supporting information independent of the core information. The first example that comes to mind is the functional analysis Iwata et al. (1984/1992); specifically, functional analysis performed by others not expertly trained in the procedures (e.g., parents, teachers, etc.).

The need for a secondary axis becomes relevant when we need information on the internal validity of those procedures (e.g., procedural fidelity). For example, were some of these procedures not implemented as designed, this may either inflate or deflate the variability observed.

Hybrid Figures-Multielement + Bars

The scr_bar_support function supports the inclusion of a bar chart in the background of an existing scr_plot. For such draws, these are typically presented as a background to the primary data. At this point, given the limited applicability of this particular use case, the range of Y values is bounded between 0 and 100 (i.e., a percentage).

The values supplied to scr_bar_support are primarily related to the data drawn (i.e., mapping to data) and the optional guide line. The mapping argument is essentially identical to that of scr_plot, scr_lines, etc. For all intents and purposes, it is easiest to think of this as you would added lines or points (but as bars).

The guide_line argument is a value between 0 and 100, which is the value at which the guide line is drawn (all of these are optional). The guide_line_color argument is the color of the guide line (e.g., ‘blue’, ‘red’), the guide_line_type argument is the type of line drawn (e.g., 1 = solid, 2 = dashed), and the guide_line_size argument is the size of the line drawn (i.e., width).

The styler argument is a function that allows for the dynamic remapping of the default style of the bars (i.e., as if they were all the same color). This is particularly useful when you want to change the color of the bars based on some criteria (e.g., >= 80% = ‘okay-ish’, < 80% = ‘questionable’). The styler argument takes a function, which essentially overrides the default behavior related to the bars. This is useful because it leverages both the available data and minimizes the degree to which that added information leads to ‘overload’ (e.g., just the concerning points are emphasized).

Functional Analysis Data/Figure

We can begin by importing the previous data from the post on Drawing Functional Analysis in R using data from Gilroy et al. (2021). This original figure is presented below:

Support for Background Bar Charts

We would add to this previous figure by using the scr_bar_support with an added column in the data frame. This column, Integrity, is a percentage value that represents the procedural fidelity of the functional analysis.

The data frame for this expanded data set is provided below:


head(csv_data)
##   Participant Condition        CTB Session Integrity
## 1           1    Demand 1.03064116       1        70
## 2           1 Attention 1.15859703       2        90
## 3           1 Attention 1.17375414       3        90
## 4           1  Tangible 0.12924269       4        85
## 5           1   Control 0.04192846       5       100
## 6           1  Tangible 0.09894769       6        90

Code for this updated figure is illustrated below:


scr_plot(
  csv_data,
  aesthetics = var_map(
    x = Session,
    y = CTB,
    p = Condition),
  family = "Times New Roman",
  mai = c(0.5,
          0.5,
          0.5,
          0.5),
  omi = c(0.25,
          0.25,
          0.25,
          0.25)) |>
  scr_yoverride(c(-.175, 6),
                yticks = c(0, 1, 2, 3, 4, 5, 6)) |>
  scr_xoverride(c(0.5, 16.5),
                xticks = 1:16,
                xtickslabs = as.character(1:16)) |>
  scr_bar_support(color = rgb(.8, .8, .8, alpha = 1),
                  guide_line = 80,
                  guide_line_color = "blue",
                  guide_line_type = 2,
                  guide_line_size = 1,
                  label = "Procedural Fidelity",
                  adj = 0.55,
                  mapping = var_map(
                    x = Session,
                    y = Integrity
                  )) |>
  scr_lines(size = 1) |>
  scr_points(cex = 3,
             pch = list("Control" = 21,
                        "Attention" = 22,
                        "Demand" = 24,
                        "Tangible" = 8),
             fill = list("Control" = "black",
                         "Attention" = "white",
                         "Demand" = "white",
                         "Tangible" = "black"),
             color = list("Control" = "black",
                          "Attention" = "black",
                          "Demand" = "black",
                          "Tangible" = "black")) |> 
  scr_xlabel("Session",
            cex = 1.25) |>
  scr_ylabel("Combined Target Behavior (Per Minute)",
            cex = 1.25) |>
  scr_title("Analog Functional Analyses and Assocated Procedural Fidelity",
            cex = 1.5) |>
  scr_legend(position = list(x = 13,
                             y = 4),
             legend = c("Toy Play",
                        "Attention",
                        "Demand",
                        "Tangible"),
             col = c("black",
                     "black",
                     "black",
                     "black"),
             bg = "white",
             pt_bg = c("black",
                       "white",
                       "white",
                       "black"),
             lty = c(1, 1, 1, 1),
             pch = c(16, 22, 24, 8),
             #adj = c(1, 1),
             bty = "y",
             pt_cex = 2.25,
             cex = 1.25,
             text_col = "black",
             horiz = FALSE,
             box_lty = 1)

Dynamic Style Remapping

The styler argument can be used to augment how these bar charts are drawn, rendering them conditionally based on some property of the data. For our purposes here, the data are only interesting when fidelity is “bad” (i.e., < 80%), so let’s construct a relevant styler function.

See the data below:


bar_styler <- function(data_frame, ...) {
  # Note: the ellipses (...) are used to capture all arguments passed to the function
  input_list <- list(...)

  # We extract the plotting frame *originally* used to draw each bar
  local_frame <- input_list[["plot_frame"]]

  # We override the color of the bar here, depending on the value supplied 
  # Note: The values are converted to 0-1 here, since we have to match the range of the original y axis for annoying reasons
  local_frame[local_frame$pct >= .80, "col"] <- "white"
  # We make *bad*  or *low* integrity another color to highlight the session
  local_frame[local_frame$pct < .80, "col"] <- "lightgray"

  rect(local_frame$X - 0.25,
    0,
    local_frame$X + 0.25,
    local_frame$mod_y,
    col = local_frame$col
  )
}

Once we have designed this function, we would then supply it as an argument to the same function call from before.

See the data below:


  ... # Note: original preceeding code unchanged
  scr_bar_support(color = rgb(.8, .8, .8, alpha = 1),
                  guide_line = 80,
                  guide_line_color = "blue",
                  guide_line_type = 2,
                  guide_line_size = 1,
                  label = "Procedural Fidelity",
                  adj = 0.55,
                  styler = bar_styler,
                  mapping = var_map(
                    x = Session,
                    y = Integrity
                  )) |>
  ... # Note: original code to follow

With all the re-mapping now supplied, we run the following code to produce the final figure.


bar_styler <- function(data_frame, ...) {
  input_list <- list(...)

  local_frame <- input_list[["plot_frame"]]

  local_frame[local_frame$pct >= .80, "col"] <- "white"
  local_frame[local_frame$pct < .80, "col"] <- "lightgray"

  rect(local_frame$X - 0.25,
    0,
    local_frame$X + 0.25,
    local_frame$mod_y,
    col = local_frame$col
  )
}

scr_plot(
  csv_data,
  aesthetics = var_map(
    x = Session,
    y = CTB,
    p = Condition),
  family = "Times New Roman",
  mai = c(0.5,
          0.5,
          0.5,
          0.5),
  omi = c(0.25,
          0.25,
          0.25,
          0.25)) |>
  scr_yoverride(c(-.175, 6),
                yticks = c(0, 1, 2, 3, 4, 5, 6)) |>
  scr_xoverride(c(0.5, 16.5),
                xticks = 1:16,
                xtickslabs = as.character(1:16)) |>
  scr_bar_support(color = rgb(.8, .8, .8, alpha = 1),
                  guide_line = 80,
                  guide_line_color = "blue",
                  guide_line_type = 2,
                  guide_line_size = 1,
                  styler = bar_styler,
                  label = "Procedural Fidelity",
                  adj = 0.55,
                  mapping = var_map(
                    x = Session,
                    y = Integrity
                  )) |>
  scr_lines(size = 1) |>
  scr_points(cex = 3,
             pch = list("Control" = 21,
                        "Attention" = 22,
                        "Demand" = 24,
                        "Tangible" = 8),
             fill = list("Control" = "black",
                         "Attention" = "white",
                         "Demand" = "white",
                         "Tangible" = "black"),
             color = list("Control" = "black",
                          "Attention" = "black",
                          "Demand" = "black",
                          "Tangible" = "black")) |> 
  scr_xlabel("Session",
            cex = 1.25) |>
  scr_ylabel("Combined Target Behavior (Per Minute)",
            cex = 1.25) |>
  scr_title("Analog Functional Analyses and Assocated Procedural Fidelity",
            cex = 1.5) |>
  scr_legend(position = list(x = 13,
                             y = 4),
             legend = c("Toy Play",
                        "Attention",
                        "Demand",
                        "Tangible"),
             col = c("black",
                     "black",
                     "black",
                     "black"),
             bg = "white",
             pt_bg = c("black",
                       "white",
                       "white",
                       "black"),
             lty = c(1, 1, 1, 1),
             pch = c(16, 22, 24, 8),
             #adj = c(1, 1),
             bty = "y",
             pt_cex = 2.25,
             cex = 1.25,
             text_col = "black",
             horiz = FALSE,
             box_lty = 1)

Final Notes

The goal of this post was to illustrate how there may be added layers and possibilities for visual analysis not yet strongly explored. Much of this is probably due to the fixed and static nature of tools used to visualize data in traditional software (i.e., spreadsheet tools).

The application here is not entirely novel (I personally used this strategy previously in figures), but it certainly seems like something that could see greater adoption if made more accessible.

Hopefully others can find use for this in their work!