Source code for poisson_approval.meta_analysis.NiceStatsProfileOrdinal

import numpy as np
import matplotlib.pyplot as plt
from poisson_approval.utils.Util import initialize_random_seeds
from poisson_approval.profiles.ProfileOrdinal import ProfileOrdinal
from poisson_approval.random_factories.RandProfileOrdinalUniform import RandProfileOrdinalUniform


# noinspection PyUnresolvedReferences
[docs]class NiceStatsProfileOrdinal: """Compute nice stats on ordinal profiles. Parameters ---------- tests_profile : list A list of pairs `(test, name)`, where `test` is a function :class:`ProfileOrdinal` -> `bool`, and `name` is a string. tests_strategy : list A list of pairs `(test, name)`, where `test` is a function :class:`StrategyOrdinal` -> `bool`, and `name` is a string. For these tests, we compute only the probability that a equilibrium meeting the test exists. tests_strategy_dist : list A list of pairs `(test, name)`, where `test` is a function :class:`StrategyOrdinal` -> `bool`, and `name` is a string. For these tests, we compute the distribution of numbers of equilibria meeting the test. tests_strategy_winners : list A list of pairs `(test, name)`, where `test` is a function :class:`StrategyOrdinal` -> `bool`, and `name` is a string. For these tests, we compute the distribution of numbers of winners in equilibria meeting the test. conditional_on : callable A function :class:`ProfileOrdinal` -> `bool`. factory_profiles : callable A callable that inputs nothing and outputs a profile. Default: :class:`RandProfileOrdinalUniform`, with its default parameters. Notes ----- The tests in `tests_profile` concern the profile: what proportion of profiles meet the condition? The tests in `tests_strategy` concern the strategies, in the cases where they are equilibrium. For each ordinal profile, we compute the probability (for a uniform distribution of utilities) that at least one strategy is an equilibrium and meets the given condition. The tests in `tests_strategy_dist` or `tests_strategy_winners` concern also the strategies, in the cases where they are equilibrium. Examples -------- >>> initialize_random_seeds() >>> nice_stats = NiceStatsProfileOrdinal( ... tests_profile=[ ... (lambda profile: any([strategy for strategy in profile.analyzed_strategies_ordinal.equilibria ... if strategy.profile.condorcet_winners == strategy.winners]), ... 'There exists a true equilibrium electing the CW'), ... ], ... tests_strategy=[ ... (lambda strategy: strategy.profile.condorcet_winners == strategy.winners, ... 'There exists an equilibrium that elects the CW') ... ], ... tests_strategy_dist=[ ... (lambda strategy: strategy.profile.condorcet_winners == strategy.winners, ... 'There exists an equilibrium that elects the CW') ... ], ... tests_strategy_winners = [ ... (lambda sigma: True, 'Number of possible winners') ... ], ... conditional_on=lambda profile: profile.is_profile_condorcet == 1. ... ) >>> nice_stats.run(n_samples=10) In order to display an overview of all the results, you can use :meth:`display_results`. Plot a `test_strategy` and insert a cutoff according to a `test_profile`: >>> nice_stats.plot_test_strategy(test='There exists an equilibrium that elects the CW') >>> nice_stats.plot_cutoff(test='There exists a true equilibrium electing the CW') Find a particular example or counter-example: >>> profile = nice_stats.find_example('There exists a true equilibrium electing the CW', False) >>> print(profile) <abc: 0.4236547993389047, acb: 0.12122838365799216, bac: 0.0039303209304278885, bca: 0.05394987214431912, \ cab: 0.1124259903007756, cba: 0.2848106336275805> (Condorcet winner: a) """ def __init__(self, tests_profile=None, tests_strategy=None, tests_strategy_dist=None, tests_strategy_winners=None, conditional_on=None, factory_profiles=None): self.tests_profile = [] if tests_profile is None else tests_profile self.tests_strategy = [] if tests_strategy is None else tests_strategy self.tests_strategy_dist = [] if tests_strategy_dist is None else tests_strategy_dist self.tests_strategy_winners = [] if tests_strategy_winners is None else tests_strategy_winners self.conditional_on = (lambda profile: True) if conditional_on is None else conditional_on self.factory_profiles = RandProfileOrdinalUniform() if factory_profiles is None else factory_profiles # Computed variables self.n_samples = None self.profiles = None self.results_profile = None self.results_strategy = None self.results_strategy_dist = None self.results_strategy_winners = None
[docs] def run(self, n_samples): """Run the simulations and store the results. Parameters ---------- n_samples : int Number of profiles meeting the precondition `conditional_on`. """ self.n_samples = n_samples i_sample = 0 self.profiles = [] self.results_profile = [[] for _ in range(len(self.tests_profile))] self.results_strategy = [[] for _ in range(len(self.tests_strategy))] self.results_strategy_dist = [np.zeros(2 ** 6) for _ in range(len(self.tests_strategy_dist))] self.results_strategy_winners = [np.zeros(4) for _ in range(len(self.tests_strategy_winners))] while i_sample < n_samples: profile = self.factory_profiles() if self.conditional_on(profile): i_sample += 1 else: continue # pragma: no cover - Cannot be covered because of "peephole" optimization self.profiles.append(profile.d_ranking_share) for i, (test, _) in enumerate(self.tests_profile): self.results_profile[i].append(test(profile)) for i, (test, _) in enumerate(self.tests_strategy): self.results_strategy[i].append(profile.proba_equilibrium(test=test)) for i, (test, _) in enumerate(self.tests_strategy_dist): histogram = profile.distribution_equilibria(test=test) self.results_strategy_dist[i][:len(histogram)] += histogram for i, (test, _) in enumerate(self.tests_strategy_winners): histogram = profile.distribution_winners(test=test) self.results_strategy_winners[i][:len(histogram)] += histogram for histogram in self.results_strategy_dist: histogram /= n_samples for histogram in self.results_strategy_winners: histogram /= n_samples
[docs] def plot_test_strategy(self, test, ylabel=True, legend=False, replacement_name=None, style=''): """Plot a test on strategy. Parameters ---------- test : str or int Name or index of a test in `tests_strategy`. ylabel : bool If True, the name is used in the y-label. Otherwise, the label is `P` (probability). legend : bool If True, the legend is displayed. replacement_name : str, optional If specified, it will be used instead of the test name in the plot. style : str Cf. the function ``plot`` of `matplotlib`. """ if isinstance(test, str): i = [name for _, name in self.tests_strategy].index(test) else: i = test (_, name) = self.tests_strategy[i] if replacement_name is not None: name = replacement_name plt.plot(np.arange(0, 1, 1 / self.n_samples), sorted(self.results_strategy[i]), style, label=name) plt.ylim(-0.05, 1.05) plt.xlabel('Cumulative share of ordinal preference profiles') if ylabel: plt.ylabel('$\mathbb{P}$(%s)' % name) else: plt.ylabel('$\mathbb{P}$') if legend: plt.legend()
[docs] def plot_cutoff(self, test, left='', right='', style=''): """Plot the cutoff of a test on the profile. Parameters ---------- test : str or int Name or index of a test in `tests_profile`. left : str Text to be written on the left. right : str Text to be written on the right. style : str Cf. the function ``plot`` of `matplotlib`. """ if isinstance(test, str): i = [name for _, name in self.tests_profile].index(test) else: i = test (_, name) = self.tests_profile[i] x = np.sum(self.results_profile[i]) / self.n_samples plt.plot([1 - x, 1 - x], [0., 1.], style) plt.text((1 - x) / 2, 0.1, left, horizontalalignment='center', verticalalignment='center') plt.text(1 - x / 2, 0.1, right, horizontalalignment='center', verticalalignment='center')
[docs] def display_results(self): # pragma: no cover - Annoying to test because plt.show() opens plot windows. """Display the results.""" for i, (_, name) in enumerate(self.tests_profile): print('P(%s) = %s' % (name, np.sum(self.results_profile[i]) / self.n_samples)) for i, (_, name) in enumerate(self.tests_strategy): self.plot_test_strategy(test=i) plt.show() for i, (_, name) in enumerate(self.tests_strategy_dist): print('P(%s) :' % name) true_dim = np.max(np.flatnonzero(self.results_strategy_dist[i])) + 1 print({k: self.results_strategy_dist[i][k] for k in range(true_dim)}) plt.bar(range(true_dim), self.results_strategy_dist[i][:true_dim]) plt.xticks(range(true_dim)) plt.xlabel(name) plt.ylabel('$\mathbb{P}$') plt.ylim(0, 1) plt.show() for i, (_, name) in enumerate(self.tests_strategy_winners): print('%s :' % name) true_dim = 4 print({k: self.results_strategy_winners[i][k] for k in range(true_dim)}) plt.bar(range(true_dim), self.results_strategy_winners[i][:true_dim]) plt.xticks(range(true_dim)) plt.xlabel(name) plt.ylabel('$\mathbb{P}$') plt.ylim(0, 1) plt.show()
[docs] def find_example(self, test, value=True): """Find an example profile. Parameters ---------- test : str or int Name or index of a test in `tests_profile`. value : bool Whether we want an example (True) or a counter-example (False). Returns ------- ProfileOrdinal A profile for which the test returned `value` in the simulation. """ if isinstance(test, str): i_test = [name for _, name in self.tests_profile].index(test) else: i_test = test i_profile = self.results_profile[i_test].index(value) return ProfileOrdinal(self.profiles[i_profile])