-
Notifications
You must be signed in to change notification settings - Fork 263
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add state to action analysis. #870
Changes from 3 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -135,6 +135,94 @@ def compute_normalised_state_distribution(interactions): | |
return normalized_count | ||
|
||
|
||
def compute_state_to_action_distribution(interactions): | ||
""" | ||
Returns a list (for each player) of counts of each state to action pair | ||
for a set of interactions. A state to action pair is of the form: | ||
|
||
((C, D), C) | ||
|
||
Implying that from a state of (C, D) (the first player having played C and | ||
the second playing D) the player in question then played C. | ||
|
||
The following counter object, implies that the player in question was in | ||
state (C, D) for a total of 12 times, subsequently cooperating 4 times and | ||
defecting 8 times. | ||
|
||
Counter({((C, D), C): 4, ((C, D), D): 8}) | ||
|
||
Parameters | ||
---------- | ||
interactions : list of tuples | ||
A list containing the interactions of the match as shown at the top of | ||
this file. | ||
|
||
Returns | ||
---------- | ||
state_to_C_distributions : List of Counter Object | ||
List of Counter objects where the keys are the states and actions and | ||
the values the counts. The | ||
first/second Counter corresponds to the first/second player. | ||
""" | ||
if not interactions: | ||
return None | ||
|
||
distributions = [Counter([(state, outcome[j]) for state, outcome in zip(interactions, | ||
interactions[1:])]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we make the spacing here more aesthetically pleasing? |
||
for j in range(2)] | ||
return distributions | ||
|
||
|
||
def compute_normalised_state_to_action_distribution(interactions): | ||
""" | ||
Returns a list (for each player) of normalised counts of each state to action | ||
pair for a set of interactions. A state to action pair is of the form: | ||
|
||
((C, D), C) | ||
|
||
Implying that from a state of (C, D) (the first player having played C and | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pedantic: lowercase "impying" |
||
the second playing D) the player in question then played C. | ||
|
||
The following counter object, implies that the player in question was only | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. formatting |
||
ever in | ||
state (C, D), subsequently cooperating 1/3 of the time and | ||
defecting 2/3 times. | ||
|
||
Counter({((C, D), C): 0.333333, ((C, D), D): 0.66666667}) | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. A little more explanation of what this is doing might be in order. |
||
Parameters | ||
---------- | ||
interactions : list of tuples | ||
A list containing the interactions of the match as shown at the top of | ||
this file. | ||
|
||
Returns | ||
---------- | ||
normalised_state_to_C_distributions : List of Counter Object | ||
List of Counter objects where the keys are the states and actions and | ||
the values the normalized counts.. The | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. double period |
||
first/second Counter corresponds to the first/second player. | ||
""" | ||
if not interactions: | ||
return None | ||
|
||
distribution = compute_state_to_action_distribution(interactions) | ||
normalized_distribution = [] | ||
for player in range(2): | ||
counter = {} | ||
for state in [('C', 'C'), ('C', 'D'), ('D', 'C'), ('D', 'D')]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
C_count = distribution[player].get((state, 'C'), 0) | ||
D_count = distribution[player].get((state, 'D'), 0) | ||
total = C_count + D_count | ||
if total > 0: | ||
if C_count > 0: | ||
counter[(state, 'C')] = C_count / (C_count + D_count) | ||
if D_count > 0: | ||
counter[(state, 'D')] = D_count / (C_count + D_count) | ||
normalized_distribution.append(Counter(counter)) | ||
return normalized_distribution | ||
|
||
|
||
def sparkline(actions, c_symbol='█', d_symbol=' '): | ||
return ''.join([ | ||
c_symbol if play == 'C' else d_symbol for play in actions]) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -358,6 +358,33 @@ def _build_normalised_state_distribution(self): | |
norm.append(counters) | ||
return norm | ||
|
||
@update_progress_bar | ||
def _build_normalised_state_to_action_distribution(self): | ||
""" | ||
Returns | ||
---------- | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. return var name / spacing |
||
Normalised state distribution. A list of lists of counter objects: | ||
|
||
Dictionary where the keys are the states and the values are a | ||
normalized counts of the number of times that state goes to a given | ||
action. | ||
""" | ||
norm = [] | ||
for player in self.state_to_action_distribution: | ||
counters = [] | ||
for counter in player: | ||
norm_counter = Counter() | ||
for state in [(C, C), (C, D), (D, C), (D, D)]: | ||
total = counter[(state, C)] + counter[(state, D)] | ||
if total > 0: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If The test cases catch this occurrence :) |
||
for action in [C, D]: | ||
if counter[(state, action)] > 0: | ||
norm_counter[(state, action)] = counter[(state, action)] / total | ||
counters.append(norm_counter) | ||
norm.append(counters) | ||
return norm | ||
|
||
def _build_empty_metrics(self, keep_interactions=False): | ||
""" | ||
Creates the various empty metrics ready to be updated as the data is | ||
|
@@ -385,6 +412,8 @@ def _build_empty_metrics(self, keep_interactions=False): | |
self.initial_cooperation_count = [0 for player in plist] | ||
self.state_distribution = [[Counter() for opponent in plist] | ||
for player in plist] | ||
self.state_to_action_distribution = [[Counter() for opponent in plist] | ||
for player in plist] | ||
self.good_partner_matrix = [[0 for opponent in plist] | ||
for player in plist] | ||
|
||
|
@@ -567,6 +596,26 @@ def _update_state_distribution(self, p1, p2, counter): | |
counter[(C, D)], counter[(D, C)] = counter[(D, C)], counter[(C, D)] | ||
self.state_distribution[p2][p1] += counter | ||
|
||
def _update_state_to_action_distribution(self, p1, p2, counter_list): | ||
""" | ||
During a read of the data, update the state_distribution attribute | ||
|
||
Parameters | ||
---------- | ||
|
||
p1, p2 : int | ||
The indices of the first and second player | ||
counter_list : list of collections.Counter | ||
A list of counter objects for the states to action of a match | ||
""" | ||
counter = counter_list[0] | ||
self.state_to_action_distribution[p1][p2] += counter | ||
|
||
counter = counter_list[1] | ||
for act in [C, D]: | ||
counter[((C, D), act)], counter[((D, C), act)] = counter[((D, C), act)], counter[((C, D), act)] | ||
self.state_to_action_distribution[p2][p1] += counter | ||
|
||
def _update_good_partner_matrix(self, p1, p2, cooperations): | ||
""" | ||
During a read of the data, update the good partner matrix attribute | ||
|
@@ -671,6 +720,7 @@ def _build_score_related_metrics(self, progress_bar=False, | |
self._update_normalised_cooperation(p1, p2, interaction) | ||
|
||
if p1 != p2: # Anything that ignores self interactions | ||
state_to_actions = iu.compute_state_to_action_distribution(interaction) | ||
|
||
for player in [p1, p2]: | ||
self.total_interactions[player] += 1 | ||
|
@@ -685,16 +735,19 @@ def _build_score_related_metrics(self, progress_bar=False, | |
self._update_initial_cooperation_count(p1, p2, | ||
initial_coops) | ||
self._update_state_distribution(p1, p2, state_counter) | ||
self._update_state_to_action_distribution(p1, p2, | ||
state_to_actions) | ||
self._update_good_partner_matrix(p1, p2, cooperations) | ||
|
||
if progress_bar: | ||
self.progress_bar = tqdm.tqdm(total=12 + 2 * self.nplayers, | ||
self.progress_bar = tqdm.tqdm(total=13 + 2 * self.nplayers, | ||
desc="Finishing") | ||
self._summarise_normalised_scores() | ||
self._summarise_normalised_cooperation() | ||
|
||
self.ranking = self._build_ranking() | ||
self.normalised_state_distribution = self._build_normalised_state_distribution() | ||
self.normalised_state_to_action_distribution = self._build_normalised_state_to_action_distribution() | ||
self.ranked_names = self._build_ranked_names() | ||
self.payoff_matrix = self._build_payoff_matrix() | ||
self.payoff_stddevs = self._build_payoff_stddevs() | ||
|
@@ -772,7 +825,9 @@ def summarise(self): | |
self.player = namedtuple("Player", ["Rank", "Name", "Median_score", | ||
"Cooperation_rating", "Wins", | ||
"Initial_C_rate", "CC_rate", | ||
"CD_rate", "DC_rate", "DD_rate"]) | ||
"CD_rate", "DC_rate", "DD_rate", | ||
"CC_to_C_rate", "CD_to_C_rate", | ||
"DC_to_C_rate", "DD_to_C_rate"]) | ||
|
||
states = [(C, C), (C, D), (D, C), (D, D)] | ||
state_prob = [] | ||
|
@@ -787,13 +842,28 @@ def summarise(self): | |
counts = [0 for c in counts] | ||
state_prob.append(counts) | ||
|
||
state_to_C_prob = [] | ||
for player in self.normalised_state_to_action_distribution: | ||
rates = [] | ||
for state in states: | ||
counts = [counter[(state, 'C')] for counter in player | ||
if counter[(state, 'C')] > 0] | ||
|
||
if len(counts) > 0: | ||
rate = mean(counts) | ||
else: | ||
rate = 0 | ||
|
||
rates.append(rate) | ||
state_to_C_prob.append(rates) | ||
|
||
summary_measures = list(zip(self.players, median_scores, | ||
self.cooperating_rating, median_wins, | ||
self.initial_cooperation_rate)) | ||
|
||
summary_data = [] | ||
for rank, i in enumerate(self.ranking): | ||
data = list(summary_measures[i]) + state_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 | ||
|
@@ -802,7 +872,8 @@ def write_summary(self, filename): | |
""" | ||
Write a csv file containing summary data of the results of the form: | ||
|
||
"Rank", "Name", "Median-score-per-turn", "Cooperation-rating", "Initial_C_Rate", "Wins", "CC-Rate", "CD-Rate", "DC-Rate", "DD-rate" | ||
"Rank", "Name", "Median-score-per-turn", "Cooperation-rating", "Initial_C_Rate", "Wins", "CC-Rate", "CD-Rate", "DC-Rate", "DD-rate","CC-to-C-Rate", "CD-to-C-Rate", "DC-to-C-Rate", "DD-to-C-rate" | ||
|
||
|
||
Parameters | ||
---------- | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,11 +22,27 @@ class TestMatch(unittest.TestCase): | |
Counter({('D', 'C'): 2}), | ||
Counter({('C', 'C'): 1, ('C', 'D'): 1}), | ||
None] | ||
state_to_action_distribution = [[Counter({(('C', 'D'), 'D'): 1}), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we use |
||
Counter({(('C', 'D'), 'C'): 1})], | ||
[Counter({(('D', 'C'), 'D'): 1}), | ||
Counter({(('D', 'C'), 'C'): 1})], | ||
[Counter({(('C', 'C'), 'C'): 1}), | ||
Counter({(('C', 'C'), 'D'): 1})], | ||
None] | ||
|
||
normalised_state_distribution = [ | ||
Counter({('C', 'D'): 0.5, ('D', 'C'): 0.5}), | ||
Counter({('D', 'C'): 1.0}), | ||
Counter({('C', 'C'): 0.5, ('C', 'D'): 0.5}), | ||
None] | ||
normalised_state_to_action_distribution = [[Counter({(('C', 'D'), 'D'): 1}), | ||
Counter({(('C', 'D'), 'C'): 1})], | ||
[Counter({(('D', 'C'), 'D'): 1}), | ||
Counter({(('D', 'C'), 'C'): 1})], | ||
[Counter({(('C', 'C'), 'C'): 1}), | ||
Counter({(('C', 'C'), 'D'): 1})], | ||
None] | ||
|
||
sparklines = [ '█ \n █', ' \n██', '██\n█ ', None ] | ||
|
||
def test_compute_scores(self): | ||
|
@@ -63,6 +79,37 @@ def test_compute_normalised_state_distribution(self): | |
for inter, dist in zip(self.interactions, self.normalised_state_distribution): | ||
self.assertEqual(dist, iu.compute_normalised_state_distribution(inter)) | ||
|
||
def test_compute_state_to_action_distribution(self): | ||
for inter, dist in zip(self.interactions, | ||
self.state_to_action_distribution): | ||
self.assertEqual(dist, | ||
iu.compute_state_to_action_distribution(inter)) | ||
inter = [(C, D), (D, C), (C, D), (D, C), (D, D), (C, C), (C, D)] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. mixing |
||
expected_dist =[Counter({(('C', 'C'), 'C'): 1, (('D', 'C'), 'C'): 1, | ||
(('C', 'D'), 'D'): 2, (('D', 'C'), 'D'): 1, | ||
(('D', 'D'), 'C'): 1}), | ||
Counter({(('C', 'C'), 'D'): 1, | ||
(('C', 'D'), 'C'): 2, (('D', 'C'), 'D'): 2, | ||
(('D', 'D'), 'C'): 1})] | ||
|
||
self.assertEqual(expected_dist, | ||
iu.compute_state_to_action_distribution(inter)) | ||
|
||
def test_compute_normalised_state_to_action_distribution(self): | ||
for inter, dist in zip(self.interactions, | ||
self.normalised_state_to_action_distribution): | ||
self.assertEqual(dist, | ||
iu.compute_normalised_state_to_action_distribution(inter)) | ||
inter = [(C, D), (D, C), (C, D), (D, C), (D, D), (C, C), (C, D)] | ||
expected_dist =[Counter({(('C', 'C'), 'C'): 1, (('D', 'C'), 'C'): 1 / 2, | ||
(('C', 'D'), 'D'): 1, (('D', 'C'), 'D'): 1 / 2, | ||
(('D', 'D'), 'C'): 1}), | ||
Counter({(('C', 'C'), 'D'): 1, | ||
(('C', 'D'), 'C'): 1, (('D', 'C'), 'D'): 1, | ||
(('D', 'D'), 'C'): 1})] | ||
self.assertEqual(expected_dist, | ||
iu.compute_normalised_state_to_action_distribution(inter)) | ||
|
||
def test_compute_sparklines(self): | ||
for inter, spark in zip(self.interactions, self.sparklines): | ||
self.assertEqual(spark, iu.compute_sparklines(inter)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pedantic: "The following Counter object implies that"