How to add continuous color legend to an R map made with maps - r

I'm using the R code shown below, which loads libraries maps and RColorBrewer, to create a map of the world with countries color-coded by population rank. As you can see in the image below, I'm using a green palette in which the darker the green, the larger the population.
I'd like to add a continuous color legend showing the full palette to denote that light green = small population and dark green = large population, but I can't find a way to do it via maps. Could you tell me what is the easiest way to add a continuous color legend (or color key/color scale) to my map?
# Load libraries
library(maps)
library(RColorBrewer)
# Load world data
data(world.cities)
# Calculate world population by country
world.pop = aggregate(x=world.cities$pop, by=list(world.cities$country.etc),
FUN=sum)
world.pop = setNames(world.pop, c('Country', 'Population'))
# Create a color palette
palette = colorRampPalette(brewer.pal(n=9, name='Greens'))(nrow(world.pop))
# Sort the colors in the same order as the countries' populations
palette = palette[rank(-world.pop$Population)]
# Draw a map of the world
map(database='world', fill=T, col=palette, bg='light blue')

The world map in the maps package is about 30 years old (e.g., has USSR & Yugoslavia).
Plus you have a glitch in your code that causes the overpopulated Greenland that #Jealie noticed (and India is less populated than Antarctica).
You can create a continuousish legend with a modern world using rworldmap.
library(rworldmap)
library(RColorBrewer)
#get a coarse resolution map
sPDF <- getMap()
#using your green colours
mapDevice('x11') #create a map shaped device
numCats <- 100 #set number of categories to use
palette = colorRampPalette(brewer.pal(n=9, name='Greens'))(numCats)
mapCountryData(sPDF,
nameColumnToPlot="POP_EST",
catMethod="fixedWidth",
numCats=numCats,
colourPalette=palette)
You can alter the legend adding more labels etc. by doing something like this :
mapParams <- mapCountryData(sPDF, nameColumnToPlot="POP_EST", catMethod="pretty", numCats=100, colourPalette=palette, addLegend=FALSE)
#add a modified legend using the same initial parameters as mapCountryData
do.call( addMapLegend, c( mapParams
, legendLabels="all"
, legendWidth=0.5
))
Just briefly to explore the glitch in your code. It occurs because you create a palette for the number of countries in world.cities (239) and then apply it to the number of polygons in the world database from maps (2026). So it probably gets recycled and the colours of your countries have no relation to population. The code below demonstrates the source of your problem.
#find the countries used in the maps world map
mapCountries <- unique( map('world',namesonly=TRUE) )
length(mapCountries)
#[1] 2026
#exclude those containing ':' e.g. "USA:Alaska:Baranof Island"
mapCountries2 <- mapCountries[-grep(':',mapCountries)]
length(mapCountries2)
#[1] 186
#which don't match between the map and world.cities ?
#cityCountries <- unique( world.cities$country.etc )
cityCountries <- world.pop$Country
length(cityCountries)
#[1] 239
#which countries are in the map but not in world.cities ?
mapCountries2[ is.na(match(mapCountries2,cityCountries)) ]
#includes USSR, Yugoslavia & Czechoslovakia

Within the library SDMTools there is the function legend.gradient
adding this code to the end of your code should give the desired result:
# Draw a map of the world
map(database='world', fill=T, col=palette, bg='light blue')
x = c(-20, -15, -15, -20)
y = c(0, 60, 60, 0)
legend.gradient(cbind(x = x - 150, y = y - 30),
cols = brewer.pal(n=9, name='Greens'), title = "TITLE", limits = "")
You will need to fiddle with the x & y coordinates to get the legend into the desired location however.
EDIT
The x and y coordinates also adjust the shape of the box so I changed the code so that the box shape would not change if you only alter the numbers within the legend.gradient function. Below is what this code should produce

Related

Base R Choropleth: colors aren't being applied to the map according to the order of the interval/breaks which makes the map hard to read

I created a choropleth with base R but I'm struggling with the colors. First, the colors don't follow the same order as the intervals and second, two of the intervals are using the same color, all of which makes the graph hard to read. This happens regardless of how many colors I use. It also doesn't matter whether I'm using brewer.pal or base colors.Here is a map with its respective legend illustrating the issue.
Below are the statements that I use to create the graph once data has been downloaded:
#Relevant packages:
library(dplyr)
library(RColorBrewer)
library(rgdal)
#create colors vector
pop_colors <- brewer.pal(8,"Purples")
#create breaks/intervals
pop_breaks <- c(0,20000,40000,60000,80000,100000,120000)
#apply breaks to population
cuts <- cut(cal_pop$Pop2016, pop_breaks, dig.lab = 6)
#create a vector with colors by population according to the interval they belong to:
color_breaks <- pop_colors[findInterval(cal_pop$Pop2016,vec = pop_breaks)]
Create choropleth
plot(cal_pop,col = color_breaks, main = "Calgary Population (2016)")
#create legend
legend("topleft", fill = color_breaks, legend = levels(cuts), title = "Population")
I used readOGR() command to read the shape file, which I'm linking here in case anybody is interested in taking a look at the data.
I'd appreciate any advice you could give me.
Thanks!
Your error is in this line:
color_breaks <- pop_colors[findInterval(cal_pop$Pop2016,vec = pop_breaks)]
I can't read your data file, so I'll use a built-in one from the sf package.
library(sf)
nc <- readOGR(system.file("shapes/", package="maptools"), "sids")
str(nc#data)
colors <- brewer.pal(8,"Purples")
#create breaks/intervals
sid_breaks <- c(0,2,4,6,8,10,12,20,60)
#apply breaks to population
sid_cuts <- cut(nc$SID79, sid_breaks, dig.lab = 6, include=TRUE)
#create a vector with colors by population according to the interval they belong to:
sid_colors <- colors[sid_cuts]
#Create choropleth
par(mar=c(0,0,0,0))
plot(nc, col = sid_colors)
legend("bottomleft", fill = colors, legend = levels(sid_cuts), nc=2, title = "SID (1979)", bty="n")

How to label nodes in R using ggplot

I have created a map of the labor mobility in Spain in 2014. I have followed this link to adapt the code: http://www.r-bloggers.com/mapping-flows-in-r/
Now, I would like to add the names of the cities to the map. Any ideas?
Here is the code I used:
# Load the data about the labor flow
input <- read.csv("~/Desktop/flujo.csv", sep=";")
# Now we need to associate the Spanish regions with geographical coordinates.
centroids <- read.csv("~/Desktop/coordinates.csv", sep=";")
# Join the coordinates with the cities
or.xy<- merge(input, centroids, by.x="origin", by.y="origin")
or.xy$o_name<-or.xy$origin
names(or.xy)<- c("origin", "destination", "trips", "oX", "oY","o_name")
dest.xy<- merge(or.xy, centroids, by.x="destination", by.y="origin")
dest.xy$d_name<-dest.xy$destination
names(dest.xy)<- c("origin", "destination", "trips", "oX", "oY","o_name", "dX", "dY","d_name")
# Now for plotting with ggplot2.This first step removes the axes in the resulting plot.
xquiet<- scale_x_continuous("", breaks=NULL)
yquiet<-scale_y_continuous("", breaks=NULL)
quiet<-list(xquiet, yquiet)
# Let’s build the plot. First we specify the dataframe we need, with a filter excluding flows of <10
mapa<-ggplot(dest.xy[which(dest.xy$trips>1),], aes(oY,oX))+
# The next line tells ggplot that we wish to plot line segments. The “alpha=” is line transparency and used below
geom_segment(aes(x=oX, y=oY,xend=dX, yend=dY, alpha=trips), col="white")+
# Here is the magic bit that sets line transparency – essential to make the plot readable
scale_alpha_continuous(range = c(0.07,0.07))+
# Set black background, remove axes and fix aspect ratio
theme(panel.background = element_rect(fill="black",colour='black'))+quiet+coord_equal()

Overlap image plot on a Google Map background in R

I'm trying to add this plot of a function defined on Veneto (italian region)
obtained by an image and contour:
image(X,Y,evalmati,col=heat.colors(100), xlab="", ylab="", asp=1,zlim=zlimits,main=title)
contour(X,Y,evalmati,add=T)
(here you can find objects: https://dl.dropboxusercontent.com/u/47720440/bounty.RData)
on a Google Map background.
I tried two ways:
PACKAGE RGoogleMaps
I downloaded the map mbackground
MapVeneto<-GetMap.bbox(lonR=c(10.53,13.18),latR=c(44.7,46.76),size = c(640,640),MINIMUMSIZE=TRUE)
PlotOnStaticMap(MapVeneto)
but i don't know the commands useful to add the plot defined by image and contour to the map
PACKAGE loa
I tried this way:
lat.loa<-NULL
lon.loa<-NULL
z.loa<-NULL
nx=dim(evalmati)[1]
ny=dim(evalmati)[2]
for (i in 1:nx)
{
for (j in 1:ny)
{
if(!is.na(evalmati[i,j]))
{
lon.loa<-c(lon.loa,X[i])
lat.loa<-c(lat.loa,Y[j])
z.loa<-c(z.loa,evalmati[i,j])
}
}
}
GoogleMap(z.loa ~ lat.loa*lon.loa,col.regions=c("red","yellow"),labels=TRUE,contour=TRUE,alpha.regions=list(alpha=.5, alpha=.5),panel=panel.contourplot)
but the plot wasn't like the first one:
in the legend of this plot I have 7 colors, and the plot use only these values. image plot is more accurate.
How can I add image plot to GoogleMaps background?
If the use of a GoogleMap map is not mandatory (e.g. if you only need to visualize the coastline + some depth/altitude information on the map), you could use the package marmap to do what you want. Please note that you will need to install the latest development version of marmap available on github to use readGEBCO.bathy() since the format of the files generated when downloading GEBCO files has been altered recently. The data from the NOAA servers is fine but not very accurate in your region of interest (only one minute resolution vs half a minute for GEBCO). Here is the data from GEBCO I used to produce the map : GEBCO file
library(marmap)
# Get hypsometric and bathymetric data from either NOAA or GEBCO servers
# bath <- getNOAA.bathy(lon1=10, lon2=14, lat1=44, lat2=47, res=1, keep=TRUE)
bath <- readGEBCO.bathy("GEBCO_2014_2D_10.0_44.0_14.0_47.0.nc")
# Create color palettes for sea and land
blues <- c("lightsteelblue4", "lightsteelblue3", "lightsteelblue2", "lightsteelblue1")
greys <- c(grey(0.6), grey(0.93), grey(0.99))
# Plot the hypsometric/bathymetric map
plot(bath, land=T, im=T, lwd=.03, bpal = list(c(0, max(bath), greys), c(min(bath), 0, blues)))
plot(bath, n=1, add=T, lwd=.5) # Add coastline
# Transform your data into a bathy object
rownames(evalmati) <- X
colnames(evalmati) <- Y
class(evalmati) <- "bathy"
# Overlay evalmati on the map
plot(evalmati, land=T, im=T, lwd=.1, bpal=col2alpha(heat.colors(100),.7), add=T, drawlabels=TRUE) # use deep= shallow= step= to adjust contour lines
plot(outline.buffer(evalmati),add=TRUE, n=1) # Outline of the data
# Add cities locations and names
library(maps)
map.cities(country="Italy", label=T, minpop=50000)
Since your evalmati data is now a bathy object, you can adjust its appearance on the map like you would for the map background (adjust the number and width of contour lines, adjust the color gradient, etc). plot.bath() uses both image() and contour() so you should be able to get the same results as when you plot with image(). Please take a look at the help for plot.bathy() and the package vignettes for more examples.
I am not realy inside the subject, but Lovelace, R. "Introduction to visualising spatial data in R" might help you
https://github.com/Robinlovelace/Creating-maps-in-R/raw/master/intro-spatial-rl.pdf From section "Adding base maps to ggplot2 with ggmap" with small changes and data from https://github.com/Robinlovelace/Creating-maps-in-R/archive/master.zip
library(dplyr)
library(ggmap)
library(rgdal)
lnd_sport_wgs84 <- readOGR(dsn = "./Creating-maps-in-R-master/data",
layer = "london_sport") %>%
spTransform(CRS("+init=epsg:4326"))
lnd_wgs84_f <- lnd_sport_wgs84 %>%
fortify(region = "ons_label") %>%
left_join(lnd_sport_wgs84#data,
by = c("id" = "ons_label"))
ggmap(get_map(location = bbox(lnd_sport_wgs84) )) +
geom_polygon(data = lnd_wgs84_f,
aes(x = long, y = lat, group = group, fill = Partic_Per),
alpha = 0.5)

Z - Values for polygon (shapefile) in R

my goal is to create a 3D-Visualization in R. I have a shapefile of urban districts (Ortsteile) in Berlin and want to highlight the value (inhabitants/km²) as a z-value. I have implemented the shapefile into R and coloured the value for desnity ("Einwohnerd") as followed:
library(rgdal)
library(sp)
berlin=readOGR(dsn="C...etc.", layer="Ortsteile")
berlin#data
col <- rainbow(length(levels(berlin#data$Name)))
spplot(berlin, "Einwohnerd", col.regions=col, main="Ortsteil Berlins", sub="Datensatz der Stadt Berlin", lwd=.8, col="black")
How it is posible to refer a certain polygon (urban district) to a z-value (inhabitant/km²) and how can I highlight this z-value?
Hope that someone will have an answer!
Best regars
SB
Thanks for the answer, but I am still on my wy to find out the best to use the density as z-value so that I can create a 3D Model. I found out that it is not possible to use the polygons of the shape but that it is possible to rasterize the polygon and to use a matrix for a different perspective and rotation.
Here is the code but the final 3D visualization looks not sharp and good enough. Maybe it would be better to calculate the the z-value in anther way so that the first values did not start so high or to use the center of the polygon and than to draw a column in z-direction:
library(rgdal)
library(sp)
setwd("C:\\...")
berlin=readOGR(dsn="C:\\...\\Ortsteile", layer="Ortsteile")
col <- rainbow(length(levels(berlin#data$Name)))
spplot(berlin, "Einwohnerd", col.regions=col, main="Ortsteil Berlins",
sub="Datensatz der Stadt Berlin", lwd=.8, col="black")
library(raster)
raster <- raster(nrows=100, ncols=200, extent(berlin))
test <- rasterize(berlin, raster, field="Einwohnerd")
persp(test, theta = 40, phi = 40, col = "gold", border = NA, shade = 0.5)
for(i in seq(0,90,10)){
persp(test, theta = 40, phi = i, col = "gold", border = NA, shade = 0.5)
}
library(rgl)
library(colorRamps)
mat <- matrix(test[], nrow=test#nrows, byrow=TRUE)
image(mat)
persp3d(z = mat, clab = "m")
persp3d(z = mat, col = rainbow(10),border = "black")
persp3d(z = mat, facets = FALSE, curtain = TRUE)
Is this what you had in mind?
library(ggplot2)
library(rgdal) # for readOGR(...) and spTransform(...)
library(RColorBrewer) # for brewer.pal(...)
setwd("<directory with shapefile>")
map <- readOGR(dsn=".",layer="Ortsteile")
map <- spTransform(map,CRS=CRS("+init=epsg:4839"))
map.data <- data.frame(id=rownames(map#data), map#data)
map.df <- fortify(map)
map.df <- merge(map.df,map.data,by="id")
ggplot(map.df, aes(x=long, y=lat, group=group))+
geom_polygon(aes(fill=Einwohnerd))+
geom_path(colour="grey")+
scale_fill_gradientn(colours=rev(brewer.pal(10,"Spectral")))+
theme(axis.text=element_blank())+
labs(title="Berlin Ortsteile", x="", y="")+
coord_fixed()
Explanation
This is a great question, in that it provides an example of a very basic choropleth map using ggplot in R.
Shapefiles can be read into R using readOGR(...), producing SpatialDataFrame objects. The latter have basically two sections: a polygons section containing the coordinates of the polygon boundaries, and a data section containing information from the attributes table in the shapefile. These can be referenced, respectively, as map#polygons and map#data.
The code above reads the shapefile and transforms the coordinates to epsg:4839. Then we prepend the polygon ids (stored in the rownames) to the other information in map#data, creating map.data. Then we use the fortify(...) function in ggplot to convert the polygons to a dataframe suitable for plotting (map.df). This dataframe has a column id which corresponds to the id column in map.data. Then we merge the attribute information (map.data) into map.df based on the id column.
The ggplot calls create the map layers and render the map, as follows:
ggplot: set the default dataset to map.df; identify x- and y-axis columns
geom_polygon: identify column for fill (color of polygon)
geom_path: polygon boundaries
theme: turn off axis text
labs: title, turn off x- and y-axis labels
coord_fixed: ensures that the map is not distorted
A note on scale_fill_gradientn(...): this function assigns colors to the fill values by interpolating a color palette provided in the colours= parameter. Here we use the Spectral palette from www.colorbrewer.org. Unfotrunately, this palette has the colors revered (blue - red), so we use rev(...) to reverse the color order (high=red, low=blue). If you prefer the more highly saturated colors common in matlab, use library(colorRamps) and replace the call to scale_fill_gradientn(...) with:
scale_fill_gradientn(colours=matlab.like(10))+

Plotting Great Circle Paths

I'm trying to plot some path/connection based maps but am unable to figure out how.
I see a lot of possibilities for one point based metrics (crime hotspots in London, etc. with googleVis, ggmap, etc.) but I can't find too many examples of two points based metrics (immigrations between cities, train routes, etc.) There is an example with the package geosphere, but it seems to not be available for R 3.0.2.
Ideally I would like something like this D3 example and would also like to customise the thickness, colour, etc. of the lines and the circles.
PS: I don't suppose rCharts can do this just yet, right?
Here's something I started last year and never got around to polishing properly but hopefully it should answer your question of joining points on a map with great circle lines, with the flexibility to customise lines, circles etc.
I use the (my) rworldmap package for mapping, WDI for world bank data and geosphere for the great circle lines. The aim was to plot aid flows from all donor countries to all recipient countries (one plot per donor). Below is an example plot, and below that the code. Hope it helps. Would be nice to find time to pick it up again!
Andy
library(rworldmap)
library(WDI) # WORLD BANK INDICATORS
## lines of either type may obscure more than they add
##**choose line option here
addLines <- 'gc' #'none''straight' 'gc'
if ( addLines == 'gc' ) library(geosphere)
# setting background colours
oceanCol = rgb(7,0,30,maxColorValue=255)
landCol = oceanCol
#produces a list of indicator IDs and names as a matrix
indicatorList <- WDIsearch('aid flows')
#setting up a world map shaped plot window
#*beware this is windows specific
mapDevice('windows',width=10,height=4.5)
year <- 2000
#for(indNum in 1:2)
for(indNum in 1:nrow(indicatorList))
{
indID <- indicatorList[indNum][1]
donorISO3 <- substr(indID,start=8,stop=10)
dFdonor <- WDI(indicator=indID,start=year,end=year)
#divide by 10^6 for million dollars
dFdonor[indID] <- dFdonor[indID] * 1/10^6
sPDFdonor <- joinCountryData2Map(dFdonor,nameJoinColumn='country',joinCode='NAME')
#take out Antarctica
sPDFdonor <- sPDFdonor[-which(row.names(sPDFdonor)=='Antarctica'),]
legendTitle=paste("aid flow from",donorISO3,year,"(millions US$)")
mapBubbles(sPDFdonor, nameZSize=indID, plotZeroVals=FALSE, legendHoriz=TRUE, legendPos="bottom", fill=FALSE, legendTitle=legendTitle, oceanCol=oceanCol, landCol=landCol,borderCol=rgb(50,50,50,maxColorValue=255),lwd=0.5,lwdSymbols=1)
#removed because not working , main=paste('donor', donorISO3,year)
#now can I plot lines from the centroid of the donor to the centroids of the recipients
xDonor <- sPDFdonor$LON[ which(sPDFdonor$ISO3==donorISO3) ]
yDonor <- sPDFdonor$LAT[ which(sPDFdonor$ISO3==donorISO3) ]
xRecips <- sPDFdonor$LON[ which(sPDFdonor[[indID]] > 0) ]
yRecips <- sPDFdonor$LAT[ which(sPDFdonor[[indID]] > 0) ]
amountRecips <- sPDFdonor[[indID]][ which(sPDFdonor[[indID]] > 0) ]
## straight lines
if ( addLines == 'straight' )
{
for(line in 1:length(xRecips))
{
#col <- 'blue'
#i could modify the colour of the lines by the size of the donation
#col=rgb(1,1,1,alpha=amountRecips[line]/max(amountRecips))
#moving up lower values
col=rgb(1,1,0,alpha=sqrt(amountRecips[line])/sqrt(max(amountRecips)))
lines(x=c(xDonor,xRecips[line]),y=c(yDonor,yRecips[line]),col=col, lty="dotted", lwd=0.5) #lty = "dashed", "dotted", "dotdash", "longdash", lwd some devices support <1
}
}
## great circle lines
## don't work well when donor not centred in the map
## also the loop fails at CEC & TOT because not ISO3 codes
if ( addLines == 'gc' & donorISO3 != "CEC" & donorISO3 != "TOT" )
{
for(line in 1:length(xRecips))
{
#gC <- gcIntermediate(c(xDonor,yDonor),c(xRecips[line],yRecips[line]), n=50, breakAtDateLine=TRUE)
#30/10/13 lines command failed with Error in xy.coords(x, y) :
#'x' is a list, but does not have components 'x' and 'y'
#adding sp=TRUE solved
gC <- gcIntermediate(c(xDonor,yDonor),c(xRecips[line],yRecips[line]), n=50, breakAtDateLine=TRUE, sp=TRUE)
#i could modify the colour of the lines by the size of the donation
#col=rgb(1,1,1,alpha=amountRecips[line]/max(amountRecips))
#moving up lower values
col=rgb(1,1,0,alpha=sqrt(amountRecips[line])/sqrt(max(amountRecips)))
lines(gC,col=col,lwd=0.5)
}
}
#adding coasts in blue looks nice but may distract
data(coastsCoarse)
plot(coastsCoarse,add=TRUE,col='blue')
#repeating mapBubbles with add=T to go on top of the lines
mapBubbles(sPDFdonor, nameZSize=indID, plotZeroVals=FALSE, fill=FALSE, addLegend=FALSE, add=TRUE, ,lwd=2)
#removed because not working : , main=paste('donor', donorISO3,year)
#looking at adding country labels
text(xRecips,yRecips,sPDFdonor$NAME[ which(sPDFdonor[[indID]] > 0) ],col=rgb(1,1,1,alpha=0.3),cex=0.6,pos=4) #pos=4 right (1=b,2=l,3=ab)
#add a title
nameDonor <- sPDFdonor$NAME[ which(sPDFdonor$ISO3==donorISO3) ]
mtext(paste("Aid flow from",nameDonor,year), cex = 1.8, line=-0.8)
#savePlot(paste("C:\\rProjects\\aidVisCompetition2012\\Rplots\\greatCircles\\wdiAidFlowLinesDonor",donorISO3,year,sep=''),type='png')
#savePlot(paste("C:\\rProjects\\aidVisCompetition2012\\Rplots\\greatCircles\\wdiAidFlowLinesDonor",donorISO3,year,sep=''),type='pdf')
} #end of indNum loop

Resources