Press "Enter" to skip to content

Tutorial: Conditional Branching

FindingFive supports conditional branching, a study design that allows researchers to branch participants based on their performance in an earlier block. This feature involves a few steps, but once you get the hang of it, can be used to achieve a wide range of dynamic study designs! Examples include terminating a study for some participants sooner than others or branching participants into different tasks based on a set of screening questions.

In this tutorial, we’ll give you all you need to learn how to implement this design in your FindingFive studies!


Overview

Conditional branching involves splitting and branching participants based on their responses in an earlier block. One way to use this feature is to have some participants end a study sooner than others. For example, you may want to terminate participants who don’t learn a task prior to a testing block.

You could also use conditional branching to dynamically assign participants to different tasks. For example, if you want to show right and left-handed participants tasks tailored to their dominant hands, you can probe for this characteristic in a demographic block, branch participants based on their responses, and have each branch complete a different task.

If you instead want to group participants independent of their performance on any block–perhaps to randomly place half of your participants in a control group–check out our tutorial on participant grouping. This tutorial is specifically for cases in which responses in a previous block determine a participant’s branch.

Brief Procedure Review

Conditional branching is defined in the procedure section of a study.

This image has an empty alt attribute; its file name is image-1.png

A standard procedure contains three key properties: type, blocks, and block_sequence. These properties, respectively, outline the procedure’s type, the blocks included in your study, and the order in which your participants will complete these blocks.

The below code applies to a simple procedure, in which all participants encounter all blocks in a fixed order:

{
   "type": "blocking",
   "blocks": {
        "BlockA": { 
          // a sample block definition
          "trial_templates": ["Trial1", "Trial2"],
          "pattern": {"order": "fixed"}
        },
        "BlockB": {/*insert block definition here*/}, 
        "BlockC": {/*insert block definition here*/}
   },
   "block_sequence": ["BlockA", "BlockB", "BlockC"]
 }

For full details on the procedure section, please see our study grammar.

Conditional Branching

Implementing conditional branching in FindingFive involves customizing a standard procedure. To branch participants into different blocks conditional on their performance in a previous block, FindingFive needs two additional pieces of information:

1. You must define a condition for branching your participants.

2. You must update the block sequence for each of these branches.

This formula enables you to implement both examples from our Overview section, and other designs that involve dynamically branching participants. In the next sections, we’ll go into more detail on each of these steps.

Step 1: Defining a Condition

The condition tells FindingFive what to pay attention to when branching your participants. You can think of it as including the following command: place participants who behave in X-way in Branch 1 and those who behave in Y-way in Branch 2.

To define a condition in your procedure, add the branching property to one of your block-definitions. Specifically, add this property to the block where performance should be evaluated. If you want to branch participants based on their performance in a training block, add the branching property to your training block. If you need to branch participants based on demographic questions, add the branching property to your demographics block.

In the example from the above schematic, performance in Block A determines participants’ branches, so we add branching to Block A’s definition.

  {
    "type": "blocking",
    "blocks": {
      "BlockA": {
        // a sample block definition
        "trial_templates": ["Trial1", "Trial2"],
        "pattern": {"order": "fixed"},
     	// BRANCHING PROPERTY: 
        "branching": {/*insert branching definition here*/}
      },
      "BlockB": {/*insert block definition here*/}, 
      "BlockC": {/*insert block definition here*/}
    },
    "block_sequence": [/*insert block sequence here*/]
  }

The Branching Property

The branching property is defined with up to 5 instructions: method, triggers, min_score, branches, and iterations. These instructions specify the parameters on which to branch participants.

iterations is optional, so we’ve omitted it in this tutorial. Please see our grammar reference for its full definition.

Method

method indicates the metric on which to evaluate the condition. There are two possible values: accuracy and match.

When method is accuracy, participants are branched based on their accuracy in the block. For example, if you wanted to distinguish a group of learners from non-learners (classifying learners as those who earn 70% accuracy or above on a training block) you would assign accuracy to the method instruction.

When methodis match, each participant’s branch assignment depends on whether their responses match certain target responses. You could use this assignment if you want to distinguish right from left-handed participants based on their responses to the question “Is your dominant hand your RIGHT HAND or your LEFT HAND?” In this case, what matters is whether a participant’s response matches the target response “LEFT” or “RIGHT.”

Add the method instruction to your branching property as follows:

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "method": "match"
  }
}
  

Triggers

triggers specifies the trial templates and responses that FindingFive should evaluate when branching participants.

This instruction is necessary because your branching block may contain trial templates or responses that are irrelevant to determining branches. In our handedness example (see Overview), only one trial from the demographics block determines whether participants are left or right-handed. The demographics block might also contain trials about age, gender, education level, etc. The triggers instruction lets FindingFive know to ignore the other demographics questions when determining branches.

To definetriggers, assign it a list (denoted by square brackets) of dictionaries (key-value pairings encased in curly brackets): [{dictionary 1}, {dictionary 2}, ...].

Each dictionary should define the trial template and associated response for each trigger. The number of dictionaries to include should equal the number of triggers that are relevant. In our handedness example, only one trigger determines our branches, so we only include one dictionary in the triggers instruction. If, instead, we wanted to determine handedness based on two trials — one about participants’ dominant writing hand, and one about participants’ preferred hand when lifting heavy objects — we would include two dictionaries in the definition of triggers.

Each dictionary must contain the keys “trial_template” and “response”: [{"trial_template": INSERT_VALUE, "response": INSERT_VALUE}]. The values that we insert are the trigger’s trial name and associated response name, as defined in your study. In our handedness example, the trial might have been named “HandednessTrial”, and the associated response might have been named “LeftOrRight.” The triggers instruction would therefore look like this:

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "triggers": [
      {"trial_template": "HandednessTrial", "response": "LeftOrRight"}]
  }
}

When a condition involves multiple triggers, add additional dictionaries separated by commas.

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "triggers": [
      // dictionary defining trigger 1
      {"trial_template": "insert_trial_name", "response": "insert_response_name"},
      // dictionary defining trigger 2
      {"trial_template": "insert_trial_name", "response": "insert_response_name"}]
  }
}

Min_Score

min_score is only required when method is accuracy. This property identifies a cutoff score based on participants’ performance in the given block. Participants who earn the minimum score and above will be placed in a separate branch from those with lower accuracy.

FindingFive calculates the min_score based on each participant’s performance on the trial templates defined in triggers. When multiple triggers are included, the min_score will reflect the participant’s score across triggers.

Define min_score using decimals. A required minimum accuracy of 80% will translate to: "min_score": 0.8.

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "min_score": 0.8
  }
}

Branches

branches is the instruction that uses what’s defined in method, triggers, and min_score to assign branch names to target response outcomes. For example, using branches you can name the branch that earned the min_score or higher “Learners” and the branch that did not “NonLearners.”

We make these assignments by passing branches a dictionary (key-value pairings encased in curly brackets) in which keys define branch names and values define target response outcomes.

When “method” is “accuracy.”

When method is accuracy, there are only two possible target outcomes: earning the min_score or above, or not. FindingFive automatically assigns values of true and false to these outcomes, respectively. Therefore, whenever method is accuracy, branches must assign one branch name to the value true and another to the value false.

In our learners/non-learners example (see Overview), “Learners” have earned the min_score or above, and “NonLearners” have not. We’ll thus assign “Learners” a value of true and “NonLearners” a value of false, like this:

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "branches": {"Learners": true, "NonLearners": false}
  }
}

When “method” is “match.”

When method is match, you’ll likewise assign each branch name to target response values. The spelling and capitalization must match what’s defined in the response; if a response contains the values “LEFT” and “RIGHT” as options, the values “Left” and “Right” are not considered correct target responses. In most cases, you will also want to assign one branch a response value of null. This branch is called the default branch and will catch any participant whose response does not match the target outcomes.

To illustrate this, below is some example code based on a trigger with a simple choice response containing the options “Yes”, “No”, “Maybe”, and “Not sure”.

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "branches": {"YES_branch": "Yes", "NO_branch": "No", "default_branch": null}
  }
}

In this example, participants who choose “Yes” will join the “YES_branch,” participants who choose “No” will join the “NO_branch”, and participants who choose neither will join the “default_branch.” The null term is crucial in this example, as participants who choose “Maybe” or “Not sure” will not fall into the other two branches.

In the case of multiple triggers, simply place target response outcomes in a list, enclosed in square brackets. The length of the list must match the number of triggers, with target response outcomes listed in the same order as they’re listed in the triggers instruction.

"BlockA": {
  // define trial templates and other properties here
  "branching": {
    //define other instructions here
    "branches": {
      "insert_branch1_name": ["trigger1_target", "trigger2_target", ...],
      "insert_branch2_name": ["trigger1_target", "trigger2_target", ...],
      "insert_branch3_name": ["trigger1_target", "trigger2_target", ...],
      "insert_branch4_name": null}
  }
}

The above instructions work together to “define a condition.” See the next section to learn how to update the block sequence!

Step 2: Updating the Block Sequence

The first step of implementing conditional branching involved defining a condition on which to branch participants. In this second step, you’ll update the block_sequence, specifying which blocks each branch should complete.

Remember that the block_sequence is one of the three standard properties of a procedure. A very basic block_sequence, in which all participants complete Block A, followed by Block B, and then Block C will look as follows:

{
   "type": "blocking",
   "blocks": {
        // define blocks here
   },
   "block_sequence": ["BlockA", "BlockB", "BlockC"]
 }

To specify distinct blocks for different branches, the basic block_sequence simply needs a modification: add a dictionary to the block_sequence that takes branch names as keys and matches them to lists of blocks as values. If you want Branch 1 to complete Block B and Branch 2 to complete Block C, you would insert the following dictionary into the block_sequence:

//insert this dictionary into the block_sequence
{"Branch1": ["BlockB"], "Branch2": ["BlockC"]}

Remember to account for the order in which participants should see each block. It is important to always place the block that houses the condition (Block A in our example) ahead of the branch specification dictionary. If participants haven’t seen that block, they won’t yet be placed into branches!

{
   "type": "blocking",
   "blocks": {
        // define blocks here
   },
   "block_sequence": ["BlockA", {"Branch1": ["BlockB"], "Branch2": ["BlockC"]}]
 }

You can also send each branch to multiple blocks, like this:

{
   "type": "blocking",
   "blocks": {
        // define blocks here
   },
   "block_sequence": ["BlockA", {"Branch1": ["BlockB", "BlockD"], "Branch2": ["BlockC", "BlockE"]}]
 }

Or show all participants a block, even after they’ve been branched, like this:

{
   "type": "blocking",
   "blocks": {
        // define blocks here
   },
    /* Here, all participants will complete BlockA. Branch1 will complete BlockB, while Branch2 will complete BlockC. All participants will then complete the ExitBlock.*/ 
   "block_sequence": ["BlockA", {"Branch1": ["BlockB"], "Branch2": ["BlockC"]}, "Exit Block"]
 }

The block sequence is completely customizable, so you can play with it to create a design-flow that is most appropriate for your study.

Putting it together

Conditional branching in FindingFive involves just two adjustments to a basic procedure. The first step is to define the condition, establishing branches based on how each participant satisfies the condition. The second step is to update the block sequence, outlining the order in which each of the previously defined branches should encounter various blocks.

To conclude this tutorial, we’ll revisit our examples from the Overview, outlining sample conditional branching code for each case.

Learners & NonLearners

A full implementation of conditional branching in our learners/non-learners case will look like this:

{
   "type": "blocking",
   "blocks": {
        "TrainingBlock": { 
          "trial_templates": ["TrainingTrial1", "TrainingTrial2"],
          "pattern": {"order": "fixed"},
          "branching": {
            "method": "accuracy",
            "triggers": [
              {"trial_template": "TrainingTrial1", "response": "TrainingTrial1Response"},
              {"trial_template": "TrainingTrial2", "response": "TrainingTrial2Response"}],
            "min_score": 0.7,
            "branches": {"Learners": true, "NonLearners": false}
          }
        },
        "TestingBlock": {
          "trial_templates": ["T1", "T2", "T3", "T4"],
          "pattern": {"order": "fixed"}}, 
        "ExitBlock": {
          "trial_templates": ["ThankYou"],
          "pattern": {"order": "fixed"}}
   },
   "block_sequence": ["TrainingBlock", {"Learners": ["TestingBlock"], "NonLearners": []}, "ExitBlock"]
 }

Step 1: We place the branching property in the “TrainingBlock”, as this is the block in which participants’ performance determines their branch assignment. The branching property takes on the instructions method, triggers, min_score, and branches.

Step 2: In the block sequence, we include a dictionary in which “Learners” are assigned to the “TestingBlock”, and “NonLearners” are assigned to no block. Following the dictionary, we place the “ExitBlock” so that all participants complete it, regardless of their branch assignment.

Right & Left-Handed Participants

Conditional branching in our handedness case will be implemented as follows:

{
  "type": "blocking",
  "blocks": {
    "Demographics": {
      "trial_templates": ["HandednessTrial"],
      "pattern": {"order": "fixed"},
      "branching": {
        "method": "match",
        "triggers": [{"trial_template": "HandednessTrial", "response": "RightOrLeft"}],
        "branches": {"leftHanded": "LEFT", "rightHanded": "RIGHT"}}
    },
    "LeftHandedTask": {
      "trial_templates": ["T2", "T3"],
      "pattern": {"order": "fixed"}
    }, 
    "RightHandedTask": {
      "trial_templates": ["T4", "T5"],
      "pattern": {"order": "fixed"}
    }
  },
  "block_sequence": ["Demographics", {"leftHanded": ["LeftHandedTask"], "rightHanded":["RightHandedTask"]}]
}

Step 1: We place the branching properties in the “Demographics” block with the instructions method, triggers, and branches. It does not need a min_score instruction, as the branching method is “match.”

Step 2: We add a dictionary to the block sequence in which “leftHanded” participants are assigned to the “LeftHandedTask”, and “rightHanded” participants are assigned to the “RightHanded” task.


We hope this tutorial clarifies your questions about conditional branching! It may seem daunting, but once you get the hang of it, conditional branching can be used to create many useful and intricate study designs.

As always, further questions are welcome! Please see our discussion forum, as your question may already be answered there. If you have specific inquiries, feel free to email us at researcher.help@findingfive.com.