Skip to content

Instantly share code, notes, and snippets.

@ghing
Created June 16, 2011 13:45
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/1029254 to your computer and use it in GitHub Desktop.
Save ghing/1029254 to your computer and use it in GitHub Desktop.
Custom District Builder calculators for Ohio
from publicmapping.redistricting.calculators import CalculatorBase
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.
"""
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
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.
"""
self.result = 0
argnum = 1
while ('value%d'%argnum) in self.arg_dict:
number = self.get_value('value%d'%argnum)
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("%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.
"""
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.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
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
number = self.get_value('value%d'%argnum, district)
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)
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.
"""
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:
partisan_idx = AveragePartisanIndex()
partisan_idx.arg_dict = self.arg_dict
(dem_pi, rep_pi) = partisan_idx.compute(district=district)
if rep_pi > 0.55:
rep_strong += 1
elif rep_pi >= 0.51 and rep_pi <= 0.55:
rep_lean += 1
elif rep_pi < 0.51 or dem_pi < 0.51:
even += 1
elif dem_pi >= 0.51 and dem_pi <= 0.55:
dem_lean += 1
elif dem_pi > 0.55:
dem_strong += 1
self.result = (((rep_strong * 1.5) + rep_lean + even) /
(((rep_strong + dem_strong) * 1.5) + (rep_lean + dem_lean) + (even * 2))
)
def html(self):
"""
Display the results in HTML format. Since the results for this
calculator are in tuple format for sorting purposes, it's important
to display a human readable score that explains which party the
plan is biased toward.
Returns:
An HTML SPAN element similar to the form: "<span>Democrat 5</span>" or "<span>Balanced</span>".
"""
sort = abs(self.result)
party = 'Democrat' if self.result > 0 else 'Republican'
if sort == 0:
return '<span>Balanced</span>'
else:
return '<span>%s&nbsp;%d</span>' % (party, sort)
def json(self):
"""
Generate a basic JSON representation of the result.
Returns:
A JSON object with 1 property: result.
"""
sort = abs(self.result)
party = 'Democrat' if self.result > 0 else 'Republican'
return json.dumps( {'result': '%s %d' % (party, sort)} )
def sortkey(self):
"""
How should this calculator be compared to others like it?
Sort by the absolute value of the result (farther from zero
is a worse score).
Returns:
The absolute value of the result.
"""
return abs(self.result)
class OhioSplitCounter(CalculatorBase):
def compute(self, **kwargs):
split_counter = SplitCounter()
split_counter.arg_dict = self.arg_dict
# HACK ALERT! Parse out the geolevel_id and turn it into a boundary_id
# TODO: Figure out if this is correct.
geolevel_id = self.get_value('geolevel_id')
split_counter.arg_dict['boundary_id'] = ("literal", "geolevel.%d" % geolevel_id)
split_counter.compute(**kwargs)
self.result = 0
for named_split in split_counter.result['named_splits']:
# TODO: Add logic to rule out certain counties.
self.result += 1
class SumScores(CalculatorBasse):
def compute(self, **kwargs):
self.result = 0
argnum = 1
while ('value%d'%argnum) in self.arg_dict:
number = self.get_value('value%d'%argnum)
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("%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>'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment