Documentation

API Reference

In the MX8 platform, surveys are written in Python using the Survey class.

Surveys should always start with the code block:

from survey import Survey

s = Survey(**globals())

You can import other Python libraries that are part of the standard library, e.g. datetime and math, but not modules that are not in the standard library, like pandas.

Survey

Survey class for creating surveys.

style

@property
style: Literal['traditional', 'chat']

Return the style of the survey.

Can be either 'traditional' or 'chat' depending on the configuration of the survey theme.

disable_bot_detection

disable_bot_detection() -> None

Disable bot detection mechanisms for this survey.

Appropriate use

Use this method when respondents are interacting with an interviewer who completes the survey, rather than responding on their own devices at home or work.

Bot detection tradeoff

This disables bot detection for the whole survey. Only use it when the survey collection mode makes automated bot checks inappropriate.

tag

tag(**kwargs: Any = {}) -> dict[str, Any]

Create tags for questions, piping, or temporary tag contexts.

Context manager use

When used in a with s.tag(...): block, the tags are attached to every question created inside that block.

Direct use

When passed as tags=s.tag(...), the tags are attached only to that question.

Piping

Tags can also provide values for placeholders such as {brand} in question text.

Example

from survey import Survey
s = Survey(**globals())

# Using as a context manager
with s.tag(brand="Ford", category="SUV"):
    s.text_question("What do you like about {brand} {category}s?")
    s.numeric_question("How many {brand} {category}s have you owned?")

# Passing directly to a question
s.text_question(
    "Why is {brand} your favorite?",
    tags=s.tag(brand=brand_name),
)

Parameters

NameTypeDescription
**kwargsAnyKey-value pairs to use as tags.

standard_screener

standard_screener(country: str | None = None, name: str = 'GenPop') -> None

Add a standard screener to the survey.

For many countries, MX8 has a standard screener that can be used to screen respondents. This is a standard set of questions that are used to screen respondents for the survey. The questions are standard demo questions and include weights to national averages.

Availability

Standard screeners are available only for supported country and screener-name combinations.

Weighting

Standard screener questions include weights to national averages where the selected screener supports them.

Parameters

NameTypeDescription
countrystr | NoneThe country to use for the screener. This is the country code in the MX8 system. For example, "US" for the United States, "GB" for Great Britain, or "AU" for Australia.
namestrThe name of the screener to use. This is the name of the screener in the MX8 system. The default is "GenPop".

show_message

