Experiment¶
This module contains the main experiment and experiment session objects.
What’s the difference between the Experiment and the ExperimentSession?
The Experiment class is the first thing you create in your script.py. Its purpose is simply to collect all pages and functions that you add to the experiment. For this purpose it offers decorators like setup and member. From this collection, it creates a new ExperimentSession for every new subject.
The ExperimentSession class is the main experiment organizer. This is the class that actually coordinates all the work in an ongoing experiment. When you interact with a Section’s or a Page’s exp attribute, you are actually interacting with the current ExperimentSession object.
-
class
alfred3.experiment.Experiment¶ Bases:
objectHolds pages and section and creates experiment sessions.
Experiment members can be accessed via dictionary-style square brackets and added with the augmented assigment operator
+=(see exampples).The class acts as an organizing container for pages and section for orderly instantiation of the
ExperimentSession.Examples
Create an experiment and add simple page in linear style
>>> exp = al.Experiment() >>> exp += al.Page(name="page1") >>> exp.members {"page1": Page(class="Page", name="page1")}
>>> exp["page1"] Page(class="Page", name="page1")
New in version 2.0.
-
setup_functions: List[callable] = []¶ A list of function that will be called upon creation of an experiment session. They are added with the
setup()decorator
-
finish_functions: List[callable] = []¶ A list of function that will be called upon finishing an experiment session. They are added with the
finish()decorator
-
setup(func)¶ Decorator for functions that work on the experiment session.
The decorated function can have an arbitrary name. It must take an
ExperimentSessionobject as its only argument (usually spelled as exp). You can use this decorator on as many function as you like.The purpose of this decorator is to allow manipulation of the
ExperimentSessionobject generated byExperiment.Examples
In this example, we use the the
@exp.setupdecorator to add a plugin to the experiment session’s plugin dictionary and access that same plugin later in a page hook.>>> import alfred3 as al >>> exp = al.Experiment() ... >>> @exp.setup >>> def setup(exp): # the decorated function can have any name ... exp.plugins["a"] = "mock plugin" ... >>> @exp.member >>> class HelloWorld(al.Page): ... name = "hello_world" ... ... def on_exp_access(self): ... print(self.exp.plugins["a"]) mock plugin
-
finish(func)¶ Decorator for code to be run upon
ExperimentSession.finish().The decorated function can have an arbitrary name. It must take an
ExperimentSessionobject as its only argument (usually spelled as exp). You can use this decorator on as many functions as you like.The purpose of this decorator is to allow manipulation of the
ExperimentSessionobject generated byExperimenta last time before the final page is shown. The decorated functions are the first things to be called in theExperimentSession.finish()method.A common usecase would be to conditionally assign the experiment’s final page based on subject input during the experiment.
Examples
Using the
@exp.finishdecorator for conditionally changing the final page:import alfred3 as al exp = al.Experiment() exp += al.Page(name="page1") exp.page1 += al.TextEntry("Enter text", name="text1") @exp.finish def set_final_page(exp): # the decorated function can have any name if exp.values["text1"] == "value": exp.final_page = al.Page("Option A", name="final_page_a") else: exp.final_page = al.Page("Option B", name="final_page_b")
-
member(_member=None, *, of_section: str = '_content')¶ Decorator for adding pages and sections to the experiment.
Works both with and without arguments.
- Parameters
of_section – Name of the section to which the new member belongs.
Examples
Adding a page directly to the main content section:
>>> exp = al.Experiment() ... >>> @exp.member >>> class HelloWorld(al.Page): ... name = "hello_world" ... ... def on_exp_access(self): ... self += al.Text("This is a 'hello, world!' Page.") ... >>> exp.members {"hello_world": Page(class="HelloWorld", name="hello_world")}
Adding a page to a specific section:
>>> exp = al.Experiment() ... >>> exp += al.Section(name="main") >>> @exp.member(of_section="main") >>> class HelloWorld(al.Page): ... name = "hello_world" ... title = "Hello, World!" ... def on_exp_access(self): ... self += al.Text("This is a 'hello, world!' Page.") ... >>> exp["hello_world"] Page(class="HelloWorld", name="hello_world")
-
property
final_page¶ The experiment’s final page.
You can set this property to a Page class or Page instance directly. For more complex final pages, we recommend the use of the
as_final_page()decorator.- Returns
The experiment’s final page.
- Return type
page.Page
Examples
This property will return None, if the final page was not set manually. The experiment session will then use a default final page.
>>> exp = al.Experiment() >>> exp.final_page None
Setting a class instance as final page:
>>> exp = al.Experiment() >>> exp.final_page = al.Page(name="final_page") >>> exp.final_page Page(class='Page', name='final_page')
See also
as_final_page(): Class decorator for adding custom final pages to the experiment.ExperimentSession.final_pagethe final page property of the experiment session object.
- Type
page.Page
-
as_final_page(page)¶ Class decorator for adding a custom final page to the experiment.
Use this decorator, if you want to define a new final page with full access to all experiment hooks.
Examples
>>> exp = al.Experiment() ... >>> @exp.as_final_page >>> class Final(al.Page): ... name = "final_page" ... def on_exp_access(self): ... self += al.Text("This is the final page.") ... >>> exp.final_page.name final_page
See also
final_page: The final page as a property.
-
init_members() → dict¶ Initializes all pages and sections in the members dictionary.
Also appends all members to their respective parents. If a member is already instantiated, the instance is copied to ensure uniqueness of instances across experiments.
- Returns
Dictionary of initialized sections and pages.
- Return type
-
create_session(session_id: str, config: alfred3.config.ExperimentConfig, secrets: alfred3.config.ExperimentSecrets, **urlargs)¶ Creates an experiment session.
The arguments get passed on directly to the
ExperimentSessionobject and are documented there.See also
ExperimentSessioncontains documentation on how to interact with an experiment session object.
-
append(*members, to_section: str = '_content')¶ Append members to the experiment.
- Parameters
*members – The members to append
to_section – The parent section for the members to append. All members in a function call will be appended to the same section.
Members can be classes that inherit from
SectionorPage, as well as instances of these classes or their subclasses. To append Page or Section classes to the experiment, you should use themember()decorator.While it is perfectly possible to use the
append()method, usually it is preferable to use to augmented assigment operator+=to append members to the experiment.Note
All members must have a unique name.
Examples
>>> exp = al.Experiment() >>> exp.append(Page(name="page1")) >>> exp.members {"page1": Page(class="Page", name="page1")}
-
-
class
alfred3.experiment.ExperimentSession(session_id: str, config: alfred3.config.ExperimentConfig = None, secrets: alfred3.config.ExperimentSecrets = None, **urlargs)¶ Bases:
objectCoordinates all parts of an experiment.
- Parameters
session_id – Unique session identifier
config – Non-secret experiment configuration
secrets – Secret experiment configuration, such as database credentials
**urlargs – Keyword arguments from url parameters. Will be stored in the attribute
urlargsfor access in the experiment session.
Usually, there is no need to initialize an ExperimentSession manually. The
Experimentobject will take care of that for you.You have access to this object in Page and Section hooks through their accessors
Page.expandSection.exp. It connects all parts of an experiment and allows you to access data from other pages and even other experiment sessions.Note
Because the experiment session is newly created for every new subject, you can only access it in the object-oriented style of writing an alfred experiment by deriving new
Pageand/orSectionclasses and using their hooks to add elements.See also
Object-oriented style
Page hooks
Section hooks
Experiment object
Changed in version 2.0: Many changes, including new methods and properties for improved usability. Name changed from “Experiment” to “ExperimentSession”.
-
plugins¶ A dictionary of experiment plugins for use in the experiment session.
-
finish_functions¶ A list of functions that will be called upon finishing an experiment session. They are added with the
finish()decorator
-
finished¶ Indicates whether the experiment session is finished
-
start_timestamp¶ Timestamp saved upon experiment start
-
start_time¶ Time of experiment start in seconds since epoch
-
alfred_version¶ The alfred3 version used for this experiment session
-
session_id¶ Unique session identifier
-
config¶ Non-sensitive experiment configuration. This is an
ExperimentConfigobject. It offers access to all values defined in the experiment’s config.conf through the methods get, getint, getfloat, and getboolean.
-
secrets¶ Sensitive experiment configuration (e.g. database credentials). This is an
ExperimentSecretsobject. It offers access to all values defined in the experiment’s secrets.conf through the methods get, getint, getfloat, and getboolean.
-
urlargs¶ A dictionary of parameters passed as url parameters for use in the experiment session.
-
log¶ A
QueuedLoggingInterface, offering logging through the methods debug, info, warning, error, exception, and log.
-
property
progress_bar¶ The experiment’s progress bar.
There are two options in config.conf that control the progress bar, both in the layout section:
show_progress(true/false) toggles whether a progress bar is displayed at allfix_progress_top(true/false) toggles whether the progress bar should stay at a fixed position at the top of the page, when subjects scroll down on a long page.
The progress bar can be customized by redefining it with a
element.ProgressBarof your choosing.See also
See
element.ProgressBarfor more information on how to specify a custom progress bar.Notes
The experiment-wide progress bar always receives a name of “progress_bar_”.
Examples
Example of controlling the progress bar in the config.conf:
[layout] show_progress = true fix_progress_top = false
Example of redefining the experiment-wide progress bar:
import alfred3 as al exp = al.Experiment() @exp.setup def setup(exp): exp.progress_bar = al.ProgressBar(show_text=True, bar_height="10px")
-
start()¶ Starts the experiment.
Usually, this method does not need to be called manually. It will be called automatically, when the
/experiment/starturl route is called.
-
finish()¶ Closes all pages and saves data.
Usually, this method does not need to be called manually. It will be called automatically upon entering the finished section.
-
save_data(sync: bool = False)¶ Saves data with the main and unlinked saving agents.
Usually, there is no need to call this method manually, as data is saved automatically on every move.
- Parameters
sync – If True, the experiment will only proceed after the saving task was completed.
Warning
Note that a call to this function will NOT prompt a call to the
save_data()method ofCustomSavingPageinstances in the experiment. You need to call those manually.
-
property
all_members¶ Dictionary of all sections and pages in the experiment.
Excludes the final page.
- Type
-
property
final_page¶ The experiment’s final page.
This page will be displayed after a subject has finished the experiment. It will not contain any navigation elements. You can use this property to change the final page by assigning a page of your design.
See also
You can change the final page in a similar way using
Experiment.final_page.For conditional assignment of the experiment’s final page, doing so using a function with the
Experiment.finish()decorator is the recommended way to go.
Examples
>>> import alfred3 as al >>> exp = al.ExperimentSession() >>> exp.final_page = al.Page(name="my_final_page") >>> exp.final_page Page(class="Page", name="my_final_page")
-
subpath(path: Union[str, pathlib.Path]) → pathlib.Path¶ Returns the full path of an experiment subdirectory.
If the given path is absolute, it will not be altered, but transformed to a
pathlib.Pathobject.- Returns
Absolute path
- Return type
-
read_csv_todict(path: Union[str, pathlib.Path], encoding: str = 'utf-8', **kwargs) → Iterator[dict]¶ Iterates over the rows in a .csv file, yielding dictionaries.
- Parameters
path – The path to the .csv file. Usually, you want this to be a relative path to a file in a subdirectory of the experiment directory.
encoding – Encoding of the .csv file. Defaults to ‘utf-8’.
**kwargs – Further arguments passed on to
csv.DictReader
- Yields
dict – A dictionary in which the keys are the column names.
Examples
Consider the following csv-file, located at
files/data.csvin your experiment directory:col1 , col2 , col3 text_a , text_b , text_c text_d , text_e , text_f
When building a page, usual usage would be:
import alfred3 as al exp = al.Experiment() @exp.member class CSVDemoPage(al.Page): # this could also be a Section name = "csv_demo" def on_exp_access(self): for row in self.exp.read_csv_todict("files/data.csv"): print(row)
The output would be the following:
{"col1": "text_a", "col2": "text_b", "col3": "text_c"} # first iteration {"col1": "text_d", "col2": "text_e", "col3": "text_f"} # second iteration
If you need a full list of the rows, you can wrap the function call in
list():import alfred3 as al exp = al.Experiment() @exp.member class CSVDemoPage(al.Page): # this could also be a Section name = "csv_demo" def on_exp_access(self): data = list(self.exp.read_csv_todict("files/data.csv")) print(data)
The output would be the following:
[{"col1": "text_a", "col2": "text_b", "col3": "text_c"}, {"col1": "text_d", "col2": "text_e", "col3": "text_f"}]
New in version 2.0.
-
read_csv_tolist(path: Union[str, pathlib.Path], encoding: str = 'utf-8', **kwargs) → Iterator[list]¶ Iterates over the rows in a .csv file, yielding lists.
- Parameters
path – The path to the .csv file. Usually, you want this to be a relative path to a file in a subdirectory of the experiment directory.
encoding – Encoding of the .csv file. Defaults to ‘utf-8’.
**kwargs – Further arguments passed on to
csv.reader
- Yields
list – A list of the values in one row.
Examples
Consider the following csv-file:
col1 , col2 , col3 text_a , text_b , text_c text_d , text_e , text_f
When building a page, usual usage would be:
import alfred3 as al exp = al.Experiment() @exp.member class CSVDemoPage(al.Page): # this could also be a Section name = "csv_demo" def on_exp_access(self): for row in self.exp.read_csv_tolist("files/data.csv"): print(row)
The output would be the following:
["col1", "col2", "col3"] # first iteration yields column names ["text_a", "text_b", "text_c"] # second iteration ["text_a", "text_b", "text_c"] # third iteration
If you need a full list of the rows, you can wrap the function call in
list():import alfred3 as al exp = al.Experiment() @exp.member class CSVDemoPage(al.Page): # this could also be a Section name = "csv_demo" def on_exp_access(self): data = list(self.exp.read_csv_tolist("files/data.csv")) print(data)
The output would be the following:
[["col1", "col2", "col3"], ["text_a", "text_b", "text_c"], ["text_a", "text_b", "text_c"]]
New in version 2.0.
Returns the experiment author.
- Type
-
property
path¶ Path to the experiment directory
- Type
Path
-
property
adata¶ Shortcut for accessing the additional data dictionary.
The additional data dictionary is meant to be a place where you can store data manually. It will be saved to the final dataset, where all additional data for a session will be stored as a single string.
See also
You can add specific values to a page with the
Valueelement. For each Value element, alfred will save an individual column to the final dataset.New in version 2.0.
- Type
-
property
session¶ read-only
- Returns
Current TestCondition (str or unicode)
-
append(*items)¶ Appends Sections or Pages to the experiment’s root section.
While it is perfectly possible to use the
append()method, usually it is preferable to use to augmented assigment operator+=to append members to the experiment.All members must have a unique name.
Examples
>>> import alfred3 as al >>> exp = al.ExperimentSession() >>> exp.append(al.Page(name="page1")) >>> exp.members {"page1": Page(class="Page", name="page1")}
-
encrypt(data: Union[str, int, float]) → str¶ Encrypts the input and returns the encrypted string.
In web experiments deployed via mortimer, a safe, user-specific secret key will be used for encryption.
Warning
The method will also work in offline experiments, but does NOT provide safe encryption in this case, as a PUBLIC key is used for encryption. This is only ok for testing purposes.
- Parameters
data – Input object that you want to encrypt. If the input is None, the function will return None.
- Returns
Encrypted data
- Return type
-
decrypt(data: Union[str, bytes]) → str¶ Decrypts input and returns the decrypted object as str.
The method uses the built-in Fernet instance to decrypt the input.
- Parameters
data – Encrypted string or bytes object.
- Returns
Decrypted data
- Return type
-
forward()¶ Moves the experiment forward one page.
Can be called in the
Page.custom_move()hook to implement very specific movement behavior.See also
Refer to the documentation of
Page.custom_move()for guidance on how to implement a custom movement method.New in version 2.0.
-
backward()¶ Moves the experiment backward one page.
Can be called in the
Page.custom_move()hook to implement very specific movement behavior.See also
Refer to the documentation of
Page.custom_move()for guidance on how to implement a custom movement method.New in version 2.0.
-
jump(to: Union[str, int])¶ Jumps to a specific page in the experiment.
- Parameters
to (str, int) – The name or index of the target page. Preferred usage is by name, because that reduces ambiguity.
Can be called in the
Page.custom_move()hook to implement very specific movement behavior.See also
Refer to the documentation of
Page.custom_move()for guidance on how to implement a custom movement method.New in version 2.0.
-
property
values¶ Dictionary of input elements and their current values.
Elements are identified by their name. Only elements from pages that have already been shown to a user appear in this dictionary.
You cannot (and should not) change subject data by manipulating the dictionary returned by this property.
- Returns
Dictionary of input elements and their current values.
- Return type
New in version 2.0.
-
property
session_data¶ Full dataset of the current experimental session.
Contains client information, experiment metadata, element values, movement history, and additional data.
You cannot (and should not) change subject data by manipulating the dictionary returned by this property.
- Returns
Full dataset of the current experimental session.
- Return type
New in version 2.0.
-
property
move_history¶ A list, containing the movement history for the current session.
Each entry in this list is a dictionary. The entries in each of these dictionaries are based on the
Movedataclass.You cannot (and should not) change subject data by manipulating the dictionary returned by this property.
- Returns
A list, containing a dictionary for each move of the subject in the current session.
- Return type
See also
Please refer to the documentation of
Movefor an explanation of the saved fields.New in version 2.0.
-
property
metadata¶ A dict of information about the experiment.
This dict contains general information about the experiment, such as
Start time
Experiment version
Title
Author
(…)
- Returns
A dict of information about the experiment.
- Return type
New in version 2.0.
-
get_page_data(name: str) → dict¶ Get the data dictionary of a specific page.
- Parameters
name – The name of the target page
- Returns
Data dictionary of a specific page.
- Return type
-
get_section_data(name: str) → dict¶ Get the data dictionary for all pages in a specific section.
Includes pages in child-sections of the target section.
- Parameters
name – The name of the target section
- Returns
Data dictionary of a specific section.
- Return type
New in version 2.0.
-
post_message(msg: str, title: str = '', level: str = 'info')¶ Post a message for one-time display after the next move.
Both the message and its title can contain github flavored markdown and emoji shortcodes.
The message will always be displayed only once. If the current page cannot be left, e.g. due to invalid input, it will be displayed on the current page. Otherwise, it will always be displayed on the next displayed page.
- Parameters
msg – Message main text.
title – Message title, will be displayed as a bold heading.
level – Message level, controls the display style. Can be ‘info’ (default), ‘warning’, ‘danger’, ‘success’, ‘primary’, ‘secondary’, ‘dark’, or ‘light’.
New in version 2.0.
-
property
db¶ Database object of the main mongo saving agent.
You can use this property to access multiple collections in the used database, if your monogo saving agent is connected through an account with the necessary privileges.
Note
This is the database object, which can contain multiple collections of documents. Data is always saved to a collection in a database, not to a database directly.
See also
db_mainreturns the collection, to which the main mongo saving agent writes its data.- Returns
A database object. If no mongo saving agent is present in the experiment, None is returned.
- Return type
pymongo.database.Database
New in version 2.0.
-
property
db_main¶ Document collection of the main mongo saving agent.
You can use this property to interact with the collection through the pymongo api: https://pymongo.readthedocs.io/en/stable/
- Returns
A collection object. If no mongo saving agent is present in the experiment, None is returned.
- Return type
pymongo.collections.Collection
New in version 2.0.
-
property
db_unlinked¶ Document collection of the unlinked mongo saving agent.
You can use this property to interact with the collection through the pymongo api: https://pymongo.readthedocs.io/en/stable/
- Returns
A collection object. If no unlinked mongo saving agent is present in the experiment, None is returned.
- Return type
pymongo.collections.Collection
New in version 2.0.
-
property
db_misc¶ Document collection for miscellaneous data.
You can use this property to interact with the collection through the pymongo api: https://pymongo.readthedocs.io/en/stable/
Note
This property assumes that the database credentials used by the main mongo saving agent are valid for a collection with the name misc in the same database aswell. If that is not the case, you will receive authentication errors when interacting with this property.
- Returns
A collection object. If no mongo saving agent is present in the experiment, None is returned.
- Return type
pymongo.collections.Collection
New in version 2.0.