Creating question variants

EDSL comes with a variety of features for efficiently generating different versions of questions in surveys. This notebook demonstrates methods for doing this with Scenario objects.

What is a Scenario?

A Scenario is a dictionary of one or more key/value pairs representing data or content to be added to questions; a ScenarioList is a list of Scenario objects. Scenario keys are used as question parameters that get replaced with the values when the scenarios are added to the questions, allowing you to create variants of questions efficiently. For example:

from edsl import QuestionFreeText, ScenarioList, Scenario

q = QuestionFreeText(
    question_name = "favorite",
    question_text = "What is your favorite {{ scenario.thing }}?"
)

s = ScenarioList(
    Scenario({"thing": t}) for t in ["flower", "pizza topping", "chatbot"]
)

Using scenarios

Scenarios can be added to questions when constructing a survey or when running it. Functionally, the same question context is delivered to agents and models whether they are added during or after survey construction. The difference is how the information is arranged in the results that are generated by the models.

Methods

Adding scenarios at survey construction: loop

Each question type (QuestionMultipleChoice, QuestionFreeText, etc.) has a loop() method that generates a copy of the question for each scenario in a ScenarioList that is passed to it, returning a list of the questions that are generated. The loop() method is used when survey questions are being constructed. The typical workflow is:

  • Construct a (single) Question with one or more parameters

  • Construct a ScenarioList

  • Call the loop() method on the question and pass it the scenario list

  • Pass the list of the questions to a Survey

From the example above this looks like:

from edsl import Survey

questions = q.loop(s)
survey = Survey(questions)
results = survey.run()

When the survey is run, the results that are generated will include columns for each question and answer; there are no scenario columns in the results (unless scenarios are also added when the survey is run).

Running a survey with scenarios: by

Scenarios can also be passed to a question or survey at the time that it is run. This is done by calling the by() method on a survey, passing it the scenarios, and then calling the run() method. The typical workflow is:

  • Construct a question (or survey of multiple questions) with one or more parameters

  • Construct scenarios

  • Call the by() method on the question or survey and pass it the scenarios

  • Call the run() method to administer the question or survey

From the example above this looks like:

results = q.by(s).run()

(If any agents or models have been created and specified, they would also be added in separated by() calls. See details on designing agents and selecting language models.)

Example: Looping a question with scenarios

The loop() method is called on a Question object, and takes a ScenarioList of values to be inserted in copies of the question. We can optionally use the scenario key in the question name as well (so long as it is Pythonic); otherwise, unique identifiers are added to the original question name.

We start by constructing a question that takes a parameter:

[1]:
from edsl import QuestionFreeText

q = QuestionFreeText(
    question_name = "features",
    question_text = "Briefly describe the features of this sailboat model: {{ scenario.sailboat_model }}"
)

Next we create a scenario list to pass to the loop() method. EDSL comes with many methods for generating scenarios from different data sources, such as PDF, PNG, CSV, docs, tables, dicts, lists, etc. We can use the from_source() method to construct a scenario list and specify the data type (“csv”, “pdf”, “list”, etc.). Learn about other methods for generating scenarios.

[2]:
from edsl import ScenarioList

s = ScenarioList.from_source("list", "sailboat_model", ['Laser', 'Sunfish', 'Optimist', 'Finn'])
s
[2]:

ScenarioList scenarios: 4; keys: ['sailboat_model'];

  sailboat_model
0 Laser
1 Sunfish
2 Optimist
3 Finn

Next we call the loop() method with the scenario list to create a list of the copies of the question, and verify that formatted questions have been generated:

[3]:
questions = q.loop(s)
questions
[3]:
[Question('free_text', question_name = """features_0""", question_text = """Briefly describe the features of this sailboat model: Laser"""),
 Question('free_text', question_name = """features_1""", question_text = """Briefly describe the features of this sailboat model: Sunfish"""),
 Question('free_text', question_name = """features_2""", question_text = """Briefly describe the features of this sailboat model: Optimist"""),
 Question('free_text', question_name = """features_3""", question_text = """Briefly describe the features of this sailboat model: Finn""")]

We can pass the questions to a Survey and then run it:

[4]:
from edsl import Survey

survey = Survey(questions = questions)

results = survey.run()
Job Status 🦜
Completed (1 completed, 0 failed)
Identifiers
Results UUID:
18af41f2...f014
Use Results.pull(uuid) to fetch results.
Job UUID:
782f1ff7...2e4b
Use Jobs.pull(uuid) to fetch job.
Status: Completed
Last updated: 2025-06-15 07:31:31
07:31:31
Job completed and Results stored on Coop. View Results
07:31:26
Job status: running - last update: 2025-06-15 07:31:26 AM
07:31:21
Job status: running - last update: 2025-06-15 07:31:21 AM
07:31:17
Job status: queued - last update: 2025-06-15 07:31:17 AM
07:31:16
View job progress here
07:31:16
Job details are available at your Coop account. Go to Remote Inference page
07:31:16
Job sent to server. (Job uuid=782f1ff7-f5a6-4a6a-b3d6-d8ac45462e4b).
07:31:16
Your survey is running at the Expected Parrot server...
07:31:15
Remote inference activated. Sending job to server...
Model Costs ($0.0142 / 1.42 credits total)
Service Model Input Tokens Input Cost Output Tokens Output Cost Total Cost Total Credits
openai gpt-4o 78 $0.0002 1,400 $0.0140 $0.0142 1.42
Totals 78 $0.0002 1,400 $0.0140 $0.0142 1.42

You can obtain the total credit cost by multiplying the total USD cost by 100. A lower credit cost indicates that you saved money by retrieving responses from the universal remote cache.

We can check the columns of dataset of Results that have been generated, and see that there are sets of columns for each question identifiable by question name (but no scenario columns):

[5]:
results.columns
[5]:
  0
0 agent.agent_index
1 agent.agent_instruction
2 agent.agent_name
3 answer.features_0
4 answer.features_1
5 answer.features_2
6 answer.features_3
7 cache_keys.features_0_cache_key
8 cache_keys.features_1_cache_key
9 cache_keys.features_2_cache_key
10 cache_keys.features_3_cache_key
11 cache_used.features_0_cache_used
12 cache_used.features_1_cache_used
13 cache_used.features_2_cache_used
14 cache_used.features_3_cache_used
15 comment.features_0_comment
16 comment.features_1_comment
17 comment.features_2_comment
18 comment.features_3_comment
19 generated_tokens.features_0_generated_tokens
20 generated_tokens.features_1_generated_tokens
21 generated_tokens.features_2_generated_tokens
22 generated_tokens.features_3_generated_tokens
23 iteration.iteration
24 model.frequency_penalty
25 model.inference_service
26 model.logprobs
27 model.max_tokens
28 model.model
29 model.model_index
30 model.presence_penalty
31 model.temperature
32 model.top_logprobs
33 model.top_p
34 prompt.features_0_system_prompt
35 prompt.features_0_user_prompt
36 prompt.features_1_system_prompt
37 prompt.features_1_user_prompt
38 prompt.features_2_system_prompt
39 prompt.features_2_user_prompt
40 prompt.features_3_system_prompt
41 prompt.features_3_user_prompt
42 question_options.features_0_question_options
43 question_options.features_1_question_options
44 question_options.features_2_question_options
45 question_options.features_3_question_options
46 question_text.features_0_question_text
47 question_text.features_1_question_text
48 question_text.features_2_question_text
49 question_text.features_3_question_text
50 question_type.features_0_question_type
51 question_type.features_1_question_type
52 question_type.features_2_question_type
53 question_type.features_3_question_type
54 raw_model_response.features_0_cost
55 raw_model_response.features_0_input_price_per_million_tokens
56 raw_model_response.features_0_input_tokens
57 raw_model_response.features_0_one_usd_buys
58 raw_model_response.features_0_output_price_per_million_tokens
59 raw_model_response.features_0_output_tokens
60 raw_model_response.features_0_raw_model_response
61 raw_model_response.features_1_cost
62 raw_model_response.features_1_input_price_per_million_tokens
63 raw_model_response.features_1_input_tokens
64 raw_model_response.features_1_one_usd_buys
65 raw_model_response.features_1_output_price_per_million_tokens
66 raw_model_response.features_1_output_tokens
67 raw_model_response.features_1_raw_model_response
68 raw_model_response.features_2_cost
69 raw_model_response.features_2_input_price_per_million_tokens
70 raw_model_response.features_2_input_tokens
71 raw_model_response.features_2_one_usd_buys
72 raw_model_response.features_2_output_price_per_million_tokens
73 raw_model_response.features_2_output_tokens
74 raw_model_response.features_2_raw_model_response
75 raw_model_response.features_3_cost
76 raw_model_response.features_3_input_price_per_million_tokens
77 raw_model_response.features_3_input_tokens
78 raw_model_response.features_3_one_usd_buys
79 raw_model_response.features_3_output_price_per_million_tokens
80 raw_model_response.features_3_output_tokens
81 raw_model_response.features_3_raw_model_response
82 reasoning_summary.features_0_reasoning_summary
83 reasoning_summary.features_1_reasoning_summary
84 reasoning_summary.features_2_reasoning_summary
85 reasoning_summary.features_3_reasoning_summary
86 scenario.scenario_index

