Diversity and Expertise¶

This Jupyter Notebook demonstrates how the agent-based model (ABM) of group decision-making works by walking you through the stages of the team’s collective decision-making. The idea is that the team faces a binary choice problem and each individual has access to a number of imperfect sources to guide their individual decision-making. The model assumes that one of the two options is objectively or intersubjectively correct. The main components of the agent-based model are:

  1. Sources. Each ABM consists of a set of sources and their reliabilities. Each source has a certain reliability (typically, $\geq 0.5$), which represents the probability that it produces evidence that supports the correct alternative.
  2. Agents. Each agent in the ABM is represented by their heuristic, which is modelled as the sources she has access to. Agents form their opinions by adopting the majority rule. The basic idea is that agents follow the majority of the evidences available to them.
  3. Teams. A team is a set of agents. The teams communicate internally before forming a collective decision. We consider three deliberative mechanisms:
    1. Opinion-based dynamics: agents communicate their opinion (not the underlying evidences) and the team’s collective decision follows the majority opinion.
    2. Evidence-based dynamics: agents communicate all their evidences and the team’s collective decision follows the majority evidence.
    3. Boundedly rational evidence-based dynamics: agents communicate all their evidences and the team’s collective decision follows the majority evidence—possibly with double counting.

In particular, an given team, implemented by the class Team, consists of the following building blocks:

  • A set of sources and their (heterogeneous) reliabilities, implemented by the class Sources.
  • A set of agents and their heuristics (i.e., sets of sources that they have access to, one for each agent), implemented by the class Agent.

0. Setup¶

We start with loading required packages and creating the scripts necessary for the visualizations.

In [1]:
import copy
import glob
import os
import random as rd

import utils.config as cfg
import matplotlib.pyplot as plt
import networkx as nx
import numpy as np
import pandas as pd
import pyvis
import seaborn as sns
from pyvis.network import Network

from models.sources import Sources
from models.agent import Agent
from models.team import Team
from models.generate_teams import (
    generate_diverse_team,
    generate_expert_team,
    generate_random_team,
)
In [30]:
correct_color = "forestgreen"
incorrect_color = "firebrick"
agent_initial_color = "darkorange"
source_initial_color = "teal"


def visualize(
    team: Team,
    sorting: str = "degree",
    sizing: str = "reliability",
    coloring: str = "initial",
    edge_type: str = "vertical",
    filename: str = None,
    show: bool = True,
) -> Network:
    net = Network(
        height="200px",
        width="100%",
        directed=True,
        notebook=True,
        neighborhood_highlight=True,
        cdn_resources="in_line",
        layout=True,
    )

    sources_ordered = sort_sources(team, sorting)
    sources_coloring = color_sources(team, coloring)
    sources_sizing = size_sources(team, sizing)
    agents_coloring = color_agents(team, coloring)

    for source in sources_ordered:
        net.add_node(
            f"s{source}",
            title=f"Reliability {team.sources.reliabilities[source]}",
            label=True,
            color=sources_coloring[source],
            level=1,
            size=sources_sizing[source],
        )

    for agent_no, agent in enumerate(team.members):
        net.add_node(
            agent_no,
            title=f"Agent {agent_no}",
            color=agents_coloring[agent],
            level=4,
            size=20,
        )
    net.set_edge_smooth(edge_type)
    for agent_no, agent in enumerate(team.members):
        for source in agent.heuristic:
            net.add_edge(
                source=f"s{source}", to=agent_no, color=sources_coloring[source]
            )

    net.hrepulsion(node_distance=200, damping=0.1)
    net.prep_notebook()
    return net


def sort_sources(team: Team, sorting: str = "degree") -> list:
    sources_tuples = [(0, 0)]
    heuristics = np.array([agent.heuristic for agent in team.members]).flatten()
    unique, counts = np.unique(heuristics, return_counts=True)
    source_degrees_dict = dict(zip(unique, counts))
    for source in team.sources.sources:
        if source not in source_degrees_dict.keys():
            source_degrees_dict[source] = 0

    if sorting == "degree":
        sources_tuples = [
            (
                source,
                source_degrees_dict[source],
                team.sources.reliabilities[source],
            )
            for source in team.sources.sources
        ]
    elif sorting == "reliability":
        sources_tuples = [
            (
                source,
                team.sources.reliabilities[source],
                source_degrees_dict[source],
            )
            for source in team.sources.sources
        ]
    sources_tuples.sort(key=lambda item: item[2], reverse=True)
    sources_tuples.sort(key=lambda item: item[1], reverse=True)
    sources_ordered, _, _ = zip(*sources_tuples)
    return sources_ordered


