Cartograms of the 2018 U.S. House Vote

Published by . [Permalink]

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")])
StateCD.PartyGEOID
Wisconsin4D5504
Wisconsin5R5505
Wisconsin6R5506
Wisconsin7R5507
Wisconsin8R5508
Wyoming0R5600

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.


Rooted in my dual education in computer science and communication, I make meaningful information accessible with new media, social computing, and computational social science. My media background is rooted in documentary filmmaking and this perspective continues to influence my approach to research and writing.

Mentions