CPM library
  • Home

Getting Started

  • Installation
  • Troubleshooting
  • Roadmap

Examples

  • Two associative learning models and blocking (cpm.models, cpm.generators)
  • Reinforcement learning with a two-armed bandit (cpm.models, cpm.generators, cpm.optimisation)
    • Two-armed bandits
    • Import the data
    • The model description
    • Building the model
      • Parameters
    • Fitting to data
      • Fitting the model
    • Simulation
  • Estimating metacognitive efficiency with meta-d (cpm.applications)
  • Parameter Recovery (cpm.generators, cpm.models, cpm.optimisation)
  • Model Recovery and Landscaping (cpm.models, cpm.generators, cpm.optimisation)
  • Estimating Empirical Priors (cpm.hierarchical)
  • Exploring hierarchical estimation of hyperparameters (cpm.hierarchical)
  • Scale to hypercomputing cluster

API Reference

  • cpm.applications
  • cpm.datasets
  • cpm.models
  • cpm.generators
  • cpm.optimisation
  • cpm.hierarchical
  • cpm.utils
CPM library
  • Examples
  • Reinforcement learning with a two-armed bandit (cpm.models, cpm.generators, cpm.optimisation)

In [1]:
Copied!
import cpm
version = cpm.__version__
## if cpm is below 0.18.5, throw error
if version < "0.18.5":
    raise Exception("cpm version is too old, please update to 0.18.5 or later")
else:
    print(f"cpm version is up to date\nversion: {version}\nauthor: {cpm.__author__}")
import cpm version = cpm.__version__ ## if cpm is below 0.18.5, throw error if version < "0.18.5": raise Exception("cpm version is too old, please update to 0.18.5 or later") else: print(f"cpm version is up to date\nversion: {version}\nauthor: {cpm.__author__}")
cpm version is up to date
version: 0.23.18
author: Lenard Dome

Reinforcement learning with a two-armed bandit (cpm.models, cpm.generators, cpm.optimisation)¶

This is an intermediate level tutorial, where we assume that you are familiar with the basics of reinforcement learning and the two-armed bandit problem. In this example, we will apply and fit a reinforcement learning model to a two-armed bandit problem. The model will be consist of a $\epsilon$-greedy policy and a prediction error term.

In the following tutorial, we will use prettyformatter to print some nice and organised output in the Jupyter Notebook. In your python script, you do not need to use prettyformatter.

Two-armed bandits¶

Two-armed bandit is a two-alternative forced-choice reinforcement learning problem, part of the bigger set of multi-armed bandit problems. In these problems, the person is faced with multiple choices, each with a different degree of reward. The goal of the person is to learn which choice is the best and to maximize the reward over time. In this example, we will consider a two-armed bandit problem, where the person is faced with two choices (select an item on the left or the item on the right), each with a different reward. There are 4 different items that can appear in combinations of two, and the reward for each item varies. For example, if item 1 has a chance of 0.7 of giving a reward, then you can expect to receive a reward 70% of the time when you select item 1. The problem is that the underlying structure of the item-reward mapping is unknown.

Import the data¶

First, we will import the data and get it ready for the toolbox.

In [2]:
Copied!
## toolbox and libraries
import cpm
import ipyparallel as ipp ## for parallel computing with ipython (specific for Jupyter Notebook)
import pandas as pd
import numpy

import matplotlib.cm as cmx
import matplotlib.pyplot as plt
from prettyformatter import pprint

experiment = cpm.datasets.load_bandit_data()
experiment.head(10)
## toolbox and libraries import cpm import ipyparallel as ipp ## for parallel computing with ipython (specific for Jupyter Notebook) import pandas as pd import numpy import matplotlib.cm as cmx import matplotlib.pyplot as plt from prettyformatter import pprint experiment = cpm.datasets.load_bandit_data() experiment.head(10)
Out[2]:
ppt trial arm_left arm_right reward_left reward_right response feedback
0 1 1 2 1 0.0 1.0 1 1.0
1 1 2 3 2 1.0 0.0 0 1.0
2 1 3 1 3 1.0 0.0 0 1.0
3 1 4 1 3 0.0 0.0 0 0.0
4 1 5 2 4 1.0 0.0 0 1.0
5 1 6 3 1 1.0 1.0 0 1.0
6 1 7 4 2 1.0 1.0 1 1.0
7 1 8 1 4 0.0 0.0 0 0.0
8 1 9 3 4 0.0 0.0 0 0.0
9 1 10 1 4 1.0 0.0 0 1.0

Let us look at what each column represents:

  • index: variable to identify each row - this variable is clutter.
  • left: the stimulus presented on the left side.
  • right: the stimulus presented on the right side.
  • reward_left: the reward received when the left stimulus is selected.
  • reward_right: the reward received when the right stimulus is selected.
  • ppt: the participant number.
  • responses: the response of the participant (1 for right, 0 for left).

Here, we will quickly add a column observed to specify for the toolbox, what is the dependent variable we actually want to predict.

In [7]:
Copied!
experiment['observed'] = experiment['response']
experiment['observed'] = experiment['response']

The model description¶

Let each stimulus have an associated value, which is the expected reward that can be obtained from selecting that stimulus. Let also $Q(a)$ be the estimated value of action $a$. We set the starting value for all $Q(a)$ to be nonzero and equally distributed between all stimuli.

In each trial, $t$, there are two stimuli present, so $Q(a)$ could be $Q(\text{left})$ or $Q(\text{right})$, where the corresponding Q values are derived from the associated value of the stimuli present on the left or right. More formally, we can say that the expected value of the action $a$ selected at time $t$ is given by:

\begin{equation} Q_t(a) = \mathbb{E}[R_t | A_t = a] \end{equation}

where $R_t$ is the reward received at time $t$, and $A_t$ is the action selected at time $t$. In each trial $t$, the Softmax choice rule (Bridle, 1990) will select an action (left or right) based on the following policy:

\begin{equation} P(a_t) = \frac{e^{Q_{a,t} \beta}}{\sum_{i = 1}^{k}{e^{Q_{i,t} \beta}}} \end{equation}

where $\beta$ is the inverse temperature parameter, also referred to as choice stochasticity, and $Q_{a,t}$ is the estimated value of the action $a$ at time $t$. $k$ is the number of actions available, and in our case, $k = 2$. The model uses the variant of the delta rule (Rescorla & Wagner, 1972; Rumelhart, Hinton & Williams, 1986) adapted for multi-armed bandit problems where each option has a single dimension (Barto & Sutton, 2018), reducing Rescorla-Wagner's summed error-term to the following equation, similar to single linear operators (Bush and Mosteller, 1955):

\begin{equation} \Delta Q_t(A_t) = \alpha \times \Big[ R_t - Q_t(A_t) \Big] \end{equation}

where $\alpha$ is the learning rate and $R_t$ is the reward received at time $t$, also called a teaching signal and sometimes annotated as $\lambda$. $A_t$ is the action chosen for the trial $t$. Then we update the Q-values, such as:

\begin{equation} Q_{t+1}(A_t) = Q_t(A_t) + \Delta Q_t(A_t) \end{equation}

Building the model¶

In order to use the toolbox, you will have to specify the computations for one single trial. Rest assured, you do not have to build the model from scratch. We have fully-fledged models in cpm.applications that you can use, but we also have all the building blocks in cpm.components that you can use to build your own model.

Parameters¶

Now, before we build the model, we also have to talk about the cpm.Parameter class. This class is used to specify the parameters of the model, including various bounds and priors. Let us specify the parameters for the model.

In [36]:
Copied!
from cpm.generators import Parameters, Value

parameters = Parameters(
    # freely varying parameters are indicated by specifying priors
    alpha=Value(
        value=0.5,
        lower=1e-10,
        upper=1,
        prior="truncated_normal",
        args={"mean": 0.5, "sd": 0.25},
    ),
    temperature=Value(
        value=1,
        lower=0,
        upper=20,
        prior="truncated_normal",
        args={"mean": 10, "sd": 5},
    ),
    # everything without a prior is part of the initial state of the model or fixed constructs (e.g. exemplars in general-context models of categorization)
    values = numpy.array([0.0, 0.0, 0.0, 0.0]))
from cpm.generators import Parameters, Value parameters = Parameters( # freely varying parameters are indicated by specifying priors alpha=Value( value=0.5, lower=1e-10, upper=1, prior="truncated_normal", args={"mean": 0.5, "sd": 0.25}, ), temperature=Value( value=1, lower=0, upper=20, prior="truncated_normal", args={"mean": 10, "sd": 5}, ), # everything without a prior is part of the initial state of the model or fixed constructs (e.g. exemplars in general-context models of categorization) values = numpy.array([0.0, 0.0, 0.0, 0.0]))

You can immediately see that we have two types of variables in the Parameters class: one with priors and one without. The ones with defined priors are the ones we will estimate later on, they are freely varying parameters (alpha and temperature). All freely varying parameters have to be defined with priors.

Parameters without priors are part of the initial state of the model, such as starting Q-values for each stimulus. Here we initialized them at 0.25.

That was enough preparation, let us build a model. The model will take in the parameters class and the data, and it will output all variables of interest we specify.

Note: The model will take in a variable (as its second argument) that is in our case a row in the pandas.DataFrame, behaving as a pandas.Series.

In [37]:
Copied!
import cpm
import ipyparallel as ipp  ## for parallel computing with ipython (specific for Jupyter Notebook)

@ipp.require("numpy")
def model(parameters, trial):
    # pull out the parameters
    alpha = parameters.alpha
    temperature = parameters.temperature
    values = numpy.asarray(parameters.values)
    
    # pull out the trial information
    stimulus = numpy.array([trial.arm_left, trial.arm_right]).astype(int)
    feedback = numpy.array([trial.reward_left, trial.reward_right])
    human_choice = trial.response.astype(int)

    # Equation 1. - get the value of each available action
    # Note that because python counts from 0, we need to shift
    # the stimulus identifiers by -1
    expected_rewards = values[stimulus - 1]
    # Equation 2.
    # calculate a policy based on the activations
    choice_rule = cpm.models.decision.Softmax(
        activations=expected_rewards,
        temperature=temperature
        )
    choice_rule.compute() # compute the policy
    # if the policy is NaN for an action, then we need to set it to 1
    # this corrects some numerical issues with python and infinities
    if numpy.isnan(choice_rule.policies).any():
        choice_rule.policies[numpy.isnan(choice_rule.policies)] = 1
    model_choice = choice_rule.choice() # get the choice based on the policy

    # get the received reward for the choice
    reward = feedback[human_choice]
    teacher = numpy.array([reward])
    # we now create a vector that tells our learning rule what...
    # ... stimulus to update according to the participant's choice
    what_to_update = numpy.zeros(4)
    chosen_stimulus = stimulus[human_choice] - 1
    what_to_update[chosen_stimulus] = 1

    # Equation 4.
    update = cpm.models.learning.SeparableRule(
                    weights=values,
                    feedback=teacher,
                    input=what_to_update,
                    alpha=alpha
                    )
    update.compute()
    # Equation 5.
    values += update.weights.flatten()
    # compile output
    output = {
        "trial"    : trial.trial.astype(int), # trial numbers
        "activation" : expected_rewards.flatten(), # expected reward of arms
        "policy"   : choice_rule.policies,       # policies
        "response" : model_choice,            # model's choice
        "reward"   : reward,                  # received reward
        "error"    : update.weights,          # prediction error
        "values"   : values,                  # updated values
        # dependent variable
        "dependent"  : numpy.array([choice_rule.policies[1]]),
    }
    return output
import cpm import ipyparallel as ipp ## for parallel computing with ipython (specific for Jupyter Notebook) @ipp.require("numpy") def model(parameters, trial): # pull out the parameters alpha = parameters.alpha temperature = parameters.temperature values = numpy.asarray(parameters.values) # pull out the trial information stimulus = numpy.array([trial.arm_left, trial.arm_right]).astype(int) feedback = numpy.array([trial.reward_left, trial.reward_right]) human_choice = trial.response.astype(int) # Equation 1. - get the value of each available action # Note that because python counts from 0, we need to shift # the stimulus identifiers by -1 expected_rewards = values[stimulus - 1] # Equation 2. # calculate a policy based on the activations choice_rule = cpm.models.decision.Softmax( activations=expected_rewards, temperature=temperature ) choice_rule.compute() # compute the policy # if the policy is NaN for an action, then we need to set it to 1 # this corrects some numerical issues with python and infinities if numpy.isnan(choice_rule.policies).any(): choice_rule.policies[numpy.isnan(choice_rule.policies)] = 1 model_choice = choice_rule.choice() # get the choice based on the policy # get the received reward for the choice reward = feedback[human_choice] teacher = numpy.array([reward]) # we now create a vector that tells our learning rule what... # ... stimulus to update according to the participant's choice what_to_update = numpy.zeros(4) chosen_stimulus = stimulus[human_choice] - 1 what_to_update[chosen_stimulus] = 1 # Equation 4. update = cpm.models.learning.SeparableRule( weights=values, feedback=teacher, input=what_to_update, alpha=alpha ) update.compute() # Equation 5. values += update.weights.flatten() # compile output output = { "trial" : trial.trial.astype(int), # trial numbers "activation" : expected_rewards.flatten(), # expected reward of arms "policy" : choice_rule.policies, # policies "response" : model_choice, # model's choice "reward" : reward, # received reward "error" : update.weights, # prediction error "values" : values, # updated values # dependent variable "dependent" : numpy.array([choice_rule.policies[1]]), } return output

The important bit here is that everything we want to store must be specified in the model output. There are also variables we want to update, such as the values, which again, we have to include in the model output. They will be used by other methods in the toolbox to identify key variables.

The important part is the dependent key, which outputs the main thing we fit the model to later.

Fitting to data¶

First we wrap our model specification into a Wrapper, that loops it through the data

In [38]:
Copied!
from cpm.generators import Simulator, Wrapper

wrapper = Wrapper(model=model, parameters=parameters, data=experiment[experiment.ppt == 1])
wrapper.run()
from cpm.generators import Simulator, Wrapper wrapper = Wrapper(model=model, parameters=parameters, data=experiment[experiment.ppt == 1]) wrapper.run()

Now we have ran the model on a single session, let's investagete the output. cpm is great for this because everything that you might need to look at is stored inside the Wrapper object.

In [39]:
Copied!
## print last trial
pd.Series(wrapper.simulation[len(wrapper.simulation) - 1])
## print last trial pd.Series(wrapper.simulation[len(wrapper.simulation) - 1])
Out[39]:
trial                                                        71
activation                            [0.0, 0.0868377685546875]
policy                [0.47830418979176476, 0.5216958102082354]
response                                                      0
reward                                                      1.0
error                                    [[0.0, 0.0, 0.0, 0.5]]
values        [0.988273614551872, 0.40002429485321045, 0.086...
dependent                                  [0.5216958102082354]
dtype: object

Maybe a better way to do this is to export the simulation details as a pandas.DataFrame:

In [40]:
Copied!
output = wrapper.export() # export sim data into pandas dataframe
output.tail()
output = wrapper.export() # export sim data into pandas dataframe output.tail()
Out[40]:
trial activation_0 activation_1 policy_0 policy_1 response reward error_0 error_1 error_2 error_3 values_0 values_1 values_2 values_3 dependent ppt
66 67 0.906189 0.000000 0.712220 0.287780 0 1.0 0.046906 0.000000 0.0 0.0 0.988274 0.400024 0.086838 0.5 0.287780 0
67 68 0.800049 0.953094 0.461813 0.538187 0 1.0 0.023453 0.000000 0.0 0.0 0.988274 0.400024 0.086838 0.5 0.538187 0
68 69 0.800049 0.086838 0.671110 0.328890 0 0.0 -0.000000 -0.400024 -0.0 0.0 0.988274 0.400024 0.086838 0.5 0.328890 0
69 70 0.976547 0.086838 0.708830 0.291170 0 1.0 0.011726 0.000000 0.0 0.0 0.988274 0.400024 0.086838 0.5 0.291170 0
70 71 0.000000 0.086838 0.478304 0.521696 0 1.0 0.000000 0.000000 0.0 0.5 0.988274 0.400024 0.086838 0.5 0.521696 0

Fitting the model¶

Now that we have the model, we can fit it to the data.

In [41]:
Copied!
from cpm.optimisation import minimise, FminBound

fit = FminBound(
    model=wrapper,  # Wrapper class with the model we specified from before
    data=experiment.groupby('ppt'),  # the data as a list of dictionaries
    minimisation=minimise.LogLikelihood.bernoulli,
    parallel=True,
    libraries=["numpy", "cpm", "pandas"],
    cl=5,
    prior=True,
    ppt_identifier="ppt",
    display=False,
    number_of_starts=5,
    # everything below is optional and passed directly to the scipy implementation of the optimiser
    approx_grad=True

)

fit.optimise()
from cpm.optimisation import minimise, FminBound fit = FminBound( model=wrapper, # Wrapper class with the model we specified from before data=experiment.groupby('ppt'), # the data as a list of dictionaries minimisation=minimise.LogLikelihood.bernoulli, parallel=True, libraries=["numpy", "cpm", "pandas"], cl=5, prior=True, ppt_identifier="ppt", display=False, number_of_starts=5, # everything below is optional and passed directly to the scipy implementation of the optimiser approx_grad=True ) fit.optimise()
Starting optimization 1/5 from [ 0.17866802 11.54135186]
Starting 5 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
  0%|          | 0/5 [00:00<?, ?engine/s]
Starting optimization 2/5 from [0.43577176 6.45624412]
Starting 5 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
  0%|          | 0/5 [00:00<?, ?engine/s]
Starting optimization 3/5 from [0.85512903 2.38574939]
Starting 5 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
  0%|          | 0/5 [00:00<?, ?engine/s]
Starting optimization 4/5 from [ 0.1353455  11.51970391]
Starting 5 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
  0%|          | 0/5 [00:00<?, ?engine/s]
Starting optimization 5/5 from [0.61560884 4.79701383]
Starting 5 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
  0%|          | 0/5 [00:00<?, ?engine/s]
In [42]:
Copied!
optimisation = fit.export()
optimisation.to_csv('bandit_optimised.csv')
optimisation
optimisation = fit.export() optimisation.to_csv('bandit_optimised.csv') optimisation
Out[42]:
x_0 x_1 grad_0 grad_1 task funcalls nit warnflag hessian_0 hessian_1 hessian_2 hessian_3 ppt fun
0 0.560707 14.653588 7.105427e-07 1.776357e-07 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 45 12 0 161.658910 -1.030274 -1.030274 0.079063 1 13.729248
1 0.014001 9.980408 -7.105427e-04 -3.552713e-06 CONVERGENCE: RELATIVE REDUCTION OF F <= FACTR*... 42 10 0 20851.926301 37.455916 37.455916 0.098775 2 50.814651
2 0.477092 1.966134 7.887024e-05 6.394885e-06 CONVERGENCE: RELATIVE REDUCTION OF F <= FACTR*... 30 7 0 57.077150 5.443754 5.443754 2.448869 3 48.178201
3 0.796371 10.322221 3.552714e-06 7.105427e-07 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 72 18 0 197.869939 -2.948346 -2.948346 0.103713 4 29.239348
4 0.437350 4.920923 7.105427e-07 7.105427e-07 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 36 8 0 117.979596 2.236371 2.236371 0.765790 5 32.520230
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
195 0.353022 4.384126 -7.105427e-07 0.000000e+00 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 63 14 0 97.938458 5.468136 5.468136 1.019715 196 35.821585
196 0.327815 10.236654 -7.815970e-06 0.000000e+00 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 45 12 0 346.098032 3.042619 3.042619 0.212841 197 22.914597
197 0.472791 11.242171 7.105427e-06 0.000000e+00 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 48 14 0 82.877378 -0.767760 -0.767760 0.107097 198 17.657494
198 0.510460 10.672396 -1.065814e-06 -3.552713e-07 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 39 10 0 247.438632 -0.805949 -0.805949 0.140230 199 21.168944
199 0.557276 14.038147 -3.552714e-07 1.776357e-07 CONVERGENCE: NORM OF PROJECTED GRADIENT <= PGTOL 54 17 0 129.609877 -0.500514 -0.500514 0.088060 200 15.811283

200 rows × 14 columns

In [ ]:
Copied!
import matplotlib.pyplot as plt

# Create a figure and a set of subplots
fig, axs = plt.subplots(1, 3, figsize=(16, 3))  # 1 row, 3 columns, and optional figure size

# Plot on the first subplot with a specific color
optimisation.x_0.plot(kind="hist", bins=30, label="Learning Rate", color="blue", ax=axs[0])
axs[0].set_title('Learning Rate')

# Plot on the second subplot with a different color
optimisation.x_1.plot(kind="hist", bins=30, label="Temperature", color="red", ax=axs[1])
axs[1].set_title('Temperature')

optimisation.fun.plot(kind="hist", bins=30, label="Negative Log Likelihood", color="green", ax=axs[2])
axs[2].set_title('Negative Log Likelihood')

plt.tight_layout()  # Adjust subplots to fit into figure area.
# Display the plot
plt.show()
import matplotlib.pyplot as plt # Create a figure and a set of subplots fig, axs = plt.subplots(1, 3, figsize=(16, 3)) # 1 row, 3 columns, and optional figure size # Plot on the first subplot with a specific color optimisation.x_0.plot(kind="hist", bins=30, label="Learning Rate", color="blue", ax=axs[0]) axs[0].set_title('Learning Rate') # Plot on the second subplot with a different color optimisation.x_1.plot(kind="hist", bins=30, label="Temperature", color="red", ax=axs[1]) axs[1].set_title('Temperature') optimisation.fun.plot(kind="hist", bins=30, label="Negative Log Likelihood", color="green", ax=axs[2]) axs[2].set_title('Negative Log Likelihood') plt.tight_layout() # Adjust subplots to fit into figure area. # Display the plot plt.show()
No description has been provided for this image

Simulation¶

We can instantly simulate model output from these results by using the cpm.generator.Simulator class.

In [47]:
Copied!
from cpm.generators import Simulator

simulator = Simulator(wrapper=wrapper,
                      parameters=fit.parameters, # parameters from the optimiser object is directly accessible for simulation
                      data=experiment.groupby("ppt")) # data grouped by participants
simulator.run()
from cpm.generators import Simulator simulator = Simulator(wrapper=wrapper, parameters=fit.parameters, # parameters from the optimiser object is directly accessible for simulation data=experiment.groupby("ppt")) # data grouped by participants simulator.run()
In [48]:
Copied!
simulation_data = simulator.export()
simulation_data.to_csv('bandit_simulation.csv')
simulation_data.tail()
simulation_data = simulator.export() simulation_data.to_csv('bandit_simulation.csv') simulation_data.tail()
Out[48]:
trial activation_0 activation_1 policy_0 policy_1 response reward error_0 error_1 error_2 error_3 values_0 values_1 values_2 values_3 dependent ppt
14394 67 0.752214 0.442554 0.987221 0.012779 0 0.0 -0.000000 -0.000000 -0.419191 0.0 0.753205 0.9758 0.869269 0.0 0.012779 200
14395 68 0.000000 0.442554 0.002000 0.998000 1 1.0 0.310651 0.000000 0.000000 0.0 0.753205 0.9758 0.869269 0.0 0.998000 200
14396 69 0.000000 0.333023 0.009239 0.990761 1 1.0 0.000000 0.000000 0.371690 0.0 0.753205 0.9758 0.869269 0.0 0.990761 200
14397 70 0.704713 0.000000 0.999949 0.000051 0 1.0 0.000000 0.000000 0.164556 0.0 0.753205 0.9758 0.869269 0.0 0.000051 200
14398 71 0.945339 0.753205 0.936861 0.063139 0 1.0 0.000000 0.030461 0.000000 0.0 0.753205 0.9758 0.869269 0.0 0.063139 200
In [51]:
Copied!
left_option = experiment.arm_left
right_option = experiment.arm_right
model_chosen_left = 1 - simulation_data.response
model_chosen_right = simulation_data.response
ppt = simulation_data.ppt
trials = simulation_data.trial

left = pd.DataFrame({
    "stimulus": left_option,
    "response": model_chosen_left,
    "trial": trials,
    "ppt": ppt
})

right = pd.DataFrame({
    "stimulus": right_option,
    "response": model_chosen_right,
    "trial": trials,
    "ppt": ppt
})

# # print(stimulus.shape, response.shape, trial.shape)
plot_data = pd.concat([left, right], axis=0).sort_values(by="trial")
left_option = experiment.arm_left right_option = experiment.arm_right model_chosen_left = 1 - simulation_data.response model_chosen_right = simulation_data.response ppt = simulation_data.ppt trials = simulation_data.trial left = pd.DataFrame({ "stimulus": left_option, "response": model_chosen_left, "trial": trials, "ppt": ppt }) right = pd.DataFrame({ "stimulus": right_option, "response": model_chosen_right, "trial": trials, "ppt": ppt }) # # print(stimulus.shape, response.shape, trial.shape) plot_data = pd.concat([left, right], axis=0).sort_values(by="trial")
In [52]:
Copied!
grouped = plot_data.groupby(['stimulus', 'trial']).mean().reset_index()
grouped.columns = ['stimulus', 'trial', 'response', 'ppt']
## calculate rolling mean
grouped['response_mean'] = grouped.groupby('stimulus')['response'].transform(lambda x: x.rolling(window=10, min_periods=1).mean())
grouped['response_std'] = grouped.groupby('stimulus')['response'].transform(lambda x: x.rolling(window=10, min_periods=1).std())

## rescale for each trial
grouped
grouped = plot_data.groupby(['stimulus', 'trial']).mean().reset_index() grouped.columns = ['stimulus', 'trial', 'response', 'ppt'] ## calculate rolling mean grouped['response_mean'] = grouped.groupby('stimulus')['response'].transform(lambda x: x.rolling(window=10, min_periods=1).mean()) grouped['response_std'] = grouped.groupby('stimulus')['response'].transform(lambda x: x.rolling(window=10, min_periods=1).std()) ## rescale for each trial grouped
Out[52]:
stimulus trial response ppt response_mean response_std
0 1 0 0.449438 97.067416 0.449438 NaN
1 1 1 0.582524 99.223301 0.515981 0.094106
2 1 2 0.648936 103.212766 0.560300 0.101589
3 1 3 0.633663 102.049505 0.578641 0.090696
4 1 4 0.676190 101.876190 0.598150 0.089847
... ... ... ... ... ... ...
283 4 67 0.205882 93.950980 0.220654 0.039716
284 4 68 0.259615 96.971154 0.227804 0.039644
285 4 69 0.161290 93.096774 0.219443 0.044193
286 4 70 0.237624 102.702970 0.214634 0.038420
287 4 71 0.175926 98.796296 0.211014 0.040340

288 rows × 6 columns

In [53]:
Copied!
import matplotlib.pyplot as plt
import seaborn as sns  # Seaborn is used for easy color palette access


# Create a figure and a single subplot
fig, ax = plt.subplots(figsize=(10, 6))

# Get a list of unique stimuli to iterate over
unique_stimuli = grouped['stimulus'].unique()

# Generate a color palette with the same number of colors as there are unique stimuli
colors = sns.color_palette("deep", len(unique_stimuli))

# Iterate over each unique stimulus
for stimulus, color in zip(unique_stimuli, colors):
    # Filter the grouped DataFrame for the current stimulus
    df_subset = grouped[grouped['stimulus'] == stimulus]
    
    # Plot each subset with its corresponding color
    ax.plot(df_subset['trial'], df_subset['response_mean'], label=stimulus, color=color)
    # Plot the standard deviation as a shaded region around the mean
    ax.fill_between(df_subset['trial'], df_subset['response_mean'] - df_subset['response_std'], df_subset['response_mean'] + df_subset['response_std'], color=color, alpha=0.1)

    # Scale y axis to be between 0 and 1
    ax.set_ylim([0, 1])
    # Add a title and labels
    ax.set_title('Model Choices Over Time')
    ax.set_xlabel('Trial')
    ax.set_ylabel('Probability of Choosing Item')

# Add legend to distinguish different lines
ax.legend(title='Stimulus')
# Add caption
plt.figtext(0.75, -0.05, '\n\nSimulated Data from bandit task with best fitting parameters', horizontalalignment='right')

# Show the plot
plt.show()
import matplotlib.pyplot as plt import seaborn as sns # Seaborn is used for easy color palette access # Create a figure and a single subplot fig, ax = plt.subplots(figsize=(10, 6)) # Get a list of unique stimuli to iterate over unique_stimuli = grouped['stimulus'].unique() # Generate a color palette with the same number of colors as there are unique stimuli colors = sns.color_palette("deep", len(unique_stimuli)) # Iterate over each unique stimulus for stimulus, color in zip(unique_stimuli, colors): # Filter the grouped DataFrame for the current stimulus df_subset = grouped[grouped['stimulus'] == stimulus] # Plot each subset with its corresponding color ax.plot(df_subset['trial'], df_subset['response_mean'], label=stimulus, color=color) # Plot the standard deviation as a shaded region around the mean ax.fill_between(df_subset['trial'], df_subset['response_mean'] - df_subset['response_std'], df_subset['response_mean'] + df_subset['response_std'], color=color, alpha=0.1) # Scale y axis to be between 0 and 1 ax.set_ylim([0, 1]) # Add a title and labels ax.set_title('Model Choices Over Time') ax.set_xlabel('Trial') ax.set_ylabel('Probability of Choosing Item') # Add legend to distinguish different lines ax.legend(title='Stimulus') # Add caption plt.figtext(0.75, -0.05, '\n\nSimulated Data from bandit task with best fitting parameters', horizontalalignment='right') # Show the plot plt.show()
No description has been provided for this image
Previous Next

Built with MkDocs using a theme provided by Read the Docs.
« Previous Next »