Created
June 16, 2011 13:45
-
-
Save ghing/1029254 to your computer and use it in GitHub Desktop.
Custom District Builder calculators for Ohio
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
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 %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