Skip to content

Instantly share code, notes, and snippets.

@jamescmacey
Forked from rlucioni/senate-vote-graph.py
Last active September 12, 2020 01:08
Show Gist options
  • Save jamescmacey/9aef6f5658ffdfa58c57820998eb9341 to your computer and use it in GitHub Desktop.
Save jamescmacey/9aef6f5658ffdfa58c57820998eb9341 to your computer and use it in GitHub Desktop.
New Zealand Parliament — Conscience vote graph
# 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