⚠️There's a newer version (0.2.1) of this package. Take me there.

Ranked Choice Voting — R Package

rcv helps you work directly with raw ballot image and cast vote record data to run elections.

Features

  • Read in ballot image and master lookup files
  • Merge these files to get a "readable" ballot layout
  • Conduct elections, and view a round-by-round table of results
  • Visualize the flow of voters with an interactive Sankey diagram
  • Compatible with dplyr/magrittr pipe syntax (%>%)

You can install the development version of rcv here:

devtools::install_github("ds-elections/rcv")

Created by:

The style of this README is inspired by the googlesheets R package.

Basic Workflow Demo

sf_bos_ballot is included as an example raw ballot image, and sf_bos_lookup as an example raw master lookup. Both are included as .rdas, and they are in the "WinEDS" format. This data comes from the 2016 San Francisco Board of Supervisors elections (San Francisco Department of Elections).

head(sf_bos_ballot)
## # A tibble: 6 x 1
##                                              X1
##                                           <chr>
## 1 000000900000660300000010020000406001000012800
## 2 000000900000660300000010020000406002000000001
## 3 000000900000660300000010020000406003000000001
## 4 000000900000660400000010020000406001000012300
## 5 000000900000660400000010020000406002000012500
## 6 000000900000660400000010020000406003000012100
head(sf_bos_lookup)
## # A tibble: 6 x 1
##                                                                            X1
##                                                                         <chr>
## 1 Candidate 0000121SAMUEL KWONG                                      00000010
## 2 Candidate 0000131TIM E. DONNELLY                                   00000010
## 3 Candidate 0000133DEAN PRESTON                                      00000010
## 4 Candidate 0000135JOEL ENGARDIO                                     00000010
## 5 Candidate 0000140MELISSA SAN MIGUEL                                00000010
## 6 Candidate 0000144AHSHA SAFAI                                       00000010

Cleaning Data

The streamlined version of this process is done with the clean_ballot() function. b_header and l_header are logical values, based on whether the ballot and lookup file respectively have a header for the first row.

cleaned <- clean_ballot(ballot = sf_bos_ballot, b_header = T, 
                        lookup = sf_bos_lookup, l_header = T, 
                        format = "WinEDS")
knitr::kable(head(readable(cleaned)))
contestpref_voter_idprecinct123
Board of Supervisors, District 1000006603Pct 9133SANDRA LEE FEWERNANA
Board of Supervisors, District 1000006604Pct 9133MARJAN PHILHOURDAVID LEESAMUEL KWONG
Board of Supervisors, District 1000006605Pct 9133DAVID LEERICHIE GREENBERGBRIAN J. LARKIN
Board of Supervisors, District 1000006606Pct 9133MARJAN PHILHOURDAVID LEESANDRA LEE FEWER
Board of Supervisors, District 1000006607Pct 9133BRIAN J. LARKINANDY THORNLEYJASON JUNGREIS
Board of Supervisors, District 1000006608Pct 9133MARJAN PHILHOURNANA

To access intermediate steps, the following process can be used.

# Import and label ballot image
a <- import_data(data = sf_bos_ballot, header = T) %>%
    label(image = "ballot", format = "WinEDS")

# Import and label master lookup
b <- import_data(data = sf_bos_lookup, header = T) %>%
    label(image = "lookup", format = "WinEDS")

# Merge these two tables
c <- characterize(ballot = a, lookup = b, format = "WinEDS")

knitr::kable(head(readable(c)))
contestpref_voter_id123
Board of Supervisors, District 1000006603SANDRA LEE FEWERNANA
Board of Supervisors, District 1000006604MARJAN PHILHOURDAVID LEESAMUEL KWONG
Board of Supervisors, District 1000006605DAVID LEERICHIE GREENBERGBRIAN J. LARKIN
Board of Supervisors, District 1000006606MARJAN PHILHOURDAVID LEESANDRA LEE FEWER
Board of Supervisors, District 1000006607BRIAN J. LARKINANDY THORNLEYJASON JUNGREIS
Board of Supervisors, District 1000006608MARJAN PHILHOURNANA

The readable() function takes the clean image, which is formatted for ease in computation, and formats it to be easily read manually.

Running Elections

This is done with the rcv_tally() function. sf_bos_clean is included as an example of a pre-cleaned ballot using the functions above. We will run the District 1 election from this ballot image.

results <- rcv_tally(sf_bos_clean, "Board of Supervisors, District 1")
knitr::kable(results)
round1round2round3round4round5round6round7round8round9
SANDRA LEE FEWER125501268912777128401302913093132251335414705
MARJAN PHILHOUR110671113511247113481148711680118371208613126
DAVID LEE33963408348835513622385739614093NA
RICHIE GREENBERG97498410421220127213861508NANA
BRIAN J. LARKIN747773832896956997NANANA
SAMUEL KWONG740744760785814NANANANA
JONATHAN LYENS609652679726NANANANANA
JASON JUNGREIS611626654NANANANANANA
SHERMAN R. D'SILVA557566NANANANANANANA
ANDY THORNLEY359NANANANANANANANA
NA349935223574362636673722379939234360

Sandra Lee Fewer wins in Round 9, with 14,705 votes to Marjan Philhour's 13,126. 4,360 votes were left blank, marked invalid, or exhausted in this election.

Visualizing Data

We have two recommended methods of visualizing RCV data. Both utilize a flowchart called a "Sankey diagram" to show the transfer of voters between rounds. We will use each method to visualize the transfer of voters in the San Francisco District 7 Board of Supervisors election, because District 1 has too many crossings to be readable.

Method 1 (preferred because it is interactive, quicker, and more readable) uses the networkD3 package:

d3_7 <- rcv::make_d3list(results = sf_7_results)
networkD3::sankeyNetwork(Links = d3_7$values, Nodes = d3_7$names,
                         Source = "source", Target = "target",
                         Value = "value", NodeID = "candidate", units = "voters",
                         fontSize = 12, nodeWidth = 20)

Method 2 uses the alluvial package (this type of graphic is also called an alluvial diagram):

alluvial_7 <- rcv::make_alluvialdf(image = sf_bos_clean,
                                   rcvcontest = "Board of Supervisors, District 7")
alluvial::alluvial(
  alluvial_7[,1:4], freq = alluvial_7$frequency,
  col = ifelse(alluvial_7$round4 == "NORMAN YEE", "lightgreen", "gray"),
  border = "gray", alpha = 0.7, blocks = TRUE
)

(Outputs for these are disabled due to HTML/.md conversion issues)

Copy Link

Version

Down Chevron

Install

install.packages('rcv')

Monthly Downloads

20

Version

0.2.0

License

MIT + file LICENSE

Issues

Pull Requests

Stars

Forks

Last Published

June 14th, 2017

Functions in rcv (0.2.0)