Skip to content

Instantly share code, notes, and snippets.

@eyliu
Created November 15, 2009 23:30
Show Gist options
  • Save eyliu/235563 to your computer and use it in GitHub Desktop.
Save eyliu/235563 to your computer and use it in GitHub Desktop.
Python script to automatically apportion representative seats for the Michigan Student Assembly
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright: 2009 Elson Liu
# License: BSD
# Requires: urllib2
# Requires: xlrd
"""
Download enrollment data for the University of Michigan, Ann Arbor campus
and apportion seats on the Michigan Student Assembly
"""
import os
import copy
import xlrd
import urllib2
from optparse import OptionParser
def MSAmethod(units,undergrads,rackham,nonrackham,divisor=850):
"""
Current MSA apportionment method
Enrollment:
For non-Rackham units, add the undergrad and non-Rackham grad enrollments
For Rackham, add the Rackham enrollments for all units
Apportionment:
Divide the enrollment by 850 and apply normal rounding rules
Any unit with enrolled students is guaranteed at least 1 seat
"""
print "Using current MSA apportionment method..."
# CALCULATE ENROLLMENT
enrollment = {}
for unit in units:
if unit != "Rackham":
enrollment[unit] = undergrads[unit] + nonrackham[unit]
else:
enrollment[unit] = sum(rackham.values())
# APPORTION SEATS
seats = {}
ratio = {}
for unit in units:
if enrollment[unit] > 0:
seats[unit] = max(round(float(enrollment[unit])/divisor),1)
ratio[unit] = float(enrollment[unit]/seats[unit])
else:
seats[unit] = 0
ratio[unit] = 0
return (enrollment, seats, ratio, divisor)
def divisorMethod(method,nseats,units,undergrads,rackham,nonrackham):
"""
General divisor apportionment method
Enrollment:
For non-Rackham units, add the undergrad and non-Rackham grad enrollments
For Rackham, add the Rackham enrollments for all units
Apportionment:
Divide the enrollment by 850 and apply normal rounding rules
Any unit with enrolled students is guaranteed at least 1 seat
"""
print "Using " + method.__name__.capitalize() + " apportionment method"
# CALCULATE ENROLLMENT
enrollment = {}
for unit in units:
if unit != "Rackham":
enrollment[unit] = undergrads[unit] + nonrackham[unit]
else:
enrollment[unit] = sum(rackham.values())
# APPORTION SEATS
quotient = copy.deepcopy(enrollment)
seats = {}
ratio = {}
for unit in units:
seats[unit] = 1
while sum(seats.values()) < nseats:
unit = max(quotient, key=quotient.get)
seats[unit] += 1
quotient[unit] = method(enrollment[unit],seats[unit])
for unit in units:
ratio[unit] = float(enrollment[unit]/seats[unit])
divisor = sum(enrollment.values()) / float(nseats)
return (enrollment, seats, ratio, divisor)
def jefferson(population,seats):
return int( population / (seats + 1.) )
def webster(population,seats):
return int( population / (2*seats + 1.) )
def printEnrollment(units,undergrads,rackham,nonrackham):
print "ENROLLMENT DATA:"
print "%30s \t %10s \t %7s \t %11s " % \
("Unit","Undergrads","Rackham","non-Rackham")
for unit in units:
print "%30s \t %10d \t %7d \t %11d " % \
(unit,undergrads[unit],rackham[unit],nonrackham[unit])
print ""
def printApportionment(units,enrollment,seats,ratio,divisor):
print "APPORTIONMENT DATA:"
print "%30s \t %10s \t %5s \t %21s " % \
("Unit","Enrollment","Seats","Apportionment Error")
for unit in units:
print "%30s \t %10d \t %5d \t %21f" % \
(unit,enrollment[unit],seats[unit],ratio[unit]-divisor)
print "house size = %d" % sum(seats.values())
print "divisor = %f" % divisor
MSE = sum([(r-divisor)**2 for r in ratio.values() if r != 0])/float(len(ratio))
print "MSE = %f" % MSE
def download(url,filename, chunk_size=1048576):
try:
filein = urllib2.urlopen(url)
except urllib2.URLError, msg:
print "Urllib2 error (%s)" % msg
sys.exit(1)
except socket.error, (errno, strerror):
print "Socket error (%s) for host %s (%s)" % (errno, host, strerror)
sys.exit(1)
fileout = open(filename, "wb")
while True:
try:
bytes = filein.read(chunk_size)
fileout.write(bytes)
except IOError, (errno, strerror):
print "I/O error (%s): %s" % (errno, strerror)
sys.exit(1)
if bytes == "":
break
filein.close()
fileout.close()
def main():
usage = "usage: %prog [options] arg"
description = "Download enrollment data for the University of Michigan, Ann Arbor \
campus and apportion seats on the Michigan Student Assembly"
parser = OptionParser(usage,description=description)
parser.add_option("-m", "--method", dest="method",
help="use METHOD apportionment method")
parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
default=False, help="print raw enrollment data")
parser.add_option("-q", "--quiet", action="store_false", dest="verbose",
help="do not print raw enrollment data")
(options, args) = parser.parse_args()
#if len(args) != 1:
# parser.error("incorrect number of arguments")
# IMPORT ENROLLMENT DATA
filename = "09fa102.xls"
try:
os.listdir(os.getcwd()).index(filename)
except ValueError:
print "Spreadsheet not found; downloading spreadsheet..."
url = "http://www.ro.umich.edu/report/" + filename
download(url,filename)
book = xlrd.open_workbook(filename)
sh = book.sheet_by_index(0)
# data rows have 15 columns
# unit names are in every 3rd row from row 7 to row 61
units = [] # list of academic units
rowlookup = {} # dictionary of row indices
for r in range(sh.nrows)[7:63:3]:
unit = sh.cell_value(r,0)
units.append(unit)
rowlookup[unit] = r
# total enrollment data is offset from unit name by 2 rows
# total undergraduate enrollment is in column 3
# total graduate enrollment is in column 9
# Rackham graduate enrollment is in columns 10 and 11
# non-Rackham graduate enrollment is in columns 12, 13 and 14
undergrads = {}
rackham = {}
nonrackham = {}
for unit in units:
r = rowlookup[unit]
undergrads[unit] = int(sh.cell_value(r+2,3))
rackham[unit] = int(sh.cell_value(r+2,10)) + \
int(sh.cell_value(r+2,11))
nonrackham[unit] = int(sh.cell_value(r+2,12)) + \
int(sh.cell_value(r+2,13)) + \
int(sh.cell_value(r+2,14))
if options.verbose == True:
printEnrollment(units,undergrads,rackham,nonrackham)
# APPORTION SEATS
if options.method == None:
print "No apportionment method specified."
enrollment, seats, ratio, divisor = MSAmethod(units,undergrads,rackham,nonrackham)
elif options.method.lower() == 'jefferson':
enrollment, seats, ratio, divisor = divisorMethod(jefferson,53,units,undergrads,rackham,nonrackham)
elif options.method.lower() == 'webster':
enrollment, seats, ratio, divisor = divisorMethod(webster,53,units,undergrads,rackham,nonrackham)
else:
print "Unrecognized apportionment method: %s" % options.method
enrollment, seats, ratio, divisor = MSAmethod(units,undergrads,rackham,nonrackham)
# PRINT APPORTIONMENT
printApportionment(units,enrollment,seats,ratio,divisor)
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment