Experiment — alfred3 1.4.0.a1 documentation

# 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: `object`

  Holds 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.

  `members`: dict = {}

    A dictionary of all pages and sections added to the experiment.

  `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 `ExperimentSession` object 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 `ExperimentSession` object generated by `Experiment`.

    Examples

    In this example, we use the the `@exp.setup` decorator 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 `ExperimentSession` object 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 `ExperimentSession` object generated by `Experiment` a last time before the final page is shown. The decorated functions are the first things to be called in the `ExperimentSession.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.finish` decorator 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_page` the 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

      dict

  `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 `ExperimentSession` object and are documented there.

    See also

    `ExperimentSession` contains 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 `Section` or `Page`, as well as instances of these classes or their subclasses. To append Page or Section classes to the experiment, you should use the `member()` 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: `object`

  Coordinates 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 `urlargs` for access in the experiment session.

  Usually, there is no need to initialize an ExperimentSession manually. The `Experiment` object will take care of that for you.

  You have access to this object in Page and Section hooks through their accessors `Page.exp` and `Section.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 `Page` and/or `Section` classes 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 `ExperimentConfig` object. 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 `ExperimentSecrets` object. 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 all

    - `fix_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.ProgressBar` of your choosing.

    See also

    See `element.ProgressBar` for 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/start` url 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 of `CustomSavingPage` instances 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

      dict

  property `all_sections`

    Dictionary of all sections in the experiment.

    Type

      dict

  property `all_pages`

    Dictionary of all pages in the experiment.

    Type

      dict

  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.Path` object.

    Returns

      Absolute path

    Return type

      pathlib.Path

  `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.csv` in 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.

  property `author`

    Returns the experiment author.

    Type

      str

  property `type`

    Type of the experiment

    Type

      str

  property `version`

    Experiment version

    Type

      str

  property `title`

    Experiment title

    Type

      str

  property `exp_id`

    Experiment id

    Type

      str

  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 `Value` element. For each Value element, alfred will save an individual column to the final dataset.

    New in version 2.0.

    Type

      dict

  property `additional_data`

    The additional data dictionary. See `adata`.

    Type

      dict

  property `condition`

    Current experiment condition

    Type

      str

  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

      str

  `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

      str

  `set_additional_data`(key: str, value)

    Shortcut for `DataManager.add_additional_data()`.

  `get_additional_data`(key: str)

    Shortcut for `DataManager.get_additional_data_by_key()`.

  `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

      dict

    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

      dict

    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 `Move` dataclass.

    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

      list

    See also

    Please refer to the documentation of `Move` for 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

      dict

    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

      dict

  `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

      dict

    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_main` returns 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.