Grouping participants¶
Many experiments require participants to interact in pairs or small groups. uproot makes grouping easy with the GroupCreatingWait page type, which automatically forms groups as participants arrive.
Basic group formation¶
Create a wait page that groups participants by subclassing GroupCreatingWait and setting group_size:
When participants reach this page, they wait until enough others arrive to form a group. Once a group forms, all members advance to the next page together.
See the prisoners_dilemma example
Assigning roles with after_grouping¶
Use the after_grouping callback to assign roles or initialize group members when the group forms:
class GroupPlease(GroupCreatingWait):
group_size = 2
@classmethod
def after_grouping(page, group):
for player, is_dictator in zip(players(group), [True, False]):
player.dictator = is_dictator
The callback receives the group object and runs exactly once when the group is created. All players in the group can then access their assigned attributes.
See the dictator_game example · trust_game example · ultimatum_game example · gift_exchange_game example
Using class attributes for role values¶
You can define role values as class attributes for cleaner code:
class GroupPlease(GroupCreatingWait):
group_size = 2
watch_values = (True, False)
@classmethod
def after_grouping(page, group):
for player, watched in zip(players(group), page.watch_values):
player.watched = watched
See the observed_diary example
Accessing group members¶
uproot provides several functions to access players within a group:
players(group)¶
Get all players in a group as a StorageBunch:
The StorageBunch supports iteration, filtering, and bulk operations:
# Sum contributions from all group members
total = sum(p.contribution for p in players(group))
# Filter players by attribute
cooperators = players(group).filter(_.cooperate == True)
# Find a specific player
dictator = players(group).find_one(dictator=True)
other_in_group(player)¶
For two-person groups, get the other player directly:
This function raises an error if the group doesn't have exactly two members.
See the prisoners_dilemma example · twobytwo example
others_in_group(player)¶
For groups of any size, get all other members (excluding the current player):
Group-level data storage¶
Access the shared group storage with player.group:
class Calculate(SynchronizingWait):
@classmethod
def all_here(page, group):
# Store a value at the group level
group.total_contribution = sum(p.contribution for p in players(group))
In templates, access group data:
Larger groups¶
For experiments with more than two players per group, simply increase group_size:
Use the same patterns for accessing members:
class Sync(SynchronizingWait):
@classmethod
def all_here(page, group):
total = sum(p.contribution for p in players(group))
for player in players(group):
player.payoff = ENDOWMENT - player.contribution + MPCR * total
See the public_goods_game example · beauty_contest example · minimum_effort_game example
Manual group creation¶
For custom matching logic, you can create groups programmatically using create_group() and create_groups() instead of GroupCreatingWait. This is useful when you need to:
- Match participants based on survey responses
- Form groups of different sizes
- Sort or balance groups by some criteria
Basic manual grouping¶
Use SynchronizingWait to wait for all participants, then create groups in the all_here callback:
class WaitForEveryone(SynchronizingWait):
synchronize = "session"
@classmethod
def all_here(page, session):
# Get all players and sort alphabetically by name
all_players = sorted(players(session), key=lambda p: p.name)
if len(all_players) == 1:
# Only one player
create_group(session, all_players)
else:
# Split into two groups of roughly equal size
mid = len(all_players) // 2
group_a = all_players[:mid]
group_b = all_players[mid:]
create_groups(session, [group_a, group_b])
class ShowGroup(Page):
@classmethod
def context(page, player):
group_members = sorted(players(player.group), key=lambda p: p.name)
return dict(
group_name=player.group.name,
group_members=group_members,
)
page_order = [
WaitForEveryone,
ShowGroup,
]
create_group()¶
Creates a single group from a list of players:
# Create a group from specific players
gid = create_group(session, [player1, player2])
# With a custom group name
gid = create_group(session, members, gname="custom_name")
# Allow reassigning players already in groups
gid = create_group(session, members, overwrite=True)
create_groups()¶
Creates multiple groups at once:
# Create pairs from a list of players
all_players = list(players(session))
pairs = [[all_players[i], all_players[i+1]] for i in range(0, len(all_players), 2)]
gids = create_groups(session, pairs)
Grouping by attribute¶
Match participants based on their responses:
class WaitAndMatch(SynchronizingWait):
synchronize = "session"
@classmethod
def all_here(page, session):
# Separate players by their preference
prefer_a = [p for p in players(session) if p.preference == "A"]
prefer_b = [p for p in players(session) if p.preference == "B"]
# Match players with different preferences
for p1, p2 in zip(prefer_a, prefer_b):
create_group(session, [p1, p2])
Complete example: prisoner's dilemma¶
Here's a complete two-player game showing group formation, decision collection, and payoff calculation:
class GroupPlease(GroupCreatingWait):
group_size = 2
class Dilemma(Page):
fields = dict(
cooperate=RadioField(
label="Do you wish to cooperate?",
choices=[(True, "Yes"), (False, "No")],
),
)
def set_payoff(player):
other = other_in_group(player)
match player.cooperate, other.cooperate:
case True, True:
player.payoff = 10
case True, False:
player.payoff = 0
case False, True:
player.payoff = 15
case False, False:
player.payoff = 3
class Sync(SynchronizingWait):
@classmethod
def all_here(page, group):
for player in players(group):
set_payoff(player)
class Results(Page):
@classmethod
def context(page, player):
return dict(other=other_in_group(player))
page_order = [
GroupPlease,
Dilemma,
Sync,
Results,
]
See the full prisoners_dilemma example
Summary¶
| Function | Purpose |
|---|---|
GroupCreatingWait |
Wait page that forms groups automatically |
group_size |
Number of players per group |
after_grouping(page, group) |
Callback when group forms |
players(group) |
Get all players in a group |
other_in_group(player) |
Get the other player (2-person groups) |
others_in_group(player) |
Get all other players in the group |
player.group |
Access group-level storage |
create_group() |
Programmatically create a group |