/
dispr_bar.py
158 lines (127 loc) · 4.71 KB
/
dispr_bar.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
"""
Disproportionality Bar Plot
---------------------------
The plotting function to create disproportionality bar plots.
Contents:
dispr_bar
"""
import pandas as pd
import seaborn as sns
from poli_sci_kit import utils
default_sat = 0.95
def dispr_bar(
shares,
allocations,
labels=None,
colors=None,
total_shares=None,
total_alloc=None,
percent=False,
dsat=default_sat,
axis=None,
):
"""
Plots the difference in allocated seats to received shares.
Parameters
----------
shares : list
The shares amounts or those that allocations should be compared to.
allocations : list
The allocated amounts.
labels : list (default=None)
A list of group names as labels for the x-axis.
colors : list or list of lists : optional (default=None)
The colors of the groups as hex keys.
total_shares : int (default=None)
The total share amounts.
Note: allows for subsets of the total groups.
total_alloc : int (default=None)
The total number of allocations amounts.
Note: allows for subsets of the total groups.
percent : bool (default=False)
Whether the y-axis should depict relative changes or not.
dsat : float : optional (default=default_sat)
The degree of desaturation to be applied to the colors.
axis : str : optional (default=None)
Adds an axis to plots so they can be combined.
Returns
-------
ax : matplotlib.pyplot.subplot
A bar plot with aggregate or relative seat-share differences and bar widths representing share proportions.
"""
assert len(shares) == len(
allocations
), "The number of different shares must equal the number of different seat allocations."
if total_shares and total_alloc:
share_percents = [i / total_shares for i in shares]
seat_percents = [i / total_alloc for i in allocations]
else:
share_percents = [i / sum(shares) for i in shares]
seat_percents = [i / sum(allocations) for i in allocations]
disproportionality = [
round(seat_percents[i] - p, 4) for i, p in enumerate(share_percents)
]
if percent == True:
disproportionality = [
round(disproportionality[i] / p * 100, 4)
for i, p in enumerate(share_percents)
]
if not labels:
labels = list(range(len(disproportionality) + 1)[1:])
df = pd.DataFrame(disproportionality, index=labels, columns=["disproportionality"])
if colors:
assert len(colors) == len(
shares
), "The number of colors provided doesn't match the number of counts to be displayed."
colors = [
utils.scale_saturation(rgb_trip=utils.hex_to_rgb(c), sat=dsat)
for c in colors
]
sns.set_palette(colors)
elif colors is None:
sns.set_palette("deep") # default sns palette
colors = [
utils.rgb_to_hex(c)
for c in sns.color_palette(n_colors=len(shares), desat=1)
]
ax = sns.barplot(
data=df, x=df.index, y="disproportionality", saturation=dsat, ax=axis
)
# Change widths by looping over the bars and adjusting the width/position.
bar_widths = []
bar_positions = []
prev_bar_position = 0
for bar, new_width in zip(ax.patches, share_percents):
bar.set_x(prev_bar_position)
bar.set_width(new_width * len(df))
prev_bar_position += new_width * len(df)
bar_widths.append(new_width * len(df))
bar_positions.append(prev_bar_position)
# Add heights to the top and bottom of bars.
for p in ax.patches:
height = p.get_height()
if height < 0: # compensates for text height for negative bar labels
ax.text(
x=p.get_x() + p.get_width() / 2.0,
y=(
height + 1.5 * min(abs(i) for i in disproportionality) * -1
),
s=str(height),
ha="center",
)
else:
ax.text(
x=p.get_x() + p.get_width() / 2.0,
y=height + 0.75 * min(abs(i) for i in disproportionality),
s=str(height),
ha="center",
)
ax.set_xlim([0, prev_bar_position * 1.05])
ax.axhline(0, ls="-", color="black") # so the x-axis is distinct
ax.set_xticks(
ticks=[p - (bar_widths[i] / 2.0) for i, p in enumerate(bar_positions)]
)
ax.set_xticklabels(labels=[labels[i] for i in range(len(bar_widths))])
ax.set_ylim([min(disproportionality) * 1.5, max(disproportionality) * 1.5])
ax.set_xlim([0, len(shares)])
return ax