(6) Side Effects¶
Next, we will learn how to add stimuli that change as a function of the participant's responses via the SideEffect
functionality.
Specifically, we seek to include a score into our task switching experiment a running score indicating the average accuracy of the participant.
Installing sweetbean¶
!pip install sweetbean
We will use the following timeline for our experiment. Note, that we add an additional time line variable called trial
that indexes the current trial. We will use this variable later to compute the running accuracy of the participant for every trial.
timeline = [
{'color': 'red', 'word': 'RED', 'task': 'color_naming', 'trial': 1},
{'color': 'green', 'word': 'GREEN', 'task': 'color_naming', 'trial': 2},
{'color': 'green', 'word': 'RED', 'task': 'word_reading', 'trial': 3},
{'color': 'red', 'word': 'GREEN', 'task': 'word_reading', 'trial': 4},
{'color': 'red', 'word': 'GREEN', 'task': 'word_reading', 'trial': 5},
{'color': 'red', 'word': 'RED', 'task': 'color_naming', 'trial': 6},
{'color': 'green', 'word': 'RED', 'task': 'word_reading', 'trial': 7},
{'color': 'red', 'word': 'GREEN', 'task': 'color_naming', 'trial': 8},
{'color': 'green', 'word': 'RED', 'task': 'color_naming', 'trial': 9},
{'color': 'red', 'word': 'GREEN', 'task': 'word_reading', 'trial': 10},
]
First we want to declare the timeline variables
# imports
from sweetbean.variable import TimelineVariable
color = TimelineVariable('color')
word = TimelineVariable('word')
task = TimelineVariable('task')
trial = TimelineVariable('trial')
Defining the stimuli¶
Next, we add the main stimuli for the experiment:
- a fixation cross
- a blank interval between the fixation cross and a Stroop stimulus
- a stroop stimulus
- a blank inter-trial interval at the end of the trial
Fixation cross¶
The fixation cross should display a "+" if participants are supposed to respond to the color (color-naming task) and a "x" if they are supposed to respond to the word (word reading task).
Can you define a FunctionVariable
encoding the correct string displayed for the fixation cross as a function of the task
variable?
from sweetbean.variable import FunctionVariable
# Predicates
def fixation_shape_fct(task):
# Enter your code here
# variable
fixation_shape = # Enter your code here
Solution¶
from sweetbean.variable import FunctionVariable
# Predicates
def fixation_shape_fct(task):
if task == 'color_naming':
return '+'
return 'x'
# variable
fixation_shape = FunctionVariable('fixation_shape', fixation_shape_fct, [task])
The fixation shape determines the fixation stimulus.
fixation = Text(1000, fixation_shape)
Intervals¶
Next, we define two blank intervals, one between the fixation cross and the stimulus (800ms), and another inter-trial interval (500ms).
# Enter your code here
Solution¶
from sweetbean.stimulus import Text
fixation_stimulus_interval = Text(800)
inter_trial_interval = Text(500)
Stroop stimulus¶
Eventually, we want to show the stroop stimulus to which people must respond. This one is a bit more complicated as we have to first determine the correct response to each stimulus. We will use that later to compute our accuracy score.
To encode the correctness of the response to each Stroop stimulus we will define a predicate with three input arguments. Let's say we want the participant to press f when the color is "red" in the color_naming task or the word is "RED" in the word_reading task. They should press j when the color is "green" in the color_naming task or the word is "GREEN" in the word_reading task.
The correct_key
FunctionVariable indicates the correct key for the stimulus.
Can you complete the code below based on what you learned in the previous tutorials?
from sweetbean.variable import FunctionVariable
# Predicate
def correct_key_fct(word, color, task):
# Enter your code here
# variable for the response
correct_key = # Enter your code here
Solution¶
from sweetbean.variable import FunctionVariable
# Predicate for f
def correct_key_fct(word, color, task):
if (task == 'word_reading' and word == 'RED') or \
(task == 'color_naming' and color == 'red'):
return 'f'
return 'j'
# variable for the response
correct_key = FunctionVariable('correct_key', correct_key_fct, [word, color, task])
Next, we can define the Stroop stimulus which is shown for 2000ms.
stroop = Text(2000,
word, color, ['f', 'j'],
correct_key)
Adding Side Effects to Compute an Accuracy Score¶
Now that we have our basic stimuli in place, it is time to think about how we can add a running accuracy score to the experiment.
Obtaining the single-trial accuracy from the data¶
All stimuli that have the correct_key
parameter compute a correct
value indicating whether the response was correct or not. This is also the case for the Stroop stimulus defined above.
This is a property of the data, i.e., a data variable. Let's define this data variable. In this case, we add the additional argument 1
to refer to the response of the previous trial (1 stimulus back).
from sweetbean.variable import DataVariable
# declare the data variable
correct = DataVariable('correct', 1)
Counting the number of correct trials¶
Now, we define a count for the number of accurate trials as a SharedVariable
which can be shared across different trials. We initialize it with zero.
from sweetbean.variable import SharedVariable
num_correct = SharedVariable("num_correct", 0)
Next, we write an update function that increases the number of correct trials based on the current response.
update_num_correct = FunctionVariable(
"update_num_correct", lambda score, value: score + value, [num_correct, correct]
)
Updating the number of correct trials with each stimulus presentation¶
We can consider the updating the number of correct trials as a SideEffect
of every Stroop stimulus presentation. A side effects takes in the variable to set (in this case num_correct
) and the variable it will be set to (in this case the function variable update_num_correct
).
from sweetbean.variable import SideEffect
update_accuracy_side_effect = SideEffect(num_correct, update_num_correct)
Side effects can be triggered with SweetBean stimuli. In this case, we will associate the side effect with the occurrence of the Stroop stimulus.
Let's re-define our Stroop stimulus above. This time, we will add the side effect that we just defined.
stroop = Text(2000,
word,
color,
['f', 'j'],
correct_key,
side_effects=[update_accuracy_side_effect])
Displaying the accuracy score¶
Finally, we want to display the running accuracy score. To do this, we can define another ``FunctionVariable'' that computes the accuracy as the number of correct responses divided by the number of trials.
accuracy_text = FunctionVariable("accuracy_text", lambda score, n: f"Score: {score/n}", [num_correct, trial])
accuracy_score = Text(duration=2000, text=accuracy_text)
Finishing the experiment¶
All right, now we can complete the experiment by arranging all stimuli in a block. Note that we are skipping instructions here, to keep it simple.
Our Stroop block will have the following order of events:
- Fixation cross
- Fixation-stimulus interval
- Stroop stimulus
- Accuracy score
- Inter-trial interval
from sweetbean import Block, Experiment
# Block
stroop_block = Block([fixation,
fixation_stimulus_interval,
stroop,
accuracy_score,
inter_trial_interval], timeline)
experiment = Experiment([stroop_block])
# Experiment
experiment.to_html('index.html')