Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make synthetic runners use dataframes and rename inputs so stat… #10

Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@
... )

We can instantiate the experiment using the imported function
>>> s = template_experiment()
>>> s = template_experiment(random_state=42)
>>> s # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
SyntheticExperimentCollection(name='Template Experiment', description='...',
params={'name': ...}, ...)

>>> s.name
'Template Experiment'

>>> s.variables
>>> s.variables # doctest: +ELLIPSIS
VariableCollection(...)

>>> s.domain()
Expand All @@ -26,7 +26,7 @@

>>> s.ground_truth # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE
functools.partial(<function template_experiment.<locals>.experiment_runner at 0x...>,
added_noise_=0.0)
observation_noise=0.0)

>>> s.ground_truth(1.)
2.0
Expand All @@ -42,13 +42,13 @@
<function template_experiment.<locals>.experiment_runner at 0x...>

>>> s.experiment_runner(1.)
1.8697820493137682
1.986978204931377

>>> s.experiment_runner(s.domain())
array([[1.01278404],
[1.96837574],
[2.99831988],
[3.91469561]])
array([[1.0012784 ],
[1.99683757],
[2.99983199],
[3.99146956]])

>>> s.plotter()
>>> plt.show() # doctest: +SKIP
Expand All @@ -62,6 +62,7 @@


from functools import partial
from typing import Optional

import numpy as np
from numpy.typing import ArrayLike
Expand All @@ -73,21 +74,18 @@
def template_experiment(
# Add any configurable parameters with their defaults here:
name: str = "Template Experiment",
added_noise: float = 0.1,
random_state: int = 42,
random_state: Optional[int] = None,
):
"""
A template for synthetic experiments.

Parameters:
added_noise: standard deviation of gaussian noise added to output
random_state: seed for random number generator
"""

params = dict(
# Include all parameters here:
name=name,
added_noise=added_noise,
random_state=random_state,
)

