def __init__(self, datestr, pollster, sponsor, sample_size, con, lab, lib, ukip): """Constructor. Store off data.""" # Store off the easy stuff. self.pollster = pollster self.sponsor = sponsor self.sample_size = sample_size # Parse the date string into a date object for easy sorting. self.date = datetime.datetime.strptime(datestr, '%d %b %Y') # Store all the vote percentages as if they were votes from a total # turnout of 100. votes = {CON: con, LAB: lab, LD: lib, UKP: ukip} votes[OTH] = 100 - sum(votes.values()) # Now convert those into a support dictionary. self.support = utils.calculate_support(votes) # Initialize somewhere to store the MonteCarlo results of this poll. self.mc_results = None return
def calculate_overall_support(self, year=None): """Create a support dictionary based on the number of votes in the given year. If no year is provided, use the predicted votes. """ total_votes = {} for const_name in self.constituencies.keys(): const = self.constituencies[const_name] if year is None: votes = const.sim_votes elif year == 2005: votes = const.votes_2005 else: assert year == 2010, "Year must be None, 2005 or 2010" votes = const.votes_2010 for party in votes.keys(): if party not in total_votes.keys(): total_votes[party] = votes[party] else: total_votes[party] += votes[party] support = utils.calculate_support(total_votes) return support
def analyze(self): """Analyze the results of the election.""" # Create a new Result structure to hold these results. self.result = Result() # First determine the largest party. party_list = sorted(self.parties.values(), key=attrgetter('seats'), reverse=True) self.result.largest_party = party_list[0].name self.result.most_seats_won = party_list[0].seats # Save off the numbers of seats gained by all parties. for party in self.parties: self.result.seats[party] = self.parties[party].seats # Record zero seats for any party that didn't get any. for party in (set(PARTY_NAMES) - set(self.parties)): self.result.seats[party] = 0 # Now determine whether the largest party is past or behind the winning # line (and by how much). self.result.margin_of_victory = (self.result.most_seats_won - NEEDED_FOR_MAJORITY) if self.result.margin_of_victory >= 0: self.result.summary = "{0} victory (majority {1})".format( self.result.largest_party, self.result.margin_of_victory) self.result.winner = self.result.largest_party else: self.result.summary = "Hung Parliament ({0} needs {1})".format( self.result.largest_party, (0 - self.result.margin_of_victory)) # Work out the coalitions that could conceivably take power. The # expected rules for this are as follows: # - A coalition always has CON or LAB as the senior partner # - Both CON and LAB will take LD as their preferred junior partner # - Only CON will take UKP as a junior partner # - Only LAB will take GRN as a junior partner # - LD and GRN will not join a coalition that includes UKP # - SNP will join neither a LAB nor a CON coalition # - PC will join only a LAB coalition # - OTH will join anyone # - Both LAB and CON prefer named parties to OTH if (self.result.seats[CON] + self.result.seats[LD] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-LD") elif (self.result.seats[CON] + self.result.seats[UKP] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-UKP") elif (self.result.seats[CON] + self.result.seats[LD] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-LD-OTH") elif (self.result.seats[CON] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-OTH") elif (self.result.seats[CON] + self.result.seats[UKP] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-UKP-OTH") if (self.result.seats[LAB] + self.result.seats[LD] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-GRN") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-GRN") elif (self.result.seats[LAB] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-PC") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[PC] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-GRN-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[PC] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-GRN-OTH") if len(self.result.possible_coalitions) == 0: self.result.possible_coalitions = ["NONE"] # Did UKIP win any seats in this election? if UKP in self.parties: self.result.ukip_seats = self.parties[UKP].seats else: self.result.ukip_seats = 0 # Did the Lib-Dems? if LD in self.parties: self.result.libdem_seats = self.parties[LD].seats else: self.result.libdem_seats = 0 # Did the Greens hold Brighton Pavilion? if self.constituencies["Brighton Pavilion"].winning_party == GRN: self.result.greens_hold_brighton = True else: self.result.greens_hold_brighton = False # Was the party with the most seats the popular vote winner? votes = {} for const_name in self.constituencies: const = self.constituencies[const_name] for party in const.sim_votes.keys(): if party not in votes.keys(): votes[party] = const.sim_votes[party] else: votes[party] += const.sim_votes[party] most_votes = 0 self.result.most_votes_party = None for party in votes.keys(): if votes[party] > most_votes: most_votes = votes[party] self.result.most_votes_party = party self.result.seat_winner_is_pop_winner = (self.result.largest_party == self.result.most_votes_party) # Find all the constituencies where the Conservatives are predicted # to win heavily, but UKIP did not field a candidate in 2010 - these # are the UKIP stealth targets, where they could win much more # easily than expected. no_ukips = [self.constituencies[cons] for cons in self.constituencies if self.constituencies[cons].votes_2010[UKP] == 0] con_win_no_ukip = [cons for cons in no_ukips if cons.winning_party == CON] for cons in con_win_no_ukip: vote_counts = sorted(cons.sim_votes.values(), reverse=True) margin = vote_counts[0] - vote_counts[1] if margin > 1000: self.result.ukip_stealth_targets[cons.name] = margin # Was the vote distribution sufficiently close to the initial support # figures? overall_support = utils.calculate_support(votes) support_2010 = self.calculate_overall_support(2010) self.result.support = overall_support for party in self.predicted_support: divergence = abs(self.predicted_support[party] - overall_support[party]) logger.debug("{0} support was {1}, predicted {2}, actual {3}, " "divergence {4}".format(party, support_2010[party], self.predicted_support[party], overall_support[party], divergence)) if divergence > RESULT_TOLERANCE: # This party's result is too far away from its predicted # support. This result can't stand. logger.debug("Result too far from prediction!") self.result.result_too_divergent = True return
def analyze(self): """Analyze the results of the election.""" # Create a new Result structure to hold these results. self.result = Result() # First determine the largest party. party_list = sorted(self.parties.values(), key=attrgetter('seats'), reverse=True) self.result.largest_party = party_list[0].name self.result.most_seats_won = party_list[0].seats # Save off the numbers of seats gained by all parties. for party in self.parties: self.result.seats[party] = self.parties[party].seats # Record zero seats for any party that didn't get any. for party in (set(PARTY_NAMES) - set(self.parties)): self.result.seats[party] = 0 # Now determine whether the largest party is past or behind the winning # line (and by how much). self.result.margin_of_victory = (self.result.most_seats_won - NEEDED_FOR_MAJORITY) if self.result.margin_of_victory >= 0: self.result.summary = "{0} victory (majority {1})".format( self.result.largest_party, self.result.margin_of_victory) self.result.winner = self.result.largest_party else: self.result.summary = "Hung Parliament ({0} needs {1})".format( self.result.largest_party, (0 - self.result.margin_of_victory)) # Work out the coalitions that could conceivably take power. The # expected rules for this are as follows: # - A coalition always has CON or LAB as the senior partner # - Both CON and LAB will take LD as their preferred junior partner # - Only CON will take UKP as a junior partner # - Only LAB will take GRN as a junior partner # - LD and GRN will not join a coalition that includes UKP # - SNP will join neither a LAB nor a CON coalition # - PC will join only a LAB coalition # - OTH will join anyone # - Both LAB and CON prefer named parties to OTH if (self.result.seats[CON] + self.result.seats[LD] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-LD") elif (self.result.seats[CON] + self.result.seats[UKP] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-UKP") elif (self.result.seats[CON] + self.result.seats[LD] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-LD-OTH") elif (self.result.seats[CON] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-OTH") elif (self.result.seats[CON] + self.result.seats[UKP] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("CON-UKP-OTH") if (self.result.seats[LAB] + self.result.seats[LD] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-GRN") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-GRN") elif (self.result.seats[LAB] + self.result.seats[PC] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-PC") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[PC] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-GRN-OTH") elif (self.result.seats[LAB] + self.result.seats[LD] + self.result.seats[GRN] + self.result.seats[PC] + self.result.seats[OTH] >= NEEDED_FOR_MAJORITY): self.result.possible_coalitions.append("LAB-LD-PC-GRN-OTH") if len(self.result.possible_coalitions) == 0: self.result.possible_coalitions = ["NONE"] # Did UKIP win any seats in this election? if UKP in self.parties: self.result.ukip_seats = self.parties[UKP].seats else: self.result.ukip_seats = 0 # Did the Lib-Dems? if LD in self.parties: self.result.libdem_seats = self.parties[LD].seats else: self.result.libdem_seats = 0 # Did the Greens hold Brighton Pavilion? if self.constituencies["Brighton Pavilion"].winning_party == GRN: self.result.greens_hold_brighton = True else: self.result.greens_hold_brighton = False # Was the party with the most seats the popular vote winner? votes = {} for const_name in self.constituencies: const = self.constituencies[const_name] for party in const.sim_votes.keys(): if party not in votes.keys(): votes[party] = const.sim_votes[party] else: votes[party] += const.sim_votes[party] most_votes = 0 self.result.most_votes_party = None for party in votes.keys(): if votes[party] > most_votes: most_votes = votes[party] self.result.most_votes_party = party self.result.seat_winner_is_pop_winner = ( self.result.largest_party == self.result.most_votes_party) # Find all the constituencies where the Conservatives are predicted # to win heavily, but UKIP did not field a candidate in 2010 - these # are the UKIP stealth targets, where they could win much more # easily than expected. no_ukips = [ self.constituencies[cons] for cons in self.constituencies if self.constituencies[cons].votes_2010[UKP] == 0 ] con_win_no_ukip = [ cons for cons in no_ukips if cons.winning_party == CON ] for cons in con_win_no_ukip: vote_counts = sorted(cons.sim_votes.values(), reverse=True) margin = vote_counts[0] - vote_counts[1] if margin > 1000: self.result.ukip_stealth_targets[cons.name] = margin # Was the vote distribution sufficiently close to the initial support # figures? overall_support = utils.calculate_support(votes) support_2010 = self.calculate_overall_support(2010) self.result.support = overall_support for party in self.predicted_support: divergence = abs(self.predicted_support[party] - overall_support[party]) logger.debug("{0} support was {1}, predicted {2}, actual {3}, " "divergence {4}".format(party, support_2010[party], self.predicted_support[party], overall_support[party], divergence)) if divergence > RESULT_TOLERANCE: # This party's result is too far away from its predicted # support. This result can't stand. logger.debug("Result too far from prediction!") self.result.result_too_divergent = True return