Appendix A — Analysis Skeleton (R Template)

Copy-paste starting point for a new JASP analysis file. This follows the patterns described in Chapter 5. Assumes preloadData: true (the default) in Description.qml, so the dataset argument is already loaded.

#
# Copyright (C) 2024 University of Amsterdam
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#

# Main entry point ----
# The function name must match the `func` field in Description.qml.
# Receives: jaspResults (persistent output container),
#           dataset     (preloaded data frame),
#           options     (named list of user selections from QML).
MyAnalysis <- function(jaspResults, dataset, options) {

  # 1. Can we compute?
  ready <- length(options[["variables"]]) > 0

  # 2. Validate
  if (ready)
    .myAnalysisCheckErrors(dataset, options)

  # 3. Compute (stores result in jaspResults)
  if (ready)
    .myAnalysisComputeResults(jaspResults, dataset, options)

  # 4. Create output — each function writes to jaspResults in place
  .myAnalysisMainTable(jaspResults, options, ready)
  .myAnalysisPlot(     jaspResults, options, ready)

  return()
}

# Error checking ----
.myAnalysisCheckErrors <- function(dataset, options) {
  .hasErrors(dataset,
    type                 = c("observations", "variance", "infinity"),
    all.target           = options[["variables"]],
    observations.amount  = "< 2",
    exitAnalysisIfErrors = TRUE
  )
}

# Compute (cached) ----
.myAnalysisComputeResults <- function(jaspResults, dataset, options) {
  # Already cached? Nothing to do.
  if (!is.null(jaspResults[["myAnalysisState"]]))
    return()

  # --- expensive computation goes here ---
  results <- list(
    model = lm(as.formula(paste(options[["dependent"]], "~ .")),
               data = dataset)
  )

  # Store in jaspResults — invalidated automatically when dependencies change
  state <- createJaspState(results)
  state$dependOn(c("dependent", "variables"))
  jaspResults[["myAnalysisState"]] <- state
}

# Table ----
.myAnalysisMainTable <- function(jaspResults, options, ready) {
  # 1. Skip if already cached
  if (!is.null(jaspResults[["mainTable"]])) return()

  # 2. Create & configure
  table <- createJaspTable(title = gettext("Results"))
  table$dependOn(c("variables", "dependent"))

  # 3. Define columns
  table$addColumnInfo(name = "term",     title = gettext("Term"),     type = "string")
  table$addColumnInfo(name = "estimate", title = gettext("Estimate"), type = "number")
  table$addColumnInfo(name = "se",       title = gettext("Std. Error"), type = "number")
  table$addColumnInfo(name = "p",        title = "p",                 type = "pvalue")

  # 4. Attach — displays an empty placeholder immediately
  jaspResults[["mainTable"]] <- table

  # 5. Not ready yet? Show the placeholder (dots)
  if (!ready) return()

  # 6. Retrieve cached results and fill
  results  <- jaspResults[["myAnalysisState"]]$object
  coefTab  <- summary(results$model)$coefficients

  for (i in seq_len(nrow(coefTab))) {
    table$addRows(list(
      term     = rownames(coefTab)[i],
      estimate = coefTab[i, "Estimate"],
      se       = coefTab[i, "Std. Error"],
      p        = coefTab[i, "Pr(>|t|)"]
    ))
  }
}

# Plot ----
.myAnalysisPlot <- function(jaspResults, options, ready) {
  # Only create when the user enables the checkbox
  if (!options[["residualPlot"]]) return()
  if (!is.null(jaspResults[["residualPlot"]])) return()

  plot <- createJaspPlot(title = gettext("Residuals vs. Fitted"),
                         width = 480, height = 320)
  plot$dependOn(c("variables", "dependent", "residualPlot"))
  jaspResults[["residualPlot"]] <- plot

  if (!ready) return()

  # Retrieve cached results
  results <- jaspResults[["myAnalysisState"]]$object

  plotData <- data.frame(
    fitted    = fitted(results$model),
    residuals = residuals(results$model)
  )

  plot$plotObject <- ggplot2::ggplot(plotData,
      ggplot2::aes(x = .data[["fitted"]], y = .data[["residuals"]])) +
    ggplot2::geom_point() +
    ggplot2::geom_hline(yintercept = 0, linetype = "dashed") +
    ggplot2::labs(x = gettext("Fitted values"), y = gettext("Residuals"))
}

A.0.1 Key patterns

Pattern Why
Public function (MyAnalysis) has no . prefix Called from QML via Description.qml
Private helpers start with .myAnalysis Hidden from NAMESPACE; prefix avoids name collisions across modules
ready flag checked first Lets output functions show empty placeholders before input is complete
dataset provided by JASP preloadData: true in Description.qml — data arrives ready to use, no read function needed
.myAnalysisComputeResults() stores model in jaspResults Called once from main function; table and plot retrieve it via jaspResults[["myAnalysisState"]]$object
if (!is.null(...)) return() at start of output functions Avoids recreating cached output
Table attached before filling it User sees a placeholder immediately; data fills in after computation
$dependOn(...) on every element Tells JASP when to invalidate and recompute
gettext() on all user-visible strings Enables translation (see Chapter 12)
options[["x"]] (double brackets) Avoids partial-matching bugs from $ or [