rmapsspatialscalingcartogram

How can I create multiple non-contiguous cartograms with the same underlying scale using cartogram_ncont() in R?


My Goal

I would like to visualise the spatial variation in election results on a map. This would answer the question: how did each electoral district vote? In particular, I would like to use non-contiguous cartograms and scale each district's area according to the count of votes cast for each party.

Hence, I produce one map per party, where the size of the district reflects the number of votes cast for that party in that district. For better visual recognition, the districts are coloured in the party's colour. To achieve all this, I use the function cartogram_ncont() of the package cartogram in R.

My Problem

The resulting scale is not consistent across maps. In other words, the maps are well-suited to campare where a single party did better or worse, but they are ill-suited to compare which party did better or worse. Put differently still, there currently is one "anchor district" on each map which isn't shrunk. However, I would like there to be only one "anchor district" across all maps, namely the district with the highest ballot count in the entire data set. Hence, the range of all vote counts for all parties should set the scale, not the range of vote counts for each individual party.

My Examples

See as an example the results of the two parties with the most and the least votes overall in the Upper Austrian elections in 2015:

OEVP - most votes overall

CPOE - least votes overall

My Solution?

I realise that cartogram_ncont() takes an optional argument k which determines how many districts on the map are shrunk and how many are inflated. Yet, I don't understand whether or how I can use this argument to compute all my non-contiguous cartograms to the same underlying scale.

Any hints and ideas would be very welcome for I find myself at an impasse!


Solution

  • That is an interesting question. A sample code would have been helpful for my answer.

    Playing around with the k values could be tricky. So I would like to suggest a simpler solution: just combine all variables into one value vector and use that for the cartogram.

    I have modified the example from the cartogram_ncont() man page to give you a small demonstration. I did used the sp-package, but you can easily adopt the code for sf.

    library(maptools)
    library(cartogram)
    library(rgdal)
    library(rgeos)
    
    data(wrld_simpl)
    
    # Remove uninhabited regions
    afr <- spTransform(wrld_simpl[wrld_simpl$REGION==2 & wrld_simpl$POP2005 > 0,],
                       CRS("+init=epsg:3395"))
    
    # and keep only countries with larger area
    afr <- afr[afr$AREA > 2568, ]
    
    # Create fake data
    set.seed(1234)
    afr$V1 <- runif(nrow(afr), 0, 0.08) * 100
    afr$V2 <- runif(nrow(afr), 0.3, 0.7) * 100
    afr$V3 <- 100 - afr$V2 - afr$V1
    
    # Keep the value for Egypt and Algeria constant
    # this allows us to inspect the resulting map
    afr$V1[afr$FIPS=="EG"] <- 40
    afr$V2[afr$FIPS=="EG"] <- 40
    afr$V3[afr$FIPS=="EG"] <- 40
    
    afr$V1[afr$FIPS=="AG"] <- 13
    afr$V2[afr$FIPS=="AG"] <- 13
    afr$V3[afr$FIPS=="AG"] <- 13
    
    # color vector for plotting
    afr$col <- "gray"
    afr$col[afr$FIPS=="EG"] <- "red"
    afr$col[afr$FIPS=="AG"] <- "blue"
    

    Now we need to create a SpatialDataFrame in long format. So we use rbind to bind polygons and variable values together. The cartogram is based on this new dataset.

    # There is probably a more efficient way to do this...
    
    # create temporary data
    tmp <- afr
    tmp$W <- tmp$V1      # assign V1 to new weight variable
    tmp$variable <- "V1" # add information about variable
    
    # do the same for all other variables and rbind the spatial data
    for(v in c("V2", "V3")) {
      tt <- afr
      tt$W <- tt[[v]]
      tt$variable <- v
      tmp <- rbind(tmp, tt)
    }
    
    # cartogram calculation
    afr_nc <- cartogram_ncont(tmp, "W", k = 8)
    

    Now we can plot the distorted map.

    # plot side-by-side
    par(mfrow = c(1,3))
    for(v in c("V1", "V2", "V3")) {
      plot(afr)
      plot(afr_nc[afr_nc$variable==v, ], add=T, col = afr_nc$col)
    }
    

    cartogram for 3 different variables

    # overplot new polygons
    par(mfrow = c(1,1))
    plot(afr)
    for(v in c("V1", "V2", "V3")) {
      plot(afr_nc[afr_nc$variable==v, ], add=T, col = "#00000022")
    }
    
    

    overlayed new boundaries