def color_sources(team: Team, coloring: str) -> dict:
    sources_coloring = {source: source_initial_color for source in team.sources.sources}

    for source in team.sources.sources:
        if coloring == "sources" or coloring == "agents":
            if team.sources.valences[source] == cfg.vote_for_positive:
                sources_coloring[source] = correct_color
            else:
                sources_coloring[source] = incorrect_color
    return sources_coloring


def size_sources(team: Team, sizing: str) -> dict:
    source_sizing = {source: 1 for source in team.sources.sources}
    heuristics = np.array([agent.heuristic for agent in team.members]).flatten()
    unique, counts = np.unique(heuristics, return_counts=True)
    source_degrees_dict = dict(zip(unique, counts))
    if sizing == "degree":
        source_sizing = {source: source_degrees_dict for source in team.sources.sources}
    elif sizing == "reliability":
        source_sizing = {
            source: team.sources.reliabilities[source]
            for source in team.sources.sources
        }
    minimum = min(source_sizing.values())
    maximum = max(source_sizing.values())
    for source in team.sources.sources:
        source_sizing[source] = 10 + 40 * (source_sizing[source] - minimum) / (
            maximum - minimum
        )
    return source_sizing


def color_agents(team: Team, coloring: str) -> dict:
    agents_coloring = {agent: agent_initial_color for agent in team.members}
    for agent in team.members:
        if coloring == "agents":
            if agent.opinion == cfg.vote_for_positive:
                agents_coloring[agent] = correct_color
            else:
                agents_coloring[agent] = incorrect_color
    return agents_coloring

1. A simple example¶

1.0 Initial stage¶

Let us start with considering a random team of 9 agents. More explicitly, we set the parameters as follows:

  • A set of 21 sources given by $\{s_1, \ldots, s_{21}\}$,
  • A team consisting of 9 agents given by $\{0, \ldots, 8\}$,
  • Each agent has a heuristic of size 5, i.e., each agent has access to exactly five sources.

A random team is depicted in the picture below. Let me explain the picture:

  • Nodes:
    • The initial color of the nodes represents whether they are sources or agents.
    • Sources:
      • The size of the sources is proportional to their reliability, so that bigger sources are more reliable.
      • The sources are ordered from left to right from most to least accessed. (Equally accessed sources are ordered from left to right from most to least reliable.)
    • Agents:
      • The size and ordering of the agents convey no information.
  • Edges:
    • The edges represent the agents' heuristics: which is the set of targets of the incoming edges. Accordingly, every agent has in-degree 5.
    • Hence, sources with higher out-degrees have more influence on the team decision-making.
    • The color of an edge represents the color of the associated source.

All the pictures are interactive: if you click on a source or agent, the connected nodes are highlighted, and you can move nodes around!

In [7]:
sources = Sources(
    n_sources=17, 
    reliability_distribution=("equi", 0.6, 0.2),
)
team = generate_random_team(
    sources=sources,
    heuristic_size=5,
    team_size=9,
)
team.update_opinions()
In [70]:
visualize(team, coloring="initial").show("www/team_initial.html")
www/team_initial.html
Out[70]:

1.1 Source valences¶

In the first stage, the valence of each source $s\in S$ is probabilistically determined by its reliability $p_s$ (i.e., a Bernouilli trial). The color of the sources represents whether their piece of evidence has the correct or incorrect valence, i.e., supports the objectively or intersubjectively correct or incorrect option.

In the second stage, each agent $i$ forms their opinion based on the evidences from their sources $S_i$. The color of the agents represents whether their opinion supports the correct or incorrect alternative.

Let's see how this might go in this random team. Did any source have the wrong valence? Can you figure out why? Can you predict some of the agent's opinions?

