A numerical study of fixation probabilities for strategies in the Prisoners Dilemma

This notebook contains all the Python code used to generate the plots and figures for the paper.

Here are the versions of the various libraries used:


In [1]:
%matplotlib inline

import axelrod as axl
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
import sympy as sym
import itertools
import csv
import os
import glob


from mpl_toolkits.axes_grid1 import make_axes_locatable
from pandas.util.testing import assert_frame_equal

import math
import imp
players = imp.load_source('players', '../src/players.py')
generate_cache = imp.load_source('players', '../src/generate_cache.py')
theoretic = imp.load_source('theoretic', '../src/theoretic.py')
abbreviations = imp.load_source('abbreviations', '../src/abbreviations.py')

assert axl.__version__ == '2.9.0' 
assert pd.__version__ == '0.19.2'
assert matplotlib.__version__ == '2.0.0'
assert sns.__version__ == '0.7.1'
assert np.__version__ == '1.12.1'
assert sym.__version__ == '1.0'

Description of the used strategies

Here are all the strategies used in this experiment:


In [2]:
from abbreviations import abbreviations

def abbreviate(player_name, abbreviations=abbreviations):
    """
    Return the abbreviated name of a play if one has been given
    """
    if isinstance(player_name, axl.Player):
        player_name = str(player_name)
    return abbreviations.get(player_name, player_name)

assert abbreviate("Tit For Tat") == "TfT"
assert abbreviate("Random: 0.5") == "Random"
assert abbreviate(axl.TitForTat()) == "TfT"

In [3]:
from players import selected_players
players_of_interest = set([p for p in selected_players() 
                           if ("length" not in p.classifier["makes_use_of"]) and
                              ("Incorrect" not in abbreviate(p))])
                      
assert len(players_of_interest) == 164

These players have been omitted from the analysis (even though their data was collected). This is because they use the match length or were entered in to the original data sweep by error (the FSM strategies do not use the correct initial state).


In [4]:
set(map(str, selected_players())) - set(map(str, players_of_interest))


Out[4]:
{"BackStabber: ('D', 'D')",
 'Champion',
 "DoubleCrosser: ('D', 'D')",
 "FSM Player: [(0, 'C', 0, 'C'), (0, 'D', 3, 'C'), (1, 'C', 5, 'D'), (1, 'D', 0, 'C'), (2, 'C', 3, 'C'), (2, 'D', 2, 'D'), (3, 'C', 4, 'D'), (3, 'D', 6, 'D'), (4, 'C', 3, 'C'), (4, 'D', 1, 'D'), (5, 'C', 6, 'C'), (5, 'D', 3, 'D'), (6, 'C', 6, 'D'), (6, 'D', 6, 'D'), (7, 'C', 7, 'D'), (7, 'D', 5, 'C')], 1, C",
 "FSM Player: [(0, 'C', 13, 'D'), (0, 'D', 12, 'D'), (1, 'C', 3, 'D'), (1, 'D', 4, 'D'), (2, 'C', 14, 'D'), (2, 'D', 9, 'D'), (3, 'C', 0, 'C'), (3, 'D', 1, 'D'), (4, 'C', 1, 'D'), (4, 'D', 2, 'D'), (5, 'C', 12, 'C'), (5, 'D', 6, 'C'), (6, 'C', 1, 'C'), (6, 'D', 14, 'D'), (7, 'C', 12, 'D'), (7, 'D', 2, 'D'), (8, 'C', 7, 'D'), (8, 'D', 9, 'D'), (9, 'C', 8, 'D'), (9, 'D', 0, 'D'), (10, 'C', 2, 'C'), (10, 'D', 15, 'C'), (11, 'C', 7, 'D'), (11, 'D', 13, 'D'), (12, 'C', 3, 'C'), (12, 'D', 8, 'D'), (13, 'C', 7, 'C'), (13, 'D', 10, 'D'), (14, 'C', 10, 'D'), (14, 'D', 7, 'D'), (15, 'C', 15, 'C'), (15, 'D', 11, 'D')], 1, C",
 "FSM Player: [(0, 'C', 7, 'C'), (0, 'D', 1, 'C'), (1, 'C', 11, 'D'), (1, 'D', 11, 'D'), (2, 'C', 8, 'D'), (2, 'D', 8, 'C'), (3, 'C', 3, 'C'), (3, 'D', 12, 'D'), (4, 'C', 6, 'C'), (4, 'D', 3, 'C'), (5, 'C', 11, 'C'), (5, 'D', 8, 'D'), (6, 'C', 13, 'D'), (6, 'D', 14, 'C'), (7, 'C', 4, 'D'), (7, 'D', 2, 'D'), (8, 'C', 14, 'D'), (8, 'D', 8, 'D'), (9, 'C', 0, 'C'), (9, 'D', 10, 'D'), (10, 'C', 8, 'C'), (10, 'D', 15, 'C'), (11, 'C', 6, 'D'), (11, 'D', 5, 'D'), (12, 'C', 6, 'D'), (12, 'D', 9, 'D'), (13, 'C', 9, 'D'), (13, 'D', 8, 'D'), (14, 'C', 8, 'D'), (14, 'D', 13, 'D'), (15, 'C', 4, 'C'), (15, 'D', 5, 'C')], 1, C",
 'Knowledgeable Worse and Worse',
 'Meta Majority Memory One: 32 players',
 'Meta Winner Memory One: 32 players',
 'NMWE Memory One: 32 players',
 'Stalker: D'}

