-
-
Save jamescmacey/9aef6f5658ffdfa58c57820998eb9341 to your computer and use it in GitHub Desktop.
New Zealand Parliament — Conscience vote graph
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This code is adapted from https://gist.github.com/rlucioni/8bdb1092579041ce739c | |
# Also see https://renzolucioni.com/senate-voting-relationships/ | |
# It fetches personal votes from WhereTheyStand.nz and creates a similar | |
# .gexf file. | |
# Due to the rarity of personal votes in New Zealand's Parliament, I'm not sure | |
# how useful this actually is. | |
import json | |
from itertools import combinations | |
import requests | |
import networkx as nx | |
# The list of bill document IDs. WhereTheyStand currently has no endpoint | |
# which lists bills in JSON format so the document IDs need to be listed here. | |
# The bills here should only be bills decided wholly by personal votes. | |
# To date there haven't been any "mixed voting" bills and as such this code | |
# doesn't bother checking that a vote is actually a personal vote. | |
# If it tried to process a party vote it would crash. | |
BILLS = ["BILL_89814","BILL_76343","BILL_74307","BILL_74308"] | |
OUTPUT_FILE = "votes-all-52nd.gexf" | |
def get_vote(bill,vote): | |
url = "https://wheretheystand.nz/bills/view-bill/{0}/{1}/?json=1".format(bill,vote) | |
page = requests.get(url).text | |
try: | |
data = json.loads(page) | |
return data | |
except ValueError: | |
raise Exception("Not a valid vote.") | |
# In the original senate code, this function iterated through sequential vote IDs. | |
# WhereTheyStand uses Parliament-assigned document IDs for votes and also requires | |
# both the bill document ID and the vote document ID to access a vote, so this | |
# function needs to be told which votes to get. | |
def get_all_votes(bills, votes): | |
vote_dicts = [] | |
for bill in bills: | |
for vote in votes[bill]: | |
try: | |
vote_dict = get_vote(bill,vote) | |
vote_dicts.append(vote_dict) | |
except Exception as e: | |
print(e) | |
print("Exception occurred getting a vote - skipping this one.") | |
return vote_dicts | |
# This function will return a dictionary of bills and votes from only a list of | |
# bill document IDs, by retrieving bill records first. | |
def list_votes(bills): | |
# A dictionary to contain bill document IDs as the key, and then a list of | |
# vote document IDs as the value. Both IDs are required to retrieve a vote | |
# from WhereTheyStand. | |
votes = {} | |
for bill in bills: | |
# Fetch the bill — it will contain a list of vote summaries, which will | |
# contain the document IDs of the votes which need to be fetched. | |
url = "https://wheretheystand.nz/bills/view-bill/{0}/?json=1".format(bill) | |
page = requests.get(url).text | |
try: | |
data = json.loads(page) | |
except ValueError: | |
raise Exception("Not a valid bill.") | |
bill_votes = [] | |
for vote in data.get("votes", []): | |
if vote.get("document_id", None): | |
bill_votes.append(vote["document_id"]) | |
votes[bill] = bill_votes | |
return votes | |
def vote_graph(data): | |
graph = nx.Graph() | |
# List to contain all member display names - these will be our nodes | |
all_members = [] | |
parties = {} | |
# List to contain roll_call dicts, one for each vote | |
roll_calls = [] | |
for vote in data: | |
# Dict with keys for each vote class; values are lists of names. | |
# The four keys listed here are the possible values for "position" | |
# in a vote contribution sourced from WhereTheyStand. | |
roll_call = {"Yes": [], "No": [], "Absent": [], "Abstain": []} | |
voted_members = [] | |
for contribution in vote['contributions']: | |
# Update the party for the member in the master dictionary. | |
# This is not a very precise way of doing things because votes | |
# aren't necessarily in chronological order. Since the party | |
# value relates to the member's party on the date of the vote, if | |
# it's changed (i.e. Jami-Lee Ross) then it won't be accurate. | |
person_name = contribution["person"]["name"] | |
if contribution.get("party", None): | |
parties[person_name] = contribution["party"]["name"] | |
roll_call[contribution["position"]].append(person_name) | |
if person_name not in all_members: | |
all_members.append(person_name) | |
roll_calls.append(roll_call) | |
# All combinations of 2 member display names | |
all_member_pairs = combinations(all_members, 2) | |
common_votes = {} | |
for pair in all_member_pairs: | |
common_votes[pair] = 0 | |
for vote in roll_calls: | |
yea_pairs = combinations(vote['Yes'], 2) | |
for pair in yea_pairs: | |
try: | |
common_votes[pair] += 1 | |
except KeyError: | |
# Flip member names so we can find the pair in common_votes | |
common_votes[(pair[1],pair[0])] += 1 | |
nay_pairs = combinations(vote['No'], 2) | |
for pair in nay_pairs: | |
try: | |
common_votes[pair] += 1 | |
except KeyError: | |
common_votes[(pair[1],pair[0])] += 1 | |
abstain_pairs = combinations(vote['Abstain'], 2) | |
for pair in abstain_pairs: | |
try: | |
common_votes[pair] += 1 | |
except KeyError: | |
common_votes[(pair[1],pair[0])] += 1 | |
# There's no code here to increment common votes where two members have | |
# been absent for the same vote. But it could easily be added by copying | |
# one of the above sections and using vote['Absent']. | |
for member in all_members: | |
party = parties.get(member, None) | |
if party == 'The New Zealand National Party': | |
graph.add_node(member, color='#2f8acc') | |
elif party == 'New Zealand Labour Party': | |
graph.add_node(member, color='#d82a21') | |
elif party == 'New Zealand First Party': | |
graph.add_node(member, color='#404141') | |
elif party == 'The Greens, The Green Party of Aotearoa/New Zealand': | |
graph.add_node(member, color='#0ac958') | |
elif party == 'ACT New Zealand': | |
graph.add_node(member, color='#ffd100') | |
elif party == 'United Future New Zealand': | |
graph.add_node(member, color='#4b114e') | |
elif party == 'Māori Party': | |
graph.add_node(member, color='#DC3F2E') | |
else: | |
graph.add_node(member, color='gray') | |
for pair, votes in common_votes.items(): | |
# Don't draw an edge for members with 0 votes in common | |
if votes == 0: | |
continue | |
graph.add_edge(pair[0], pair[1], weight=votes, difference=1.0/votes) | |
return graph | |
requested_votes = list_votes(BILLS) | |
vote_data = get_all_votes(BILLS, requested_votes) | |
votes = vote_graph(vote_data) | |
print(votes) | |
nx.write_gexf(votes, OUTPUT_FILE) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment