Chapter 7 State Voter Files

7.1 Motivation

Who one votes for is private, but when someone votes is a matter of public record. A state’s Secretary of State (SOS) maintains a public record of who voted. The accompanying data may vary state by state. For example, the state of New Hampshire does not record the age of the person voting. Companies such as Catalist have built their business around aggregating, cleaning, and enhancing public voter files. However, these databases can be quite expensive and are not updated at the same pace as the state voter file.

7.2 Overview

In this section we will demonstrate how to programatically download and clean the state voterfile. From there, we will identify potential voters and canvassing targets. Once those targets have been identified, they can be uploaded as a list into VAN based on their SOS ID which should be present depending on your provider.

The OH SOS website provide links to the voter file which are updated frequently. These files were last updated on July 19th, 2019 as of today (July 21st, 2019). We will combine the webscraping tools (rvest) that we previously learned and combine them with dplyr and data.table—a package that works exceptionally well with large data, see the wiki for a primer.

7.3 Exercise

As always, let’s set up our workspace.

library(rvest)
library(data.table)
library(dplyr)

The first step is to use rvest to identify the URLs that we can use to download the voter-file. To do this, use the inspector tool in your web browser and identify the HTML elements of interest. In this use case, we will specify the highlight-row class and the a (anchor) tag. The a tag is used to create hyperlinks within a document. The href attribute is used to specify the hyperlink. We can extract the href attribute using rvest::html_attr().

7.3.2 Download the voter file

Now that we have the links to the voter file, we can use R to download them using download.file(). The first argument is the link to the file, the second is the destination of that file. Here we are saving the first file into the data folder. You can also do this iteratively using purrr::map().

# download the file as a .txt.gz (that's the file format if you download it from the web)
download.file(file_urls[1], destfile = "data/SWVF_1_22 (Adams-Erie).txt.gz")

Note that the file is in .gz compressed format. We can uncompress this using R.utils::gunzip() which will return the compressed file.

R.utils::gunzip("data/SWVF_1_22 (Adams-Erie).txt.gz")

data.table reads text files using fread(). Since this file is very large, we will take only the first 10,000 observations. Do this by setting the nrows argument to 10000. Since data.table is an extension upon the data.frame, we can combine both data.table aand dplyr functions.

7.3.3 Voter file exploratoration

swvf <- fread("data/SWVF_1_22 (Adams-Erie).txt", nrows = 10000)

Do your due dilligence and look at all 106 columns. This is a great time to embrace dirty data. Are the data tidy?

