Cartograms of the 2018 U.S. House Vote

The divide between urban and rural voters has become an increasingly observable pattern in U.S. elections. Many Democratic voters pack into areas with higher population densities. Choropleth maps—where regions are shaded by a variable—often hide this reality because geographic area has little to do with the vote count.

Area cartograms can address this issue by distorting the geography to match the population. Furthermore, cartograms on different variables can present some insights. Below are three different maps of the 2018 midterm U.S. House election results by populations: total population, population of Democratic voters, and population of GOP voters.

How I Made This

I processed the data in R. The House results came from a spreadsheet maintained by David Wasserman & Ally Flinn of Cook Political Report. I also used a table from the U.S. Census to map the Congressional District shapefiles to the results.

library(maps)

all_content = readLines("https://docs.google.com/spreadsheets/d/1WxDaxD5az6kdOjJncmGph37z0BPNhV1fNAH_g7IkpC0/gviz/tq?tqx=out:csv&sheet=Sheet1")
all_content = all_content[-2]
all_content = all_content[-2]
results <- read.csv(textConnection(all_content), header = TRUE, stringsAsFactors = FALSE)
results$CD.[is.na(results$CD.)]<-0
fips <- read.csv("https://www2.census.gov/geo/docs/reference/state.txt", sep="|")
results_fips <- merge(results, fips, by.x="State", by.y="STATE_NAME")
results_fips$GEOID <- sprintf("%02d%02d", results_fips$STATE, results_fips$CD.)
tail(results_fips[,c("State", "CD.", "Party", "GEOID")])
State CD. Party GEOID
Wisconsin 4 D 5504
Wisconsin 5 R 5505
Wisconsin 6 R 5506
Wisconsin 7 R 5507
Wisconsin 8 R 5508
Wyoming 0 R 5600

To visualize this data, I need to use my trusty congressional shape files from the U.S. Census Bureau.

library(cartogram)
library(maptools)

shape <- sf::st_read(shapefile)
shape$STATEFP =  as.numeric(shape$STATEFP)
shape_data <- merge(shape, results_fips, by="GEOID")
shape_data <- shape_data[!is.na(shape_data$State) & shape_data$State != "Alaska" & shape_data$State != "Hawaii",]
shape_data$GOP.Votes <- as.numeric(gsub(",", "", shape_data$GOP.Votes))
shape_data$Dem.Votes <- as.numeric(gsub(",", "", shape_data$Dem.Votes))

Sorry, Alaska and Hawaii. Some things are easier without you.

Creating the cartogram ended up being the tricky part. I tried a few different libraries, but ended up finding the most success with topogRam. The only issue I had was getting it to work with my website. To do this, I ended up writing the JavaScript myself and loading it from a pre-saved JSON file.

library(topogram)
top <- topogram(shape=shape_data, value="Dem.Votes")
hpop <- read.csv(popfile)
hpop$GEOID <- sprintf("%04d", hpop$GEO.id2)
data <- merge(shape_data, hpop, by="GEOID")
d <- data[,c("STUSAB", "CD115FP", "Party", "HC01_EST_VC01", "Dem.Votes", "GOP.Votes")]
top2 <- topogram(shape=d, value="HC01_EST_VC01")
write(top2$x$shape, "images/test.json")

That is all there is to it. The end results look a bit strange (and a bit like Russia according to some observers), but I think they do a good job at showing where each respective party’s voters are located.