I would like to generate a SpatialPolygonsDataFrame that I can use to plot a cartogram whose polygons are proportional to a variable.

A clear example is how FiveThirtyEight created a hex cartogram with each US state made up of hex polygons equating to the number of electoral college seats they have. The data they use is straight forward, US geodata + electoral college distribution by state + state names. The outcome is here: https://projects.fivethirtyeight.com/2016-election-forecast/#electoral-map

I tried to reproduce the cartogram using the code below but can't quite get some aspect of the initial object: Specifically, how do I merge the electoral college data with the US geodata so that I have 538 observations? All reproductions I have seen start off from a ready SpatialPolygonsDataFrame object file from FiveThirtyEight.

Below is my work so far, trying to merge the two objects but ending up with a dataframe without the polygons (50 rows instead of 538).

Get US state level shapefile from GADM:

us_states <- raster::getData("GADM", country = "United States", level = 1)


class       : SpatialPolygonsDataFrame 
features    : 51 
extent      : -179.1506, 179.7734, 18.90986, 72.6875  (xmin, xmax, ymin, ymax)
crs         : +proj=longlat +datum=WGS84 
variables   : 10

Get electoral_college from https://worldpopulationreview.com/state-rankings/electoral-votes-by-state

electoral_college <- read.csv("./ecollege.csv")

Merge the two:

geocoll <- merge(x=us_states, y=electoral_college, by.x="NAME_1", by.y="State")

This results in a df (geocoll) without the polygons. Ideally, I would want x polygons (hex, circles or squares) for a state. 55 for California, for example.

1 Answer 1


Pretty close! You need to make use of sp::merge() instead of merge from base R in order to get a SpatialPolygonsDataFrame instead of a data frame without geometry information.


us_states <- raster::getData("GADM", country = "United States", level = 1)

electoral_college <- read.csv("ecollege.csv")

geocoll <- sp::merge(x = us_states, 
                     y = electoral_college, 
                     by.x = "NAME_1", 
                     by.y = "State")

#> [1] "SpatialPolygonsDataFrame"
#> attr(,"package")
#> [1] "sp"

#>       NAME_1 GID_0        NAME_0   GID_1 VARNAME_1 NL_NAME_1 TYPE_1 ENGTYPE_1
#> 1    Alabama   USA United States USA.1_1   AL|Ala.      <NA>  State     State
#> 2     Alaska   USA United States USA.2_1 AK|Alaska      <NA>  State     State
#> 3    Arizona   USA United States USA.3_1  AZ|Ariz.      <NA>  State     State
#> 4   Arkansas   USA United States USA.4_1   AR|Ark.      <NA>  State     State
#> 5 California   USA United States USA.5_1 CA|Calif.      <NA>  State     State
#> 6   Colorado   USA United States USA.6_1  CO|Colo.      <NA>  State     State
#>   CC_1 HASC_1 electoralVotesNumber peoplePerElector      Pop
#> 1 <NA>  US.AL                    9         563687.4  5073187
#> 2 <NA>  US.AK                    3         246007.7   738023
#> 3 <NA>  US.AZ                   11         663945.3  7303398
#> 4 <NA>  US.AR                    6         505107.7  3030646
#> 5 <NA>  US.CA                   55         727183.2 39995077
#> 6 <NA>  US.CO                    9         658068.7  5922618
  • Thank you @falk-env. I tried your approach using sp::merge but I ended up with same number of observations (51) instead of 538.
    – ma991ng
    Aug 6 at 14:51
  • Yeah, that's because these are two unrelated problems in my opinion. What you did here, was to join additional attributes on your spatial data with a 1:1 relation. That is your data basis now. Your next step would involve finding a grid representation for your polygons (= individual states) respecting their position with the number of cells being based on the value given in electoralVotesNumber. I'd open a new question covering this issue.
    – falk-env
    Aug 6 at 15:01
  • I see why that is a different question, and in hindsight that was the real question I should have been asking.
    – ma991ng
    Aug 6 at 16:26
  • Here is the new question - gis.stackexchange.com/questions/437657/…
    – ma991ng
    Aug 6 at 16:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.