ggrepel: plot labels outside map - r

I'm doing a map of China biggest cities in terms of population using ggplot2.
The initial dataset was in a shapefile therefore I used readOGR to convert the data into a Spatial vector object and then using fortify I turned the map into a dataframe so I could use ggplot2.
My main problem was that by using geom_label the labels kept overlapping each other so I changed to geom_label_repel but now some of the cities labels are placed outside the map. Is there any way of forcing geom_label_repel to keep the labels inside the map?
Cheers!
library(rgdal)
library(raster)
library(ggplot2)
ctry_spdf <- readOGR(dsn=".",layer="COUNTRIES_ALL",verbose=F)
china<-subset(ctry_spdf,ctry_spdf#data$CNTRY_NAME=="China")
cities <- readOGR(dsn=".",layer="cities")
chinese_cities <- intersect(cities,china)
china_df<-fortify(china, region="OBJECTID")
china_ff <- merge(china_df, china#data, by.x = "id", by.y = "OBJECTID")
cities_df <- as(chinese_cities, "data.frame")
plot_china<- c(geom_polygon(data=china_ff, aes(long, lat, group = group)))
plot_cities<-c(geom_point(data=cities_df,aes(x=POINT_X, y=POINT_Y,col="red")))
#plot_labels<-c(geom_label(data=cities_df,aes(x=POINT_X, y=POINT_Y,label=CITY_NAME))) --previous
methodology overlapping
plot_labels<-c(geom_label_repel(
arrow = arrow(length = unit(0.03, "npc"), type = "closed", ends = "first"),
force = 10,
data=cities_df,aes(x=POINT_X, y=POINT_Y,label=CITY_NAME) ) )
ggplot()+plot_china+plot_cities+plot_labels+coord_equal() + labs(title = "China", x = "long", y =
"lat", color = "City") +scale_color_manual(labels = c("Big Cities"), values = c("red"))
See map here

Related

geom_points in ggmap not filling by variable

I'm using the Taiwan housing data found on UCI ML repository.
I'm trying to plot the houses on a map using ggplot, and fill the points with the house_price_unit_area. However, when I use fill = house_price_unit_area in the aesthetic call, it doesn't fill the points based on price, but rather it leaves them black.
Any suggestions on how to fix this? Code included below, as well as a screenshot of what is produced.
library(ggplot)
library(ggmap)
library(readxl)
df <- read_xlsx("data/real_estate.xlsx")
df$No = NULL
colnames(df)= c("trans_date",
"house_age",
"distance_to_nearest_mrt",
"number_of_conv_store",
"lat",
"long",
"house_price_unit_area",
"id")
world <- map_data(database = "world", regions = "Taiwan")
ggmap(get_stamenmap(bbox = c(left = 121.4, right = 121.64, bottom=24.9,top=25.1),location = "Taiwan"))+
geom_point(data =df, mapping = aes(x=long,y=lat, fill = house_price_unit_area))+
scale_fill_viridis_b()
I switch the fill argument for col and got this:
library(ggplot)
library(ggmap)
library(readxl)
df <- read_xlsx("Real estate valuation data set.xlsx")
df$No = NULL
colnames(df)= c("trans_date",
"house_age",
"distance_to_nearest_mrt",
"number_of_conv_store",
"lat",
"long",
"house_price_unit_area",
"id")
world <- map_data(database = "world", regions = "Taiwan")
ggmap(get_stamenmap(bbox = c(left = 121.4, right = 121.64, bottom=24.9,top=25.1),location = "Taiwan"))+
geom_point(data =df, mapping = aes(x=long,y=lat, col = house_price_unit_area))+
scale_fill_viridis_b()
output:

Coloring polygons (made with concaveman) in ggplot by column

I'm adapting code for a map made by someone else that uses a package concaveman to generate concave hulls from points. The goal is to plot a number of different polygons in the oceans, and to color-code them by a grouping variable. The code works great to make a map of all the polygons and color-code them by identity:
library(sf)
library(concaveman)
library(data.table)
library(ggplot2)
dat <- data.table(longitude = c(-131.319783, -131.141266, -131.08165, -131.079066, -130.894966,
-131.063783, -131.10855, -131.215533, -131.189816, -131.14565,
-131.200866, -131.046466, -130.94055, -130.928983, -130.7513,
-130.8406, -130.833433, -130.830666, -130.82205, -130.89, -63.3666666666667,
-63.3666666666667, -63.1666666666667, -64.1833333333333, -63.3166666666667,
-63.3, -63.85, -63.9333333333333, -63.9333333333333, -63.5833333333333,
-63.5833333333333, -63.7, -63.7, -63.2833333333333, -63.5833333333333,
-63.95, -64.1833333333333, -63.8833333333333, -63.8, -63.2166666666667,
-5.6788, -5.4408, -5.6835, -5.424, -5.6475, -5.4371, -5.6181,
-5.4446, -5.6753, -5.4366, -5.6746, -5.4448, -5.6642, -5.4411,
-5.666, -5.4408, -5.624, -5.4321, -5.6806, -5.4473),
latitude = c(52.646633, 52.589683, 52.556516, 52.559816, 52.402916, 52.5983,
52.554216, 52.550883, 52.539166, 52.658216, 52.627966, 52.481733,
52.486033, 52.469033, 52.469166, 52.261833, 52.292133, 52.301066,
52.3523, 52.366966, 48.4666666666667, 48.4666666666667, 48.65,
49.0166666666667, 48.8166666666667, 48.8166666666667, 49.1, 48.8666666666667,
48.8666666666667, 48.8, 48.8166666666667, 48.4833333333333, 48.4833333333333,
48.8, 48.8166666666667, 48.8833333333333, 49.05, 49.0833333333333,
48.7166666666667, 48.6666666666667, 54.7201, 54.6033, 54.7191,
54.5733, 54.7225, 54.5923, 54.7261, 54.6076, 54.719, 54.5978,
54.7195, 54.6108, 54.7204, 54.6062, 54.7214, 54.5923, 54.7275,
54.592, 54.7207, 54.6188),
group = c(rep('NEPac',20),rep('NWAtl',20),rep('NEAtl',20))
)
split <- split(dat, dat$group)
split.sf <- lapply(split, st_as_sf, coords = c("longitude", "latitude"))
concave <- lapply(split.sf, concaveman, concavity = 3, length_threshold = 2)
concave.binded <- do.call('rbind', concave)
concave.spdf <- as_Spatial(concave.binded)
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = group, color = group))
However, I can't figure out how to fill the polygons by anything other than whatever group is. Here is my attempt:
concave.spdf$ocean <- c('P','A','A')
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = ocean, color = ocean))
Which throws this error: Error in FUN(X[[i]], ...) : object 'ocean' not found
I think the issue is that split groups the polygons by identity when passed to concaveman, but if I change that, they won't plot correctly (because the points of different polygons will be merged). How do I keep the polygons plotted individually but color them by a grouping variable? (If it's possible I'd prefer to stick with concaveman for aesthetic reasons in the true plot [which is much more complicated than this reprex] -- I know that if I use a different approach to plotting the polygons this would be easier.)
The simplest way to do this is by adding a scale_fill_manual:
ggplot() +
geom_polygon(data = concave.spdf,
aes(x = long, y = lat, group = group, fill = group)) +
scale_fill_manual(values = c("red", "green", "blue"),
labels = c("Ocean 1", "Ocean 2", "Ocean 3"))
Arguably, a better method is to convert to a simple features collection, to which you can add any columns you like, and automatically plot with geom_sf
concave.spdf <- st_as_sf(concave.spdf)
concave.spdf$ocean <- c("Ocean 1", "Ocean 2", "Ocean 3")
ggplot(concave.spdf) +
geom_sf(aes(fill = ocean))
Note that this automatically gives the correct co-ordinate proportions too.

