Choropleth map in ggplot with polygons that have holes - r

I'm trying to draw a choropleth map of Germany showing poverty rate by state (inspired by this question).
The problem is that some of the states (Berlin, for example) are completely surrounded by other states (Brandenburg), and I'm having trouble getting ggplot to recognize the "hole" in Brandenburg.
The data for this example is here.
library(rgdal)
library(ggplot2)
library(RColorBrewer)
map <- readOGR(dsn=".", layer="germany3")
pov <- read.csv("gerpoverty.csv")
mrg.df <- data.frame(id=rownames(map#data),ID_1=map#data$ID_1)
mrg.df <- merge(mrg.df,pov, by="ID_1")
map.df <- fortify(map)
map.df <- merge(map.df,mrg.df[,c("id","poverty")], by="id")
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
Notice how the colors for Berlin and Brandenburg (in the northeast) are identical. They shouldn't be - Berlin's poverty rate is much lower than Brandenburg. It appears that ggplot is rendering the Berlin polygon and then rendering the Brandenburg polygon over it, without the hole.
If I change the call to geom_polygon(...) as suggested here, I can fix the Berlin/Brandenburg problem, but now the three northernmost states are rendered incorrectly.
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(group=poverty, fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
What am I doing wrong??

This is just an expansion on #Ista's answer, which does not require that one knows which states (Berlin, Bremen) need to be rendered last.
This approach takes advantage of the fact that fortify(...) generates a column, hole which identifies whether a group of coordinates are a hole. So this renders all regions (id's) with any holes before (e.g. underneath) the regions without holes.
Many thanks to #Ista, without whose answer I could not have come up with this (believe me, I spent many hours trying...)
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(data=map.df[map.df$id %in% map.df[map.df$hole,]$id,],aes(fill=poverty))+
geom_polygon(data=map.df[!map.df$id %in% map.df[map.df$hole,]$id,],aes(fill=poverty))+
geom_path(colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()

You can plot the island polygons in a separate layer, following the example on the ggplot2 wiki. I've modified your merging steps to make this easier:
mrg.df <- data.frame(id=rownames(map#data),ID_1=map#data$ID_1)
mrg.df <- merge(mrg.df,pov, by="ID_1")
map.df <- fortify(map)
map.df <- merge(map.df,mrg.df, by="id")
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=poverty), color = "grey50", data =subset(map.df, !Id1 %in% c("Berlin", "Bremen")))+
geom_polygon(aes(fill=poverty), color = "grey50", data =subset(map.df, Id1 %in% c("Berlin", "Bremen")))+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()
As an unsolicited act of evangelism, I encourage you to consider something like
library(ggmap)
qmap("germany", zoom = 6) +
geom_polygon(aes(x=long, y=lat, group=group, fill=poverty),
color = "grey50", alpha = .7,
data =subset(map.df, !Id1 %in% c("Berlin", "Bremen")))+
geom_polygon(aes(x=long, y=lat, group=group, fill=poverty),
color = "grey50", alpha= .7,
data =subset(map.df, Id1 %in% c("Berlin", "Bremen")))+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))
to provide context and familiar reference points.

Just to add another small improvement to #Ista's and #jhoward's answers (thanks a lot for your help!).
The modification of #jhoward could be easily wrapped in a small function like this
gghole <- function(fort){
poly <- fort[fort$id %in% fort[fort$hole,]$id,]
hole <- fort[!fort$id %in% fort[fort$hole,]$id,]
out <- list(poly,hole)
names(out) <- c('poly','hole')
return(out)
}
# input has to be a fortified data.frame
Then, one doesn't need to recall every time how to extract holes info. The code would look like
ggplot(map.df, aes(x=long, y=lat, group=group)) +
geom_polygon(data=gghole(map.df)[[1]],aes(fill=poverty),colour="grey50")+
geom_polygon(data=gghole(map.df)[[2]],aes(fill=poverty),colour="grey50")+
# (optionally). Call by name
# geom_polygon(data=gghole(map.df)$poly,aes(fill=poverty),colour="grey50")+
# geom_polygon(data=gghole(map.df)$hole,aes(fill=poverty),colour="grey50")+
scale_fill_gradientn(colours=brewer.pal(5,"OrRd"))+
labs(x="",y="")+ theme_bw()+
coord_fixed()

Alternatively you could create that map using rworldmap.
library(rworldmap)
library(RColorBrewer)
library(rgdal)
map <- readOGR(dsn=".", layer="germany3")
pov <- read.csv("gerpoverty.csv")
#join data to the map
sPDF <- joinData2Map(pov,nameMap='map',nameJoinIDMap='VARNAME_1',nameJoinColumnData='Id1')
#default map
#mapPolys(sPDF,nameColumnToPlot='poverty')
colours=brewer.pal(5,"OrRd")
mapParams <- mapPolys( sPDF
,nameColumnToPlot='poverty'
,catMethod="pretty"
,numCats=5
,colourPalette=colours
,addLegend=FALSE )
do.call( addMapLegend, c( mapParams
, legendLabels="all"
, legendWidth=0.5
))
#to test state names
#text(pov$x,pov$y,labels=pov$Id1)

Related

plot GPS position over the road

How can I plot GPS trajectory over road and zoom on that road?
Can someone please take a point (40.74640013950355, -73.98755303328286, in Manhattan) and plot it over the corresponding road network [may be a grid 600ft by 600ft]. Please edit the code below to illustrate -
lat <- 40.74640013950355
long <- -73.98755303328286
tbl <- tibble(lat, long)
ggplot(data = tbl,
aes(x = lat,
y = long)) +
geom_point()
Once I know how to plot the road and I can overlay my trajectory data by modifying tbl above.
Thanks
There is no big difficulty to achieve such plot, starting from the example given in tigris library:
library(tigris)
library(ggplot2)
library(ggthemes)
roads <- roads("Maine", "031")
gg <- ggplot() + geom_sf(data = roads, color="black", fill="white", size=0.25) + theme_map()
lat <- 43.5; long <- -70.6; bbox = 0.02
bbox_gg = coord_sf(xlim=c(long-bbox/2, long+bbox/2), ylim=c(lat-bbox/2, lat+bbox/2))
gg + geom_point(data=data.frame(lat, long), aes(x=long, y=lat), size=4, color="red") + bbox_gg
What is done here is just adding a geom_point() aesthetic on top of the geom_sf() layer. We can use a kind of bounding box coordinate limit to adjust the zoom
EDIT
If you need some road names on your map, you can add this to the plot:
geom_sf_label(data=subset(roads, roads$RTTYP!="M"), aes(label=FULLNAME))
here I use subset to avoid plotting all little road names. Eventually, you might want to find a way to zoom/subset your data before plotting, because it's gonna be too long to do it like this.

Ggplot2 - Map polygons aes fill in ggplot() versus geom()

I am practicing the ggplot2 grammar of graphics on World maps using the base R dataset and the ggplot2 and mapproj packages.
When building a map that colours countries by a random variable (called "CountryColour" in the following example):
world_map <- map_data("world")
country_colours <- data.frame(region = c(names(table(world_map$region))),
colour= sample(c(1:20), length(names(table(world_map$region))), replace = TRUE))
world_map <- merge(world_map, country_colours)
world_map <- world_map[order(world_map$region, world_map$order),]
I happened to include the aes(fill) argument in the ggplot component:
ggplot(world_map[abs(world_map$long) < 180,], aes(x=long, y=lat, group=group, fill=colour)) +
geom_polygon(color="black") +
coord_map(projection = "mercator")
Now, if I include it in the ggplot() component itself I get EXACTLY the same output:
ggplot(world_map[abs(world_map$long) < 180,], aes(x=long, y=lat, group=group)) +
geom_polygon(aes(fill=colour), color="black") +
coord_map(projection = "mercator")
I would like to understand the conceptual difference between placing the fill function in one place or the other.

Plot a map in R - show only external borders

My problem is with an Israel map, but I'd ask the question with US data which exists in the maps library to make it easier. I guess it should work.
I know how to control the borders of a map both on the basic plot and in ggplot. I can draw it with or without plots. However, I would like sometimes to draw only the external borders of the map, and not the borders across states (like in the drawing below).
library(ggplot2)
library(maps)
all_states <- map_data("state")
ggplot(all_states, aes(x=long, y=lat, group=group, fill = region)) +
geom_polygon(color = "black", size = 1) + coord_equal(ratio=1)
Another question which is a bit more complex:
Let's now imagine I have a way to group specific states to 'regions'. Let's imagine we apply a category of 'south' and 'north' to every state.
Then I would like to differentiate and be able to control 3 different type of borders - (1) the external border of the country; (2) the external border of every region; (3) the external border of each state.
Thanks!
Alan
Or, use a proper shapefile:
library(rgdal)
library(ggplot2)
if (!file.exists("israel_geojson.tgz")) download.file("https://s3.amazonaws.com/osm-polygons.mapzen.com/israel_geojson.tgz", "israel_geojson.tgz")
(untar("israel_geojson.tgz", list = TRUE))
## [1] "./israel/" "./israel/admin_level_2.geojson"
## [3] "./israel/admin_level_other.geojson" "./israel/admin_level_95.geojson"
## [5] "./israel/admin_level_11.geojson" "./israel/admin_level_94.geojson"
## [7] "./israel/admin_level_5.geojson" "./israel/regions.geojson"
## [9] "./israel/admin_level_1.geojson" "./israel/admin_level_12.geojson"
## [11] "./israel/admin_level_3.geojson" "./israel/admin_level_7.geojson"
## [13] "./israel/admin_level_0.geojson" "./israel/admin_level_13.geojson"
## [15] "./israel/admin_level_10.geojson" "./israel/admin_level_6.geojson"
## [17] "./israel/admin_level_15.geojson" "./israel/admin_level_4.geojson"
## [19] "./israel/admin_level_9.geojson" "./israel/admin_level_8.geojson"
According to Mapzen, their admin layer 2 is the outline.
israel <- readOGR("./israel/admin_level_2.geojson")
israel_map <- fortify(israel)
ggplot() +
geom_map(data=israel_map, map=israel_map, aes(long, lat, map_id=id),
color="#2b2b2b", fill="white") +
ggalt::coord_proj("+proj=aeqd +lat_0=31.471357089512118 +lon_0=35.189208984375") +
ggthemes::theme_map()
As answered by Camille, you can use dplyr::summarise() to merge the borders of a shapefile. For example:
library(dplyr)
library(ggplot2)
shp <- ... # Load your shapefile here
shp %>%
summarise() %>%
ggplot() +
geom_sf()
With USA it is easy
library(ggplot2)
library(maps)
border <- map_data("usa")
border is a data frame so you can manipulate it ex:
border %>% mutate(part = ifelse(region == "main", "south", "north"))
Then you can plot
all_states <- map_data("state")
ggplot(all_states, aes(x=long, y=lat, group=group, fill = region)) +
geom_polygon(color = "red", size = 5, data = border) + coord_equal(ratio=1) +
geom_polygon(size = 1) + coord_equal(ratio=1)
or you can draw whatever you want
However I doubt that there are information about israel regions in the maps package. You need to download shape files that contain the information about the israel borders. There are here http://www.gadm.org/country (Rspatial polygon). You will google out what you can do with them

Continuous colour gradient that applies to a single geom_polygon element with ggplot2 in R

I am creating a choropleth map for CO2 emissions in Europe using ggplot2 and ggmap in R. The fill colour for each country corresponds to its CO2 emissions. The colours and continuous legend are implemented with scale_fill_gradient. However, I would also like for a single country to have a colour that is not from the continuous legend (for later use in a Shiny application). I have not worked out how to have scale_fill_gradient only apply to one of multiple geom_polygon layers.
Here is my initial map:
To reproduce this map, download the map data from http://www.naturalearthdata.com/http//www.naturalearthdata.com/download/50m/cultural/ne_50m_admin_0_countries.zip
Here is the code:
library(maptools)
library(ggplot2)
library(ggmap)
# read in a world map
WorldMap <- readShapePoly(fn="ne_50m_admin_0_countries/ne_50m_admin_0_countries")
# reduce shape file to a filtered data frame
WorldMapDf <- fortify(WorldMap,region='iso_a2')
# read in CO2 emissions
GEO <- c('FR','AT','BE','DE','DK','FI','GB','IE','NL','NO','SE')
CO2 <- c(59.5,86.9,137.4,425.8,303.9,353.2,380.3,427.0,476.4,1.8,47.6)
CO2_df <- data.frame(GEO,CO2)
# the range of values that the colour scale should cover
colorbar_range <- range(CO2_df$CO2)
mean_price <- mean(colorbar_range)
# merge map polygons with electricity CO2 emissions
CO2Map <- merge(WorldMapDf, CO2_df, by.x="id", by.y="GEO", all.x = T)
CO2Map <- CO2Map[order(CO2Map$order),]
#limit data to main Europe
europe.limits <- data.frame(matrix(c(35.50,-11.43,70,31.11),nrow=2,ncol=2,byrow = T))
names(europe.limits) <- c('lat','long')
CO2MapSubset <- subset(CO2Map, long > min(europe.limits$lon) & long < max(europe.limits$lon) & lat > min(europe.limits$lat) & lat < max(europe.limits$lat))
# create x and y limits
xrange <- c(min(CO2MapSubset$long),max(CO2MapSubset$long))
yrange <- c(min(CO2MapSubset$lat),max(CO2MapSubset$lat))
initial_map <- ggplot(data=CO2MapSubset) + # data layer
geom_polygon(aes(x=long, y=lat, group=group, fill=CO2)) +
coord_map(projection = "mercator",xlim=xrange,ylim=yrange) +
scale_fill_gradient2(low='gold',mid = "white",high='firebrick2',na.value = "lightgrey",midpoint=mean_price,limits=colorbar_range, name='CO2 (g/kWh)') +
geom_path(aes(x=long, y=lat, group=group), color='black',size=0.2)
# display map
initial_map
Now, I would like to make one country a different colour (for example, blue) that is not on the continuous colour scale as shown in the legend.
I thought I could do this by adding an additional geom_polygon layer to the initial map. To make Denmark blue, I tried this:
map_with_selected_country <- initial_map +
geom_polygon(data = CO2MapSubset[CO2MapSubset$id == 'DK',], aes(x=long, y=lat, group=group, fill='blue'))
but I get the error message: 'Error: Discrete value supplied to continuous scale' because the fill 'blue' conflicts with scale_fill_gradient2. Is there a way to make scale_fill_gradient2 only point to one dataset? Or is there another way to tackle this problem?
Here's an example:
library(ggplot2)
map <- map_data("world")
map$value <- setNames(sample(1:50, 252, T), unique(map$region))[map$region]
p <- ggplot(map, aes(long, lat, group=group, fill=value)) +
geom_polygon() +
coord_quickmap(xlim = c(-50,50), ylim=c(25,75))
p + geom_polygon(data = subset(map, region=="Germany"), fill = "red")
Germany is overplotted using a red fill color:
You can adapt this example to fit your needs.

Converting polygons into latitude and longitude in R/Excel

I have a data set about all the counties in Minnesota, and one of the columns is its shape. For each county it looks something like this:
For Aitkin County:
<Polygon><outerBoundaryIs><LinearRing><coordinates>-93.051956,46.15767700000001,0 -93.434006,46.15313,0 -93.43261,46.240253,0 -93.80480900000001,46.23817100000001,0 -93.80933400000001,46.580681,0 -93.77426199999999,46.59050400000001,0 -93.77412400000001,46.802605,0 -93.77500100000002,47.030445,0 -93.058258,47.022362,0 -93.05964600000001,46.766071,0 -93.05208600000002,46.417576,0 -93.051956,46.15767700000001,0</coordinates></LinearRing></outerBoundaryIs></Polygon>
I'm fairly new to R and know nothing about Google API, HTML, etc. I'm trying to use the ggplot2 and maps packages to create an intensity map for various aspects of all the counties in Minnesota. Is there a way to use these coordinates as they are to make a layer of counties, or do I need to do something else?
Here's the code I have so far:
Map of MN:
library(maps)
library(ggplot2)
all_states <- map_data("state")
mn<-subset(all_states, region %in% c("minnesota"))
p<-ggplot()
p<-p+geom_polygon(data=mn, aes(x=long, y=lat, group=group), colour="black", fill="white")
p
And my plan is to modify the following to apply to each county, once I get those polygons:
dataset <- data.frame(region=states,val=runif(49, 0,1))
us_state_map <- map_data('state')
map_data <- merge(us_state_map, dataset, by='region', all=T)
map_data <- map_data[order(map_data$order), ]
(qplot(long, lat, data=map_data, geom="polygon", group=group, fill=val)
+ theme_bw() + labs(x="", y="", fill="")
+ scale_fill_gradient(low='#EEEEEE', high='darkgreen')
+ opts(title="Title",
legend.position="bottom", legend.direction="horizontal"))
Any suggestions would be greatly appreciated!

Resources