In [5]:
with open("../data/reference_keys.csv", "r") as f:
    reader = csv.reader(f)
    reference_keys = {player: eval(keys) for player, keys in reader}
    
assert reference_keys['ALLCorALLD'] == ['axelrodproject']
assert reference_keys['Cooperator'] == ['Axelrod1984', 'Mittal2009', 'Press2012']

In [6]:
with open("../tex/list_of_players.tex", "w") as f:
    for player in sorted(players_of_interest, key=str):
        latex_name = "{}".format(player).replace("_", "\_")
        f.write("\item {}".format(latex_name))
        abbreviation = abbreviate(player)
        if abbreviation != player.name:
            f.write("(\\textbf{{{}}})".format(abbreviation))
        if player.classifier["stochastic"]:
            f.write(" - \\textit{Stochastic}")
        else:
            f.write(" - \\textit{Deterministic}")
        try:
            mem = int(player.classifier["memory_depth"])
        except OverflowError:
            mem = "\(\infty\)"
        if player.name == "Grudger":
            mem = 1  # Overwrite incorrect classification
        f.write(" - \\textit{{Memory depth}}: {}".format(mem))
        try:
            f.write(". \cite{{{}}}\n".format(", ".join(sorted(reference_keys[str(player)]))))
        except KeyError:
            f.write(".\n")

Here are some summary information about the strategies


In [7]:
def clean_mem(n):
    try:
        return int(n)
    except OverflowError:
        return -1

player_info = pd.DataFrame([[abbreviate(p), p.classifier["stochastic"], clean_mem(p.classifier['memory_depth'])] 
                        for p in players_of_interest], 
                       columns=["Player", "Stochastic", "Memory Depth"])

In [8]:
temp_df = pd.DataFrame(player_info.groupby("Stochastic")["Player"].count()).reset_index().rename(columns={"Player": "Count"})
for bool in [True, False]:
    filename = "../tex/num_stochastic.tex" if bool else "../tex/num_deterministic.tex"
    with open(filename, "w") as f:
        num = temp_df[temp_df["Stochastic"] == bool]["Count"].iloc[0]
        print(num, bool)
        f.write(str(num))
        
with open("../tex/num_strategies.tex", "w") as f:
    num = len(players_of_interest)
    f.write(str(num))
    print(num)
    
with open("../tex/num_strategies_axelrod.tex", "w") as f:
    num = len(axl.strategies)
    f.write(str(num))
    print(num)


43 True
121 False
164
186

In [9]:
mem_df = pd.DataFrame(player_info.groupby("Memory Depth")["Player"].count()).transpose()
mem_df.rename(index={"Player": "Count"}, inplace=True)
cols = mem_df.columns.tolist()
mem_df = mem_df[cols[1:] + [cols[0]]]
mem_df


Out[9]:
Memory Depth 0 1 2 3 4 5 6 9 10 11 12 16 20 40 200 -1
Count 3 29 12 8 2 6 1 1 5 1 1 2 2 2 1 88

In [10]:
def clean_latex(string):
    """Replace some special carachters"""
    string = string.replace("textbackslashpi", "pi")
    string = string.replace("textbackslashphi", "phi")
    string = string.replace("\\$", "$")
    string = string.replace("\\$", "$")
    string = string.replace("\\textasciicircum", "^")
    string = string.replace("\_", "_")
    string = string.replace("2_2_2", "2\_2\_2")
    string = string.replace("1_1_1", "1\_1\_1")
    for i in range(1, 4):
        string = string.replace("TF{}".format(i), "\\textbf{{TF{}}}".format(i))
    return string

with open("../tex/memory_depth_count.tex", "w") as f:
    string = clean_latex(mem_df.to_latex()).replace("-1", "\(\infty\)")
    f.write(string)