We can access built-in methods for analyzing results, e.g., printing a table:

[6]:
results.select("answer.*")
[6]:
  answer.features_1 answer.features_2 answer.features_3 answer.features_0
0 The Sunfish is a popular and iconic sailboat known for its simplicity and ease of use, making it ideal for beginners and recreational sailing. Key features of the Sunfish include: 1. **Design**: The Sunfish has a distinctive, lightweight, and flat-bottomed hull design, which contributes to its stability and ease of handling. It typically measures about 13.9 feet in length and has a beam (width) of around 4.1 feet. 2. **Rigging**: It features a lateen rig, which comprises a single, triangular sail mounted on a simple, two-piece mast and boom. The lateen sail is easy to rig and adjust, making it accessible for novice sailors. 3. **Construction**: Traditionally, Sunfish sailboats were made of fiberglass, which provides durability and low maintenance. The hull is designed to be unsinkable, with foam flotation built in. 4. **Portability**: The boat is lightweight, generally around 120 pounds, which makes it easy to transport on a trailer or car top and launch from a beach or dock. 5. **Performance**: While designed for simplicity, the Sunfish can be quite fast and responsive, offering a fun sailing experience. It performs well in a variety of wind conditions. 6. **Popularity**: Due to its affordability, ease of use, and widespread availability, the Sunfish is one of the most popular sailboats in the world, with a strong community and numerous racing events dedicated to it. Overall, the Sunfish is celebrated for its versatility, making it a great choice for both casual sailing and competitive racing. The Optimist sailboat is a small, single-handed dinghy designed specifically for young sailors. Here are some of its key features: 1. **Size and Design**: The Optimist is a pram dinghy, which means it has a flat bow. It is typically 7 feet 9 inches (2.36 meters) long, making it compact and manageable for children. 2. **Simplicity**: The design is straightforward, with a single sail and a simple rigging system, which makes it ideal for beginners to learn the basics of sailing. 3. **Stability**: The flat-bottomed hull provides excellent stability, which is crucial for young sailors who are just starting to learn how to balance and control a sailboat. 4. **Lightweight**: The boat is lightweight, usually around 77 pounds (35 kilograms), making it easy to handle both on and off the water. 5. **Durability**: Optimists are typically made from fiberglass or polyethylene, materials known for their durability and resistance to the elements, ensuring the boat can withstand the rigors of training and racing. 6. **International Class**: The Optimist is recognized as an international class by World Sailing, and it is one of the most popular sailboats for youth racing worldwide, with a strong presence in competitive sailing events. 7. **Training and Racing**: It is widely used for training young sailors and is often the first step in a racing career, with many sailing programs and clubs offering Optimist classes and competitions. These features make the Optimist an excellent choice for introducing children to the sport of sailing. The Finn is a single-handed, cat-rigged sailboat that is primarily used for racing. It was designed by Rickard Sarby in 1949 and has been an Olympic class since 1952, although it was removed from the Olympic program after the 2020 Tokyo Games. Here are some key features of the Finn: 1. **Design and Construction**: The Finn has a robust and sturdy design, typically made from fiberglass or a combination of fiberglass and carbon fiber. This makes it durable and suitable for various weather conditions. 2. **Hull**: The hull is approximately 4.5 meters (14.8 feet) in length, with a beam of about 1.5 meters (4.9 feet). The hull design allows for excellent maneuverability and stability. 3. **Rigging**: The Finn is equipped with a single sail, a large fully-battened mainsail, which is controlled by a sophisticated rigging system. The sail is typically made from high-performance materials like Mylar or Dacron. 4. **Mast and Boom**: The mast is free-standing and often made from aluminum or carbon fiber, which provides flexibility and strength. The boom is also designed to optimize sail shape and performance. 5. **Sailing Characteristics**: The Finn is known for its demanding and tactical sailing, requiring skill and physical fitness. It is responsive and quick to accelerate, making it popular among competitive sailors. 6. **Adjustability**: The boat features adjustable controls for sail shape, such as the outhaul, cunningham, and vang, allowing sailors to fine-tune performance based on wind conditions. 7. **Competitive Racing**: The Finn class has a strong international presence with numerous regattas and championships, making it a prestigious class in competitive sailing. Overall, the Finn is celebrated for its challenging yet rewarding sailing experience, making it a favorite among experienced sailors and competitive racers. The Laser sailboat is a popular single-handed racing dinghy known for its simplicity, performance, and widespread use in competitive sailing. Here are some key features: 1. **Design and Construction**: The Laser has a sleek, lightweight design with a flat hull that enhances speed and maneuverability. It is typically constructed from fiberglass, making it durable and easy to handle both on and off the water. 2. **Rigging**: The Laser uses a single sail rig, which is simple and efficient. It features a sleeved sail that fits over the mast, and the rigging is minimal, making it easy to set up and sail. 3. **Sail Options**: The Laser offers different sail sizes to accommodate various skill levels and conditions, including the Standard, Radial, and 4.7 rigs. This versatility makes it suitable for a wide range of sailors, from beginners to advanced racers. 4. **Performance**: The Laser is renowned for its speed and agility, making it a favorite in competitive sailing. Its responsive handling allows sailors to quickly adjust to changing wind and water conditions. 5. **One-Design Class**: The Laser is a one-design class, meaning all boats are built to the same specifications. This ensures a level playing field in races, where skill and tactics are the primary determinants of success. 6. **Popularity and Accessibility**: The Laser is one of the most popular sailboats globally, with a strong presence in both recreational and competitive sailing communities. Its simplicity and affordability make it accessible to a broad range of sailors. 7. **Olympic Class**: The Laser is an Olympic class boat, featured in both men's and women's sailing events, further cementing its status as a premier racing dinghy.

