Ternary Plots

[1]:
import poisson_approval as pa
from fractions import Fraction

SimplexToProfile

Poisson Approval relies on the package python-ternary to draw plots on the simplex of \(\mathbb{R}^3\). The coordinates of a point are denoted right, top and left because they are respectively equal to 1 at the right, top and left corners of the triangular figure. They typically represent shares of different types in a profile. In order to draw plots, we need a SimplexToProfile object that will generate the needed profiles:

[2]:
simplex_to_profile = pa.SimplexToProfile(
    pa.ProfileNoisyDiscrete,
    right_type='c>a~b',
    top_type=('abc', 0.5, 0.01),
    left_type=('bac', 0.5, 0.01),
    d_type_fixed_share={('abc', 0.1, 0.01): Fraction(1, 10),
                        ('abc', 0.9, 0.01): Fraction(1, 10)}
)

The above syntax defines a function simplex_to_profile that maps a tuple (right, top, left) to a profile defined as:

  • The class of profile is ProfileNoisyDiscrete,

  • A fixed share 1/10 of voters are of type \((abc, 0.1, 0.01)\), and a fixed share 1/10 of voters are of type \((abc, 0.9, 0.01)\),

  • The other voters, i.e. a share 8/10, are distributed between right_type, top_type and left_type, in respective proportions that are given by the input tuple (right, top, left).

For example:

[3]:
simplex_to_profile(right=Fraction(17, 80), top=Fraction(52, 80), left=Fraction(11, 80))
[3]:
<abc 0.1 ± 0.01: 1/10, abc 0.5 ± 0.01: 13/25, abc 0.9 ± 0.01: 1/10, bac 0.5 ± 0.01: 11/100, c>a~b: 17/100> (Condorcet winner: a)

Scale

All the plots require a resolution parameter called scale. For example, if scale=10, then the simplex is divided into cells of diameter 1/10 and the value of the plotted function is computed at the center of each cell. For more information, cf. the documentation of the package python-ternary. In this tutorial, we define a global parameter SCALE and we will use it for all the plots.

[4]:
SCALE = 21

Number of Equilibria

By default, the function ternary_plot_n_equilibria computes the ordinal equilibria, i.e. those where all voters having the same ranking cast the same ballot:

[5]:
pa.ternary_plot_n_equilibria(
    simplex_to_profile,
    scale=SCALE,
    title='Number of ordinal equilibria')
[5]:
(<Figure size 504x360 with 2 Axes>, TernaryAxesSubplot: -9223371904852674332)
../_images/tutorials_tutorial_ternary_plots_12_1.png

Using the option meth, you can investigate other kinds of equilibria:

[6]:
pa.ternary_plot_n_equilibria(
    simplex_to_profile,
    scale=SCALE,
    title='Number of group equilibria',
    meth='analyzed_strategies_group')
[6]:
(<Figure size 504x360 with 2 Axes>, TernaryAxesSubplot: 132002423279)
../_images/tutorials_tutorial_ternary_plots_14_1.png

Depending on the class of profile, the possible values of the option meth may be:

  • 'analyzed_strategies_ordinal' (the default),

  • 'analyzed_strategies_group' (for profiles where a reasonable notion of group is defined, such as ProfileNoisyDiscrete),

  • 'analyzed_strategies_pure' (for discrete profiles such as ProfileDiscrete or ProfileTwelve).

Winners at Equilibrium

The function ternary_plot_n_equilibria works similarly:

[7]:
pa.ternary_plot_winners_at_equilibrium(
    simplex_to_profile,
    scale=SCALE,
    title='Winners in ordinal equilibria')
[7]:
(<Figure size 360x360 with 1 Axes>, TernaryAxesSubplot: -9223371904852273148)
../_images/tutorials_tutorial_ternary_plots_18_1.png
[8]:
pa.ternary_plot_winners_at_equilibrium(
    simplex_to_profile,
    scale=SCALE,
    title='Winners in group equilibria',
    legend_title='Winners',
    meth='analyzed_strategies_group')
[8]:
(<Figure size 360x360 with 1 Axes>, TernaryAxesSubplot: -9223371904852057000)
../_images/tutorials_tutorial_ternary_plots_19_1.png

Winning Frequencies in Fictitious Play or Iterated Voting

By default, the function ternary_plot_winning_frequencies computes the winning frequencies in fictitious play, with an initialization in sincere strategy, and with all update ratios in \(1 / \log(t + 1)\):

[9]:
pa.ternary_plot_winning_frequencies(simplex_to_profile, scale=SCALE, n_max_episodes=100)
[9]:
(<Figure size 360x360 with 2 Axes>, TernaryAxesSubplot: 132002943727)
../_images/tutorials_tutorial_ternary_plots_22_1.png

You can change this behavior with the optional parameters of the function:

[10]:
pa.ternary_plot_winning_frequencies(
    simplex_to_profile,
    scale=SCALE,
    meth='iterated_voting',
    init='random_tau_undominated',
    samples_per_point=10,
    perception_update_ratio=1,
    ballot_update_ratio=1,
    winning_frequency_update_ratio=pa.one_over_t,
    n_max_episodes=100,
    title='Winning frequencies in iterated voting',
    legend_title='Winners'
)
[10]:
(<Figure size 360x360 with 2 Axes>, TernaryAxesSubplot: -9223371904850416903)
../_images/tutorials_tutorial_ternary_plots_24_1.png

Convergence Rate in Fictitious Play or Iterated Voting

The function ternary_plot_convergence computes the convergence frequency in fictitious play or iterated voting, which is defined as the proportion of initializations that lead to convergence within n_max_episodes iterations. Its syntax is similar to ternary_plot_winning_frequencies.

[11]:
pa.ternary_plot_convergence(simplex_to_profile, scale=SCALE, n_max_episodes=100,
                            init='random_tau', samples_per_point=10)
[11]:
(<Figure size 504x360 with 2 Axes>, TernaryAxesSubplot: 132005039936)
../_images/tutorials_tutorial_ternary_plots_27_1.png

Customize the Plot

Grid and lines:

[12]:
figure, tax = pa.ternary_figure()
tax.gridlines_simplex(multiple=0.1)
tax.horizontal_line_simplex(0.1)
tax.left_parallel_line_simplex(0.2)
tax.right_parallel_line_simplex(0.3)
tax.line_simplex((1, 0, 0), (0.2, 0.5, 0.3))
../_images/tutorials_tutorial_ternary_plots_30_0.png

Titles and annotations:

[13]:
figure, tax = pa.ternary_figure()
tax.set_title_padded('The title of the figure')
tax.right_corner_label('The right label')
tax.top_corner_label('The top label')
tax.left_corner_label('The left label')
tax.annotate_simplex('An annotation', (0.33, 0.33, 0.33))
../_images/tutorials_tutorial_ternary_plots_32_0.png

Advanced Intensity Heat Maps

First, define a function that maps a point of the simplex to a number:

[14]:
def f(right, top, left):
    return (right**2 + top) / (left + 1)

Then use the method heatmap_intensity:

[15]:
figure, tax = pa.ternary_figure(scale=SCALE)
tax.heatmap_intensity(f,
                      left_label='left',
                      right_label='right',
                      top_label='top')
tax.set_title_padded('An intensity heat map')
../_images/tutorials_tutorial_ternary_plots_37_0.png

Advanced Candidate Heat Maps

First, define a function that maps a point of the simplex to a list of size 3 (associated with candidates a, b, c):

[16]:
def g(right, top, left):
    a = top**.5
    b = left**2
    c = 1 - a - b
    return [a, b, c]

Then use the method heatmap_candidates:

[17]:
figure, tax = pa.ternary_figure(scale=SCALE)
tax.heatmap_candidates(g,
                       left_label='left',
                       right_label='right',
                       top_label='top',
                       legend_title='Candidates',
                       legend_style='palette')
tax.set_title_padded('A candidate heat map')
../_images/tutorials_tutorial_ternary_plots_42_0.png