-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathnamentlicheAbstimmungen.py
More file actions
247 lines (188 loc) · 8.25 KB
/
namentlicheAbstimmungen.py
File metadata and controls
247 lines (188 loc) · 8.25 KB
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import os
import random
import pandas as pd
import networkx as nx
from networkx import Graph
def deserialize(lp: int) -> list:
"""
Diese Funktion lädt alle Abstimmungen einer Legislaturperiode in einen individuellen pandas.Dataframe und fügt sie
einer Liste an. Die Abstimmungen liegen als .xls oder .xlsx vor. Der Dataframe hat die folgende Struktur:
Wahlperiode Sitzungnr Abstimmnr Fraktion/Gruppe Name Vorname Titel ja nein Enthaltung ungültig nichtabgegeben Bezeichnung Bemerkung
20 3 1 Fraktion Mustermann Max Dr. 1 0 0 0 0 Max Mustermann Lorem Ipsum
:param lp: Die Legislaturperiode, deren namentliche Abstimmungen geladen werden sollen.
:return: Eine Liste mit pandas.Dataframe, die jeweils eine namentliche Abstimmung enthalten.
"""
dfs = []
folder_path = f"Abstimmungen/LP{lp}"
for file in os.listdir(folder_path):
if file.endswith('.xls') or file.endswith('.xlsx'):
file_path = os.path.join(folder_path, file)
df = pd.read_excel(file_path)
dfs.append(df)
return dfs
def append_edges(node_list: list, G: Graph) -> None:
"""
Diese Funktion fügt zwischen allen Knoten, die in node_list übergeben werden, entweder eine Kante im Graphen G hinzu,
sofern noch keine Kante existiert, oder inkrementiert das Kantengewicht, sofern eine Kante existiert.
:param node_list: Die Knotenliste.
:param G: Der Graph, der erweitert wird.
:return: None
"""
for i in range(len(node_list)):
for j in range(i + 1, len(node_list)):
node1 = node_list[i]
node2 = node_list[j]
if G.has_edge(node1, node2):
G[node1][node2]['weight'] += 1
else:
G.add_edge(node1, node2, weight=1)
def generate_network(dfl) -> Graph:
"""
Mit dieser Funktion wird ein Abstimmungsnetzwerk aus einer List von Dataframes generiert.
:param dfl: Die List mit pandas.Dataframes, generiert von deserialize().
:return: Das Abstimmungsnetzwerk als networkx.Graph.
"""
'''
Alle Einzelabstimmungen, welche in Dataframes gespeichert sind, werden zu einer Abstimmung zusammengefasst, damit
wir dem Graphen, den wir mit dem Bezeichner G1 instanziieren, für jede einzigartige Fraktion, ein Knoten hinzugefügt
werden kann.
'''
combined_df = pd.concat(dfl, ignore_index=True)
G = nx.Graph()
unique_fraktionen = combined_df['Fraktion/Gruppe'].unique()
# unique_fraktionen = [e for e in unique_fraktionen if e not in ["DIE LINKE.", "Fraktionslos", "BSW"]]
G.add_nodes_from(unique_fraktionen)
'''
Wir iterieren über alle Einzelabstimmungen und fügen genau dann eine Kante zwischen Fraktionen hinzu, wenn sie bei
einer Abstimmung die gleiche Entscheidung für 'ja' oder 'nein' getroffen haben. Haben die Fraktionen bereits zuvor
diesselbe Entscheidung getroffen, wird das Kantengewicht ihrer Kante inkrementiert.
'''
for df in dfl:
unique_fraktionen = df['Fraktion/Gruppe'].unique()
# unique_fraktionen = [e for e in unique_fraktionen if e not in ["DIE LINKE.", "Fraktionslos", "BSW"]]
voted_1 = []
voted_0 = []
'''
Wir behandeln frei Fälle: Die Fraktion stimmt mehrheitlich 'Ja', die Fraktion stimmt mehrheitlich 'Nein' oder
die Fraktion enthält sich mehrheitlich.
'''
for fraktion in unique_fraktionen:
if df.loc[df['Fraktion/Gruppe'] == fraktion, 'ja'].mean() > 0.5:
voted_1.append(fraktion)
elif df.loc[df['Fraktion/Gruppe'] == fraktion, 'Enthaltung'].mean() > 0.5:
continue
else:
voted_0.append(fraktion)
append_edges(voted_1, G)
append_edges(voted_0, G)
return G
def generate_random_network(s: int, n: int) -> Graph:
"""
Diese Funktion generiert ein zufälliges Abstimmungsnetzwerk, d. h. alle abstimmenden Fraktionen entscheiden willkürlich.
:param s: Die Zahl der Abstimmungen als Integer
:param n: Die Zahl der Knoten als Integer.
:return: Das zufällige Abstimmungsnetzwerk als networkx.Graph.
"""
G = nx.Graph()
for i in range(n):
G.add_node(i)
for _ in range(s):
voted_0, voted_1 = [], []
for x in G.nodes:
if random.choice([True, False]):
voted_0.append(x)
else:
voted_1.append(x)
for i in range(len(voted_0)):
for j in range(i + 1, len(voted_0)):
node1 = voted_0[i]
node2 = voted_0[j]
if G.has_edge(node1, node2):
G[node1][node2]['weight'] += 1
else:
G.add_edge(node1, node2, weight=1)
for i in range(len(voted_1)):
for j in range(i + 1, len(voted_1)):
node1 = voted_1[i]
node2 = voted_1[j]
if G.has_edge(node1, node2):
G[node1][node2]['weight'] += 1
else:
G.add_edge(node1, node2, weight=1)
return G
def generate_full_network(s: int, n: int) -> Graph:
'''
Diese Funktion generiert ein minimal polarisiertes Abstimmungsnetzwerk, d. h. abstimmenden Fraktionen treffen bei
jeder Abstimmung die gleiche Entscheidung.
:param s: Die Zahl der Abstimmungen als Integer.
:param n: Die Zahl der Knoten als Integer.
:return: Das minimal polarisierte Abstimmungsnetzwerk als networkx.Graph.
'''
G = nx.Graph()
for i in range(n):
G.add_node(i)
for _ in range(s):
voted_0 = []
[voted_0.append(x) for x in G.nodes]
for i in range(len(voted_0)):
for j in range(i + 1, len(voted_0)):
node1 = voted_0[i]
node2 = voted_0[j]
if not G.has_edge(node1, node2):
G.add_edge(node1, node2, weight=s)
return G
def generate_polarized_network(s: int, n: int) -> Graph:
"""
Diese Funktion generiert ein polarisiertes Abstimmungsnetzwerk mit zwei Communties.
:param s: Die Zahl der Abstimmungen als Integer.
:param n: Die Zahl der Knoten als Integer.
:return: Das polarisierte Abstimmungsnetzwerk als networkx.Graph.
"""
G = nx.Graph()
for i in range(n):
G.add_node(i)
split = int(round(len(G.nodes)/2))
voted_0, voted_1 = list(G.nodes)[:split], list(G.nodes)[split:]
print(voted_0)
print(voted_1)
for i in range(len(voted_0)):
for j in range(i + 1, len(voted_0)):
node1 = voted_0[i]
node2 = voted_0[j]
if not G.has_edge(node1, node2):
G.add_edge(node1, node2, weight=s)
for i in range(len(voted_1)):
for j in range(i + 1, len(voted_1)):
node1 = voted_1[i]
node2 = voted_1[j]
if not G.has_edge(node1, node2):
G.add_edge(node1, node2, weight=s)
return G
def calculate_weighted_modularity(G: Graph, communities: list, weight='weight') -> float:
"""
Die Berechnung der Modularität einer Partitionierung eines gewichteten Graphen nach Newman.
:param G: Der gewichtete networkx.Graph.
:param communities: Die Einteilung in Communities als Liste von Listen.
:param weight: Das verwendete Label für Gewicht im Graphen.
:return: Die Modularität als float.
"""
# Berechnung der Gesamtsumme der Kantenwichte
m = 0
for e in G.edges(data=True):
m += e[2]['weight']
# Zuordnung von jedem Knoten zu seiner Community
node_to_community = {node: i for i, community in enumerate(communities) for node in community}
# Initialisierung der Modularität
Qw = 0
# Iteration über alle Knotenpaare
for i in G.nodes():
for j in G.nodes():
if i == j:
continue
if node_to_community[i] == node_to_community[j]: # Nur wenn beide Knoten in der gleichen Community sind
w_ij = G[i][j][weight] if G.has_edge(i, j) else 0
s_i = sum(G[i][k][weight] for k in G[i])
s_j = sum(G[j][k][weight] for k in G[j])
Qw += (w_ij - s_i * s_j / (2 * m))
# Teilen durch 2m, um die Modularität zu erhalten
return Qw / (2 * m)