Running a question with scenarios

If we instead want to add the scenarios to the question when it is run, we simply add them with the by() method. This will re-administer a question for each scenario:

[7]:
results = q.by(s).run()
Job Status 🦜
Completed (4 completed, 0 failed)
Identifiers
Results UUID:
3651d1d9...d593
Use Results.pull(uuid) to fetch results.
Job UUID:
c52979ae...c7a8
Use Jobs.pull(uuid) to fetch job.
Status: Completed
Last updated: 2025-06-15 07:31:38
07:31:38
Job completed and Results stored on Coop. View Results
07:31:33
Job status: queued - last update: 2025-06-15 07:31:33 AM
07:31:32
View job progress here
07:31:32
Job details are available at your Coop account. Go to Remote Inference page
07:31:32
Job sent to server. (Job uuid=c52979ae-0779-43ee-8aaf-df90595dc7a8).
07:31:32
Your survey is running at the Expected Parrot server...
07:31:31
Remote inference activated. Sending job to server...
Model Costs ($0.0142 / 0.00 credits total)
Service Model Input Tokens Input Cost Output Tokens Output Cost Total Cost Total Credits
openai gpt-4o 78 $0.0002 1,400 $0.0140 $0.0142 0.00
Totals 78 $0.0002 1,400 $0.0140 $0.0142 0.00