In [71]:
sources.update_valences()
team.update_opinions()
visualize(team=team, coloring="sources").show("www/team_sources.html")
www/team_sources.html
Out[71]:

1.2 Agent opinions¶

Let us consider the opinions of the agents in this random team. As mentioned before, the color of the agents represents whether their opinion favours the correct or incorrect option.

Let's see how this goes in this random team. Did anyone form the wrong opinion? Can you figure out why?

In [72]:
visualize(team=team, coloring="agents").show("www/team_agents.html")
www/team_agents.html
Out[72]:

1.3 The team’s collective decision¶

The outcomes may be no surprise to you at this point, but let's determine the team's collective decision for each of the deliberative mechanisms.

Before going over the different deliberative mechanisms we create a script that will nicely print the collective decisions:

In [100]:
def collective_decision(list_of_items: list, item_type: str) -> None:
    n_correct: int = len([item for item in list_of_items if item == cfg.vote_for_positive])
    n_incorrect: int = len([item for item in list_of_items if item == cfg.vote_for_negative])
    print(f"Correct {item_type}: {n_correct} | Incorrect {item_type}: {n_incorrect}")
    if n_correct > n_incorrect:
        print("=> Hence, the team's decision is correct!")
    elif n_correct < n_incorrect: 
        print("=> Hence, the team's decision is incorrect!")
    else:
        print("=> Hence, the evidences lead to a tie!")

A. Opinion-based dynamics¶

In [101]:
opinions_valences = [agent.opinion for agent in team.members]
collective_decision(opinions_valences, "opinions")
Correct opinions: 3 | Incorrect opinions: 6
=> Hence, the team's decision is incorrect!

B. Evidence-based dynamics¶

In [102]:
evidences_accessed = set(
    [source for agent in team.members for source in agent.heuristic]
)
evidences_valences = [
    sources.valences[source] for source in evidences_accessed
]
collective_decision(evidences_valences, "evidences")
Correct evidences: 8 | Incorrect evidences: 7
=> Hence, the team's decision is correct!

C. Boundedly rational evidence-based dynamics¶

In [103]:
evidences_valences = [
    sources.valences[source] for agent in team.members for source in agent.heuristic
]
collective_decision(evidences_valences, "evidences")
Correct evidences: 20 | Incorrect evidences: 25
=> Hence, the team's decision is incorrect!

2 Different teams¶

In this repository we implemented three types of teams:

  1. Expert teams consisting of the best-performing agents;
  2. Diverse teams consisting of a (cognitively) diverse set of agents;
  3. Random teams consisting of a randomly selected agents.

As mentioned earlier, each agent’s heuristic is represented by a set of sources. We assume that all agents have the same heuristic size (i.e., access the same number of sources), typically 5.

For any given set of sources and their reliabilities, we can generate expert, diverse and random teams. For example, to generate the expert team, the task is as follows:

Input 1: A set of sources and their reliabilities.

Input 2: A team size.

Input 3: A heuristic size.


Output: The team consisting of the best-performing agents out of all possible agents.

Once a team has been constructed, the team decision-making works as illustrated in Section 1.

2.1 The expert team¶

Below you can see what the expert team may look like. Notice that the members of the expert team only have access to a small number of sources, namely the sources with the highest reliability.

In [104]:
team = generate_expert_team(sources=sources, heuristic_size=5, team_size=9)
visualize(team).show("www/expert_team.html")
www/expert_team.html
Out[104]:

2.2 A diverse team¶

Below you can see what a maximally diverse team might look like. Notice that a maximally diverse team has access to all (or virtually all) sources — regardless of their reliability.

In [105]:
team = generate_diverse_team(sources=sources, heuristic_size=5, team_size=9)
visualize(team).show("www/diverse_team.html")
www/diverse_team.html
Out[105]:

2.3 A random team¶

Below you can see what a random team might look like. Notice that a random team has access to many (but typically not all) sources — regardless of their reliability.

In [69]:
team = generate_random_team(sources=sources, heuristic_size=5, team_size=9)
visualize(team).show("www/random_team.html")
www/random_team.html
Out[69]:

3. Thanks¶

I hope this brief illustration helped to understand the inner workings of the agent-based model. Thanks for showing interest!