Specify number bins when passing sf object to ggmap R

I have a dataframe object, created by reading in a shape file with sf::read_sf and merged with some pre-existing data with a common geography column:
boundaries <- sf::read_sf('./shapefile')
map <- merge(boundaries, data, by.x = "InterZone",
by.y = "IntermediateZone2011Code", all.x = FALSE, duplicateGeoms = TRUE)
This is then overlaid using ggmap on top of a provider tile obtained with the sf get_map function:
myMap <- get_map(location = c(lon = -2.27, lat = 57.1), zoom = 6,
maptype="toner", crop=FALSE, source = 'stamen')
ggmap(myMap) +
geom_sf(data = map, aes(fill=as.factor(column1)), inherit.aes = FALSE) +
scale_fill_brewer(palette = "OrRd") +
coord_sf(crs = st_crs(4326)) +
labs(x = 'Longitude', y = 'Latitude', fill = 'column1') +
ggtitle('column1')
The issue is that this auto creates hundreds of bins.
I have been looking through the documentation but cannot find an additional argument to specify the number of bins. How can I make it clear to breakdown the column by a fixed number of bins and then map this?
Without a reproducible example it is hard to say exactly what is going on, but it looks like you might be converting a continuous variable into a factor with fill=as.factor(column1).
One option is you remove as.factor and use scale_fill_continuous or some other continuous color scale of your choice.
Another option is to look into cut, where you bin continuous data by specifying the number of bins, or the specific start and end points of your bins.
# Make n bins
map$data_bin <- cut(map$column, breaks = n )
# Or make specific start and end points for bins
map$data_bin <- cut(map$column, breaks = c(-Inf,50,100,Inf) )