Illustrating the Approximate moran process


In [11]:
cached_outcomes = pd.read_csv("../data/outcomes.csv", header=None, 
                              names=["Player 1", "Player 2", "Score 1", "Score 2", "Iteration"])

cached_outcomes["Player 1"] = cached_outcomes["Player 1"].apply(lambda x: abbreviate(x))
cached_outcomes["Player 2"] = cached_outcomes["Player 2"].apply(lambda x: abbreviate(x))

cached_outcomes = cached_outcomes[cached_outcomes["Player 1"].isin(player_info["Player"]) & 
                                  cached_outcomes["Player 2"].isin(player_info["Player"])]

assert len(cached_outcomes.index) == 1169001

In [12]:
cached_outcomes.head()


Out[12]:
Player 1 Player 2 Score 1 Score 2 Iteration
0 Alternator Hunter Prober 2 2.985 3.010 1
1 Alternator Hunter Prober 3 0.015 4.990 1
2 Adaptive Adaptive 2.950 2.950 1
3 Alternator Hunter Prober 4 0.150 4.900 1
4 Adaptive Adaptive Tit For Tat 2.955 2.955 1

In [13]:
is_stochastic = dict(zip(player_info["Player"], player_info["Stochastic"]))

In [14]:
temp_df = pd.DataFrame(cached_outcomes.groupby(["Player 1", "Player 2"]).count()).reset_index()
temp_df["Outcome count"] = temp_df["Score 1"]
temp_df.drop(["Score 1", "Score 2", "Iteration"], axis=1, inplace=True)

In [15]:
temp_df["Stochastic"] = ((temp_df["Player 1"].map(lambda p: is_stochastic[p]) | 
                          temp_df["Player 2"].map(lambda p: is_stochastic[p])) & 
                         (temp_df["Outcome count"] > 1))

In [16]:
temp_df.head()


Out[16]:
Player 1 Player 2 Outcome count Stochastic
0 $\phi$ $\phi$ 1 False
1 $\phi$ $\pi$ 1 False
2 $\phi$ $e$ 1 False
3 $\phi$ 2TfT 1 False
4 $\phi$ Gradual 1 False

Validating the model

Here we create a number of plots comparing the theoretic fixation probability with the observed fixation probability for a number of initial starting populations.


In [17]:
validation = pd.read_csv("../data/fixation_validation.csv", index_col=False)

In [18]:
validation.tail()


Out[18]:
Repetitions N i Player 1 Player 2 Theoretic Simulated
1059 1000 20 10 Calculator Random: 0.5 0.085562 0.084
1060 1000 20 19 Calculator Random: 0.5 0.700176 0.712
1061 1000 20 1 Cooperator Tit For Tat 0.050000 0.049
1062 1000 20 10 Cooperator Tit For Tat 0.500000 0.502
1063 1000 20 19 Cooperator Tit For Tat 0.950000 0.952

In [19]:
markers = itertools.cycle(('X', '+', 'o')) 
marker_size = 50
fontsize=16

for names, df in validation.groupby(["Player 1", "Player 2"]):
    df.sort_values("N", inplace=True)
    
    repetitions = df["Repetitions"].iloc[0]
    assert all(df["Repetitions"] == repetitions)
    
    # Get names instead of repr (to drop some parameters)
    names = [abbreviate(name) for name in names]
    title = "{} and {} over {} repetitions".format(*names, repetitions)
    filename = "{}_v_{}".format(*names)

    for substr in [": ", ".", ":", " "]:
        filename = filename.replace(substr, "_")
    
    plt.figure()

    labels = ["1", "N / 2", "N - 1"]
    index_sets = [df["i"] == 1, df["i"]  == df["N"] / 2, df["i"] == df["N"] - 1]
    for label, index in zip(labels, index_sets):
        d = df[index]
        plt.plot(d["N"], d["Theoretic"], label="")
        plt.scatter(d["N"], d["Simulated"], marker=next(markers), 
                        label="$x_{{{}}}$".format(label), s=marker_size)
    plt.xticks(d["N"])
    plt.title(title, fontsize=fontsize)
    plt.ylim(0, 1.1)
    plt.ylabel("{} fixation probability".format(names[0]))
    plt.xlabel("$N$")
    plt.legend(fontsize=15)
    plt.savefig("../img/{}.pdf".format(filename))


/home/vince/anaconda3/envs/moran/lib/python3.6/site-packages/ipykernel/__main__.py:6: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame

See the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
/home/vince/anaconda3/envs/moran/lib/python3.6/site-packages/matplotlib/pyplot.py:524: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).
  max_open_warning, RuntimeWarning)