You can obtain the total credit cost by multiplying the total USD cost by 100. A lower credit cost indicates that you saved money by retrieving responses from the universal remote cache.

The results now include columns for the single question but with a separate row for each scenario:

[8]:
results.columns
[8]:
  0
0 agent.agent_index
1 agent.agent_instruction
2 agent.agent_name
3 answer.features
4 cache_keys.features_cache_key
5 cache_used.features_cache_used
6 comment.features_comment
7 generated_tokens.features_generated_tokens
8 iteration.iteration
9 model.frequency_penalty
10 model.inference_service
11 model.logprobs
12 model.max_tokens
13 model.model
14 model.model_index
15 model.presence_penalty
16 model.temperature
17 model.top_logprobs
18 model.top_p
19 prompt.features_system_prompt
20 prompt.features_user_prompt
21 question_options.features_question_options
22 question_text.features_question_text
23 question_type.features_question_type
24 raw_model_response.features_cost
25 raw_model_response.features_input_price_per_million_tokens
26 raw_model_response.features_input_tokens
27 raw_model_response.features_one_usd_buys
28 raw_model_response.features_output_price_per_million_tokens
29 raw_model_response.features_output_tokens
30 raw_model_response.features_raw_model_response
31 reasoning_summary.features_reasoning_summary
32 scenario.sailboat_model
33 scenario.scenario_index
[9]:
results.select("sailboat_model", "features")  # results.select("scenario.*", "answer.*") is equivalent here
[9]:
  scenario.sailboat_model answer.features