glimpse(swvf)
## Observations: 10,000
## Variables: 106
## $ SOS_VOTERID                   <chr> "OH0016238254", "OH0019414074", "O…
## $ COUNTY_NUMBER                 <int> 6, 2, 6, 9, 18, 13, 18, 18, 13, 4,…
## $ COUNTY_ID                     <int> 21511, 1010005, 40055, 482703, 204…
## $ LAST_NAME                     <chr> "KUETHER", "GEMLICK", "KITCHEN", "…
## $ FIRST_NAME                    <chr> "BARBARA", "JODI", "LESLIE", "AMAN…
## $ MIDDLE_NAME                   <chr> "A", "LYN", "L", "LEIGH", "J", "L"…
## $ SUFFIX                        <chr> "", "", "", "", "", "", "", "", ""…
## $ DATE_OF_BIRTH                 <chr> "1969-11-15", "1972-08-19", "1969-…
## $ REGISTRATION_DATE             <chr> "1998-02-23", "2007-01-29", "2008-…
## $ VOTER_STATUS                  <chr> "ACTIVE", "ACTIVE", "ACTIVE", "ACT…
## $ PARTY_AFFILIATION             <chr> "", "", "", "", "", "", "", "D", "…
## $ RESIDENTIAL_ADDRESS1          <chr> "725 OAKWOOD DR", "3464 WOODHAVEN …
## $ RESIDENTIAL_SECONDARY_ADDR    <chr> "", "", "", "", "", "", "", "", ""…
## $ RESIDENTIAL_CITY              <chr> "MINSTER", "LIMA", "ST MARYS", "MO…
## $ RESIDENTIAL_STATE             <chr> "OH", "OH", "OH", "OH", "OH", "OH"…
## $ RESIDENTIAL_ZIP               <int> 45865, 45806, 45885, 45050, 44105,…
## $ RESIDENTIAL_ZIP_PLUS4         <int> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ RESIDENTIAL_COUNTRY           <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ RESIDENTIAL_POSTALCODE        <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ MAILING_ADDRESS1              <chr> "", "", "", "", "", "", "", "", ""…
## $ MAILING_SECONDARY_ADDRESS     <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ MAILING_CITY                  <chr> "", "", "", "", "", "", "", "", ""…
## $ MAILING_STATE                 <chr> "", "", "", "", "", "", "", "", ""…
## $ MAILING_ZIP                   <int> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ MAILING_ZIP_PLUS4             <int> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ MAILING_COUNTRY               <chr> "", "", "", "", "", "", "", "", ""…
## $ MAILING_POSTAL_CODE           <chr> "", "", "", "", "", "", "", "", ""…
## $ CAREER_CENTER                 <chr> "", "APOLLO CAREER CENTER", "", ""…
## $ CITY                          <chr> "", "", "ST. MARYS CITY", "MONROE …
## $ CITY_SCHOOL_DISTRICT          <chr> "", "", "ST MARYS CITY SD", "", "C…
## $ COUNTY_COURT_DISTRICT         <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ CONGRESSIONAL_DISTRICT        <int> 4, 4, 4, 8, 11, 2, 11, 16, 2, 14, …
## $ COURT_OF_APPEALS              <int> 3, 3, 3, 12, 8, 12, 8, 8, 12, 11, …
## $ EDU_SERVICE_CENTER_DISTRICT   <chr> "AUGLAIZE COUNTY ESC", "ALLEN COUN…
## $ EXEMPTED_VILL_SCHOOL_DISTRICT <chr> "", "", "", "", "", "", "", "", ""…
## $ LIBRARY                       <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ LOCAL_SCHOOL_DISTRICT         <chr> "MINSTER LOCAL SD (AUGLAIZE)", "SH…
## $ MUNICIPAL_COURT_DISTRICT      <chr> "", "LIMA", "", "", "CLEVELAND", "…
## $ PRECINCT_NAME                 <chr> "PRECINCT MINSTER N", "SHAWNEE H",…
## $ PRECINCT_CODE                 <chr> "06AAZ", "02AFU", "06AAE", "09-P-A…
## $ STATE_BOARD_OF_EDUCATION      <int> 1, 1, 1, 3, 11, 10, 11, 5, 10, 7, …
## $ STATE_REPRESENTATIVE_DISTRICT <int> 84, 4, 82, 53, 9, 65, 12, 16, 65, …
## $ STATE_SENATE_DISTRICT         <int> 12, 12, 1, 4, 21, 14, 25, 24, 14, …
## $ TOWNSHIP                      <chr> "JACKSON TOWNSHIP", "Township Shaw…
## $ VILLAGE                       <chr> "MINSTER VILLAGE", "", "", "", "",…
## $ WARD                          <chr> "", "", "ST. MARYS WARD 3", "", "C…
## $ `PRIMARY-03/07/2000`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/07/2000`          <chr> "", "", "", "", "", "", "X", "X", …
## $ `SPECIAL-05/08/2001`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/06/2001`          <chr> "", "", "", "", "", "", "X", "", "…
## $ `PRIMARY-05/07/2002`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/05/2002`          <chr> "", "", "", "", "", "", "X", "", "…
## $ `SPECIAL-05/06/2003`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/04/2003`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-03/02/2004`          <chr> "", "", "", "", "", "", "D", "", "…
## $ `GENERAL-11/02/2004`          <chr> "", "X", "", "", "", "", "X", "", …
## $ `SPECIAL-02/08/2005`          <chr> "", "", "", "", "", "", "", "", "X…
## $ `PRIMARY-05/03/2005`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/13/2005`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/08/2005`          <chr> "", "", "", "", "", "", "X", "", "…
## $ `SPECIAL-02/07/2006`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-05/02/2006`          <chr> "", "", "", "", "", "", "D", "", "…
## $ `GENERAL-11/07/2006`          <chr> "X", "", "", "", "", "", "X", "X",…
## $ `PRIMARY-05/08/2007`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/11/2007`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/06/2007`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-11/06/2007`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-12/11/2007`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-03/04/2008`          <chr> "", "", "", "", "", "", "D", "D", …
## $ `PRIMARY-10/14/2008`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/04/2008`          <chr> "X", "", "", "X", "", "X", "X", "X…
## $ `GENERAL-11/18/2008`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `PRIMARY-05/05/2009`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/08/2009`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/15/2009`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `PRIMARY-09/29/2009`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/03/2009`          <chr> "", "", "", "X", "", "", "X", "", …
## $ `PRIMARY-05/04/2010`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-07/13/2010`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/07/2010`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/02/2010`          <chr> "", "", "", "X", "", "X", "X", "X"…
## $ `PRIMARY-05/03/2011`          <chr> "X", "", "", "", "", "", "", "", "…
## $ `PRIMARY-09/13/2011`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/08/2011`          <chr> "", "", "", "X", "", "X", "X", "",…
## $ `PRIMARY-03/06/2012`          <chr> "", "", "", "", "", "", "D", "", "…
## $ `GENERAL-11/06/2012`          <chr> "X", "", "X", "X", "", "X", "X", "…
## $ `PRIMARY-05/07/2013`          <chr> "", "", "", "", "", "X", "", "", "…
## $ `PRIMARY-09/10/2013`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `PRIMARY-10/01/2013`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/05/2013`          <chr> "", "", "", "X", "", "", "X", "", …
## $ `PRIMARY-05/06/2014`          <chr> "", "", "", "", "", "", "D", "", "…
## $ `GENERAL-11/04/2014`          <chr> "", "", "", "X", "", "X", "X", "X"…
## $ `PRIMARY-05/05/2015`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/15/2015`          <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA…
## $ `GENERAL-11/03/2015`          <chr> "", "", "", "X", "", "", "", "", "…
## $ `PRIMARY-03/15/2016`          <chr> "", "", "", "", "", "", "", "D", "…
## $ `GENERAL-06/07/2016`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/13/2016`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/08/2016`          <chr> "", "X", "", "X", "", "X", "X", "X…
## $ `PRIMARY-05/02/2017`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `PRIMARY-09/12/2017`          <chr> "", "", "", "", "", "", "X", "X", …
## $ `GENERAL-11/07/2017`          <chr> "", "", "", "", "", "", "", "X", "…
## $ `PRIMARY-05/08/2018`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-08/07/2018`          <chr> "", "", "", "", "", "", "", "", ""…
## $ `GENERAL-11/06/2018`          <chr> "", "", "X", "X", "", "", "X", "X"…
## $ `PRIMARY-05/07/2019`          <chr> "", "", "", "", "", "", "", "", ""…

Nope! They are not tidy. Currently each row represents one voter and the events are on the columns following the format ELECTION TYPE-MM/DD/YYYY. The date of the election should be its own column as should the type (i.e. general, primary, or special). These data need to be converted to a longer format where each row is a unique combination of one voter and election. This means that if an individual voted in five elections, there would be five observations.

There are two main steps that need to be taken to tidy up these data. Collect the column headers and their respective values into two new columns: election, and party. Since the column headers contain two variables of interest, namely the election type and election date, the column will be split into two.

Usually we would use gather() or pivot_longer() from tidyr. However, these data are quite large and we need to write preformative code. data.table uses data.table::melt() to preform the same operation but is much faster. In this example we specify the id columns. These are the columns that will not be gathered into a new column. We also specify the name of the variable that will be created from the column headers with the variable.name argument. Next, we specfiy the name of the column that will contain the values.

sw_gathered <- swvf %>% 
  melt(id = 1:46, variable.name = "election", value.name = "party") %>% 
  janitor::clean_names()

Preview the results of melt(). The example below uses dplyr::distinct() to identify unique value pairs.

# Now we can view the results. The election column can be easily split into two different columns. The election type (i.e. primary or general) and the date of the election. Next, the result column looks like there is a lot of room for cleaning. There are "" where NA should be. 
distinct(sw_gathered, election, party)
##                election party
##   1: PRIMARY-03/07/2000      
##   2: PRIMARY-03/07/2000     D
##   3: PRIMARY-03/07/2000     X
##   4: PRIMARY-03/07/2000     R
##   5: PRIMARY-03/07/2000     L
##  ---                         
## 170: GENERAL-11/06/2018     X
## 171: PRIMARY-05/07/2019      
## 172: PRIMARY-05/07/2019     X
## 173: PRIMARY-05/07/2019     R
## 174: PRIMARY-05/07/2019     D

Notice that there are multiple values for party. What are these?

# what are the unique values?
distinct(sw_gathered, party)
##    party
## 1:      
## 2:     D
## 3:     X
## 4:     R
## 5:     L
## 6:  <NA>
## 7:     C
## 8:     G

7.3.4 Tidying

The SOS website has a downloadable data dictionary. The Voter File Layout says:

The data dictionary Variable filed name with election type and date of each election. Value for this field indicates how the voter voted in that election.

Abbr. Party Name
C Constitution Party
D Democrat Party
E Reform Party
G Green Party
L Libertarian Party
N Natural Law Party
R Republican Party
S Socialist Party
X Voted without declaring party affiliation
Blank Indicates that there is no voting record for this voter for this election

We can use this to clean up the party field using case_when(). To create two separate columns for the election type and date, we can split on the first -. tidyr::separate() will split the column into two or more columns based on the sep argument. Once the election_date column has been created, we will need to parse it accordingly using lubridate. Since the date column is formatted as MM/DD/YYYY we can use lubridate::mdy() to parse it to class Date.

sw_clean <- sw_gathered %>% 
  tidyr::separate(election, into = c("election_type", "election_date"), sep = "-") %>% 
  mutate(election_date = lubridate::mdy(election_date),
         party = case_when(
           party == "C" ~ "Constitution",
           party == "D" ~ "Democrat",
           party == "E" ~ "Reform",
           party == "G" ~ "Green",
           party == "L" ~ "Libertarian",
           party == "N" ~ "Natural Law",
           party == "R" ~ "Republican",
           party == "S" ~ "Socialist",
           party == "X" ~ "Independent"
         ))