Add a scale bar and a north arrow outside of the plot area of a facetted map plot

I wish to add a scale bar and a north arrow outside of the plot area of a facetted map plot.
As an example, consider the following facetted map plot, as seen in a blog post by Max Marchi (link):
# Load the data
airports <- read.csv("https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat", header = FALSE)
colnames(airports) <- c("ID", "name", "city",
"country", "IATA_FAA", "ICAO", "lat", "lon",
"altitude", "timezone", "DST")
routes <- read.csv("https://github.com/jpatokal/openflights/raw/master/data/routes.dat", header = FALSE)
colnames(routes) <- c("airline", "airlineID",
"sourceAirport", "sourceAirportID",
"destinationAirport", "destinationAirportID",
"codeshare", "stops", "equipment")
# Getting the data ready for plotting
# * For a detailed explanation on setting up the data I suggest consulting Max Marchi's post:
# http://www.milanor.net/blog/maps-in-r-plotting-data-points-on-a-map/
library(plyr)
departures <- ddply(routes, .(sourceAirportID), "nrow")
names(departures)[2] <- "flights"
arrivals <- ddply(routes, .(destinationAirportID), "nrow")
names(arrivals)[2] <- "flights"
airportD <- merge(airports, departures, by.x = "ID",
by.y = "sourceAirportID")
airportA <- merge(airports, arrivals, by.x = "ID",
by.y = "destinationAirportID")
airportD$type <- factor("departures")
airportA$type <- factor("arrivals")
# The final data frame used for plotting
airportDA <- rbind(airportD, airportA)
# Get the map of Europe from Google Maps
library(ggmap)
map <- get_map(location = 'Europe', zoom = 4)
# Make a facetted map plot
library(ggplot2)
facet.gmap <- ggmap(map) +
geom_point(aes(x = lon, y = lat,
size = sqrt(flights)),
data = airportDA, alpha = .5) +
scale_size(breaks = sqrt(c(1, 5, 10, 50, 100, 500)),
labels = c(1, 5, 10, 50, 100, 500), name = "routes") +
theme(legend.position = "bottom",
legend.direction="horizontal") +
guides(size=guide_legend(nrow=1)) +
facet_wrap( ~ type, ncol=2)
facet.gmap
I wish to add a scale bar and a north arrow outside of the plot area, at the right side of the plot legend for instance, to obtain something like this:
I tried using the scalebar and north functions of the ggsn package, however these functions only allow adding scale bars and north arrows inside of the plot area. My attempts using these functions are as follows:
# Adding a scale bar (but can't positioned it outside of the plot area)
library(ggsn)
bb <- attr(map, "bb")
bb2 <- data.frame(long = unlist(bb[c(2, 4)]), lat = unlist(bb[c(1,3)]))
scale.bar <- facet.gmap +
scalebar(data = bb2, dist = 250, dd2km = TRUE,
model = "WGS84",
st.size=2.5,
height=0.015,
location = "topleft",
facet.var='airportDA$type',
facet.lev='departures',
anchor = c(
x = bb$ll.lon + 0.05 * (bb$ur.lon - bb$ll.lon),
y = bb$ll.lat + 0.9 * (bb$ur.lat - bb$ll.lat) ))
scale.bar
# Adding a north arrow (but can't positioned it outside of the plot area)
north2(scale.bar,x=0.12,y=0.90, scale=0.09, symbol=3)
Does anyone have any suggestion? Thanks in advance.
I actually figured it out! I know I'm like 7 months late to the show...
remove 'airport$' from facet.var, and it suddenly works.
scale.bar <- facet.gmap +
scalebar(data = bb2, dist = 250, dd2km = TRUE,
model = "WGS84",
st.size=2.5,
height=0.015,
location = "topleft",
facet.var='type',
facet.lev="arrivals",
anchor = c(
x =-10,
y = 65 ))
scale.bar
# Adding a north arrow (but can't positioned it outside of the plot area)
north2(scale.bar,x=0.82,y=0.17, scale=0.09, symbol=3)

Plotting small multiples with a for loop in R

I'm trying to plot a map small multiples grid that shows hurricanes/tropical storms that have intersected with Florida since 1900. I used some spatial queries to subset the database of all Atlantic storms for this project.
I'm now plotting a line shapefile of my limited number of hurricane tracks on top of polygons of the state of Florida, some contiguous states, a few major cities in Florida and, of course, Lake Okeechobee. Here's the simple code:
library(maptools)
library(gdata)
library(RColorBrewer)
setwd("~/hurricanes")
# read shapefiles
florida <- readShapePoly("florida.shp")
south <- readShapePoly("south.shp")
hurricanes <- readShapeLines("hurricanes-florida.shp")
cities <- readShapePoints("cities.shp")
lakes <- readShapePoly("lakes.shp")
# miami, orlando and tallahassee (in FL)
cities <- subset(cities, ST == "FL")
# don't need ALL the 'canes
hurricanes1900 <- subset(hurricanes, Season >= 1900)
mycolors <- brewer.pal(5, "YlOrRd")
pdf(file = "hurricanemaps.pdf", ,width=8,height=20)
par(mfrow=c(15,5), mar=c(1,1,1,1))
for(i in 1:nrow(hurricanes1900))
{
plot(south, col="#e6e6e6", border = "#999999")
plot(florida, col="#999999", border = "#999999", add = TRUE)
plot(lakes, col="#ffffff", border = "#999999", add = TRUE)
plot(cities, pch=10, cex=.1,col="#000000", bg="#e38d2c", lwd=1, add = TRUE)
plot(hurricanes1900[i,], col = mycolors[cut(hurricanes$MAX_Wind_W, breaks = 5)],
lwd=3, add = TRUE); title(hurricanes1900$Title[i])
}
dev.off()
Three big issues I'm encountering:
1) The loop is giving me a map of each storm. I would prefer to have the code produce a Florida/South map in the grid for each year (even on those years without storms) and all the storms for that year, preferably with labels.
2) I would like to set the colors for wind speed among ALL the storms, not just those in each particular row of the loop. That way strong storms (like Andrew in 1992) show as darker even when they are the only storm of the year. Perhaps I can solve this my recoding a categorical (H1, H2, etc) variable that can be styled accordingly.
3) Assuming I can figure out No. 1, I'm having trouble getting labels to render on each storm path. The maptools documentation isn't great.
Anyway, here's the output so far (the title is a concatenation of two fields in the shapefile):
The real issue is No. 1. Thanks in advance for your help.
Given there is no reproducible data, I collected some data for this demonstration. Please provide a minimal reproducible data for SO users from next time. That will help you receive more help.
What you want to achieve can be done with ggplot2. If you want to draw a map for each year, you can use facet_wrap(). If you want to add colors based on wind, you can do that in aes() when you draw paths. If you want to add hurricanes' names, you can use the ggrepel package, which allows you to provide annotations with an ease. Note that, if you want to draw smooth paths, you further need data process.
library(stringi)
library(tibble)
library(raster)
library(ggplot2)
library(ggthemes)
library(ggrepel)
library(RColorBrewer)
library(data.table)
# Get some data. Credit to hmbrmstr for a few lines in the following code.
mylist <- c("http://weather.unisys.com/hurricane/atlantic/2007H/BARRY/track.dat",
"http://weather.unisys.com/hurricane/atlantic/2007H/TEN/track.dat",
"http://weather.unisys.com/hurricane/atlantic/2006H/ERNESTO/track.dat",
"http://weather.unisys.com/hurricane/atlantic/2006H/ALBERTO/track.dat")
temp <- rbindlist(
lapply(mylist, function(x){
foo <- readLines(x)
foo <- read.table(textConnection(gsub("TROPICAL ", "TROPICAL_",
foo[3:length(foo)])),
header=TRUE, stringsAsFactors=FALSE)
year <- stri_extract_first(str = x, regex = "[0-9]+")
name <- stri_extract_first(str = x, regex = "[A-Z]{2,}")
cbind(foo, year, name)
}
))
### Add a fake row for 2017
temp <- temp %>%
add_row(ADV = NA, LAT = NA, LON = NA, TIME = NA, WIND = NA,
PR = NA, STAT = NA, year = 2017, name = NA)
### Prepare a map
usa <- getData('GADM', country = "usa", level = 1)
mymap <- subset(usa, NAME_1 %in% c("Florida", "Arkansas", "Louisiana",
"Alabama", "Georgia", "Tennessee",
"Mississippi",
"North Carolina", "South Carolina"))
mymap <- fortify(mymap)
# Create a data.table for labeling hurricanes later.
label <- temp[, .SD[1], by = name][complete.cases(name)]
g <- ggplot() +
geom_map(data = mymap, map = mymap,
aes(x = long, y = lat, group = group, map_id = id),
color = "black", size = 0.2, fill = "white") +
geom_path(data = temp, aes(x = LON, y = LAT, group = name, color = WIND), size = 1) +
scale_color_gradientn(colours = rev(brewer.pal(5, "Spectral")), name = "Wind (mph)") +
facet_wrap(~ year) +
coord_map() +
theme_map() +
geom_text_repel(data = label,
aes(x = LON, y = LAT, label = name),
size = 2,
force = 1,
max.iter = 2e3,
nudge_x = 1,
nudge_y = -1) +
theme(legend.position = "right")

Resources