show_message(
    message: str,
    image: MediaItem | None = None,
    require_response: bool = True,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> StringResponse | BoolResponse | IntResponse | ListResponse | None

Show a message to the user.

Presents a message to the user. This could be an introductory message, a warning, or any text based piece of information.

Response requirement

By default respondents must acknowledge the message before continuing. Set require_response=False to show the message without requiring acknowledgement.

Example

from survey import Survey
s = Survey(**globals())

s.show_message("Welcome to our market research survey! Your feedback is"
       "important to us as it helps us understand market trends"
       "and consumer behavior. This survey is designed to gather"
       "information about your experiences and preferences related"
       "to our industry and company. Your responses will be kept"
       "confidential and used only for research purposes. Thank"
       "you for taking the time to participate!")

Parameters

NameTypeDescription
messagestrThe message to present to the user
require_responseboolWhether the user must acknowledge the message
imageMediaItem | NoneAn image to show with the message, from the s.media collection
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

get_consent

get_consent(
    consent_text: str | None = None,
    topic: str | None = None,
    client: str | None = None,
    image: MediaItem | None = None,
) -> BoolResponse

Get consent for the survey.

Asks the user the standard MX8 consent question. Terminates the respondent if they do not agree to the consent.

Consent termination

Respondents who do not agree to the consent question are terminated automatically.

Custom consent text

Pass consent_text to replace the standard MX8 consent text. topic, client, and image customize the standard consent question.

Example

from survey import Survey
s = Survey(**globals())

Parameters

NameTypeDescription
consent_textstr | NoneCustom consent text to show instead of the standard MX8 consent text.
topicstr | NoneTopic to display in the standard consent question.
clientstr | NoneClient description to use in the standard consent question.
imageMediaItem | NoneImage to show with the consent question, from the s.media collection.

text_question

text_question(
    question: str,
    image: MediaItem | None = None,
    default: str | list[str] | None = None,
    short: bool = False,
    recodes: dict[str, str] | None = None,
    validation_instructions: str | None = None,
    quality_threshold: int = 3,
    termination_threshold: int = 1,
    max_attempts: int = 3,
    custom_validator: Callable[[Any], str | None] | None = None,
    number_of_responses: int = 1,
    min_responses: int | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> StringResponse | ListResponse

Add a text question to the survey.

Asks an open ended question to the respondent.

Clarification and termination

If the response does not meet quality_threshold, the respondent is asked to clarify. If the response still does not meet termination_threshold after max_attempts, the survey terminates. To prevent termination, set quality_threshold to 1.

Multiple responses

number_of_responses and min_responses are only supported in landscape orientation.

Example

from survey import Survey
s = Survey(**globals())

s.text_question("When you think about cars, which brands immediately come to mind?")
from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.text_question(
        "What do you like about {brand} cars?",
        tags=s.tag(brand=brand),
    )

Custom Errors

Text questions by default are validated by AI to ensure that the response is a valid response. You can provide custom instructions for the validation by using the validation_instructions parameter.

By default, users are given 3 attempts to provide a valid response. If they fail to do so, the survey will terminate. You can change the maximum number of attempts by using the max_attempts parameter, and you can disable the termination by setting the terminate_after_max_attempts parameter to False.

If disabled, the validation error will be included in any calls to s.count_validation_errors().

If you do not want to use the AI validation, you can provide a custom_validator function that takes the response as a parameter and returns a string if the response is invalid. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

e.g. custom_validator=lambda x: "Are you sure you meant Sleepy Cows?" if x == "Sleepy Cows" else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
imageMediaItem | Nonean image to show with the question, from the s.media collection
defaultstr | list[str] | Nonethe default answer to use in generating test data. If omitted, test data uses a random string of 10 characters.
shortboolwhether to use a short text input field
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
validation_instructionsstr | Nonea string describing AI validation rules for the question. Responses are scored on a scale of 1 to 5.
quality_thresholdintthe minimum score (on a scale of 1-5) required for a valid response, defaults to 3
termination_thresholdintthe minimum score required to terminate, defaults to 1, e.g. no termination
max_attemptsintthe maximum number of attempts to get a valid response
custom_validatorCallable[[Any], str | None] | Nonefunction that returns a custom error message
number_of_responsesintthe number of responses to collect. Only supported in landscape orientation.
min_responsesint | Nonethe minimum number of responses to collect. Only supported in landscape orientation.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

text_highlighter_question

text_highlighter_question(
    question: str,
    text_to_highlight: str,
    color: str = '#FFFF00',
    default: str | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> ListResponse

Add a text highlighter question to the survey.

Asks the respondent to highlight one or more parts of a given text.

Example

from survey import Survey
s = Survey(**globals())

s.text_highlighter_question(
    "Please highlight the parts of the following text that you find most interesting:",
    text_to_highlight="The quick brown fox jumps over the lazy dog."
)

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
text_to_highlightstrthe text that the respondent will highlight parts of
colorstrthe color to use for highlighting. Must be a hex color string.
defaultstr | Nonethe default highlights to use in generating test data. If omitted, test data uses random highlights.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

numeric_question

numeric_question(
    question: str,
    image: MediaItem | None = None,
    min_max: tuple[int, int] = DEFAULT_MIN_MAX,
    default: int | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> IntResponse

Add a numeric question to the survey.

This presents the user with a numeric input for choosing a value between a range of numbers.

Range validation

The respondent must enter a number between the inclusive minimum and maximum values in min_max.

Example

from survey import Survey
s = Survey(**globals())

s.numeric_question("What year were you born in?",
                   min_max=(1900, 2024))

Recodes

Recodes can be used to change the values of the responses.

Recodes for numeric question take the format of a dictionary where the key is the range of values to be recoded, and the value is the value to recode to. You can also use percentages to recode a percentage of the values.

Example

from survey import Survey
s = Survey(**globals())

s.numeric_question("How old are you?",
                    min_max=(18, 100),
                    recodes={
                        "18-25": "18-25",
                        "26-54": "26-54",
                        "55-74": "55-74",
                        "75-100": "75+"
                    })

s.numeric_question("How many times have you visited our website?",
                    min_max=(0, 100),
                    recodes={
                        "0-30%": "Low",
                        "31-70%": "Medium",
                        "71-100%": "High"
                    })

Default values

If no default value is specified, then the test data will consist of random values between the min and max value.

from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.numeric_question(
        "How many {brand} cars have you owned?",
        min_max=(0, 10),
        recodes={
            "0-30%": "Low",
            "31-70%": "Medium",
            "71-100%": "High",
        },
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention, e.g.

custom_validator=lambda x: "Are you sure that you're 150 years old" if x > 150 else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
min_maxtuple[int, int]the inclusive minimum and maximum numeric values allowed
defaultint | Nonethe default value to use when generating test data
imageMediaItem | Nonean image to show with the question, from the s.media collection
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

select_question

select_question(
    question: str,
    options: list[str | MediaItem],
    image: MediaItem | None = None,
    randomize: bool = False,
    other_options: list[str] | None = None,
    disabled_options: list[str] | None = None,
    fixed_options: list[str] | None = None,
    specify_option: str | None = None,
    specify_text: str = DEFAULT_SPECIFY_TEXT,
    default: str | list[str] | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    skip_empty: bool = False,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    style: ChoiceQuestionStyle = 'default',
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> StringResponse

Add a multiple-choice question to the survey.

This presents the user with a number of choices from which the user can choose exactly one.

Example

from survey import Survey
s = Survey(**globals())

s.select_question("What is your gender?",
                   options=["Male", "Female", "Non-Binary"])

Randomization

Set randomize to True to randomize the order of the options. If you have any additional options you do not want to have randomized, then you can specify these in the fixed_options parameter.

Default values

If no default value is specified, then the test data will consist of random values chosen from the options. If a list is provided, one value is sampled once when the question is created.

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question on a five point scale, but want to store the response on a three point scale, you can use recodes to map the response to the correct value.

Disabled Options

Options can be disabled using the disabled_options parameter. This is useful if you want to disable an option based on a previous response. For example, if you want to ask a question about what they enjoyed about the experience, and then ask about what they didn't enjoy, you can disable the options they selected in the first question.

Other, please specify

If you want to allow the user to specify an option that is not in the list, you can set the specify_option parameter. This will present the user with an additional option to specify their own value.

from survey import Survey
s = Survey(**globals())

s.select_question("On a scale of 1 to 5, how much do you like this product?",
    options=["1", "2", "3", "4", "5"],
    recodes={
        "1": "dislike",
        "2": "dislike",
        "3": "neutral",
        "4": "like",
        "5": "like"})
from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.select_question(
        "How many {brand} cars have you owned?",
        options=["0", "1", "2", "3", "4+"],
        recodes={
            "0": "never owned",
            "1": "single owner",
            "2": "loyalist",
            "3": "loyalist",
            "4+": "enthusiast",
        },
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

e.g. custom_validator=lambda x: "Are you sure you meant Sleepy Cows?" if x == "Sleepy Cows" else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
optionslist[str | MediaItem]a list of strings or items from s.media containing the options
imageMediaItem | Nonean image to show with the question, from the s.media collection
randomizeboolwhether to randomize the order of the options
other_optionslist[str] | Nonea list of additional options to present to the user
disabled_optionslist[str] | Nonea list of strings containing options to be disabled
fixed_optionslist[str] | Nonea list of strings containing the fixed options
skip_emptyboolif set to True, the question will be skipped if there are no options
specify_optionstr | Nonean "other" option that the respondent can specify
specify_textstrtext to show when the respondent selects specify_option; defaults to "Please specify"
defaultstr | list[str] | Nonethe default value or values to use when generating test data
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the options
show_image_labelboolwhether to show the image label or just to show the image
image_sizetuple[int, int] | Nonethe bounding box size of the image to display
styleChoiceQuestionStylepresentation style for the options.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

multi_select_question

multi_select_question(
    question: str,
    options: list[str | MediaItem],
    max_options: int | None = None,
    image: MediaItem | None = None,
    randomize: bool = False,
    other_options: list[str] | None = None,
    disabled_options: list[str] | None = None,
    fixed_options: list[str] | None = None,
    exclusive_options: list[str] | None = None,
    specify_option: str | None = None,
    specify_text: str = DEFAULT_SPECIFY_TEXT,
    default: list[str] | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    skip_empty: bool = False,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    style: ChoiceQuestionStyle = 'default',
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> ListResponse

Add a multi-select question to the survey.

This presents the user with a number of choices from which the user can choose one or more.

Example

from survey import Survey
s = Survey(**globals())

s.select_question("What is your gender?",
                   options=["Male", "Female", "Non-Binary"])

Randomization

Set randomize to True to randomize the order of the options. If you have any additional options you do not want to have randomized, then you can specify these in the fixed_options parameter.

Default values

If no default value is specified, then the test data will consist of random values chosen from the options.

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question about what they enjoyed about the experience, and classify it into different categories, you can use recodes to map the response to the correct value.

Disabled Options

Options can be disabled using the disabled_options parameter. This is useful if you want to disable an option based on a previous response. For example, if you want to ask a question about what they enjoyed about the experience, and then ask about what they didn't enjoy, you can disable the options they selected in the first question.

Exclusive options

Options listed in exclusive_options are mutually exclusive with other selected options.

Other, please specify

If you want to allow the user to specify an option that is not in the list, you can set the specify_option parameter. This will present the user with an additional option to specify their own value.

from survey import Survey
s = Survey(**globals())

enjoyment = s.multi_select_question("What did you enjoy about the experience?",
    options=["The food", "The service", "The atmosphere", "The price"],
    fixed_options=["Other"],
    exclusive_options=["Other"],
    recodes={
        "The food": "food",
        "The service": "service",
        "The atmosphere": "atmosphere",
        "The price": "price",
        "Other": "other"})

if "Other" in enjoyment:
    s.text_question("What else did you enjoy about the experience?")
from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.select_question(
        "Which of the following do you associate with {brand} cars?",
        options=["Reliability", "Safety", "Performance", "Luxury"],
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

e.g. custom_validator=lambda x: "Are you sure you meant Sleepy Cows?" if "Sleepy Cows" in x else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
optionslist[str | MediaItem]a list of strings or items from s.media containing the options
max_optionsint | Nonethe maximum number of options to be selected, defaults to all options
imageMediaItem | Nonean image to show with the question, from the s.media collection
randomizeboolwhether to randomize the order of the options
other_optionslist[str] | Nonea list of additional options to present to the user
disabled_optionslist[str] | Nonea list of strings containing options to be disabled
fixed_optionslist[str] | Nonea list of strings containing the fixed options
exclusive_optionslist[str] | Nonea list of strings containing the exclusive options
specify_optionstr | Nonean "other" option that the respondent can specify
specify_textstrtext to show when the respondent selects specify_option; defaults to "Please specify"
skip_emptyboolif set to True, the question will be skipped if there are no options
defaultlist[str] | Nonethe default values to use when generating test data
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the options
show_image_labelboolwhether to show the image label or just to show the image
image_sizetuple[int, int] | Nonethe bounding box size of the image to display
styleChoiceQuestionStylepresentation style for the options.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

grid_select_question

grid_select_question(
    question: str,
    rows: list[str | MediaItem],
    row_name: str,
    options: list[str | MediaItem],
    style: GridSelectStyle = 'radio',
    randomize: bool = False,
    randomize_options: bool = False,
    other_options: list[str] | None = None,
    fixed_options: list[str] | None = None,
    specify_option: str | None = None,
    specify_text: str = DEFAULT_SPECIFY_TEXT,
    image: MediaItem | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    skip_empty: bool = False,
    default: dict[str, str | list[str]] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a grid select question to the survey.

This presents the user with a grid of options where they can select one option from each row.

Example

from survey import Survey
s = Survey(**globals())

s.grid_select_question(
    "Please rate the following aspects of the product",
    row_name="Product Aspect",
    rows=["Quality", "Price", "Service", "Delivery"],
    options=["Poor", "Fair", "Good", "Very Good", "Excellent"],
)

Randomization

Set randomize=True to randomize rows and randomize_options=True to randomize options. fixed_options remain fixed when option randomization is used.

Default values

Defaults are provided as a dictionary keyed by row. List values are sampled once when the question is created.

Other, please specify

Use specify_option to add an option where the respondent can provide their own value.

Skip empty

If skip_empty=True, the question is skipped when there are no rows or no options.

Style

style controls the grid control style, either "button" or "radio".

Recodes: Recodes map responses to different values. This is useful when you want to classify responses into categories.

s.grid_select_question(
    "Please rate the following aspects of the product",
    row_name="Product Aspect",
    rows=["Quality", "Price", "Service", "Delivery"],
    options=["Poor", "Fair", "Good", "Very Good", "Excellent"],
    recodes={
        "Poor": "1",
        "Fair": "2",
        "Good": "3",
        "Very Good": "4",
        "Excellent": "5",
    },
)

Custom Errors: By default, custom_validator is set to a straight-line check that raises an error if the respondent enters the same value for each cell. You can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrThe question to ask the respondent.
rowslist[str | MediaItem]Strings or media items containing the rows.
row_namestrLabel to use for each row in topics.
optionslist[str | MediaItem]Strings or media items containing the options.
styleGridSelectStyleThe style of the question ("button" or "radio"). Defaults to "radio".
randomizeboolWhether to randomize the order of the rows.
randomize_optionsboolWhether to randomize the order of the options.
other_optionslist[str] | NoneAdditional options to present to the user.
fixed_optionslist[str] | NoneFixed options that should not be randomized.
specify_optionstr | NoneAn "other" option that the respondent can specify.
specify_textstrText to show when the respondent selects specify_option.
imageMediaItem | NoneAn image to show with the grid, from the s.media collection.
recodesdict[str, str] | NoneA dictionary of recodes to apply to the question.
custom_validatorCallable[[str], str] | NoneA function that returns a custom error message.
image_label_fieldstr | NoneThe field to use as the label for media items in the rows.
show_image_labelboolWhether to show the image label or just the image in the rows.
image_sizetuple[int, int] | NoneThe bounding box size of the image to display.
skip_emptyboolIf True, skip the question when there are no rows or options.
defaultdict[str, str | list[str]] | NonePer-row defaults; list values are sampled once at creation.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tag(...).
**_kwargsAnyAdditional question configuration.

grid_multi_select_question

grid_multi_select_question(
    question: str,
    rows: list[str | MediaItem],
    row_name: str,
    options: list[str | MediaItem],
    style: GridMultiSelectStyle = 'checkbox',
    randomize: bool = False,
    randomize_options: bool = False,
    other_options: list[str] | None = None,
    fixed_options: list[str] | None = None,
    exclusive_options: list[str] | None = None,
    specify_option: str | None = None,
    specify_text: str = DEFAULT_SPECIFY_TEXT,
    image: MediaItem | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    skip_empty: bool = False,
    default: dict[str, list[str]] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a grid multi-select question to the survey.

This presents the user with a grid of options where they can select multiple options from each row.

Example

from survey import Survey
s = Survey(**globals())

s.grid_multi_select_question("What do you associate with these brands? Select all that apply",
    rows=["Coke", "Pepsi", "Dasani", "Fanta"],
    options=["Tasty", "Refreshing", "Cool", "Value", "Convenient"])

Randomization

Set randomize=True to randomize rows and randomize_options=True to randomize options. fixed_options remain fixed when option randomization is used.

Default values

Defaults are provided as a dictionary keyed by row.

Exclusive options

Options listed in exclusive_options are mutually exclusive with other selected options in the same row.

Other, please specify

Use specify_option to add an option where the respondent can provide their own value.

Skip empty

If skip_empty=True, the question is skipped when there are no rows or no options.

Style

style controls the grid control style, either "button" or "checkbox".

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question about what they enjoyed about the experience, and classify it into different categories, you can use recodes to map the response to the correct value.

Example

from survey import Survey
s = Survey(**globals())

s.grid_multi_select_question("What do you associate with these brands? Select all that apply",
    row_name="Brand",
    rows=["Coke", "Pepsi", "Dasani", "Fanta"],
    options=["Tasty", "Refreshing", "Cool", "Value", "Convenient"],
    recodes={
        "Tasty": "Product",
        "Refreshing": "Product",
        "Cool": "Brand",
        "Value": "Pricing",
        "Convenient": "Distribution"
    })

Custom Errors

By default, the custom_validator parameter is set to a straight-line check that raises an error if the respondent enters the same value for each cell but you can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
rowslist[str | MediaItem]the rows to show in the grid
row_namestrthe name of the row
optionslist[str | MediaItem]the options available for each row
styleGridMultiSelectStylethe question style, either "button" or "checkbox"; defaults to "checkbox"
randomizeboolwhether to randomize the order of the rows
randomize_optionsboolwhether to randomize the order of the options
other_optionslist[str] | Noneadditional options to present to the user
fixed_optionslist[str] | Noneoptions that should remain fixed in place
exclusive_optionslist[str] | Noneoptions that should be mutually exclusive
specify_optionstr | Nonean "other" option that the respondent can specify
specify_textstrtext to show when the respondent selects specify_option
imageMediaItem | Nonean image to show with the grid, from the s.media collection
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
defaultdict[str, list[str]] | Nonedefault values to use when generating test data
skip_emptyboolif True, skip the question when there are no rows
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the rows
show_image_labelboolwhether to show the image label or just to show the image in the rows
image_sizetuple[int, int] | Nonethe bounding box size of the image to display
custom_validatorCallable[[str], str] | Nonea function to return a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

grid_rating_question

grid_rating_question(
    question: str,
    rows: list[str | MediaItem],
    row_name: str,
    image: MediaItem | None = None,
    number_of_points: int | None = None,
    style: RatingQuestionStyle = 'slider',
    first_point: int | None = None,
    labels: dict[int, str] | None = None,
    randomize: bool = False,
    recodes: dict[str, str] | None = None,
    dont_know_option: str = '',
    skip_empty: bool = False,
    custom_validator: Callable[[str], str] | None = None,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    default: dict[str, int] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a grid rating question to the survey.

This presents the user with a grid of options where they can rate each row on user defined scale.

Example

from survey import Survey
s = Survey(**globals())

s.grid_rating_question("Please rate the following aspects of the product",
    rows=["Quality", "Price", "Service", "Delivery"],
    row_name="Product Aspect",
    number_of_points=5)

Randomization

Set randomize=True to randomize the row order.

Default values

Defaults are provided as a dictionary keyed by row.

Style and labels

style, number_of_points, first_point, and labels control the rating scale shown for each row.

Using the don't know option

Use dont_know_option to add a "Don't know" option. It is reported using the provided text and stored as a sentinel value.

Skip empty

If skip_empty=True, the question is skipped when there are no rows.

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question about what they enjoyed about the experience, and classify it into different categories, you can use recodes to map the response to the correct value.

Example

from survey import Survey
s = Survey(**globals())

s.grid_rating_question("Please rate the following aspects of the product",
    row_name="Product Aspect",
    rows=["Quality", "Price", "Service", "Delivery"],
    number_of_points=5,
    recodes={
        "1": "Poor",
        "2": "Poor",
        "3": "Fair",
        "4": "Fair",
        "5": "Good"
    })

Custom Errors

By default, the custom_validator parameter is set to a straight-line check that raises an error if the respondent enters the same value for each cell but you can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
rowslist[str | MediaItem]a list of strings or media items containing the rows
row_namestrthe name of the row
imageMediaItem | Nonean image to show with the grid, from the s.media collection
number_of_pointsint | Nonethe number of points to rate the row on, defaults to 5 or the number of labels
styleRatingQuestionStylethe style of the question, either "slider", "button" or "star"
first_pointint | Nonethe value of the first point, defaults to 1 or the first label
labelsdict[int, str] | Nonea dictionary of labels to use on the question
randomizeboolwhether to randomize the order of the rows
defaultdict[str, int] | Nonea dictionary of default values to use when generating test data
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
dont_know_optionstrlabel to use for the don't-know option, if any.
skip_emptyboolif set to True, the question will be skipped if there are no rows
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the rows
show_image_labelboolwhether to show the image label or just to show the image in the rows
image_sizetuple[int, int] | Nonethe bounding box size of the images to display
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

grid_numeric_question

grid_numeric_question(
    question: str,
    rows: list[str | MediaItem],
    row_name: str,
    columns: list[str] | None = None,
    column_name: str | None = None,
    image: MediaItem | None = None,
    min_max: tuple[int, int] = DEFAULT_MIN_MAX,
    randomize: bool = False,
    randomize_columns: bool = False,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[int], str] | None = None,
    autosum_columns: bool = False,
    autosum_rows: bool = False,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    default: dict[str, int | dict[str, int]] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a grid numeric question to the survey.

This presents the user with a grid of options where they can select one option from each row.

Example

from survey import Survey
s = Survey(**globals())

s.grid_numeric_question("How many hours do you spend per week on the following activities?",
    rows=["Work", "Sleep", "Exercise", "Socializing"],
    row_name="Activity",
    columns=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
    randomize=True,
    )

Randomization

Set randomize=True to randomize rows and randomize_columns=True to randomize columns.

Default values

Defaults are provided as a dictionary keyed by row, or by row and column when columns are used.

Range validation

Each numeric response must be between the inclusive minimum and maximum values in min_max.

Autosum

Set autosum_columns or autosum_rows to require responses to sum correctly by column or row.

Recodes

Recodes can be used to change the values of the responses.

Recodes for numeric question take the format of a dictionary where the key is the range of values to be recoded, and the value is the value to recode to. You can also use percentages to recode a percentage of the values.

Example

from survey import Survey
s = Survey(**globals())

s.grid_numeric_question("How many hours do you spend per week on the following activities?",
    rows=["Work", "Sleep", "Exercise", "Socializing"],
    row_name="Activity",
    columns=["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
    randomize=True,
    recodes={
        "0-30%": "Low",
        "31-70%": "Medium",
        "71-100%": "High"
    })

Custom Errors

By default, the custom_validator parameter is set to a straight-line check that raises an error if the respondent enters the same value for each cell but you can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
rowslist[str | MediaItem]a list of strings containing the rows
row_namestrthe name of the row
columnslist[str] | Nonea list of strings containing the columns
column_namestr | Nonethe name of the column
imageMediaItem | Nonean image to show with the grid, from the s.media collection
min_maxtuple[int, int]the minimum and maximum values for the question
randomizeboolwhether to randomize the order of the rows
randomize_columnsboolwhether to randomize the order of the columns
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
defaultdict[str, int | dict[str, int]] | Nonea dictionary of default values to use when generating test data
autosum_columnsboolwhether to autosum the columns
autosum_rowsboolwhether to autosum the rows
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the rows
show_image_labelboolwhether to show the image label or just to show the image in the rows
image_sizetuple[int, int] | Nonethe bounding box size of the images to display
custom_validatorCallable[[int], str] | Nonefunction that returns a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

this_or_that_question

this_or_that_question(
    question: str,
    row_options: list[list[str | MediaItem]],
    randomize: bool = False,
    randomize_columns: bool = False,
    default: dict[str, str | list[str]] | None = None,
    recodes: dict[str, str] | None = None,
    image: MediaItem | None = None,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    custom_validator: Callable[[int], str] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a this or that question to the survey.

This presents the user with a series of questions where they can select one option from each row.

Example

from survey import Survey
s = Survey(**globals())

s.this_or_that_question("Which of the following do you prefer?",
    row_options=[
        ["Apples", "Oranges"],
        ["Coke", "Pepsi"],
        ["Dogs", "Cats"],
        ["Winter", "Summer"]
    ])

Randomization

Set randomize=True to randomize row order and randomize_columns=True to randomize the order of options within each row.

Default values

Defaults are provided as a dictionary keyed by row id. List values are sampled once when the question is created.

Media options

row_options may contain media items. Use image_label_field, show_image_label, and image_size to control media option labels and display size.

Recodes

Recodes can be used to change the values of the responses.

Recodes for this or that question take the format of a dictionary where the key is the range of values to be recoded, and the value is the value to recode to. You can also use percentages to recode a percentage of the values.

Example

from survey import Survey
s = Survey(**globals())

s.this_or_that_question("Which of the following do you prefer?",
    row_options=[
        ["Apples", "Oranges"],
        ["Coke", "Pepsi"],
        ["Dogs", "Cats"],
        ["Winter", "Summer"]
    ])

Custom Errors

By default, the custom_validator parameter is set to a straight-line check that raises an error if the respondent enters the same value for each cell but you can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
row_optionslist[list[str | MediaItem]]a list of lists containing the options
randomizeboolwhether to randomize the order of the rows
randomize_columnsboolwhether to randomize the order of the columns
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
defaultdict[str, str | list[str]] | Noneper-row defaults; list values are sampled once at creation
imageMediaItem | Nonean image to show with the question, from the s.media collection
image_label_fieldstr | Nonethe media field to use as the image label
show_image_labelboolwhether to show the image label or just to show the image in the rows
image_sizetuple[int, int] | Nonethe bounding box size of the images to display
custom_validatorCallable[[int], str] | Nonefunction that returns a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

this_or_that_rating_question

this_or_that_rating_question(
    question: str,
    row_options: list[list[str]],
    row_name: str = 'scale',
    number_of_points: int | None = None,
    style: ThisOrThatRatingStyle = 'slider',
    first_point: int | None = None,
    dont_know_option: str = '',
    randomize: bool = False,
    default: dict[str, int] | None = None,
    recodes: dict[str, str] | None = None,
    image: MediaItem | None = None,
    custom_validator: Callable[[int], str] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a this or that rating question to the survey.

This presents the user with a series of questions where they can rate one option from each row.

Example

from survey import Survey
s = Survey(**globals())

s.this_or_that_rating_question("Which of the following do you prefer?",
    number_of_points=5,
    row_options=[
        ["Apples", "Oranges"],
        ["Coke", "Pepsi"],
        ["Dogs", "Cats"],
        ["Winter", "Summer"]
    ])

Randomization

Set randomize=True to randomize row order.

Default values

Defaults are provided as a dictionary keyed by row name.

Style and scale

style, number_of_points, first_point, and row_options define the rating scale shown for each row.

Using the don't know option

Use dont_know_option to add a "Don't know" option. It is reported using the provided text and stored as a sentinel value.

Recodes

Recodes can be used to change the values of the responses.

Recodes for this or that question take the format of a dictionary where the key is the range of values to be recoded, and the value is the value to recode to. You can also use percentages to recode a percentage of the values.

Example

from survey import Survey
s = Survey(**globals())

s.this_or_that_rating_question("Which of the following do you prefer?",
    row_options=[
        ["Apples", "Oranges"],
        ["Coke", "Pepsi"],
        ["Dogs", "Cats"],
        ["Winter", "Summer"]
    ])

Custom Errors

By default, the custom_validator parameter is set to a straight-line check that raises an error if the respondent enters the same value for each cell but you can override this by passing a custom validator function.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
row_optionslist[list[str]]a list of lists containing the options
row_namestrthe name of the row (default "scale")
number_of_pointsint | Nonethe number of points to rate the row on, defaults to 5
styleThisOrThatRatingStylethe style of the question, either "slider" or "button"
first_pointint | Nonethe value of the first point, defaults to 1
dont_know_optionstrOptionally add a "Don't know" option to the question using this text.
randomizeboolwhether to randomize the order of the rows
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
defaultdict[str, int] | Nonea dictionary of default values to use when generating test data
imageMediaItem | Nonean image to show with the question, from the s.media collection
custom_validatorCallable[[int], str] | Nonefunction that returns a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

show_image

show_image(image: MediaItem, number_seconds: int = 5) -> StringResponse | BoolResponse | IntResponse | ListResponse | None

Show an image to the respondent.

This will show an image to the respondent for a set number of seconds.

Timed display

The image is displayed for number_seconds seconds before the respondent can continue.

Response requirement

The respondent does not provide an answer; this method is for controlled image exposure.

Example

from survey import Survey
s = Survey(**globals())

for media in s.media:
    s.show_image("Take a look at this image for 5 seconds",
                image=media,
                number_seconds=5)

Parameters

NameTypeDescription
imageMediaItemthe image to show
number_secondsintthe number of seconds to show the image

play_video

play_video(
    video: MediaItem,
    end_message: str = 'Thank you for watching the video!',
    start_message: str = "Hit play when you're ready to watch the video.",
    dial: str = 'none',
    dial_labels: dict[int, str] | None = None,
    dial_caption: str = 'Please adjust the slider to reflect your feelings as you watch the video.',
    start_position: int | None = None,
) -> StringResponse | BoolResponse | IntResponse | ListResponse | None

Play a video to the respondent.

This will play a video to the respondent and then present them with a message at the end, which defaults to "Thank you for watching the video!".

Video security

Videos are DRM protected to prevent copying or screen recording. Secure videos are watermarked with the respondent's IP address, and video links expire after 60 seconds.

Example

from survey import Survey
s = Survey(**globals())

for media in s.media:
    s.play_video(video=media)

    ... now ask questions about the video

Dial testing

You can also add a dial to the video to allow the respondent to indicate their feelings as they watch the video. The dial can be set to one of the following values:

  • "none" - no dial is shown
  • "right" - a dial is shown on the right of the video
  • "bottom" - a dial is shown at the bottom of the video

The dial defaults to a range of 0-100, with captions "Dislike", "Neutral", and "Like" but you can set the labels and caption for the dial using the dial_labels and dial_caption parameters.

Parameters

NameTypeDescription
videoMediaItemthe video to play, from the s.media collection
end_messagestrthe message to show the respondent at the end of the video
start_messagestrthe message to show the respondent at the start of the video
dialstrthe position of the dial, either "none", "right", or "bottom", defaults to "none"
dial_labelsdict[int, str] | Nonethe labels to show on the dial
dial_captionstrthe caption to show above the dial
start_positionint | Nonestarting value for the dial; defaults to the midpoint of the dial labels

show_embedded_media

show_embedded_media(question: str, url: str, duration: int) -> StringResponse

Embed external content from a URL in the survey.

Supported providers

Embedded media currently supports YouTube URLs only.

Example

from survey import Survey
s = Survey(**globals())

s.show_embedded_media("Watch this video", "https://www.youtube.com/watch?v=2gcsgfzqN8k", duration=231)

Parameters

NameTypeDescription
questionstrthe question to show above the embedded media
urlstrthe URL to embed in the survey
durationintthe number of seconds the respondent must wait before continuing

get_location

get_location(
    validation_type: str | None = None,
    reporting_type: str | None = None,
    default: str | None = None,
    number_options: int = 5,
    tags: dict[str, Any] | None = None,
    strict: bool = False,
    **_kwargs: Any = {},
) -> StringResponse

Ask the respondent for their location and validate it with a follow-up gotcha question.

Adds two questions to the survey. The first asks for the user's ZIP code, and the second validates that ZIP code based on validation_type. The location is reported based on the reporting_type.

Example

s.get_location(validation_type="state", default="90210")

Recodes

The location question does not support recodes.

Parameters

NameTypeDescription
validation_typestr | Nonethe type of location to validate against; defaults to city
reporting_typestr | Nonethe type of location to report, defaults to region
defaultstr | Nonethe default ZIP code to use in test data. If omitted, test data uses random values chosen from the options.
number_optionsintthe number of matching locations to show the respondent
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.
strictboolwhether to terminate the respondent if they enter invalid location data

quota

quota(
    name: str,
    criteria: bool,
    quota: float = 0.0,
    min_respondents: int = 0,
    max_respondents: int | None = None,
) -> Quota

Create a quota line.

Use this to define an individual quota line that can be passed to s.set_quota().

Quota line only

This method only defines a quota line. Pass the returned quota to s.set_quota() to apply quota logic to the survey.

Minimum and maximum respondents

min_respondents enforces a minimum fill for the line. max_respondents caps the number of respondents assigned to the line.

Example

from survey import Survey

s = Survey(**globals())

age = s.numeric_question(question="How old are you?",
                        min_max=(18, 100),
                        recodes={
                            "0-17": "Under 18",
                            "18-34": "18-34",
                            "35-54": "35-54",
                            "55-120": "55+"
                        })

gender = s.select_question(question="What is your gender?",
                                options=["Male", "Female"])

s.set_quota(
    name="Quads",
    quotas=[
        s.quota("Younger Men", criteria=(age < 40) & (gender == "Male"), quota=0.25),
        s.quota("Older Men", criteria=(age >= 40) & (gender == "Male"), quota=0.25),
        s.quota("Younger Women", criteria=(age < 40) & (gender == "Female"), quota=0.25),
        s.quota("Older Women", criteria=(age >= 40) & (gender == "Female"), quota=0.25)
    ]
)

You can also specify a minimum number of respondents that must be included in this quota line by passing the min_respondents parameter.

Example

from survey import Survey

s = Survey(**globals())

s.set_quota(
    name="Age Quota",
    quotas=[
        s.quota("Under 18", criteria=(age < 18), quota=0.1, min_respondents=50),
        s.quota("18-34", criteria=(age >= 18) & (age < 35), quota=0.3, min_respondents=100),
        s.quota("35-54", criteria=(age >= 35) & (age < 55), quota=0.3, min_respondents=100),
        s.quota("55+", criteria=(age >= 55), quota=0.3, min_respondents=100)
    ]
)

If you only pass in values with min_respondents, then the quota will not be used for weighting, but it will still be used to terminate respondents that do not meet the criteria.

You can optionally set max_respondents to cap the number of respondents assigned to a quota line. If omitted, that line has no explicit cap.

Parameters

NameTypeDescription
namestrthe name to give this quota line when it is reported
criteriaboolthe boolean condition based on responses to other questions that the respondent must meet to be included in this quota line
quotafloatthe proportion of respondents that should fall into this quota
min_respondentsintthe minimum number of respondents that must be included in this quota line
max_respondentsint | Noneoptional maximum number of respondents for this quota line

set_quota

set_quota(name: str, quotas: list[Quota], exclusive: bool = True) -> QuotaResponse

Set a quota group based on a number of quota lines.

Takes a group of individual quotas from s.quota() and creates a quota group.

Termination behavior

Respondents are terminated when they do not match any quota line or when their matching quota line is over quota.

Exclusive quotas

By default quotas are exclusive and each respondent fills one matching quota line. If exclusive=False, respondents may be included in multiple matching quota lines.

Example

from survey import Survey

s = Survey(**globals())

age = s.numeric_question(question="How old are you?",
                        min_max=(18, 100),
                        recodes={
                            "0-17": "Under 18",
                            "18-34": "18-34",
                            "35-54": "35-54",
                            "55-120": "55+"
                        })

gender = s.select_question(question="What is your gender?",
                                options=["Male", "Female"])

# Check age and gender quotas
s.set_quota(
    name="Quads",
    quotas=[
        s.quota("Younger Men", criteria=(age < 40) & (gender == "Male"), quota=0.25),
        s.quota("Older Men", criteria=(age >= 40) & (gender == "Male"), quota=0.25),
        s.quota("Younger Women", criteria=(age < 40) & (gender == "Female"), quota=0.25),
        s.quota("Older Women", criteria=(age >= 40) & (gender == "Female"), quota=0.25)
    ]
)

Parameters

NameTypeDescription
namestrthe name of this group of quotas, for example "demos"
quotaslist[Quota]a list of quotas to be added, each created using s.quota()
exclusiveboolwhether the quotas are exclusive, defaults to True. If set to False, then respondents can be included in multiple quota lines, but they will still be terminated if they do not meet any of the quota criteria.

get_least_filled

get_least_filled(number: int, from_list: list[Any], quota: str) -> list[Any]

Get the least filled quotas from a list of elements in a quota.

Selects the least-filled items from a list for balanced follow-up questions.

Selection only

This function returns the least-filled items from the list, but does not terminate the respondent based on quota state.

Terminating by quota

To terminate respondents based on quota state, use s.set_quota() to establish quotas.

Example

from survey import Survey

s = Survey(**globals())

brand_list = ['Acura', 'Alfa Romeo', 'Aston Martin', 'Audi', 'Bentley',
              'BMW', 'Bugatti', 'Buick', 'Cadillac', 'Chevrolet', 'Chrysler',
              'Citroen', 'Dodge', 'Ferrari', 'Fiat', 'Ford', 'Geely', 'Genesis',
              'GMC', 'Honda', 'Hyundai', 'Infiniti', 'Jaguar', 'Jeep', 'Kia',
              'Koenigsegg', 'Lamborghini', 'Land Rover', 'Lexus', 'Lincoln',
              'Lotus', 'Maserati', 'Mazda', 'McLaren', 'Mercedes-Benz', 'Mini',
              'Mitsubishi', 'Nissan', 'Pagani', 'Peugeot', 'Polestar', 'Porsche',
              'Ram', 'Renault', 'Rolls-Royce', 'Subaru', 'Suzuki', 'Tesla', 'Toyota',
              'Volkswagen', 'Volvo']


brands = s.get_least_filled(number=10,
                            from_list=brand_list,
                            quota="Brand familiarity")

familiar_brands = s.multi_select_question(
    question="Which of the following car brands have you heard of?",
    options=brands)

Parameters

NameTypeDescription
numberintthe number of items to return
from_listlist[Any]the list of items to choose from
quotastrthe name of the quota to check against

terminate

terminate(reason: str) -> NoReturn

Terminate the survey.

Use this when the respondent does not qualify for a specific reason.

Immediate termination

This immediately adds a terminal survey element and stops execution.

Quota termination

Use s.set_quota() for quota-based termination; it also terminates respondents who do not qualify for the quotas.

Example

if not has_xbox:
    s.terminate(
        "Sorry you don't qualify for this survey "
        "because you don't have an Xbox"
    )

Parameters

NameTypeDescription
reasonstrThe reason to give to the user to explain why they don't qualify.

terminate_if

terminate_if(condition: bool, reason: str) -> None

Terminate the survey if a condition is met.

Conditionally stops the survey for a respondent.

Condition evaluation

If condition is true, this calls s.terminate(reason) immediately. If it is false, survey execution continues.

Example

s.terminate_if(
    not has_xbox,
    "Sorry you don't qualify for this survey because you don't have an Xbox",
)

Parameters

NameTypeDescription
conditionboolThe condition to check and terminate the user if it's True
reasonstrThe reason to give to the user to explain why they don't qualify.

complete

complete(
    message: str = 'Thank you for completing our survey! See you again.',
) -> None

Complete the survey.

Call this function at the end of the survey to complete the survey and thank the user.

Completion behavior

This adds a non-terminated completion element. Use s.terminate() instead when the respondent should be screened out.

Example

from survey import Survey
s = Survey(**globals())

# Add questions here

s.complete()

Parameters

NameTypeDescription
messagestrThe message to display to the user upon completing the survey.

rating_question

rating_question(
    question: str,
    image: MediaItem | None = None,
    number_of_points: int | None = None,
    style: RatingQuestionStyle = 'slider',
    first_point: int | None = None,
    labels: dict[int, str] | None = None,
    default: int | None = None,
    recodes: dict[str, str] | None = None,
    dont_know_option: str = '',
    custom_validator: Callable[[str], str] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> IntResponse

Add a rating-scale question to the survey.

This presents the respondent with a numeric scale and records the selected point as an integer response.

There are three available styles:

  • style="slider" - For the slider style, the user can drag the slider.
  • style="button" - For the button style, the user can click on the button they want.

In both the slider and button styles, the rating scale is presented as a number with optional text labels for appropriate points on the scale.

Style and scale

style, number_of_points, first_point, and labels control how the rating scale is displayed and what values it records.

Example

from survey import Survey
s = Survey(**globals())

s.rating_question("How much do you like this product?",
                  number_of_points=5,
                  style="slider",
                  labels={
                      1: "Dislike",
                      3: "Neutral",
                      5: "Like"
                  })

Default values

If no default value is specified, then the test data will consist of random values chosen from the options.

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question about what they enjoyed about the experience, and classify it into different categories, you can use recodes to map the top two box response to a different value.

from survey import Survey
s = Survey(**globals())

s.rating_question("On a scale of 1 to 5, how much do you like this product?",
    number_of_points=5,
    style="slider",
    labels={
        1: "Dislike",
        3: "Neutral",
        5: "Like"
    },
    recodes={
        "1": "dislike",
        "2": "dislike",
        "3": "neutral",
        "4": "like",
        "5": "like"})

Using the don't know option

If you want to add a "Don't know" option to the question, then you can specify the text to use for the "Don't know" option using the dont_know_option parameter. If the user selects this option then the response will be recorded as a value of -999 in the survey data and reported as the text specified in the dont_know_option parameter.

from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.rating_question(
        "How do you rate {brand} cars?",
        number_of_points=5,
        style="slider",
        labels={1: "Dislike", 3: "Neutral", 5: "Like"},
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
number_of_pointsint | Nonethe number of points on the scale (default to 5 or the number of labels)
first_pointint | Nonethe first point on the scale (default to 1 or the first label)
styleRatingQuestionStylethe style of the scale, either "slider", "button", or "star"
labelsdict[int, str] | Nonea dictionary of text labels for each point on the scale
imageMediaItem | Nonean image to show with the question, from the s.media collection
defaultint | Nonethe default value to use when generating test data
dont_know_optionstrOptionally add a "Don't know" option to the question using this text.
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

net_promoter_score_question

net_promoter_score_question(
    question: str,
    image: MediaItem | None = None,
    number_of_points: int | None = 11,
    style: RatingQuestionStyle = 'slider',
    first_point: int | None = None,
    labels: dict[int, str] | None = None,
    default: int | None = None,
    recodes: dict[str, str] | None = None,
    dont_know_option: str = '',
    custom_validator: Callable[[str], str] | None = None,
    min_promoter_score: int | None = None,
    max_detractor_score: int | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> IntResponse

Add a Net Promoter Score question to the survey.

This asks the respondent how likely they are to recommend a product, brand, service, or experience. By default, it uses an 11-point scale from 0 to 10 with endpoint labels for "Not at all likely" and "Extremely likely".

There are three available styles:

  • style="slider" - For the slider style, the user can drag the slider.
  • style="button" - For the button style, the user can click on the button they want.

In both the slider and button styles, the NPS scale is presented as a number with optional text labels for appropriate points on the scale.

Style and scale

style, number_of_points, first_point, and labels control how the rating scale is displayed and what values it records.

Promoter and detractor scoring

min_promoter_score sets the minimum score classified as promoter, and max_detractor_score sets the maximum score classified as detractor.

Example

from survey import Survey
s = Survey(**globals())

s.net_promoter_score_question(
    "How likely are you to recommend this product to a friend or colleague?",
    number_of_points=11,
    style="slider",
    labels={
        0: "Not at all likely",
        10: "Extremely likely",
    },
)

Default values

If no default value is specified, then the test data will consist of random values chosen from the options.

Recodes

Recodes are used to map the response to a different value. For NPS questions, recodes are most often used to map raw scores into detractor, passive, and promoter groups.

from survey import Survey
s = Survey(**globals())

s.net_promoter_score_question(
    "How likely are you to recommend this product to a friend or colleague?",
    number_of_points=11,
    style="slider",
    recodes={
        "0": "detractor",
        "1": "detractor",
        "2": "detractor",
        "3": "detractor",
        "4": "detractor",
        "5": "detractor",
        "6": "detractor",
        "7": "passive",
        "8": "passive",
        "9": "promoter",
        "10": "promoter",
    },
)

Using the don't know option

If you want to add a "Don't know" option to the question, then you can specify the text to use for the "Don't know" option using the dont_know_option parameter. If the user selects this option then the response will be recorded as a value of -999 in the survey data and reported as the text specified in the dont_know_option parameter.

from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

for brand in car_brands:
    s.net_promoter_score_question(
        "How likely are you to recommend {brand} cars to a friend or colleague?",
        number_of_points=11,
        style="slider",
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
number_of_pointsint | Nonethe number of points on the NPS scale; defaults to 11 for a 0-10 scale
first_pointint | Nonethe first point on the scale (default to 1 or the first label)
styleRatingQuestionStylethe style of the scale, either "slider", "button", or "star"
labelsdict[int, str] | Nonea dictionary of labels (default {0:"Not at all likely", 10:"Extremely likely"})
imageMediaItem | Nonean image to show with the question, from the s.media collection
defaultint | Nonethe default value to use when generating test data
dont_know_optionstrOptionally add a "Don't know" option to the question using this text.
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
min_promoter_scoreint | Noneminimum score required to classify a respondent as a promoter
max_detractor_scoreint | Nonemaximum score that classifies a respondent as a detractor
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

ranking_question

ranking_question(
    question: str,
    options: list[str | MediaItem],
    min_options: int | None = None,
    max_options: int | None = None,
    style: ChoiceQuestionStyle = 'button',
    labels: tuple[str, str] = ('Least', 'Most'),
    randomize: bool = False,
    fixed_options: list[str] | None = None,
    other_options: list[str] | None = None,
    skip_empty: bool = False,
    default: list[str] | None = None,
    recodes: dict[str, str] | None = None,
    custom_validator: Callable[[str], str] | None = None,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    dont_know_option: str = '',
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> ListResponse

Add a ranking question to the survey.

This presents the user with a number of choices to rank in order of preference.

Ranking limits

min_options and max_options control how many options the respondent must rank.

Randomization

Set randomize=True to randomize option order. fixed_options remain fixed when randomization is used.

Other options

Use other_options to add additional options to the ranking list.

Using the don't know option

Use dont_know_option to add a "Don't know" option to the question.

Example

from survey import Survey
s = Survey(**globals())

s.ranking_question("Rank the following car brands in order of preference",
                    options=["Ford", "Toyota", "Honda", "Tesla"],
                    labels=("Least", "Most"),

Default values

If no default value is specified, then the test data will consist of the options returned in a random order.

Recodes

Recodes are used to map the response to a different value. This is useful for when you want to map a response to a different value. For example, if you want to ask a question about what they enjoyed about the experience, and ranks the different activities, you can use recodes to map the top two box response to a different value.

from survey import Survey
s = Survey(**globals())

s.ranking_question("Rank the following activities in order of preference",
                    options=["Eating", "Drinking", "Sleeping", "Working"],
                    labels=("Least", "Most"),
                    recodes={
                        "Eating": "eating",
                        "Drinking": "drinking",
                        "Sleeping": "sleeping",
                        "Working": "working"
                    })
from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

brand_cars = {
    "Ford": ["Fiesta", "Focus", "Mustang", "F150"],
    "Toyota": ["Corolla", "Camry", "Prius", "RAV4"],
    "Honda": ["Civic", "Accord", "CR-V", "Pilot"],
    "Tesla": ["Model S", "Model 3", "Model X", "Model Y"],
}

for brand in car_brands:
    s.ranking_question(
        "Rank the following {brand} cars in order of preference?",
        options=brand_cars[brand],
        labels=("Least", "Most"),
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

e.g. custom_validator=lambda x: "Are you sure you meant Sleepy Cows?" if "Sleepy Cows" in x else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
optionslist[str | MediaItem]a list of strings or media items containing the options
min_optionsint | Nonethe minimum number of options to be ranked, defaults to all options
max_optionsint | Nonethe maximum number of options to be ranked, defaults to all options
styleChoiceQuestionStylepresentation style for the options.
labelstuple[str, str]a tuple of text labels for the least and most options
randomizeboolwhether to randomize the order of the options
fixed_optionslist[str] | Nonea list of options that will not be randomized
other_optionslist[str] | Nonea list of additional options to present to the user
defaultlist[str] | Nonethe default ranking to use when generating test data
skip_emptyboolif set to True, the question will be skipped if there are no options
recodesdict[str, str] | Noneresponse mappings to use when reporting this question
dont_know_optionstrOptionally add a "Don't know" option to the question using this text.
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
image_label_fieldstr | Nonethe field to use as the label for the image if using media items in the rows
show_image_labelboolwhether to show the image label or just to show the image in the rows
image_sizetuple[int, int] | Nonethe bounding box size of the images to display
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

media

@property
media: MediaCollection

Access the media collection.

The media collection contains images and videos that can be used in questions. The media are added to the survey automatically and can be uploaded from the MX8 dashboard. Additional columns added in the dashboard will be added to the media collection.

Each item in the media collection is a MediaItem object. You can access the media collection by iterating over the media object, and pass it to the image parameter of questions.

Columns can be accessed using dot notation. For example, if you have a column called "brand" in your media collection, then you can access it using media.brand. You can also get all the values of a column using the get_media_values function.

If you have not uploaded any media to the survey, then a dummy media collection will be used that contains dummy images with the following columns:

  • name
  • description
  • product
  • brand
  • campaign
  • agency
  • id

You can create your own metadata columns in the MX8 dashboard and these will be added to the media collection after you have created the survey and uploaded the media.

The columns are automatically used when reporting the results. For example, if you have a column called "brand" and you ask a question about the brand, then the results will be automatically grouped by brand.

Example

from survey import Survey
s = Survey(**globals())

for media in s.media:
    s.select_question("What do you think of this image?",
                            options=["Like", "Dislike"],
                            image=media)

get_media_values

get_media_values(column: str) -> list[str]

Get all values of the passed metadata column from the media collection.

This is useful if you want to get all the values of a column in the media collection and use them to create questions.

Example

from survey import Survey
s = Survey(**globals())

car_brands = s.get_media_values("brand")

for car in s.media:
    s.select_question("What brand is this car?",
                            options=car_brands,
                            image=car)

Parameters

NameTypeDescription
columnstrthe name of the media metadata column to read from each media item. The column must exist on each media item.

get_image

get_image(**kwargs: Any = {}) -> MediaItem

Get the image with the specified values from the media collection.

Media lookup

Keyword arguments filter the media collection by metadata fields.

Single match

This returns the first matching image. If no image matches, the lookup fails.

Example

from survey import Survey
s = Survey(**globals())

ford_image = s.get_image(brand="Ford")
s.image_question("What do you think of this Ford car?", image=ford_image)

s.complete()

get_video

get_video(**kwargs: Any = {}) -> MediaItem

Get the video with the specified values from the media collection.

Media lookup

Keyword arguments filter the media collection by metadata fields.

Single match

This returns the first matching video. If no video matches, the lookup fails.

Example

from survey import Survey
s = Survey(**globals())

ford_video = s.get_video(brand="Ford")
s.play_video(video=ford_video)

s.complete()

get_images

get_images(**kwargs: Any = {}) -> list[MediaItem]

Get the images with the specified values from the media collection.

Media lookup

Keyword arguments filter the media collection by metadata fields.

Multiple matches

This returns all matching images.

Example

from survey import Survey
s = Survey(**globals())

for image in s.get_images(brand="Ford"):
    s.image_question("What do you think of this Ford car?", image=image)

s.complete()

get_videos

get_videos(**kwargs: Any = {}) -> list[MediaItem]

Get the videos with the specified values from the media collection.

Media lookup

Keyword arguments filter the media collection by metadata fields.

Multiple matches

This returns all matching videos.

Example

from survey import Survey
s = Survey(**globals())

for video in s.get_videos(brand="Ford"):
    s.play_video(video=video)

s.complete()

store_value

store_value(
    name: str,
    value: str | int | bool,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> StringResponse | BoolResponse | IntResponse | ListResponse

Store a calculated value for reporting.

Use this to add a value calculated from previous answers to the survey data. For survey logic, keep using normal Python variables instead of retrieving stored reporting values.

Reporting

Stored values are added to the survey as calculated variables and can be tagged.

Example

from datetime import datetime
from survey import Survey
s = Survey(**globals())

birth_year = s.numeric_question("What year were you born in?",
                    min_max=(1920, 2020),
                    )

age = datetime.now().year - birth_year
s.store_value("age", age)

s.terminate_if(age < 18, "Sorry this survey is for 18+ only.")

Parameters

NameTypeDescription
namestrthe name of the value to store
valuestr | int | boolthe value to store for reporting
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

random

random() -> float

Return a random float between 0 and 1.

Respondent-stable randomness

The value is generated from the respondent's random seed or respondent id, so it is stable for the same respondent context.

randomize

randomize(items: Sequence[Any], flatten: bool = False) -> list[Any]

Randomizes a list of items.

Typically used when iterating over a list of items that you want to ask questions about.

Grouped items

Nested lists stay together during top-level randomization, so you can randomize a set of options while keeping grouped options adjacent.

Example

from survey import Survey
s = Survey(**globals())

car_brands = s.randomize(["Ford", "Toyota", "Honda", "Tesla"])

for brand in s.randomize(car_brands):
    s.select_question(
        "What do you think of {brand} cars?",
        options=["Like", "Dislike"],
        tags=s.tag(brand=brand),
    )

Example with flattening

from survey import Survey

s = Survey(**globals())

genres = [
    "Jazz",
    ["Punk Rock", "Classic Rock", "Indie Rock"],  # keep this group together
    "Hip-Hop",
    ["House", "Techno"],  # keep this group together
]

all_genres = s.randomize(genres, flatten=True)

for genre in all_genres:
    s.select_question(
        "What do you think of {genre} music?",
        options=["Like", "Dislike", "Neutral"],
        tags=s.tag(genre=genre),
    )

Parameters

NameTypeDescription
itemsSequence[Any]The sequence of items to randomize.
flattenboolIf True, flatten any nested lists after randomizing the top-level items.

conjoint_question

conjoint_question(
    question: str,
    concepts: list[dict[str, Any]] | list[MediaItem],
    task_sets: dict[str, list[list[str]]],
    concept_id_tag: str = 'id',
    required_tags: list[str] | None = None,
    dont_know_option: str = '',
    randomize: bool = False,
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a choice-based conjoint question to the survey.

Presents one task set as a series of select-style questions.

Task assignment

Each respondent is assigned one task set using least-fill quota behavior.

Dict-backed concepts

Dict-backed concepts use concept_id_tag as the option value and attach required_tags to the selected response.

Media-backed concepts

Media-backed concepts use existing media option rendering, labels, and media tags.

Fixed don't know option

dont_know_option is added as a fixed non-concept option when provided.

Randomization

Set randomize=True to randomize option order within each conjoint task.

Parameters

NameTypeDescription
questionstrThe question to ask for each conjoint task.
conceptslist[dict[str, Any]] | list[MediaItem]Concept dictionaries or media items to use in tasks.
task_setsdict[str, list[list[str]]]Mapping of task set id to tasks, where each task contains concept ids.
concept_id_tagstrTag containing the concept id used by task sets.
required_tagslist[str] | NoneConcept tags to require and report with selected dict-backed concepts.
dont_know_optionstrOptional fixed non-concept option label.
randomizeboolWhether to randomize option order within each task.
image_label_fieldstr | NoneMedia field to use as the display label for media concepts.
show_image_labelboolWhether to show media labels.
image_sizetuple[int, int] | NoneBounding box size for media options.
tagsdict[str, Any] | NoneTags to attach to each conjoint task question.

max_diff_question

max_diff_question(
    question: str,
    items: list[str | MediaItem] | list[list[str | MediaItem]],
    labels: list[str],
    image: MediaItem | None = None,
    randomize: bool = False,
    custom_validator: Callable[[str], str] | None = None,
    dont_know_option: str = '',
    image_label_field: str | None = None,
    show_image_label: bool = True,
    image_size: tuple[int, int] | None = None,
    tags: dict[str, Any] | None = None,
    **_kwargs: Any = {},
) -> DictResponse

Add a MaxDiff question to the survey.

This presents the user with a series of sets of items, and asks the user to select the most and least preferred items in each set.

The items are presented in a random order, and the user is asked to select the most and least preferred items in each set. The items are presented in a random order, and the user is asked to select the most and least preferred items in each set.

Randomization

Set randomize=True to randomize item order within each MaxDiff set.

Using the don't know option

Use dont_know_option to add a fixed "Don't know" option to each MaxDiff task.

Example

from survey import Survey
s = Survey(**globals())

car_brands = ["Ford", "Toyota", "Honda", "Tesla", "Chevrolet", "BMW", "Audi", "Mercedes"]

s.max_diff_question("Which of the following car brands do you prefer?",
                    items=car_brands,
                    labels=["Least", "Most"])

Portrait orientation

If you want to use portrait orientation, then the questions are asked multiple times, once for each label, and you must include a placholder for the labels in the question text. For example, if you have a question "Which of the following cars do you prefer?", and you want to use portrait orientation, then include a placeholder for the label in the question text, i.e., "Which of the following cars do you prefer the {label}?"

from survey import Survey
s = Survey(**globals())

car_brands = ["Ford", "Toyota", "Honda", "Tesla", "Chevrolet", "BMW", "Audi", "Mercedes"]

s.max_diff_question("Which of the following cars do you prefer the **{label}**?",
                    items=car_brands,
                    labels=["Least", "Most"])

Default values

If no default value is specified, then the test data will consist of random values chosen from the options.

from survey import Survey
s = Survey(**globals())

car_brands = s.multi_select_question(
    "When you think about cars, which brands immediately come to mind?",
    options=["Ford", "Toyota", "Honda", "Tesla"],
)

brand_cars = {
    "Ford": ["Fiesta", "Focus", "Mustang", "F150"],
    "Toyota": ["Corolla", "Camry", "Prius", "RAV4"],
    "Honda": ["Civic", "Accord", "CR-V", "Pilot"],
    "Tesla": ["Model S", "Model 3", "Model X", "Model Y"],
}

for brand in car_brands:
    s.max_diff_question(
        "Which of the following {brand} cars do you prefer?",
        items=brand_cars[brand],
        labels=["Least", "Most"],
        tags=s.tag(brand=brand),
    )

Custom Errors

If you want to return a specific response to the user if they enter a specific value, you can use the custom_validator parameter. This is typically a lambda function that takes the response as a parameter and returns a string if the response is invalid.

It is typically used for "gotcha" questions, where the user is asked to enter a specific value to ensure they are paying attention.

e.g. custom_validator=lambda x: "Are you sure you meant Sleepy Cows?" if "Sleepy Cows" in x else None

Parameters

NameTypeDescription
questionstrthe question to ask the respondent
itemslist[str | MediaItem] | list[list[str | MediaItem]]items to compare, or predefined sets of items to compare
labelslist[str]the two labels to use for the most and least preferred items
imageMediaItem | Nonean image to show with the question, from the s.media collection
randomizeboolif set to True, the order of the items will be randomized
custom_validatorCallable[[str], str] | Nonefunction that returns a custom error message
dont_know_optionstrlabel to use for the don't-know option, if any.
image_label_fieldstr | Nonelabel field to use for media items in the options.
show_image_labelboolwhether to show image labels for media options.
image_sizetuple[int, int] | Nonebounding box size for media options.
tagsdict[str, Any] | NoneTags to attach to this question, from s.tags(...). You can also attach tags using the with s.tag(...): context manager.

count_validation_errors

count_validation_errors(question: str | None = None) -> int

Return the number of validation errors in the survey, or for a specific question.

Scope

If question is omitted, this counts all current respondent validation errors. If question is provided, only errors for questions with that exact text are counted.

Parameters

NameTypeDescription
questionstr | NoneIf provided, only count validation errors for questions with this exact text.

authenticate_by_phone_number

authenticate_by_phone_number(
    question_1: str,
    question_2: str = 'Please enter the code we sent you.',
    termination_message: str = 'Sorry, the code you entered was incorrect',
    store_phone_number: bool = False,
    image: MediaItem | None = None,
    default_country: str | None = None,
) -> StringResponse

Authenticate the respondent by phone number.

Asks the respondent for their phone number and sends a verification code.

OTP authentication

The respondent must enter the one-time password sent to their phone number before the survey continues.

Phone number storage

If store_phone_number=True, the response contains the phone number. Otherwise, the stored response text is "obfuscated".

Response fields

The returned response always exposes the phone number as response.phone_number and the one-time password as response.otp.

Example

from survey import Survey
s = Survey(**globals())

s.authenticate_by_phone_number("This is a high security survey, please enter your phone number" +
                               "to continue, and we will send you a verification code.")

Parameters

NameTypeDescription
question_1strthe question that asks the respondent to enter their phone number
question_2strthe question that asks the respondent to enter the verification code
termination_messagestrthe message to show the respondent if the code is incorrect
store_phone_numberboolif set to True, the phone number will be stored in the survey data
imageMediaItem | Nonean image to show with the phone number question, from the s.media collection
default_countrystr | Nonethe default country code to use for the phone number

call_api

call_api(
    url: str,
    method: HttpMethod,
    data: dict | None = None,
    params: dict | None = None,
    headers: dict | None = None,
    timeout: int = 10,
) -> StringResponse

Call an external HTTPS API.

Allowed domains

External HTTPS API calls are only available for pre-approved domains.

Request payloads

Use params for GET query parameters and data for POST request bodies.

Timeout and errors

A disallowed URL domain raises a validation error.

:param url: The URL of the request. :param method: HTTP method. Must be "GET" or "POST". :param data: Data to send for POST requests. :param params: Query parameters for GET requests. :param headers: HTTP headers for the request. :param timeout: Request timeout in seconds. :return: Response object from the HTTP request. :raises ValueError: If the URL domain is not allowed.

get_all_children_age_gender

get_all_children_age_gender(
    min_age: int,
    max_age: int,
    age_recodes: dict[int, str] | None = None,
    gender_map: dict[str, str] | None = None,
    question_1_text: str | None = None,
    question_2_text: str | None = None,
    question_3_text: str | None = None,
) -> list[StringResponse]

Ask the user a series of questions to get the ages and genders of their children.

Returns a list of StringResponse objects, one for each child, with the following tags attached:

  • age: the recoded age of the child
  • gender: the gender of the child
  • name: the name of the child to present to the respondent, e.g. 16 year old son.

Return shape and tags

The returned list contains one response per child, tagged with age, gender, and display name.

No children

If the user has no children, an empty list is returned.

Default question sequence

Unless custom question text is provided, this asks for child count, each child's age, and each child's gender.

Age recodes

age_recodes maps raw ages to reported age groups.

Gender mapping

gender_map maps gender responses to child terminology such as son or daughter.

The default questions are:

  1. "How many children do you have between the ages of {min_age} and {max_age}?"
  2. "For each child, please let us know their age"
  3. "Now, please let us know their gender

By default, genders are Male and Female and are mapped to son and daughter using the gender_map parameter.

Age recodes may be specified as a dictionary mapping the age to the recode value, e.g.

{
    5: "Pre-school",
    6: "Primary",
    7: "Primary",
    8: "Primary",
    9: "Primary",
    10: "Primary",
    11: "Secondary",
    12: "Secondary",
    13: "Secondary",
    14: "Secondary",
    15: "Secondary",
    16: "Secondary",
    17: "Secondary",
}

Example


from survey import Survey
s = Survey(**globals())
children = s.get_all_children_age_gender(
    min_age=5,
    max_age=18,
)
for child in children:
    s.text_question(
        "Thinking about {child}, what is their favorite subject in school?",
        tags=s.tag(child=child.name),
    )

s.complete()

Parameters

NameTypeDescription
min_ageintthe minimum age of the children
max_ageintthe maximum age of the children
age_recodesdict[int, str] | Nonea dictionary to recode the ages
gender_mapdict[str, str] | Nonea dictionary to map genders to child terminology
question_1_textstr | Nonethe text to use for the first question
question_2_textstr | Nonethe text to use for the second question
question_3_textstr | Nonethe text to use for the third question

kid_picker_question

kid_picker_question(
    min_age: int = 13,
    max_age: int = 17,
    age_recodes: dict[int, str] | None = None,
    gender_map: dict[str, str] | None = None,
    question_1_text: str | None = None,
    question_2_text: str | None = None,
    question_3_text: str | None = None,
    question_4_text: str | None = None,
) -> StringResponse | None

Ask for child age and gender, then select the child available to take the survey.

This asks a series of questions to collect eligible children and then asks which child is available.

Returns the selected child as a StringResponse with age, gender, and name attributes.

Return shape and tags

The returned child can be used directly in survey logic and tags. The response value is the raw age and gender, child.age is the recoded age if age_recodes is provided, child.gender is the raw gender response, and child.name is the respondent-facing child label.

No children

If the user has no children, None is returned.

Default question sequence

Unless custom question text is provided, this asks for child count, age, gender, and which child is available to take the survey now.

Age recodes

age_recodes maps raw ages to reported age groups.

Gender mapping

gender_map maps gender responses to child terminology such as son or daughter.

The default questions are:

  1. "How many children do you have between the ages of {min_age} and {max_age}?"
  2. "For each child, please let us know their age"
  3. "Now, please let us know their gender"
  4. "Which, if any, of your children are available to take the survey now?"

By default, genders are Male and Female and the children are referred to as son and daughter using the default gender map:

{
    "Male": "son",
    "Female": "daughter"
}

Age recodes can be specified as a dictionary mapping the age to the recode value as in the get_all_children question.

Example


from survey import Survey
s = Survey(**globals())
child = s.kid_picker_question(
    min_age=5,
    max_age=18,
)
if child:
    s.show_message(
        "Please ask your {child} to take the survey now.",
        child=child.name,
        tags=s.tag(selected_child_age=child.age, selected_child_gender=child.gender),
    )
else:
    s.terminate("Sorry, this survey is for children only.")

s.complete()

Parameters

NameTypeDescription
min_ageintthe minimum age of the children
max_ageintthe maximum age of the children
age_recodesdict[int, str] | Nonea dictionary to recode the ages
gender_mapdict[str, str] | Nonea dictionary to map the genders to child terminology
question_1_textstr | Nonethe text to use for the first question
question_2_textstr | Nonethe text to use for the second question
question_3_textstr | Nonethe text to use for the third question
question_4_textstr | Nonethe text to use for the fourth question

get_exposed_values

get_exposed_values(
    source: str,
    exposed_dimension: str,
    allowed_values: list[str] | None = None,
    number_allowed_unknown_values: int = 0,
) -> ListResponse

Return a list of exposure values for a configured dimension.

Looks up exposure values from a configured exposure source.

Pre-loaded exposures

Exposure data must be pre-loaded into MX8 for the configured source and exposed_dimension.

Example

from survey import Survey
s = Survey(**globals())

products = ["SUV", "Sedan", "Coupe"]

exposed_products = s.get_exposed_values(
    source="campaign-123",
    exposed_dimension="product",
    allowed_values=products,
    number_allowed_unknown_values=1,
)

for product in products:
    with s.tag(product=product,
               exposed=product in exposed_products):
        s.select_question(
            "What do you think of {product} cars?",
            options=["Like", "Dislike"],
        )

        # Remaining question about the product...

s.complete()

Parameters

NameTypeDescription
sourcestrthe source of the exposures, set with the server side integration, e.g. 'campaign-123'
exposed_dimensionstrthe exposure dimension to return, e.g. 'brand' or 'product'
allowed_valueslist[str] | Nonean optional list of known values to filter the returned exposures by
number_allowed_unknown_valuesintthe number of values that are not in the allow-list that are allowed before the question is failed.

tag_exposed_media

tag_exposed_media(
    source: str,
    field_name: str,
    exposed_dimension: str = 'brand',
    number_allowed_unknown_values: int | None = None,
    number_allowed_unknown_brands: int | None = None,
) -> None

Tags media items in the survey as exposed based on IP address matching.

Tags media items by matching a media metadata field against the respondent's exposure values.

Pre-loaded exposures

Exposure data must be pre-loaded into MX8 for the configured source and exposed_dimension.

IP matching

Exposure lookup is based on the current respondent's IP address.

Deprecated alias

number_allowed_unknown_brands is a deprecated alias for number_allowed_unknown_values. Provide only one of these parameters.

Example

from survey import Survey
s = Survey(**globals())

s.tag_exposed_media(
    source="campaign-123",
    field_name="brand",
    exposed_dimension="brand",
    number_allowed_unknown_values=1,
)

for media in s.media:
    s.select_question(
        "What do you think of this car?",
        options=["Like", "Dislike"],
        image=media,
        exposed=media.exposed,
    )
s.complete()

Parameters

NameTypeDescription
sourcestrthe source of the exposures, set with the server side integration, e.g. 'campaign-123'
field_namestrthe name of the field in the media collection to match against the exposures
exposed_dimensionstrthe exposure dimension to match against, defaults to 'brand'
number_allowed_unknown_valuesint | Nonethe number of values that are not in the allow-list that are allowed before the lookup is failed
number_allowed_unknown_brandsint | Nonedeprecated alias for number_allowed_unknown_values

insert_block

insert_block(name: str) -> None

Insert a block into the survey at the current position.

Blocks are defined in the MX8 dashboard and can be used to add custom questions to surveys without modifying the survey code.

Dashboard-defined blocks

The block name must refer to a block configured in the MX8 dashboard.

Missing blocks

If the named block is unavailable, block insertion fails during survey processing.

Parameters

NameTypeDescription
namestrthe name of the block