0 Laser The Laser sailboat is a popular single-handed racing dinghy known for its simplicity, performance, and widespread use in competitive sailing. Here are some key features: 1. **Design and Construction**: The Laser has a sleek, lightweight design with a flat hull that enhances speed and maneuverability. It is typically constructed from fiberglass, making it durable and easy to handle both on and off the water. 2. **Rigging**: The Laser uses a single sail rig, which is simple and efficient. It features a sleeved sail that fits over the mast, and the rigging is minimal, making it easy to set up and sail. 3. **Sail Options**: The Laser offers different sail sizes to accommodate various skill levels and conditions, including the Standard, Radial, and 4.7 rigs. This versatility makes it suitable for a wide range of sailors, from beginners to advanced racers. 4. **Performance**: The Laser is renowned for its speed and agility, making it a favorite in competitive sailing. Its responsive handling allows sailors to quickly adjust to changing wind and water conditions. 5. **One-Design Class**: The Laser is a one-design class, meaning all boats are built to the same specifications. This ensures a level playing field in races, where skill and tactics are the primary determinants of success. 6. **Popularity and Accessibility**: The Laser is one of the most popular sailboats globally, with a strong presence in both recreational and competitive sailing communities. Its simplicity and affordability make it accessible to a broad range of sailors. 7. **Olympic Class**: The Laser is an Olympic class boat, featured in both men's and women's sailing events, further cementing its status as a premier racing dinghy.
1 Sunfish The Sunfish is a popular and iconic sailboat known for its simplicity and ease of use, making it ideal for beginners and recreational sailing. Key features of the Sunfish include: 1. **Design**: The Sunfish has a distinctive, lightweight, and flat-bottomed hull design, which contributes to its stability and ease of handling. It typically measures about 13.9 feet in length and has a beam (width) of around 4.1 feet. 2. **Rigging**: It features a lateen rig, which comprises a single, triangular sail mounted on a simple, two-piece mast and boom. The lateen sail is easy to rig and adjust, making it accessible for novice sailors. 3. **Construction**: Traditionally, Sunfish sailboats were made of fiberglass, which provides durability and low maintenance. The hull is designed to be unsinkable, with foam flotation built in. 4. **Portability**: The boat is lightweight, generally around 120 pounds, which makes it easy to transport on a trailer or car top and launch from a beach or dock. 5. **Performance**: While designed for simplicity, the Sunfish can be quite fast and responsive, offering a fun sailing experience. It performs well in a variety of wind conditions. 6. **Popularity**: Due to its affordability, ease of use, and widespread availability, the Sunfish is one of the most popular sailboats in the world, with a strong community and numerous racing events dedicated to it. Overall, the Sunfish is celebrated for its versatility, making it a great choice for both casual sailing and competitive racing.
2 Optimist The Optimist sailboat is a small, single-handed dinghy designed specifically for young sailors. Here are some of its key features: 1. **Size and Design**: The Optimist is a pram dinghy, which means it has a flat bow. It is typically 7 feet 9 inches (2.36 meters) long, making it compact and manageable for children. 2. **Simplicity**: The design is straightforward, with a single sail and a simple rigging system, which makes it ideal for beginners to learn the basics of sailing. 3. **Stability**: The flat-bottomed hull provides excellent stability, which is crucial for young sailors who are just starting to learn how to balance and control a sailboat. 4. **Lightweight**: The boat is lightweight, usually around 77 pounds (35 kilograms), making it easy to handle both on and off the water. 5. **Durability**: Optimists are typically made from fiberglass or polyethylene, materials known for their durability and resistance to the elements, ensuring the boat can withstand the rigors of training and racing. 6. **International Class**: The Optimist is recognized as an international class by World Sailing, and it is one of the most popular sailboats for youth racing worldwide, with a strong presence in competitive sailing events. 7. **Training and Racing**: It is widely used for training young sailors and is often the first step in a racing career, with many sailing programs and clubs offering Optimist classes and competitions. These features make the Optimist an excellent choice for introducing children to the sport of sailing.
3 Finn The Finn is a single-handed, cat-rigged sailboat that is primarily used for racing. It was designed by Rickard Sarby in 1949 and has been an Olympic class since 1952, although it was removed from the Olympic program after the 2020 Tokyo Games. Here are some key features of the Finn: 1. **Design and Construction**: The Finn has a robust and sturdy design, typically made from fiberglass or a combination of fiberglass and carbon fiber. This makes it durable and suitable for various weather conditions. 2. **Hull**: The hull is approximately 4.5 meters (14.8 feet) in length, with a beam of about 1.5 meters (4.9 feet). The hull design allows for excellent maneuverability and stability. 3. **Rigging**: The Finn is equipped with a single sail, a large fully-battened mainsail, which is controlled by a sophisticated rigging system. The sail is typically made from high-performance materials like Mylar or Dacron. 4. **Mast and Boom**: The mast is free-standing and often made from aluminum or carbon fiber, which provides flexibility and strength. The boom is also designed to optimize sail shape and performance. 5. **Sailing Characteristics**: The Finn is known for its demanding and tactical sailing, requiring skill and physical fitness. It is responsive and quick to accelerate, making it popular among competitive sailors. 6. **Adjustability**: The boat features adjustable controls for sail shape, such as the outhaul, cunningham, and vang, allowing sailors to fine-tune performance based on wind conditions. 7. **Competitive Racing**: The Finn class has a strong international presence with numerous regattas and championships, making it a prestigious class in competitive sailing. Overall, the Finn is celebrated for its challenging yet rewarding sailing experience, making it a favorite among experienced sailors and competitive racers.

Posting to Coop

Coop is a platform for creating, storing and sharing LLM-based research and validating it with human respondents. We can post surveys, agents, results and notebooks, such as this one. Learn more about using Coop.

[ ]:
from edsl import Notebook

nb = Notebook(path = "question_loop_scenarios.ipynb")

nb.push(
    description = "Question method `loop` for creating questions with scenarios",
    alias = "question-loop-scenarios",
    visibility = "public"
)