Created
November 15, 2009 23:30
-
-
Save eyliu/235563 to your computer and use it in GitHub Desktop.
Python script to automatically apportion representative seats for the Michigan Student Assembly
This file contains hidden or 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
#!/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