Skip to content

Instantly share code, notes, and snippets.

@ghing
Created August 10, 2011 16:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ghing/1137317 to your computer and use it in GitHub Desktop.
Save ghing/1137317 to your computer and use it in GitHub Desktop.
Custom calculators for Ohio District Builder instance
import sys
from django.conf import settings
from publicmapping.redistricting.calculators import CalculatorBase, SplitCounter, Roeck
from decimal import Decimal
class ValueRange(CalculatorBase):
"""
Determine a value, and indicate if it falls within a range
This calculator differs from the default Range calculator because it
checks the value argument (usually passed from another calculator)
rather than operating on a district or plan.
It also differs in that it checks if the value is >= min and <= max
rather than > min and < max
"""
def compute(self, **kwargs):
"""
Calculate and determine if a value lies within a range.
"""
self.result = 0
val = self.get_value('value')
minval = self.get_value('min',district)
maxval = self.get_value('max',district)
if val is None or minval is None or maxval is None:
return
if float(val) >= float(minval) and float(val) <= float(maxval):
self.result += 1
class Difference(CalculatorBase):
"""
Return the difference of two values.
For districts or numbers, this calculator will return the difference of two
arguments.
The first argument should be assigned the argument name "value1" and
the second argument should be assigned the argument name "value2". The
difference will then be calculated as value1 - value2.
"""
def compute(self, **kwargs):
"""
Calculate the sum of a series of values.
Keywords:
district -- A district whose values should be subtracted.
version -- Optional. The version of the plan, defaults to the
most recent version.
list -- A list of values to sum, when summing a set of
ScoreArguments.
"""
if settings.DEBUG:
sys.stderr.write('Entering Difference.compute()')
self.result = 0
if 'value1' in self.arg_dict and 'value2' in self.arg_dict:
value1 = self.get_value('value1')
value2 = self.get_value('value2')
self.result = value1 - value2
if settings.DEBUG:
sys.stderr.write('value1 = %d, value2 = %d, self.result = %d\n' % (value1, value2, self.result))
def html(self):
"""
Generate an HTML representation of the difference score. This
is represented as a decimal formatted with commas or "n/a".
Returns:
The result wrapped in an HTML SPAN element: "<span>1</span>".
"""
if isinstance(self.result, Decimal):
result = locale.format("%d", self.result, grouping=True)
return '<span>%s</span>' % result
elif not self.result is None:
return '<span>%s</span>' % self.result
else:
return '<span>N/A</span>'
class MultiplyValues(CalculatorBase):
"""
Multiply all values.
For districts, this calculator will multiply a series of arguments.
For plans, this calculator will multiply a series of arguments across
all districts. If a literal value is included in a plan calculation,
that literal value is combined with the subject value for each
district.
For lists of numbers, this calculator will return the product of the list.
Each argument should be assigned the argument name "valueN", where N
is a positive integer. The summation will add all arguments, starting
at position 1, until an argument is not found.
This calculator takes an optional "target" argument. If passed this
argument, the calculator will return a string suitable for display in
a plan summary
"""
def compute(self, **kwargs):
"""
Calculate the product of a series of values.
Keywords:
district -- A district whose values should be summed.
plan -- A plan whose district values should be summed.
version -- Optional. The version of the plan, defaults to the
most recent version.
list -- A list of values to multiplying, when multiplying a set of
ScoreArguments.
"""
if settings.DEBUG:
sys.stderr.write("Entering MultiplyValues.compute()\n")
self.result = 0
if settings.DEBUG:
sys.stderr.write("self.arg_dict = %s\n" % (self.arg_dict))
argnum = 1
while ('value%d'%argnum) in self.arg_dict:
number = self.get_value('value%d'%argnum)
if settings.DEBUG:
sys.stderr.write("value%d found in arg_dict. value%d = %d\n" % (argnum, argnum, number))
if not number is None:
if argnum == 1:
self.result = number
else:
self.result *= number
argnum += 1
if settings.DEBUG:
sys.stderr.write("Leaving MultiplyValues.compute()\n")
def html(self):
"""
Generate an HTML representation of the summation score. This
is represented as a decimal formatted with commas or "n/a".
Returns:
The result wrapped in an HTML SPAN element: "<span>1</span>".
"""
if isinstance(self.result, Decimal):
result = locale.format("%d", self.result, grouping=True)
return '<span>%s</span>' % result
elif not self.result is None:
return '<span>%s</span>' % self.result
else:
return '<span>N/A</span>'
class AveragePartisanIndex(CalculatorBase):
"""
Calculate the partisan index across a number of elections.
"""
def compute(self, **kwargs):
"""
Compute the representational fairness.
Keywords:
plan -- A plan whose set of districts should be evaluated for
representational fairness.
version -- Optional. The version of the plan, defaults to the
most recent version.
"""
if settings.DEBUG:
sys.stderr.write("Leaving AveragePartisanIndex.compute()\n")
districts = []
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
districts = plan.get_districts_at_version(version, include_geom=False)
elif 'district' in kwargs :
districts.append(kwargs['district'])
else:
return
district_count = 0
plan_dem_pi_sum = 0.0
plan_rep_pi_sum = 0.0
for district in districts:
if district.district_id == 0:
# do nothing with the unassigned districts
continue
district_count += 1
if settings.DEBUG:
sys.stderr.write("district_count = %d\n" % (district_count))
argnum = 0
dem_pi_sum = 0.0
rep_pi_sum = 0.0
avg_dem_pi = 0.0
avg_rep_pi = 0.0
while (('democratic%d' % (argnum+1,)) in self.arg_dict and
('republican%d' % (argnum+1,)) in self.arg_dict):
argnum += 1
dem = self.get_value('democratic%d'%argnum, district)
rep = self.get_value('republican%d'%argnum, district)
if dem is None or rep is None:
continue
dem = float(dem)
rep = float(rep)
if dem == 0.0 and rep == 0.0:
continue
dem_pi = dem / (rep + dem)
rep_pi = rep / (rep + dem)
dem_pi_sum += dem_pi
rep_pi_sum += rep_pi
avg_dem_pi = dem_pi_sum / argnum
avg_rep_pi = rep_pi_sum / argnum
plan_dem_pi_sum += avg_dem_pi
plan_rep_pi_sum += avg_rep_pi
plan_avg_dem_pi = plan_dem_pi_sum / district_count
plan_avg_rep_pi = plan_rep_pi_sum / district_count
self.result = (plan_avg_dem_pi, plan_avg_rep_pi)
if settings.DEBUG:
sys.stderr.write("self.result = %s\n" % (str(self.result)))
sys.stderr.write("Leaving AveragePartisanIndex.compute()\n")
class OhioPartisanBalance(CalculatorBase):
"""
This calculator is similar to the RepresentationalFairness calculator but it
uses the average of a number of election results to calculate the partisan
indices and returns a single score as described in the following rubric:
In the competition, each newly-created district will be rated as follows:
Strong Republican: Republican index in excess of 55%
Lean Republican: Republican index between 51 - 55%
Even District: Republican or Democratic index less than 51%
Lean Democrat: Democratic index between 51 - 55%
Strong Democrat: Democratic index in excess of 55%
To determine the overall partisan balance for a plan, the following
calculation will be used:
1.Multiply the number of Strong Republican Districts by 1.5. Add this
figure to the number of Lean Republican Districts and the number of Even
Districts.
2.Multiply the number of districts which are Strong Republican or
Democratic by 1.5. Add this to the number of districts which Lean
Republican or Democratic plus 2 times the number of even districts.
3.Divide the number of arrived at in step one with the number arrived at in
step 2. Convert to a percentage (rounded to 1/10th of one percent) to
arrive at the Republican balance for the plan.
"""
def compute(self, **kwargs):
"""
Compute the representational fairness.
Keywords:
plan -- A plan whose set of districts should be evaluated for
representational fairness.
version -- Optional. The version of the plan, defaults to the
most recent version.
Returns:
Does not return a value, but sets self.result to be a dictionary
with a number of key, value pairs:
value -- The Republican balance for the plan.
strong_rep -- Number of strong Republican districts
lean_rep -- Number of lean Republican districts
even -- Number of even districts
lean_dem -- Number of lean Democratic districts
strong_dem -- Number of strong Democratic districts
"""
if settings.DEBUG:
sys.stderr.write('Entering OhioPartisanBalance.compute()\n')
self.result = {}
self.result['value'] = None
self.result['strong_rep'] = None
self.result['lean_rep'] = None
self.result['even'] = None
self.result['lean_dem'] = None
self.result['strong_dem'] = None
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
districts = plan.get_districts_at_version(version, include_geom=False)
else:
return
rep_strong = 0
rep_lean = 0
even = 0
dem_strong = 0
dem_lean = 0
for district in districts:
if settings.DEBUG:
sys.stderr.write("district = %s\n" % (district))
if district.district_id == 0:
# do nothing with the unassigned districts
continue
partisan_idx = AveragePartisanIndex()
partisan_idx.arg_dict = self.arg_dict
partisan_idx.compute(district=district)
(dem_pi, rep_pi) = partisan_idx.result
if settings.DEBUG:
sys.stderr.write("District %d: dem_pi = %f, rep_pi = %f\n" % (district.district_id, dem_pi, rep_pi))
if rep_pi > 0.55:
rep_strong += 1
if settings.DEBUG:
sys.stderr.write("District %d is strong Republican\n" % (district.district_id))
elif rep_pi >= 0.51 and rep_pi <= 0.55:
rep_lean += 1
if settings.DEBUG:
sys.stderr.write("District %d is lean Republican\n" % (district.district_id))
elif (rep_pi >= 0.5 and rep_pi < 0.51) or (dem_pi >= 0.5 and dem_pi < 0.51):
even += 1
if settings.DEBUG:
sys.stderr.write("District %d is even\n" % (district.district_id))
elif dem_pi >= 0.51 and dem_pi <= 0.55:
dem_lean += 1
if settings.DEBUG:
sys.stderr.write("District %d is lean Democrat\n" % (district.district_id))
elif dem_pi > 0.55:
dem_strong += 1
if settings.DEBUG:
sys.stderr.write("District %d is strong Democrat\n" % (district.district_id))
if settings.DEBUG:
sys.stderr.write("Strong Republican: %d, Lean Republican: %d, Strong Democrat: %d, Lean Democrat: %d, Even: %d\n" % (rep_strong, rep_lean, dem_strong, dem_lean, even))
self.result['value'] = (((rep_strong * 1.5) + rep_lean + even) /
(((rep_strong + dem_strong) * 1.5) + (rep_lean + dem_lean) + (even * 2))
)
self.result['strong_rep'] = rep_strong
self.result['lean_rep'] = rep_lean
self.result['even'] = even
self.result['lean_dem'] = dem_lean
self.result['strong_dem'] = dem_strong
if settings.DEBUG:
sys.stderr.write("self.result['value'] = %f\n" % (self.result['value']))
if settings.DEBUG:
sys.stderr.write('Leaving OhioPartisanBalance.compute()\n')
def html(self):
"""
Generate an HTML representation of the score. This
is represented as a percentage or "n/a"
@return: A number formatted similar to "1.00%", or "n/a"
"""
if not self.result['value'] is None:
return ("<span>%0.2f%%</span>" % (self.result['value'] * 100))
else:
return "<span>n/a</span>"
class OhioElectoralDisproportionality(CalculatorBase):
def compute(self, **kwargs):
"""
Generate the Electoral Disproportionality for an Ohio plan.
Parameters:
plan -- Plan to score.
version -- Version of plan to score. Defaults to most recent version.
Returns:
Does not return a value, but sets self.result to be a dictionary
with a number of key, value pairs:
value -- The electoral disproportionality score for the plan (see below).
strong_rep -- Number of strong Republican districts
lean_rep -- Number of lean Republican districts
even -- Number of even districts
lean_dem -- Number of lean Democratic districts
strong_dem -- Number of strong Democratic districts
The electoral disproportionality is calculated by calculating the partisan
balance for a plan, converting it from a decimal to a percentage
(by multiplying by 100) and then subtracting the state political index.
"""
self.result = {}
self.result['value'] = None
self.result['strong_rep'] = None
self.result['lean_rep'] = None
self.result['even'] = None
self.result['lean_dem'] = None
self.result['strong_dem'] = None
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
partisan_balance_calc = OhioPartisanBalance()
partisan_balance_calc.arg_dict = self.arg_dict
partisan_balance_calc.compute(**kwargs)
partisan_balance = partisan_balance_calc.result['value'] * 100
state_political_index = float(self.get_value('state_political_index'))
self.result['value'] = partisan_balance - state_political_index
self.result['strong_rep'] = partisan_balance_calc.result['strong_rep']
self.result['lean_rep'] = partisan_balance_calc.result['lean_rep']
self.result['even'] = partisan_balance_calc.result['even']
self.result['lean_dem'] = partisan_balance_calc.result['lean_dem']
self.result['strong_dem'] = partisan_balance_calc.result['strong_dem']
def html(self):
"""
Generate an HTML representation of the score. This
is represented as a percentage or "n/a"
@return: A number formatted similar to "1.00%", or "n/a"
"""
if not self.result['value'] is None:
return ("<span>%0.2f%%</span>" % (self.result['value']))
else:
return "<span>n/a</span>"
class OhioRepresentationalFairnessScore(CalculatorBase):
def compute(self, **kwargs):
"""
Generate the Representational Fairness score for an Ohio plan.
Parameters:
plan -- Plan to score.
version -- Version of plan to score. Defaults to most recent version.
Returns:
Does not return a value, but sets self.result to be a dictionary
with a number of key, value pairs:
value -- The representational fairness score for the plan (see below).
strong_rep -- Number of strong Republican districts
lean_rep -- Number of lean Republican districts
even -- Number of even districts
lean_dem -- Number of lean Democratic districts
strong_dem -- Number of strong Democratic districts
The representational fairness score is determined by the following
algorithm depending on the legislative body of the plan.
For Congressional plans:
(25 - electoral disproportionality) * 4
For State House/State Senate plans:
(25 - electoral disproportionality) * 2
For State Senate plans:
25 - (num_splits / 2)
"""
self.result = {}
self.result['value'] = None
self.result['strong_rep'] = None
self.result['lean_rep'] = None
self.result['even'] = None
self.result['lean_dem'] = None
self.result['strong_dem'] = None
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
calc = OhioElectoralDisproportionality()
argnum = 0
while (('democratic%d' % (argnum+1,)) in self.arg_dict and
('republican%d' % (argnum+1,)) in self.arg_dict):
argnum += 1
calc.arg_dict['democratic%d' % (argnum)] = self.arg_dict['democratic%d' % (argnum)]
calc.arg_dict['republican%d' % (argnum)] = self.arg_dict['republican%d' % (argnum)]
calc.arg_dict['state_political_index'] = self.arg_dict['state_political_index']
calc.compute(**kwargs)
electoral_disproportionality = calc.result['value']
self.result['strong_rep'] = calc.result['strong_rep']
self.result['lean_rep'] = calc.result['lean_rep']
self.result['even'] = calc.result['even']
self.result['lean_dem'] = calc.result['lean_dem']
self.result['strong_dem'] = calc.result['strong_dem']
congressional_legislative_body_id = self.get_value('congressional_legislative_body_id')
state_house_legislative_body_id = self.get_value('state_house_legislative_body_id')
state_senate_legislative_body_id = self.get_value('state_senate_legislative_body_id')
if (plan.legislative_body.id == congressional_legislative_body_id):
self.result['value'] = (25 - electoral_disproportionality) * 4
elif (plan.legislative_body.id == state_house_legislative_body_id or
plan.legislative_body.id == state_senate_legislative_body_id):
self.result['value'] = (25 - electoral_disproportionality) * 2
else:
# Error. TODO: Do something here.
pass
def html(self):
"""
Generate an HTML representation of the score. This
is represented as a percentage or "n/a"
@return: A number formatted similar to "1.00%", or "n/a"
"""
if not self.result['value'] is None:
return ("<span>%0.2f%%</span>" % (self.result['value']))
else:
return "<span>n/a</span>"
class OhioSplitCounter(CalculatorBase):
def compute(self, **kwargs):
if settings.DEBUG:
sys.stderr.write('Entering OhioSplitCounter.compute()\n')
sys.stderr.write('kwargs = %s\n' % (kwargs))
self.result = 0
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
else:
raise UnboundLocalError
# HACK ALERT! Parse out the geolevel_id and turn it into a boundary_id
# NOTE: geolevel_id should be the id of the geolevel in the database, not
# the id in the configuration file. In the test instance at least
# the database geolevel ids are:
#
# 3 - county
# 2 - mcdplace
# 1 - block
geolevel_id = self.get_value('geolevel_id')
# The way Jim defined splits to be counted in the Ohio Scoring Rubric is to count all
# the fragments created by a district and to ignore cases where a district is entirely
# contained in a county.
#
# So, we'll find all the counties whose boundaries are crossed by a district. This will
# find districts that create county fragments and rule out districts that are entirely
# contained in a county. However, this set will also contain counties that are entirely
# contained in a district. So, we'll also need to get the set of counties that are entirely
# contained in a district.
# Find all geolevel boundaries that districts in this plan cross
crosses_set = plan.find_geolevel_relationships(geolevel_id, version=version, de_9im='T*T******')
# Find all geolevel boundaries that districts in this plan contain
contains_set = plan.find_geolevel_relationships(geolevel_id, version=version, de_9im='T*****FF*')
if settings.DEBUG:
sys.stderr.write('crosses_set = %s\n' % (crosses_set))
sys.stderr.write('contains_set = %s\n' % (contains_set))
if not crosses_set is None:
for crossed_relationship in crosses_set:
# TODO: Add logic to rule out certain counties.
if crossed_relationship not in contains_set:
if settings.DEBUG:
district_name = crossed_relationship[0]
geounit_name = crossed_relationship[3]
sys.stderr.write('Geounit %s is split by district %s\n' %
(geounit_name, district_name))
self.result += 1
else:
if settings.DEBUG:
district_name = crossed_relationship[0]
geounit_name = crossed_relationship[3]
sys.stderr.write('Geounit %s is entirely contained in district %s, not counting as split\n' %
(geounit_name, district_name))
else:
if settings.DEBUG:
sys.stderr.write('ERROR: crosses_set is None\n')
if settings.DEBUG:
sys.stderr.write('self.result = %d\n' % self.result)
sys.stderr.write('Leaving OhioSplitCounter.compute()\n')
class OhioSplitScore(CalculatorBase):
def compute(self, **kwargs):
"""
Compute the split score for Ohio plans.
Parameters:
plan -- Plan to score.
version -- Version of plan to score. Defaults to most recent version.
Returns:
Dictionary with two key, value pairs:
value -- The split score for the plan (see below).
num_splits -- The number of splits detected.
The split score is determined by the following algorithm depending on the
legislative body of the plan.
For Congressional plans:
50 - num_splits
For State House plans:
25 - (num_splits / 4)
For State Senate plans:
25 - (num_splits / 2)
"""
self.result = {}
self.result['value'] = 0
self.result['num_splits'] = 0
if 'plan' in kwargs:
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
calc = OhioSplitCounter()
calc.arg_dict['geolevel_id'] = self.arg_dict['geolevel_id']
calc.compute(**kwargs)
num_splits = calc.result
self.result['num_splits'] = num_splits
if settings.DEBUG:
sys.stderr.write('num_splits = %d\n' % (num_splits))
congressional_legislative_body_id = self.get_value('congressional_legislative_body_id')
state_house_legislative_body_id = self.get_value('state_house_legislative_body_id')
state_senate_legislative_body_id = self.get_value('state_senate_legislative_body_id')
if (plan.legislative_body.id == congressional_legislative_body_id):
self.result['value'] = 50.0 - num_splits
elif (plan.legislative_body.id == state_house_legislative_body_id):
self.result['value'] = 25.0 - (num_splits / 4.0)
elif (plan.legislative_body.id == state_senate_legislative_body_id):
self.result['value'] = 25.0 - (num_splits / 2.0)
else:
raise ValueError("Plan's legislative body ID of %d is not one of the congressional, state house or state senate IDs (%d, %d, %d" %
(plan.legislative_body.id, congressional_legislative_body_id, state_house_legislative_body_id, state_senate_legislative_body_id))
def html(self):
"""
Generate an HTML representation of the score. This
is represented as a percentage or "n/a"
@return: A number formatted similar to "1.00%", or "n/a"
"""
if not self.result['value'] is None:
return ("<span>%0.2f%%</span>" % (self.result['value']))
else:
return "<span>n/a</span>"
class SumPlanScores(CalculatorBase):
"""SumValues will try to get an argument for all districts in a plan, even
if the argument is a plan score. This works around this."""
def compute(self, **kwargs):
self.result = 0
argnum = 1
while ('value%d'%argnum) in self.arg_dict:
number = self.get_value('value%d'%argnum)
# HACK ALERT!: Workaround to CalculatorBase.get_value not converting
# floats to Decimals. -ghing@mcic.org 2011-08-04
if isinstance(number, float):
number = Decimal('%f' % number)
if not number is None:
self.result += number
argnum += 1
def html(self):
"""
Generate an HTML representation of the summation score. This
is represented as a decimal formatted with commas or "n/a".
Returns:
The result wrapped in an HTML SPAN element: "<span>1</span>".
"""
if isinstance(self.result, Decimal):
result = locale.format("%f", self.result, grouping=True)
return '<span>%s</span>' % result
elif not self.result is None:
return '<span>%s</span>' % self.result
else:
return '<span>N/A</span>'
pass
# TODO: Implement this
class Message(CalculatorBase):
def compute(self, **kwargs):
self.result = ''
if 'message' in self.arg_dict:
self.result = self.get_value('message')
def html(self):
return self.result
class PartisanDifferential(CalculatorBase):
"""
Compute the plan's partisan Differential.
This calculator only operates on Plans.
This calculator requires three arguments: 'democratic', 'republican',
and 'range'
"""
def compute(self, **kwargs):
"""
Compute the partisan differential.
@keyword plan: A L{Plan} whose set of districts should be
evaluated for competitiveness.
@keyword version: Optional. The version of the plan, defaults to
the most recent version.
"""
if not 'plan' in kwargs:
return
plan = kwargs['plan']
version = kwargs['version'] if 'version' in kwargs else plan.version
districts = plan.get_districts_at_version(version, include_geom=False)
try:
partisan_differential_range = float(self.get_value('range'))
except:
partisan_differential_range = .05
fair = 0
for district in districts:
if district.district_id == 0:
continue
tmpdem = self.get_value('democratic',district)
tmprep = self.get_value('republican',district)
if tmpdem is None or tmprep is None:
continue
dem = float(tmpdem)
rep = float(tmprep)
if dem == 0.0 and rep == 0.0:
continue
pidx_rep = rep / (dem + rep)
pidx_dem = dem / (dem + rep)
partisan_differential = abs(pidx_rep - pidx_dem)
if partisan_differential <= partisan_differential_range:
fair += 1
self.result = { 'value': fair }
class OhioCompetitiveness(CalculatorBase):
ranges = ('0.05', '0.10', '0.15', '0.5')
def compute(self, **kwargs):
if settings.DEBUG:
sys.stderr.write('Entering OhioCompetitiveness.compute()\n')
sys.stderr.write('self.arg_dict = %s\n' % (self.arg_dict))
self.result = {}
self.result['value'] = None
self.result['highly_competitive'] = None
self.result['generally_competitive'] = None
self.result['generally_noncompetitive'] = None
self.result['highly_noncompetitive'] = None
counts_in_range = {}
for cptv_range in self.ranges:
partisan_differential_calc = PartisanDifferential()
partisan_differential_calc.arg_dict['democratic'] = self.arg_dict['democratic']
partisan_differential_calc.arg_dict['republican'] = self.arg_dict['republican']
partisan_differential_calc.arg_dict['range'] = ('literal', cptv_range)
partisan_differential_calc.compute(**kwargs)
counts_in_range[cptv_range] = int(partisan_differential_calc.result['value'])
if settings.DEBUG:
sys.stderr.write('counts_in_range = %s\n' % (counts_in_range))
self.result['highly_competitive'] = counts_in_range['0.05']
self.result['generally_competitive'] = counts_in_range['0.10'] - counts_in_range['0.05']
self.result['generally_noncompetitive'] = counts_in_range['0.15'] - counts_in_range['0.10']
self.result['highly_noncompetitive'] = counts_in_range['0.5'] - counts_in_range['0.15']
if settings.DEBUG:
sys.stderr.write('Highly competitive districts: %d\n' % (self.result['highly_competitive']))
sys.stderr.write('Generally competitive districts: %d\n' % (self.result['generally_competitive']))
sys.stderr.write('Generally non-competitive districts: %d\n' % (self.result['generally_noncompetitive']))
sys.stderr.write('Highly non-competitive districts: %d\n' % (self.result['highly_noncompetitive']))
self.result['value'] = (
self.result['highly_competitive'] * 3 +
self.result['generally_competitive'] * 2 +
self.result['generally_noncompetitive'] * 1
)
if settings.DEBUG:
sys.stderr.write('Leaving OhioCompetitiveness.compute()\n')
def html(self):
"""
Generate an HTML representation of the summation score. This
is represented as a decimal formatted with commas or "n/a".
Returns:
The result wrapped in an HTML SPAN element: "<span>1</span>".
"""
if isinstance(self.result['value'], Decimal):
result = locale.format("%d", self.result['value'], grouping=True)
return '<span>%s</span>' % result
elif not self.result['value'] is None:
return '<span>%s</span>' % self.result['value']
else:
return '<span>N/A</span>'
class OhioCompactnessScore(CalculatorBase):
"""Wrapper for compactness score."""
def compute(self, **kwargs):
self.result = 0
calc = Roeck()
calc.compute(**kwargs)
self.result = calc.result['value'] * 100
def html(self):
"""
Generate an HTML representation of the summation score. This
is represented as a decimal formatted with commas or "n/a".
Returns:
The result wrapped in an HTML SPAN element: "<span>1</span>".
"""
if isinstance(self.result, Decimal):
result = locale.format("%f", self.result, grouping=True)
return '<span>%s</span>' % result
elif not self.result is None:
return '<span>%s</span>' % self.result
else:
return '<span>N/A</span>'
import sys
import csv
from optparse import make_option
from datetime import datetime, timedelta
from redistricting.models import *
from redistricting.ohcalculators import OhioSplitCounter, OhioCompetitiveness, OhioRepresentationalFairnessScore, OhioSplitScore, OhioCompactnessScore
import re
# Add this to the option list of your management command and use get_plans()
# to retrieve plans to be able to filter
plan_filtering_option_list = (
make_option('-p', '--plan', dest='plan_id', default=None, action='store', help='Choose a single plan to list.'),
make_option('-s', '--shared', dest='is_shared', default=False, action='store_true', help='Only list shared plans'),
make_option('-e', '--last-edited', dest='last_edited', default=False, action='store', help='Only list plans edited before or after a certain time period.'),
make_option('-V', '--versions', dest='versions', default=False, action='store', help='Only list plans with a minimum or maximum number of versions.'),
make_option('-i', '--ids-only', dest='ids_only', default=False, action='store_true', help='Only display plans IDs.'),
make_option('-u', '--users', dest='users', default=None, action='store', help="Only list plans by these users"),
make_option('-U', '--exclude-users', dest='exclude_users', default=None, action='store', help="Don't list plans by these users"),
)
def get_plans(options):
"""Helper for management commands that filter plans with command line options."""
# Grab all of the plans from the database
plan_id = options.get('plan_id')
if plan_id:
plan_ids = re.split('\s*,\s*', plan_id)
plans = Plan.objects.filter(pk__in=plan_ids)
else:
plans = Plan.objects.all()
last_edited = options.get("last_edited")
if last_edited:
if last_edited[0] in ('-', '+') and last_edited[-1:] in ('w', 'd'):
last_edited_days = int(last_edited[1:-1])
if last_edited[-1:] == 'w':
last_edited_days *= 7
td = timedelta(days=last_edited_days)
if last_edited[0] == '-':
plans = plans.filter(edited__gt=(datetime.now() - td))
else:
# last_edited[0] == '+'
plans = plans.filter(edited__lt=(datetime.now() - td))
else:
sys.stderr.write('Invalid format for last-edited value. It must be in the format +Xd, -Xd, +Xw, -Xw where X is an integer.')
versions = options.get("versions")
if versions:
if versions[0] in ('-', '+'):
version_limit = versions[1:]
if versions[0] == '-':
plans = plans.filter(version__lt=version_limit)
else:
# versions[0] == '+'
plans = plans.filter(version__gt=version_limit)
users = options.get("users")
if users:
user_list = re.split('[\s,]+', users)
plans = [p for p in plans if p.owner.username in (user_list)]
exclude_users = options.get("exclude_users")
if exclude_users:
exclude_user_list = re.split('[\s,]+', exclude_users)
plans = [p for p in plans if p.owner.username not in (exclude_user_list)]
# Filter out all non-shared plans if specified
if options.get("is_shared"):
plans = [p for p in plans if p.is_shared]
return plans
def score_plan(plan):
"""
Score a plan according to the Ohio scoring rubric.
This chains together a bunch of custom calculators for Ohio.
It's a quick and dirty helper to facilitate export of a score
CSV either through a view or management command.
"""
total_score = 0
calc = OhioSplitScore()
calc.arg_dict['geolevel_id'] = ('literal', '3')
calc.arg_dict['congressional_legislative_body_id'] = ('literal', '1')
calc.arg_dict['state_house_legislative_body_id'] = ('literal', '2')
calc.arg_dict['state_senate_legislative_body_id'] = ('literal', '3')
calc.compute(plan=plan)
split_score = calc.result['value']
num_splits = calc.result['num_splits']
if settings.DEBUG:
sys.stderr.write("Split Score: %f\n" % (split_score))
compactness_calc = OhioCompactnessScore()
compactness_calc.compute(plan=plan)
compactness_score = compactness_calc.result
if settings.DEBUG:
sys.stderr.write("Compactness Score: %f\n" % (compactness_score))
competitiveness_calc = OhioCompetitiveness()
competitiveness_calc.arg_dict['democratic'] = ('subject', 'demtot')
competitiveness_calc.arg_dict['republican'] = ('subject', 'reptot')
competitiveness_calc.compute(plan=plan)
competitiveness_score = competitiveness_calc.result['value']
if settings.DEBUG:
sys.stderr.write("Competitiveness Score: %d\n" % (competitiveness_score))
rep_fairness_calc = OhioRepresentationalFairnessScore()
rep_fairness_calc.arg_dict['democratic1'] = ('subject', 'demtot')
rep_fairness_calc.arg_dict['republican1'] = ('subject', 'reptot')
rep_fairness_calc.arg_dict['state_political_index'] = ('literal', '51.4')
rep_fairness_calc.arg_dict['congressional_legislative_body_id'] = ('literal', '1')
rep_fairness_calc.arg_dict['state_house_legislative_body_id'] = ('literal', '2')
rep_fairness_calc.arg_dict['state_senate_legislative_body_id'] = ('literal', '3')
rep_fairness_calc.compute(plan=plan)
rep_fairness_score = rep_fairness_calc.result['value']
if settings.DEBUG:
sys.stderr.write("Representational Fairness Score: %f\n" % (rep_fairness_score))
total_score = split_score + compactness_score + competitiveness_score + rep_fairness_score
if settings.DEBUG:
sys.stderr.write("Total Score: %f\n" % (total_score))
return {
'num_splits': num_splits,
'split_score': split_score,
'compactness': compactness_score,
'highly_competitive': competitiveness_calc.result['highly_competitive'],
'generally_competitive': competitiveness_calc.result['generally_competitive'],
'generally_noncompetitive': competitiveness_calc.result['generally_noncompetitive'],
'highly_noncompetitive': competitiveness_calc.result['highly_noncompetitive'],
'competitiveness': competitiveness_score,
'strong_rep': rep_fairness_calc.result['strong_rep'],
'lean_rep': rep_fairness_calc.result['lean_rep'],
'even': rep_fairness_calc.result['even'],
'lean_dem': rep_fairness_calc.result['lean_dem'],
'strong_dem': rep_fairness_calc.result['strong_dem'],
'rep_fairness': rep_fairness_score,
'total': total_score }
def plan_score_csv(plans, csv_file):
"""
Output plan scores in a CSV file.
Parameters:
plans -- Queryset of redistricting.models.Plan objects
csv_file -- File object to write CSV to
"""
csv_fields = (
'id',
'name',
'username',
'legislative_body',
'edited',
'split_score',
'compactness',
'competitiveness',
'rep_fairness',
# Jim asked to suppress the total score since it doesn't make
# much sense because of the way scores for State Senate and
# State House plans are combined.
# -ghing@mcic.org 2011-08-10
# 'total'
)
# We probably want to wrap this in an if settings.DEBUG once we
# work out the scoring issues. -ghing@mcic.org 2011-08-10
csv_fields = csv_fields + (
'num_splits',
'highly_competitive',
'generally_competitive',
'generally_noncompetitive',
'highly_noncompetitive',
'strong_rep',
'lean_rep',
'even',
'lean_dem',
'strong_dem',
)
score_writer = csv.DictWriter(csv_file, csv_fields, dialect='excel')
csv_field_values = {
'id': 'ID',
'name': 'NAME',
'username': 'USERNAME',
'legislative_body': 'LEGISLATIVE_BODY',
'edited': 'EDITED',
'split_score': 'SPLIT_SCORE',
'compactness': 'COMPACTNESS_SCORE',
'competitiveness': 'COMPETITIVENESS_SCORE',
'rep_fairness': 'REPRESENATIONAL_FAIRNESS_SCORE',
# Jim asked to suppress the total score since it doesn't make
# much sense because of the way scores for State Senate and
# State House plans are combined.
# -ghing@mcic.org 2011-08-10
#'total': 'TOTAL_SCORE'
}
# We probably want to wrap this in an if settings.DEBUG once we
# work out the scoring issues. -ghing@mcic.org 2011-08-10
csv_field_values['num_splits'] = 'NUM_SPLITS'
csv_field_values['highly_competitive'] = 'highly_competitive'.upper()
csv_field_values['generally_competitive'] = 'generally_competitive'.upper()
csv_field_values['generally_noncompetitive'] = 'generally_noncompetitive'.upper()
csv_field_values['highly_noncompetitive'] = 'highly_noncompetitive'.upper()
csv_field_values['strong_rep'] = 'strong_rep'.upper()
csv_field_values['lean_rep'] = 'lean_rep'.upper()
csv_field_values['even'] = 'even'.upper()
csv_field_values['lean_dem'] = 'lean_dem'.upper()
csv_field_values['strong_dem'] = 'strong_dem'.upper()
score_writer.writerow(csv_field_values)
for plan in plans:
if settings.DEBUG:
sys.stderr.write("Scoring plan '%s' with id %d - started at %s\n" % (
plan.name, plan.id, datetime.now()))
scores = score_plan(plan)
scores['id'] = plan.id
scores['name'] = plan.name
scores['username'] = plan.owner.username
scores['legislative_body'] = plan.legislative_body.name
scores['edited'] = plan.edited
# Suppress total score. DictWriter.writerow() complains if there are values in
# the dict that are not specified when initializing the writer.
# -ghing@mcic.org 2011-08-10
del(scores['total'])
score_writer.writerow(scores)
if settings.DEBUG:
sys.stderr.write("Scoring plan '%s' with id %d - ended at %s\n" % (
plan.name, plan.id, datetime.now()))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment