Beneath the video, we provide a detailed summary of the topics and code covered in the code along.
At the bottom of this page, you will have the opportunity to validate your work via auto-graded assessments that evaluate the functions covered in the code along.
Although we strongly suggest completing the code along on your own, you can find completed code from the code along in our course code repository.
Learning objectives
In this code along, we will use Monte Carlo simulation to estimate the winner of the 2016 US Presidential election using polling data at three different points in time. To do so, we will revisit the
SimulateMultipleElections()
SimulateMultipleElections() function that we introduced in the core text, which we reproduce below. This function largely consists of appealing to a
SimulateOneElection()
SimulateOneElection() subroutine, which returns the electoral college votes for each of two US presidential candidates in a simulated election.
SimulateOneElection() function examines the polling percentage (for candidate 1) in each state and adds noise to this percentage to reflect the fact that polls can only sample a small portion of the electorate and may be influenced by the effects of random noise.
As we saw in the code along on simulating craps, the work of generating pseudorandom numbers is passed to a low-level subroutine. In this case, that subroutine is
AddNoise()
AddNoise(), which takes a polling average and a margin of error and simulates a true polling number with a 95% chance of being within the margin of error. This function requires
RandNormal()
RandNormal(), a built-in function that generates a pseudorandom decimal according to the standard normal distribution (which has a mean equal to 0 and a standard deviation equal to 1).
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
AddNoise(poll, marginOfError)
x ← RandNormal()
x ← x/2(95% chance of x being between -1 and 1)
x ← x *marginOfError(now x is in range)
return x + poll
AddNoise(poll, marginOfError)
x ← RandNormal()
x ← x/2 (95% chance of x being between -1 and 1)
x ← x * marginOfError (now x is in range)
return x + poll
AddNoise(poll, marginOfError)
x ← RandNormal()
x ← x/2 (95% chance of x being between -1 and 1)
x ← x * marginOfError (now x is in range)
return x + poll
Code along summary
Setup
To complete this code along, you will need to build upon the starter code that we provided in the previous code along on parsing election data. Ensure that you have an election directory under your go/src source code folder and that it contains a main.go file, an io.go file (with completed functions from the previous code along), and a data folder containing four data files that are explained further in the previous code along.
Your main.go file should contain the following code from our work on parsing election data.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package main
import(
"fmt"
)
funcmain(){
fmt.Println("Simulating the 2016 US Presidential election.")
package main
import (
"fmt"
)
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
}
package main
import (
"fmt"
)
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
}
Writing a function to simulate multiple elections
We start with implementing
SimulateMultipleElections()
SimulateMultipleElections(), and we will implement subroutines as we encounter them.
SimulateMultipleElections()
SimulateMultipleElections() takes four input parameters:
a map
polls
polls that maps the name of each state to that state’s polling percentages of candidate 1 (Clinton), where the polling percentage for candidate 2 (Trump) can be obtained by subtracting candidate 1’s polling percentage from 1;
a map
electoralVotes
electoralVotes that maps the name of each state to the number of Electoral College votes (as an unsigned integer) that the winner of that state receives;
an integer
numTrials
numTrials representing the number of Monte Carlo simulations to run;
a decimal
marginOfError
marginOfError representing the margin of error of all polls, which we assume is a constant.
Note: There is no reason for
numTrials
numTrials to be a (signed) integer and the values of
electoralVotes
electoralVotes to be unsigned integers. In this course, we will generally only use signed integers, even if the underlying variable is never negative. However, we make the values of
electoralVotes
electoralVotes unsigned to give us practice with this variable type.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// to fill in
}
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// to fill in
}
We will start by declaring three variables
winCount1
winCount1,
winCount2
winCount2, and
tieCount
tieCount, which respectively corresponding to the number of simulations won by candidate 1, the number of simulations won by candidate 2, and the number of simulations in which the two candidates tie.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0// oh no!
// to fill in
}
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
// to fill in
}
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
// to fill in
}
Eventually, we will normalize each of these counts by dividing them by the total number of trials, and then return the resulting ratios.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
// to fill in
//divide number of wins (and ties) by number of trials
probability1 := float64(winCount1) / float64(numTrials)
probability2 := float64(winCount2) / float64(numTrials)
tieProbability := float64(tieCount) / float64(numTrials)
return probability1, probability2, tieProbability
}
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
// to fill in
//divide number of wins (and ties) by number of trials
probability1 := float64(winCount1) / float64(numTrials)
probability2 := float64(winCount2) / float64(numTrials)
tieProbability := float64(tieCount) / float64(numTrials)
return probability1, probability2, tieProbability
}
We fill in the interior of
SimulateMultipleElections()
SimulateMultipleElections() by running
numTrials
numTrials total simulations. Each simulation, we call
SimulateOneElection()
SimulateOneElection(), which will take all of the inputs of
SimulateMultipleElections()
SimulateMultipleElections() except for
numTrials
numTrials and return the number of electoral votes for each of candidate 1 and 2 in a simulated election. Based on who has more votes in this simulation (or if there is a tie), we then update the appropriate count variable.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
//simulate an election n times and update counts each time
for i := 0; i < numTrials; i++ {
//call SimulateOneElection as a subroutine
votes1, votes2 := SimulateOneElection(polls, electoralVotes, marginOfError)
// did candidate 1 or candidate 2 win?
if votes1 > votes2 {
winCount1++
} else if votes1 < votes2 {
winCount2++
} else { //tie!
tieCount++
}
}
//divide number of wins (and ties) by number of trials
probability1 := float64(winCount1) / float64(numTrials)
probability2 := float64(winCount2) / float64(numTrials)
tieProbability := float64(tieCount) / float64(numTrials)
return probability1, probability2, tieProbability
}
//SimulateMultipleElections takes polling data as a map of state names to floats (for candidate 1), along with a map of state names to electoral votes, a number of trials, and a margin of error in the polls.
//It returns three values: the estimated probabilities of candidate 1 winning, candidate 2 winning, and a tie.
func SimulateMultipleElections(polls map[string]float64, electoralVotes map[string]uint, numTrials int, marginOfError float64) (float64, float64, float64) {
// keep track of number of simulated elections won by each candidate (and ties)
winCount1 := 0
winCount2 := 0
tieCount := 0 // oh no!
//simulate an election n times and update counts each time
for i := 0; i < numTrials; i++ {
//call SimulateOneElection as a subroutine
votes1, votes2 := SimulateOneElection(polls, electoralVotes, marginOfError)
// did candidate 1 or candidate 2 win?
if votes1 > votes2 {
winCount1++
} else if votes1 < votes2 {
winCount2++
} else { //tie!
tieCount++
}
}
//divide number of wins (and ties) by number of trials
probability1 := float64(winCount1) / float64(numTrials)
probability2 := float64(winCount2) / float64(numTrials)
tieProbability := float64(tieCount) / float64(numTrials)
return probability1, probability2, tieProbability
}
Simulating a single election
We now turn to implementing
SimulateOneElection()
SimulateOneElection(). As we mentioned above, this function takes all of the same parameters as
SimulateMultipleElections()
SimulateMultipleElections() except for
numTrials
numTrials. It returns two unsigned integers corresponding to the number of electoral college votes for candidate 1 and 2, respectively. We begin by declaring two unsigned integers to hold these votes, which we will eventually return.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
//to fill in
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
//to fill in
return collegeVotes1, collegeVotes2
}
SimulateOneElection()
SimulateOneElection() needs to run the simulation over all states, and we can grab the state names and the current polling value by ranging over the keys and values of
polls
polls. We can then access the state’s number of electoral votes with
electoralVotes[state]
electoralVotes[state].
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// to fill in
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// to fill in
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// to fill in
}
return collegeVotes1, collegeVotes2
}
Because the polling value is not a precise estimate, we will first adjust the polling value by adding some randomized noise that is a normally distributed random variable with mean equal to zero and standard deviation equal to half of the polls’ margin of error, which we will pass to a subroutine
AddNoise()
AddNoise().
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// to fill in
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// to fill in
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// to fill in
}
return collegeVotes1, collegeVotes2
}
Now that we have an adjusted polling value, we must check whether it is greater than or equal to 0.5. If so, then we can conclude that candidate 1 won the state in this simulation, and otherwise, we can conclude that candidate 2 won the state in this simulation.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// now we check who won state based on the adjusted poll ...
if adjustedPoll >= 0.5{
// candidate 1 wins! give them the EC votes for the state
collegeVotes1 += numVotes
}else{
//candidate 2 wins!
collegeVotes2 += numVotes
}
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// now we check who won state based on the adjusted poll ...
if adjustedPoll >= 0.5 {
// candidate 1 wins! give them the EC votes for the state
collegeVotes1 += numVotes
} else {
//candidate 2 wins!
collegeVotes2 += numVotes
}
}
return collegeVotes1, collegeVotes2
}
//SimulateOneElection takes a map of state names to polling percentages,
//along with a map of state names to electoral college votes and a margin of error.
//It returns the number of EC votes for each of the two candidates in one simulated election.
func SimulateOneElection(polls map[string]float64, electoralVotes map[string]uint, marginOfError float64) (uint, uint) {
var collegeVotes1 uint = 0
var collegeVotes2 uint = 0
// range over all the states, and simulate the election in each state.
for state, poll := range polls {
//grab the number of electoral votes in the state
numVotes := electoralVotes[state]
// let's adjust polling value with some randomized "noise"
adjustedPoll := AddNoise(poll, marginOfError)
// now we check who won state based on the adjusted poll ...
if adjustedPoll >= 0.5 {
// candidate 1 wins! give them the EC votes for the state
collegeVotes1 += numVotes
} else {
//candidate 2 wins!
collegeVotes2 += numVotes
}
}
return collegeVotes1, collegeVotes2
}
Note: We would obtain the same result if we were instead to check if
adjustedPoll
adjustedPoll is greater than 0.5, because since we will be generating a random decimal number, the chances that
adjustedPoll
adjustedPoll is exactly equal to 0.5 are essentially zero.
Adding random noise to a polling value
We now turn to implementing
AddNoise()
AddNoise(), which takes as input a polling percentage in a state and the margin of error and returns an adjusted polling percentage corresponding to a simulated polling percentage. We first generate a random number
x
x from the standard normal distribution, which has mean equal to 0 and standard deviation equal to 1. Go implements this with a
NormFloat64()
NormFloat64() function in the
"rand"
"rand" package.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
x := rand.NormFloat64()// random number from standard normal distribution
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
// to fill in
}
The standard error of generating x is equal to 2 (twice the standard deviation of the standard normal distribution from which we just sampled), and so we can obtain a number with standard error equal to 1 by halving
x
x.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
x := rand.NormFloat64()// random number from standard normal distribution
x = x / 2// x has ~95% chance of being between -1 and 1
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
// to fill in
}
We then can ensure that the process of generating
x
x has margin of error equal to
marginOfError
marginOfError by multiplying
x
x by
marginOfError
marginOfError.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
x := rand.NormFloat64()// random number from standard normal distribution
x = x / 2// x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
// to fill in
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
// to fill in
}
We have now obtained our desired noise value, and so we add the value of
x
x to the existing polling value to ensure that the value that we return has mean equal to
pollingValue
pollingValue and margin of error equal to
marginOfError
marginOfError.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
x := rand.NormFloat64()// random number from standard normal distribution
x = x / 2// x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
return x + pollingValue
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
return x + pollingValue
}
//AddNoise takes a polling value for candidate 1 and a margin of error. It returns an adjusted polling value for candidate 1 after adding random noise.
func AddNoise(pollingValue, marginOfError float64) float64 {
x := rand.NormFloat64() // random number from standard normal distribution
x = x / 2 // x has ~95% chance of being between -1 and 1
x = x * marginOfError // x has 95% chance of being -marginOfError and marginOfError
return x + pollingValue
}
Running our election simulator
We are now ready to run our Monte Carlo simulation. We revisit our main.go file, which reads in the electoral votes and polling data. The data directory contains three files, and we will begin our work by reading in the first file.
earlyPolls.csv: polls from summer 2016.
conventions.csv: polls from around the Republican and Democratic National Conventions in mid- and late July 2016.
debates.csv: polls from around the presidential debates, in late September through mid-October 2016.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
package main
import(
"fmt"
)
funcmain(){
fmt.Println("Simulating the 2016 US Presidential election.")
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.05
// to fill in
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.05
// to fill in
}
Now that all its inputs are set, we call
SimulateMultipleElections()
SimulateMultipleElections() and store the resulting probabilities of each candidate winning (and the probability of a tie). We then print these probabilities to the console.
STOP: After completing main.go with the code below, we are now ready to run our code. In a terminal, navigate to our go/src/election directory. Compile all of the code in this directory using go build, and then execute the command ./election (Mac) or election.exe (Windows). What do you find? Is it what you expected?
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
funcmain(){
fmt.Println("Simulating the 2016 US Presidential election.")
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.05
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.05
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
Click 👇 Run to try it yourself!
When we run our code, we obtain a surprising result: Clinton wins 99.9% of the simulations!
Perhaps our simulation is too confident. Let us therefore increase the margin of error to 10%, which produces a very conservative simulation: even if a candidate could be polling at 60% in a state poll, this margin of error implies that there is still a 5% chance of the true polling value being either greater than 70% or less than 50%; that is, there is a 2.5% chance that the other candidate is actually leading.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
funcmain(){
fmt.Println("Simulating the 2016 US Presidential election.")
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.1
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/earlyPolls.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.1
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
Click 👇 Run to try it yourself!
Even with increasing the margin of error, however, Clinton’s dominance over the simulation is still pronounced, as she is leading 98.7% of simulations.
Perhaps Clinton simply had an early lead. To test this hypothesis, let us change the input to
ReadPollingData()
ReadPollingData() to
"conventions.csv"
"conventions.csv", and then compile and run our simulation again.
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
funcmain(){
fmt.Println("Simulating the 2016 US Presidential election.")
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/conventions.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.1
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
func main() {
fmt.Println("Simulating the 2016 US Presidential election.")
electoralVoteFile := "data/electoralVotes.csv"
pollFile := "data/conventions.csv"
electoralVotes := ReadElectoralVotes(electoralVoteFile)
polls := ReadPollingData(pollFile)
numTrials := 1000000
marginOfError := 0.1
probability1, probability2, probabilityTie := SimulateMultipleElections(polls, electoralVotes, numTrials, marginOfError)
fmt.Println("Estimated probability of a candidate 1 win is", probability1)
fmt.Println("Estimated probability of a candidate 2 win is", probability2)
fmt.Println("Estimated probability of a tie is", probabilityTie)
}
Click 👇 Run to try it yourself!
Clinton’s lead has actually widened — she now wins 99.3% of the simulations!
STOP: Verify that the lead gets even wider when we change the input to ReadPollingData() to
"debates.txt"
"debates.txt".
These simulations should give us pause. Even though we have what seems like a conservative simulation, we are predicting a Clinton victory very confidently, and our prediction of that victory is more confident than major media outlets, which predicted a Clinton victory in the 60-90 percent range. To understand why our simulation is so confident, we need to pass our work to an epilogue to reflect on the assumptions of our model and the inherent difficulties that are always present when trying to simulate an election from polls.
Check your work from the code along
We now provide autograders in the window below (or via a direct link) allowing you to check your work for the following functions: