The Analysis Lifecycle

In jamovi, an analysis isn’t just a single function call. It’s a lifecycle designed to provide a fluid, “instant” user experience. To achieve this, jamovi separates the creation of the results structure from the actual calculation.

The Two-Stage Execution

Every jamovi analysis (inheriting from jmvcore::Analysis) has two primary entry points in its R6 class: .init() and .run().

Note

What is R6? Don’t let the name intimidate you! R6 is simply the framework jamovi uses to organize your analysis code into a single, clean structure. If you’ve used functions in R, you’re already 90% of the way there.

1. The .init() Phase

The .init() function is responsible for UI Responsiveness. Its job is to create the “skeleton” of your results as quickly as possible.

2. The .run() Phase

The .run() function is where the Heavy Lifting happens.


Why this separation is vital

Imagine you are running a complex analysis on a dataset with 100,000 rows.

  1. Without separation: You change an option. The UI freezes for 5 seconds while R calculates. Finally, the table appears. The user feels disconnected.
  2. With jamovi’s Lifecycle: You change an option. Instantly, a table skeleton appears with the correct headers and empty rows. A loading spinner might appear. 2 seconds later, the numbers pop into the cells.

This “instant feedback” is what makes jamovi feel like a modern application rather than a traditional script-based environment.

Example: A Dynamic Table Skeleton

Suppose your analysis allows users to select multiple dependent variables. You want the table to show one row for each variable.

ttestClass <- R6::R6Class("ttestClass",
    inherit = ttestBase,
    private = list(
        .init = function() {
            # Get the list of dependent variables from options
            deps <- self$options$deps
            
            # Access the table defined in .r.yaml
            table <- self$results$myTable
            
            # Add a row for each variable immediately
            for (dep in deps) {
                table$addRow(rowKey=dep, values=list(
                    var=dep
                ))
            }
        },
        .run = function() {
            # Perform the actual t-test for each variable
            # and populate the existing rows
            # ...
        }
    )
)

By defining the rows in .init(), the user sees the table grow or shrink the moment they add or remove variables from the list, even before the t-test has finished running.


Summary of Differences

Feature.init().run()
Primary GoalUI ResponsivenessAccuracy & Calculation
SpeedMust be near-instantCan take time
Data AccessLimited (metadata only)Full access to data
ResultsCreates the “Skeleton”Fills the “Content”

Next Step: Now that you understand the lifecycle, let’s learn how to create Dynamic Tables.