head(sw_clean)
##    sos_voterid county_number county_id last_name first_name middle_name
## 1 OH0016238254             6     21511   KUETHER    BARBARA           A
## 2 OH0019414074             2   1010005   GEMLICK       JODI         LYN
## 3 OH0019419095             6     40055   KITCHEN     LESLIE           L
## 4 OH0019489283             9    482703     GRACE     AMANDA       LEIGH
## 5 OH0015384921            18   2044314    CARNER    TIFFANY           J
## 6 OH0020115764            13   6100757 VAN SCYOC      SUSAN           L
##   suffix date_of_birth registration_date voter_status party_affiliation
## 1           1969-11-15        1998-02-23       ACTIVE                  
## 2           1972-08-19        2007-01-29       ACTIVE                  
## 3           1969-12-26        2008-01-09       ACTIVE                  
## 4           1974-11-09        2008-02-01       ACTIVE                  
## 5           1971-08-28        2016-05-25       ACTIVE                  
## 6           1973-08-01        2008-09-18       ACTIVE                  
##   residential_address1 residential_secondary_addr residential_city
## 1       725 OAKWOOD DR                                     MINSTER
## 2    3464 WOODHAVEN LN                                        LIMA
## 3      122 CONCORD AVE                                    ST MARYS
## 4 932 SLEEPY HOLLOW DR                                      MONROE
## 5    13902 BENWOOD AVE                                   CLEVELAND
## 6    1090 S MUSCOVY DR                                    LOVELAND
##   residential_state residential_zip residential_zip_plus4
## 1                OH           45865                    NA
## 2                OH           45806                    NA
## 3                OH           45885                    NA
## 4                OH           45050                    NA
## 5                OH           44105                    NA
## 6                OH           45140                    NA
##   residential_country residential_postalcode mailing_address1
## 1                  NA                     NA                 
## 2                  NA                     NA                 
## 3                  NA                     NA                 
## 4                  NA                     NA                 
## 5                  NA                     NA                 
## 6                  NA                     NA                 
##   mailing_secondary_address mailing_city mailing_state mailing_zip
## 1                        NA                                     NA
## 2                        NA                                     NA
## 3                        NA                                     NA
## 4                        NA                                     NA
## 5                        NA                                     NA
## 6                        NA                                     NA
##   mailing_zip_plus4 mailing_country mailing_postal_code
## 1                NA                                    
## 2                NA                                    
## 3                NA                                    
## 4                NA                                    
## 5                NA                                    
## 6                NA                                    
##                career_center           city        city_school_district
## 1                                                                      
## 2       APOLLO CAREER CENTER                                           
## 3                            ST. MARYS CITY            ST MARYS CITY SD
## 4                               MONROE CITY                            
## 5                                           CLEVELAND MUNICIPAL CITY SD
## 6 GREAT OAKS CAREER CAMPUSES                                           
##   county_court_district congressional_district court_of_appeals
## 1                    NA                      4                3
## 2                    NA                      4                3
## 3                    NA                      4                3
## 4                    NA                      8               12
## 5                    NA                     11                8
## 6                    NA                      2               12
##   edu_service_center_district exempted_vill_school_district library
## 1         AUGLAIZE COUNTY ESC                                    NA
## 2            ALLEN COUNTY ESC                                    NA
## 3                                                                NA
## 4           BUTLER COUNTY ESC                                    NA
## 5                                                                NA
## 6                                                                NA
##         local_school_district municipal_court_district
## 1 MINSTER LOCAL SD (AUGLAIZE)                         
## 2    SHAWNEE LOCAL SD (ALLEN)                     LIMA
## 3                                                     
## 4    MONROE LOCAL SD (BUTLER)                         
## 5                                            CLEVELAND
## 6                                                     
##           precinct_name precinct_code state_board_of_education
## 1    PRECINCT MINSTER N         06AAZ                        1
## 2             SHAWNEE H         02AFU                        1
## 3 PRECINCT ST. MARYS 3A         06AAE                        1
## 4              MONROE 2      09-P-AKN                        3
## 5        CLEVELAND-02-Q      18-P-ALJ                       11
## 6      MIAMI TOWNSHIP X      13-P-ACY                       10
##   state_representative_district state_senate_district          township
## 1                            84                    12  JACKSON TOWNSHIP
## 2                             4                    12  Township Shawnee
## 3                            82                     1 ST MARYS TOWNSHIP
## 4                            53                     4    LEMON TOWNSHIP
## 5                             9                    21                  
## 6                            65                    14         MIAMI TWP
##           village             ward election_type election_date party
## 1 MINSTER VILLAGE                        PRIMARY    2000-03-07  <NA>
## 2                                        PRIMARY    2000-03-07  <NA>
## 3                 ST. MARYS WARD 3       PRIMARY    2000-03-07  <NA>
## 4                                        PRIMARY    2000-03-07  <NA>
## 5                 CLEVELAND WARD 2       PRIMARY    2000-03-07  <NA>
## 6                                        PRIMARY    2000-03-07  <NA>

7.3.5 Creating targets

One thing that you will need to do is identify your potential voter base. These voters are sometimes referred to as your “targets” or your voter “universe”. Each voter is a unique culmination of experience, opinions, and biases. As such, not every person will be willing to vote for your candidate or maybe to even vote at all. Some people are habitual voters who vote in every election and always along party lines. Others may only sometimes vote in a general election. And others might not vote consistently along party lines.

Due to the never ending complexities that are peoples’ preferences and persuasions it is important to break the pool of potential voters down into smaller groups which I will refer to as tiers. We will break our voters into three distinct tiers. These categorizations are rather crude. Work with your state leadership team to determine the best way to segment your voter base.

Tier 1. Base voters - registered Democrats who have voted Democrat in a primary Tier 2. Motivation - are not registered with a party but have voted Democrat in a primary Tier 3. Persuasion - folks that have voted Democrat in a priimary but aren’t registered as such

