Skip to content

Commit

Permalink
updated to 2016 election
Browse files Browse the repository at this point in the history
  • Loading branch information
GaryBoone committed Jun 13, 2016
1 parent 5e3f238 commit a4cd1c4
Show file tree
Hide file tree
Showing 7 changed files with 171 additions and 77 deletions.
7 changes: 4 additions & 3 deletions api.go
Expand Up @@ -13,7 +13,7 @@ import (
)

// sort polls by most recent first so we use the most recent data
const apiUrl = "http://elections.huffingtonpost.com/pollster/api/polls.json?sort=updated&topic=2012-president&state="
const apiUrl = "http://elections.huffingtonpost.com/pollster/api/polls.json?sort=updated&topic=%s&state=%s"

type Responses struct {
Choice *string
Expand Down Expand Up @@ -61,8 +61,9 @@ type Poll struct {
}

// readPollingApi reads the data from the Pollster API.
func readPollingApi(state string) []byte {
resp, err := http.Get(apiUrl + state)
func readPollingApi(topic, state string) []byte {
url := fmt.Sprintf(apiUrl, topic, state)
resp, err := http.Get(url)
if err != nil {
log.Fatal(err)
}
Expand Down
2 changes: 1 addition & 1 deletion cdf_test.go
@@ -1,7 +1,7 @@
//
// cdf_test.go
//
// go test election2012.go state.go api.go cdf.go parse.go college.go cdf_test.go
// $ go test github.com/GaryBoone/PresidentialMonteCarlo
//

package main
Expand Down
62 changes: 60 additions & 2 deletions college.go
Expand Up @@ -5,11 +5,69 @@
package main

type College struct {
votes int
dem2008 bool
votes int
lastElection bool
}

// 2012 Electoral College
// format: "State": College{number of votes, did Democrat win?},
var college = map[string]College{
"AL": College{9, false},
"AK": College{3, false},
"AZ": College{11, false},
"AR": College{6, false},
"CA": College{55, true},
"CO": College{9, true},
"CT": College{7, true},
"DE": College{3, true},
"DC": College{3, true},
"FL": College{29, true},
"GA": College{16, false},
"HI": College{4, true},
"ID": College{4, false},
"IL": College{20, true},
"IN": College{11, false},
"IA": College{6, true},
"KS": College{6, false},
"KY": College{8, false},
"LA": College{8, false},
"ME": College{4, true},
"MD": College{10, true},
"MA": College{11, true},
"MI": College{16, true},
"MN": College{10, true},
"MS": College{6, false},
"MO": College{10, false},
"MT": College{3, false},
"NE": College{5, false},
"NV": College{6, true},
"NH": College{4, true},
"NJ": College{14, true},
"NM": College{5, true},
"NY": College{29, true},
"NC": College{15, false},
"ND": College{3, false},
"OH": College{18, true},
"OK": College{7, false},
"OR": College{7, true},
"PA": College{20, true},
"RI": College{4, true},
"SC": College{9, false},
"SD": College{3, false},
"TN": College{11, false},
"TX": College{38, false},
"UT": College{6, false},
"VT": College{3, true},
"VA": College{13, true},
"WA": College{12, true},
"WV": College{5, false},
"WI": College{10, true},
"WY": College{3, false},
}

// 2008 Electoral College
// format: "State": College{number of votes, did Democrat win?},
var college2008 = map[string]College{
"AL": College{9, false},
"AK": College{3, false},
"AZ": College{11, false},
Expand Down
106 changes: 69 additions & 37 deletions election2012.go
@@ -1,25 +1,31 @@
//
// election2012.go
// election.go
//
// An electoral college Monte Carlo simulation based on 2012 presidential polling.
//
// To run:
// $ go run election2012.go state.go api.go cdf.go parse.go college.go
// An electoral college Monte Carlo simulation based on 2016 presidential polling.
//
// To build and run:
// $ cd $GOPATH
// $ mkdir -p src/github.com/GaryBoone/
// $ cd src/github.com/GaryBoone/
// $ git clone https://github.com/GaryBoone/PresidentialMonteCarlo
// $ cd ../../..
// $ go install github.com/GaryBoone/PresidentialMonteCarlo
// $ bin/PresidentialMonteCarlo//
// Author: Gary Boone gary.boone@gmail.com
// History: 2012-09-17 • initial version
// 2012-09-21cleanup, upload to github
// History:
// 2012-09-25simulations in parallel
// 2012-09-24 • minimum σ
// • command line parameters
// • days until election countdown
// 2012-09-25 • simulations in parallel
// 2012-09-21 • cleanup, upload to github
// 2012-09-17 • initial version
// Notes:
//
// The state-by-state presidential polling data is provided by the Pollster API:
// http://elections.huffingtonpost.com/pollster/api
//
// Example API call:
// wget -O - 'http://elections.huffingtonpost.com/pollster/api/polls.json?topic=2012-president&state=OH'
// wget -O - 'http://elections.huffingtonpost.com/pollster/api/polls.json?topic=2016-president&state=OH'
//
// Read the logfile for details.
//
Expand All @@ -38,12 +44,21 @@ import (
"time"
)

const swingStates = "FL,OH,NC,VA,WI,CO,IA,NV,NH"
const (
democraticCandidate = "Clinton"// "Obama"
republicanCandidate = "Trump" // "Romney"
electionYear = 2016 // 2012
electionDay = 8 // 6
swingStates = "CO,FL,IA,NC,NH,NV,OH,PA,VA,WI"
)

var (
acceptableSize int
numSimulations int
min_σ float64
pollTopic = fmt.Sprintf("%d-president", electionYear)
loc, _ = time.LoadLocation("America/New_York")
electionDate = time.Date(electionYear, time.November, electionDay, 0, 0, 0, 0, loc)
)

func init() {
Expand Down Expand Up @@ -92,17 +107,17 @@ func loadStateData(state string, polls []Poll) (prob StateProbability) {
continue
}

var obama, romney, size int
obama, romney, size = parsePoll(state, poll)
if obama == 0 || romney == 0 {
log.Printf(" Missing value (Obama=%v, Romney=%v) for %v state poll by '%v'. Skipping.\n",
obama, romney, state, *poll.Pollster)
var democrat, republican, size int
democrat, republican, size = parsePoll(state, poll, pollTopic)
if democrat == 0 || republican == 0 {
log.Printf(" Missing value (Democrat=%v, Republican=%v) for %v state poll by '%v'. Skipping.\n",
democrat, republican, state, *poll.Pollster)
continue
}

log.Printf(" adding %-30s %10s : O(%v), R(%v), N(%v)\n",
truncateString(pollster, 30), date[:10], obama, romney, size)
prob.update(obama, romney, size)
log.Printf(" adding %-30s %10s : Democrat(%v), Republican(%v), poll size(%v)\n",
truncateString(pollster, 30), date[:10], democrat, republican, size)
prob.update(democrat, republican, size)
if prob.N > float64(acceptableSize) {
return
}
Expand All @@ -120,9 +135,15 @@ func simulateObamaVotes(states []StateProbability, r *rand.Rand) int {
}

func loadProbability(state string) StateProbability {
body := readPollingApi(state)
body := readPollingApi(pollTopic, state)
polls := parseJson(body)
log.Printf("Found %v polls in %v.\n", len(polls), state)

msg := ""
if strings.Contains(swingStates, state) {
msg = ", a swing state"
}
log.Printf("Found %v polls in %v%s.\n", len(polls), state, msg)

prob := loadStateData(state, polls)
prob.logStateProbability()
return prob
Expand All @@ -142,7 +163,7 @@ func initializeSimulations() []StateProbability {
prob := <-results
stateProbabilities[i] = prob
if i == 0 {
fmt.Printf("Collecting survey data for the great state of %v", prob.state)
fmt.Printf("Collecting survey data for the great states of %v", prob.state)
} else {
fmt.Printf(", %v", prob.state)
}
Expand Down Expand Up @@ -186,35 +207,46 @@ func runSimulations(probs []StateProbability) (int, int) {
return wins, votes
}

// Let's say election day begins on midnight Eastern Time on Nov 6, 2012
func daysUntilElection() int {
now := time.Now()
// Midnight Nov 6 is Eastern Standard Time, not DST, so 5 hours behind UTC
electionDay := time.Date(2012, time.November, 6, 5, 0, 0, 0, time.UTC)
return int(math.Ceil(float64(electionDay.Sub(now)) / (24 * 60 * 60 * 1000000000.0)))
return int(math.Ceil(float64(electionDate.Sub(now)) / (24 * 60 * 60 * 1000000000.0)))
}

func reportProbalities(probs []StateProbability) {
numStatesWithoutPolls := 0
fmt.Println("\nSwing States:")
for _, st := range probs {
if st.N==0 {
numStatesWithoutPolls++
if strings.Contains(swingStates, st.state) {
fmt.Printf("%s has no polls yet, so it is assigned to %s based on %d outcome.\n", st.state, democraticCandidate, electionYear-4)
}
} else {
if strings.Contains(swingStates, st.state) {
fmt.Printf("Probability of %s winning %v: %4.2f%%\n", democraticCandidate, st.state, 100.0*st.DemocratProbability)
}
}
}
fmt.Printf("%d states have no polls, so were assigned %d outcomes\n", numStatesWithoutPolls, electionYear-4)
}

func main() {
flag.Parse()
initializeLog()

fmt.Println("Election 2012 Monte Carlo Simulation")
fmt.Println("Election %s Monte Carlo Simulation", electionYear)
fmt.Printf("There are %v days until the election.\n\n", daysUntilElection())

stateProbalities := initializeSimulations()

fmt.Println("\nSwing States:")
for _, st := range stateProbalities {
if strings.Contains(swingStates, st.state) {
fmt.Printf("Probability of Obama winning %v: %4.2f%%\n", st.state, 100.0*st.ObamaProbability)
}
}

reportProbalities(stateProbalities)

wins, totalVotes := runSimulations(stateProbalities)

fmt.Printf("\nObama re-election probability: %.2f%% \n", 100.0*float64(wins)/float64(numSimulations))
demWinProb := 100.0*float64(wins)/float64(numSimulations)
fmt.Printf("\n%s election probability: %.2f%%\n", democraticCandidate, demWinProb)
fmt.Printf("%s election probability: %.2f%%\n", republicanCandidate, 100.0 - demWinProb)
avgVotes := float64(totalVotes) / float64(numSimulations)
roundedVotes := int(math.Floor(avgVotes + 0.5))
fmt.Printf("Average electoral votes for Obama: %v\n\n", roundedVotes)

fmt.Printf("Average electoral votes for %s: %v\n", democraticCandidate, roundedVotes)
fmt.Printf("Average electoral votes for %s: %v\n", republicanCandidate, 538 - roundedVotes)
}
2 changes: 1 addition & 1 deletion license.txt
@@ -1,4 +1,4 @@
Copyright (c) 2012 Gary Boone
Copyright (c) 2012-2016 Gary Boone

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

Expand Down
27 changes: 15 additions & 12 deletions parse.go
Expand Up @@ -6,10 +6,11 @@ package main

import (
"log"
"fmt"
"strings"
)

func parseResponses(state string, poll Poll, responses []Responses) (obama, romney int) {
func parseResponses(state string, poll Poll, responses []Responses) (democrat, republican int) {
for _, resp := range responses {
if resp.Choice == nil {
log.Printf(" No Choice for %v state poll by '%v'. Skipping.\n",
Expand All @@ -21,25 +22,25 @@ func parseResponses(state string, poll Poll, responses []Responses) (obama, romn
state, *poll.Pollster)
continue
}
if strings.EqualFold(*resp.Choice, "obama") {
obama = *resp.Value
if strings.EqualFold(*resp.Choice, democraticCandidate) {
democrat = *resp.Value
}
if strings.EqualFold(*resp.Choice, "romney") {
romney = *resp.Value
if strings.EqualFold(*resp.Choice, republicanCandidate) {
republican = *resp.Value
}
}
return
}

func parseSubpopulation(state string, poll Poll, sub Subpopulations) (obama, romney, size int) {
func parseSubpopulation(state string, poll Poll, sub Subpopulations) (democrat, republican, size int) {
if sub.Observations == nil {
log.Printf(" No N for %v state poll by '%v'. Skipping.\n",
state, *poll.Pollster)
return
}

size = *sub.Observations
obama, romney = parseResponses(state, poll, sub.Responses)
democrat, republican = parseResponses(state, poll, sub.Responses)
return
}

Expand All @@ -59,24 +60,26 @@ func parseDateAsString(poll Poll) string {
return date
}

func parsePoll(state string, poll Poll) (obama, romney, size int) {
func parsePoll(state string, poll Poll, topic string) (democrat, republican, size int) {
for _, question := range poll.Questions {
if question.Topic != nil && strings.EqualFold(*question.Topic, "2012-president") {
if question.Topic != nil && strings.EqualFold(*question.Topic, topic) {
// given multiple subpopulations, prefer likely voters
switch len(question.Subpopulations) {
case 1:
obama, romney, size = parseSubpopulation(state, poll, question.Subpopulations[0])
democrat, republican, size = parseSubpopulation(state, poll, question.Subpopulations[0])
default:
foundLikelyVoters := false
for _, sub := range question.Subpopulations {
if sub.Name != nil && strings.EqualFold(*sub.Name, "Likely Voters") {
obama, romney, size = parseSubpopulation(state, poll, sub)
democrat, republican, size = parseSubpopulation(state, poll, sub)
foundLikelyVoters = true
}
}
if !foundLikelyVoters {
log.Printf(" No Likely voters in multi-subpopulation poll for "+
msg := fmt.Sprintf(" No Likely voters in multi-subpopulation poll for "+
"%v state poll by '%v'. Skipping.\n", state, *poll.Pollster)
fmt.Printf(msg)
log.Printf(msg)
}
}
}
Expand Down

0 comments on commit a4cd1c4

Please sign in to comment.