Expand All @@ -102,13 +100,13 @@ def template_experiment(
# Define experiment runner
rng = np.random.default_rng(random_state)

def experiment_runner(x: ArrayLike, added_noise_=added_noise):
def experiment_runner(conditions: ArrayLike, observation_noise: float = 0.01):
"""A function which simulates noisy observations."""
x_ = np.array(x)
y = x_ + 1.0 + rng.normal(0, added_noise_, size=x_.shape)
x_ = np.array(conditions)
y = x_ + 1.0 + rng.normal(0, observation_noise, size=x_.shape)
return y

ground_truth = partial(experiment_runner, added_noise_=0.0)
ground_truth = partial(experiment_runner, observation_noise=0.0)
"""A function which simulates perfect observations"""

def domain():
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from functools import partial
from typing import Optional, Union

import numpy as np
import pandas as pd

from autora.experiment_runner.synthetic.utilities import SyntheticExperimentCollection
from autora.variable import DV, IV, ValueType, VariableCollection
Expand Down Expand Up @@ -73,8 +75,7 @@ def expected_value_theory(
resolution=10,
minimum_value=-1,
maximum_value=1,
added_noise: float = 0.01,
random_state: int = 180,
random_state: Optional[int] = None,
):
"""
Expected Value Theory
Expand All @@ -86,7 +87,6 @@ def expected_value_theory(
resolution:
minimum_value:
maximum_value:
added_noise:
random_state:

"""
Expand All @@ -98,17 +98,19 @@ def expected_value_theory(
resolution=resolution,
choice_temperature=choice_temperature,
value_lambda=value_lambda,
added_noise=added_noise,
random_state=random_state,
)

rng = np.random.default_rng(random_state)

variables = get_variables(
minimum_value=minimum_value, maximum_value=maximum_value, resolution=resolution
)
rng = np.random.default_rng(random_state)

def experiment_runner(X: np.ndarray, added_noise_=added_noise):
def experiment_runner(
conditions: Union[pd.DataFrame, np.ndarray, np.recarray],
observation_noise: float = 0.01,
):
X = np.array(conditions)
Y = np.zeros((X.shape[0], 1))
for idx, x in enumerate(X):
value_A = value_lambda * x[0]
Expand All @@ -117,8 +119,8 @@ def experiment_runner(X: np.ndarray, added_noise_=added_noise):
probability_a = x[1]
probability_b = x[3]

expected_value_A = value_A * probability_a + rng.normal(0, added_noise_)
expected_value_B = value_B * probability_b + rng.normal(0, added_noise_)
expected_value_A = value_A * probability_a + rng.normal(0, observation_noise)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would call this value_noise which is specific to the expected utility theory instead of observation_noise

expected_value_B = value_B * probability_b + rng.normal(0, observation_noise)

# compute probability of choosing option A
p_choose_A = np.exp(expected_value_A / choice_temperature) / (
Expand All @@ -128,9 +130,12 @@ def experiment_runner(X: np.ndarray, added_noise_=added_noise):

Y[idx] = p_choose_A

return Y
experiment_data = pd.DataFrame(conditions)
experiment_data.columns = [v.name for v in variables.independent_variables]
experiment_data[variables.dependent_variables[0].name] = Y
return experiment_data

ground_truth = partial(experiment_runner, added_noise_=0.0)
ground_truth = partial(experiment_runner, observation_noise=0.0)

def domain():
X = np.array(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
from functools import partial
from typing import Optional, Union

import numpy as np
import pandas as pd

from autora.experiment_runner.synthetic.economics.expected_value_theory import (
get_variables,
Expand All @@ -10,7 +12,6 @@

def prospect_theory(
name="Prospect Theory",
added_noise=0.01,
choice_temperature=0.1,
value_alpha=0.88,
value_beta=0.88,
Expand All @@ -20,7 +21,7 @@ def prospect_theory(
resolution=10,
minimum_value=-1,
maximum_value=1,
rng=np.random.default_rng(),
random_state: Optional[int] = None,
):
"""
Parameters from
Expand All @@ -44,7 +45,6 @@ def prospect_theory(
"""

params = dict(
added_noise=added_noise,
choice_temperature=choice_temperature,
value_alpha=value_alpha,
value_beta=value_beta,
Expand All @@ -54,15 +54,20 @@ def prospect_theory(
resolution=resolution,
minimum_value=minimum_value,
maximum_value=maximum_value,
rng=rng,
random_state=random_state,
name=name,
)

variables = get_variables(
minimum_value=minimum_value, maximum_value=maximum_value, resolution=resolution
)
rng = np.random.default_rng(random_state)

def experiment_runner(X: np.ndarray, added_noise_=added_noise):
def experiment_runner(
conditions: Union[pd.DataFrame, np.ndarray, np.recarray],
observation_noise=0.01,
):
X = np.array(conditions)
Y = np.zeros((X.shape[0], 1))
for idx, x in enumerate(X):
# power value function according to:
Expand Down Expand Up @@ -113,8 +118,8 @@ def experiment_runner(X: np.ndarray, added_noise_=added_noise):
x[3] ** coefficient + (1 - x[3]) ** coefficient
) ** (1 / coefficient)

expected_value_A = value_A * probability_a + rng.normal(0, added_noise_)
expected_value_B = value_B * probability_b + rng.normal(0, added_noise_)
expected_value_A = value_A * probability_a + rng.normal(0, observation_noise)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, I would call observation_noise instead value_noise

expected_value_B = value_B * probability_b + rng.normal(0, observation_noise)

# compute probability of choosing option A
p_choose_A = np.exp(expected_value_A / choice_temperature) / (
Expand All @@ -124,9 +129,12 @@ def experiment_runner(X: np.ndarray, added_noise_=added_noise):

Y[idx] = p_choose_A

return Y
experiment_data = pd.DataFrame(conditions)
experiment_data.columns = [v.name for v in variables.independent_variables]
experiment_data[variables.dependent_variables[0].name] = Y
return experiment_data

ground_truth = partial(experiment_runner, added_noise_=0.0)
ground_truth = partial(experiment_runner, observation_noise=0.0)

def domain():
v_a = variables.independent_variables[0].allowed_values
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
from functools import partial
from typing import Optional, Union

import numpy as np
import pandas as pd

from autora.experiment_runner.synthetic.utilities import SyntheticExperimentCollection
from autora.variable import DV, IV, ValueType, VariableCollection


def luce_choice_ratio(
name="Luce-Choice-Ratio",
added_noise=0.01,
resolution=8,
maximum_similarity=10,
focus=0.8,
rng=np.random.default_rng(),
random_state: Optional[int] = None,
):
"""
Luce-Choice-Ratio
Expand All @@ -23,7 +24,7 @@ def luce_choice_ratio(
resolution: number of allowed values for stimulus DVs
maximum_similarity: upperbound for DVs
focus: parameter measuring participant focus
rng: integer used to seed the random number generator
random_state: integer used to seed the random number generator

Shepard-Luce Choice Rule according to:
- Equation (4) in Logan, G. D., & Gordon, R. D. (2001).
Expand All @@ -32,39 +33,42 @@ def luce_choice_ratio(
- Equation (5) in Luce, R. D. (1963). Detection and recognition.

Examples:
First we seed numpy to get replicable results:
>>> np.random.seed(42)

We can instantiate a Shepard-Cue Choice Experiment. We use a seed to get replicable results:
>>> l_s_experiment = luce_choice_ratio(rng=42)
>>> l_s_experiment = luce_choice_ratio(random_state=42)

We can look at the name of the experiment:
>>> l_s_experiment.name
'Luce-Choice-Ratio'

To call the ground truth, we can use an attribute of the experiment:
>>> l_s_experiment.ground_truth(np.array([[1,2,3,4]]))
array([[0.21052632]])
similarity_category_A1 ... choose_A1
0 1 ... 0.210526
<BLANKLINE>
[1 rows x 5 columns]

We can also run an experiment:
>>> l_s_experiment.experiment_runner(np.array([[1,2,3,4]]))
array([[0.21016246]])
similarity_category_A1 ... choose_A1
0 1 ... 0.20779
<BLANKLINE>
[1 rows x 5 columns]

To plot the experiment use:
>>> l_s_experiment.plotter()
>>> plt.show() # doctest: +SKIP

"""

minimum_similarity = 1 / maximum_similarity

params = dict(
name=name,
added_noise=added_noise,
maximum_similarity=maximum_similarity,
minimum_similarity=minimum_similarity,
resolution=resolution,
focus=focus,
rng=rng,
random_state=random_state,
)

similarity_category_A1 = IV(
Expand Down Expand Up @@ -121,19 +125,22 @@ def luce_choice_ratio(
dependent_variables=[choose_A1],
)

rng = np.random.default_rng(random_state)

def experiment_runner(
X: np.ndarray,
conditions: Union[pd.DataFrame, np.ndarray, np.recarray],
focus_: float = focus,
added_noise_: float = added_noise,
observation_noise=0.01,
):
X = np.array(conditions)
Y = np.zeros((X.shape[0], 1))
for idx, x in enumerate(X):
similarity_A1 = x[0]
similarity_A2 = x[1]
similarity_B1 = x[2]
similarity_B2 = x[3]

y = (similarity_A1 * focus + np.random.normal(0, added_noise_)) / (
y = (similarity_A1 * focus + rng.normal(0, observation_noise)) / (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here it is somewhat fine because we add it at the end and then normalize. You might still want to call it decision_noise because it is applied before the division.

similarity_A1 * focus
+ similarity_A2 * focus
+ similarity_B1 * (1 - focus_)
Expand All @@ -145,10 +152,12 @@ def experiment_runner(
elif y >= 1:
y = 0.9999
Y[idx] = y
experiment_data = pd.DataFrame(conditions)
experiment_data.columns = [v.name for v in variables.independent_variables]
experiment_data[choose_A1.name] = Y
return experiment_data

return Y

ground_truth = partial(experiment_runner, added_noise_=0.0)
ground_truth = partial(experiment_runner, observation_noise=0.0)

def domain():
similarity_A1 = variables.independent_variables[0].allowed_values
Expand Down
Loading