To begin this process, we want to identify everyone who has voted Democrat in a primary. We will filter sw_clean to these criteria. Then we will count the number of times each voter has voted for a Democrat. This will then be joined back to the original data frame so we can identify those people who have voted Democrat even if it conflicts with their party registration.

# One thing that you will need to do is to identify your target base voters. These will most likely be those who have voted Democrat in previous primaries. Then we will compare those people with their registered party. 
primary_dems <- sw_clean %>% 
  filter(election_type == "PRIMARY",
         party == "Democrat") %>% 
  count(sos_voterid, party)

head(primary_dems)
## # A tibble: 6 x 3
##   sos_voterid  party        n
##   <chr>        <chr>    <int>
## 1 OH0010002426 Democrat     1
## 2 OH0010004408 Democrat     1
## 3 OH0010012148 Democrat     1
## 4 OH0010012441 Democrat     3
## 5 OH0010013144 Democrat     1
## 6 OH0010015404 Democrat     1

Now that we have a table of everyone who has voted for a Democrat in a primary, we need to join this back onto the original voter file. The motivation for this is that by joining back onto the orginal table we can then use some of the other information that it provides such as party affiliation and birth year, among others.

# join these back to the original voter file to get additional information 
potential_targets <- inner_join(primary_dems, swvf, 
                                by = c("sos_voterid" = "SOS_VOTERID")) %>% 
  janitor::clean_names() %>% 
  select(sos_voterid, party_affiliation, precinct_name,
         date_of_birth, registration_date, party, n) %>% 
  mutate(age = as.integer((Sys.Date() - lubridate::ymd(date_of_birth)) / 365))

select(potential_targets, sos_voterid, precinct_name, party_affiliation, age, n) %>% 
  arrange(-n) %>% 
  head()
## # A tibble: 6 x 5
##   sos_voterid  precinct_name          party_affiliation   age     n
##   <chr>        <chr>                  <chr>             <int> <int>
## 1 OH0010322183 PRECINCT ASHTABULA 2-A D                    68    14
## 2 OH0015532776 ATHENS 3-4             D                    64    14
## 3 OH0010329359 PRECINCT ASHTABULA 5-A D                    57    13
## 4 OH0014653051 EAST CLEVELAND-03-C    D                    79    13
## 5 OH0014799002 PARMA-04-A             D                    60    13
## 6 OH0014805561 PARMA-05-A             D                    66    13

Let’s create some cross tables to identify how many times people of each party voted for a Demorat in a primary.

# generate some cross-tabs. Everyone loves cross tabs.
count(potential_targets, party_affiliation, party) %>% 
  mutate(prop = round(n / sum(n), 2))
## # A tibble: 4 x 4
##   party_affiliation party        n  prop
##   <chr>             <chr>    <int> <dbl>
## 1 ""                Democrat  1093  0.3 
## 2 D                 Democrat  1828  0.51
## 3 G                 Democrat     4  0   
## 4 R                 Democrat   663  0.18

What is most telling from this table is that 30% of individuals who voted for a Democrat in the primary have no party affiliation. Now that we have a data frame with voter IDs and their registered party. We can begin segmenting this group based on the afore mentioned definitions. This will be done with a basic case_when() statement.

# Tier 1 base dems will be those that are registered dem and have voted dem
# we  will tier our targets

# Tier 2 will be our motivation group. 
# Those that aren't registered with a party, but have voted Dem


# Tier 3 will be our persuasion group. 
# These are the folks that have voted Dem but aren't registered as such. 

# export thiws and upload to VAN or other management software. 
targets <- potential_targets %>% 
  mutate(tier = case_when(
    party_affiliation == "D" ~ 1,
    party_affiliation == "" ~ 2,
    !party_affiliation %in% c("", "D") ~ 3
  ))

Now select voter ID and tier, write as a csv, and bulk upload into VAN!

## # A tibble: 3 x 5
##    tier med_age n_primaries tier_size     p
##   <dbl>   <dbl>       <int>     <int> <dbl>
## 1     1      59        8071      1828 0.700
## 2     2      56        2026      1093 0.176
## 3     3      62        1436       667 0.125