Skip to content

Commit

Permalink
Merge pull request #1301 from gaffney2010/fix-rs
Browse files Browse the repository at this point in the history
Fix ResultSet __eq__ to handle nans
  • Loading branch information
gaffney2010 committed Mar 30, 2020
2 parents 4e351f1 + 7adee95 commit f91811e
Show file tree
Hide file tree
Showing 3 changed files with 101 additions and 18 deletions.
64 changes: 47 additions & 17 deletions axelrod/result_set.py
Expand Up @@ -2,6 +2,7 @@
import csv
import itertools
from multiprocessing import cpu_count
from typing import List
import warnings

import numpy as np
Expand Down Expand Up @@ -117,9 +118,12 @@ def _reshape_out(
alternative=0,
)

self.wins = self._reshape_two_dim_list(sum_per_player_repetition_df["Win"])
self.scores = self._reshape_two_dim_list(sum_per_player_repetition_df["Score"])
self.normalised_scores = self._reshape_two_dim_list(normalised_scores_series)
self.wins = self._reshape_two_dim_list(
sum_per_player_repetition_df["Win"])
self.scores = self._reshape_two_dim_list(
sum_per_player_repetition_df["Score"])
self.normalised_scores = self._reshape_two_dim_list(
normalised_scores_series)

self.cooperation = self._build_cooperation(
sum_per_player_opponent_df["Cooperation count"]
Expand Down Expand Up @@ -166,7 +170,8 @@ def _reshape_out(
self.ranked_names = self._build_ranked_names()

self.payoff_matrix = self._build_summary_matrix(self.payoffs)
self.payoff_stddevs = self._build_summary_matrix(self.payoffs, func=np.std)
self.payoff_stddevs = self._build_summary_matrix(self.payoffs,
func=np.std)

self.payoff_diffs_means = self._build_payoff_diffs_means()
self.cooperating_rating = self._build_cooperating_rating()
Expand Down Expand Up @@ -266,7 +271,9 @@ def _build_good_partner_matrix(self, good_partner_series):
# interactions.
row.append(0)
else:
row.append(good_partner_dict.get((player_index, opponent_index), 0))
row.append(
good_partner_dict.get((player_index, opponent_index),
0))
good_partner_matrix.append(row)
return good_partner_matrix

Expand Down Expand Up @@ -334,13 +341,15 @@ def _build_normalised_state_distribution(self):
for counter in player:
total = sum(counter.values())
counters.append(
Counter({key: value / total for key, value in counter.items()})
Counter(
{key: value / total for key, value in counter.items()})
)
normalised_state_distribution.append(counters)
return normalised_state_distribution

@update_progress_bar
def _build_state_to_action_distribution(self, state_to_action_distribution_series):
def _build_state_to_action_distribution(self,
state_to_action_distribution_series):
state_to_action_key_map = {
"CC to C count": ((C, C), C),
"CC to D count": ((C, C), D),
Expand Down Expand Up @@ -396,7 +405,8 @@ def _build_normalised_state_to_action_distribution(self):
return normalised_state_to_action_distribution

@update_progress_bar
def _build_initial_cooperation_count(self, initial_cooperation_count_series):
def _build_initial_cooperation_count(self,
initial_cooperation_count_series):
initial_cooperation_count_dict = initial_cooperation_count_series.to_dict()
initial_cooperation_count = [
initial_cooperation_count_dict.get(player_index, 0)
Expand All @@ -411,7 +421,7 @@ def _build_normalised_cooperation(self):
normalised_cooperation = [
list(np.nan_to_num(row))
for row in np.array(self.cooperation)
/ sum(map(np.array, self.match_lengths))
/ sum(map(np.array, self.match_lengths))
]
return normalised_cooperation

Expand All @@ -426,7 +436,8 @@ def _build_initial_cooperation_rate(self, interactions_series):
with warnings.catch_warnings():
warnings.simplefilter("ignore")
initial_cooperation_rate = list(
np.nan_to_num(np.array(self.initial_cooperation_count) / interactions_array)
np.nan_to_num(np.array(
self.initial_cooperation_count) / interactions_array)
)
return initial_cooperation_rate

Expand All @@ -451,7 +462,8 @@ def _build_eigenmoses_rating(self):
The eigenmoses rating as defined in:
http://www.scottaaronson.com/morality.pdf
"""
eigenvector, eigenvalue = eigen.principal_eigenvector(self.vengeful_cooperation)
eigenvector, eigenvalue = eigen.principal_eigenvector(
self.vengeful_cooperation)

return eigenvector.tolist()

Expand Down Expand Up @@ -575,7 +587,8 @@ def _build_tasks(self, df):
]
sum_per_player_opponent_task = df.groupby(groups)[columns].sum()

ignore_self_interactions_task = df["Player index"] != df["Opponent index"]
ignore_self_interactions_task = df["Player index"] != df[
"Opponent index"]
adf = df[ignore_self_interactions_task]

groups = ["Player index", "Repetition"]
Expand All @@ -589,7 +602,8 @@ def _build_tasks(self, df):
groups = ["Player index"]
column = "Initial cooperation"
initial_cooperation_count_task = adf.groupby(groups)[column].sum()
interactions_count_task = adf.groupby("Player index")["Player index"].count()
interactions_count_task = adf.groupby("Player index")[
"Player index"].count()

return (
mean_per_reps_player_opponent_task,
Expand All @@ -609,6 +623,18 @@ def __eq__(self, other):
other : axelrod.ResultSet
Another results set against which to check equality
"""

def list_equal_with_nans(v1: List[float], v2: List[float]) -> bool:
"""Matches lists, accounting for NaNs."""
if len(v1) != len(v2):
return False
for i1, i2 in zip(v1, v2):
if np.isnan(i1) and np.isnan(i2):
continue
if i1 != i2:
return False
return True

return all(
[
self.wins == other.wins,
Expand All @@ -628,8 +654,10 @@ def __eq__(self, other):
self.cooperating_rating == other.cooperating_rating,
self.good_partner_matrix == other.good_partner_matrix,
self.good_partner_rating == other.good_partner_rating,
self.eigenmoses_rating == other.eigenmoses_rating,
self.eigenjesus_rating == other.eigenjesus_rating,
list_equal_with_nans(self.eigenmoses_rating,
other.eigenmoses_rating),
list_equal_with_nans(self.eigenjesus_rating,
other.eigenjesus_rating),
]
)

Expand Down Expand Up @@ -699,7 +727,8 @@ def summarise(self):
rates = []
for state in states:
counts = [
counter[(state, C)] for counter in player if counter[(state, C)] > 0
counter[(state, C)] for counter in player if
counter[(state, C)] > 0
]

if len(counts) > 0:
Expand All @@ -722,7 +751,8 @@ def summarise(self):

summary_data = []
for rank, i in enumerate(self.ranking):
data = list(summary_measures[i]) + state_prob[i] + state_to_C_prob[i]
data = list(summary_measures[i]) + state_prob[i] + state_to_C_prob[
i]
summary_data.append(self.player(rank, *data))

return summary_data
Expand Down
7 changes: 7 additions & 0 deletions axelrod/tests/unit/test_eigen.py
Expand Up @@ -15,6 +15,13 @@ def test_identity_matrices(self):
self.assertAlmostEqual(evalue, 1)
assert_array_almost_equal(evector, _normalise(numpy.ones(size)))

def test_zero_matrix(self):
mat = numpy.array([[0, 0], [0, 0]])
evector, evalue = principal_eigenvector(mat)
self.assertTrue(numpy.isnan(evalue))
self.assertTrue(numpy.isnan(evector[0]))
self.assertTrue(numpy.isnan(evector[1]))

def test_2x2_matrix(self):
mat = numpy.array([[2, 1], [1, 2]])
evector, evalue = principal_eigenvector(mat)
Expand Down
48 changes: 47 additions & 1 deletion axelrod/tests/unit/test_resultset.py
Expand Up @@ -7,7 +7,7 @@
import pandas as pd
from axelrod.result_set import create_counter_dict
from axelrod.tests.property import prob_end_tournaments, tournaments
from numpy import mean, nanmedian, std
from numpy import mean, nan, nanmedian, std

from dask.dataframe.core import DataFrame
from hypothesis import given, settings
Expand Down Expand Up @@ -190,6 +190,52 @@ def test_init(self):
self.assertEqual(rs.players, self.players)
self.assertEqual(rs.num_players, len(self.players))

def _clear_matrix(self, matrix):
for i, row in enumerate(matrix):
for j, _ in enumerate(row):
matrix[i, j] = 0

def test_ne_vectors(self):
rs_1 = axelrod.ResultSet(
self.filename,
self.players,
self.repetitions
)

rs_2 = axelrod.ResultSet(
self.filename,
self.players,
self.repetitions
)

# A different vector
rs_2.eigenmoses_rating = (-1, -1, -1)

self.assertNotEqual(rs_1, rs_2)

def test_nan_vectors(self):
rs_1 = axelrod.ResultSet(
self.filename,
self.players,
self.repetitions
)
# Force a broken eigenmoses, by replacing vengeful_cooperation with
# zeroes.
self._clear_matrix(rs_1.vengeful_cooperation)
rs_1.eigenmoses_rating = rs_1._build_eigenmoses_rating()

rs_2 = axelrod.ResultSet(
self.filename,
self.players,
self.repetitions
)
# Force a broken eigenmoses, by replacing vengeful_cooperation with
# zeroes.
self._clear_matrix(rs_2.vengeful_cooperation)
rs_2.eigenmoses_rating = rs_2._build_eigenmoses_rating()

self.assertEqual(rs_1, rs_2)

def test_init_multiprocessing(self):
rs = axelrod.ResultSet(
self.filename,
Expand Down

0 comments on commit f91811e

Please sign in to comment.