Chloropleth map in R looks bizarre - r

I have a data set of ~25,000 people that have complete postal codes. I'm trying to create a map of Canada at the FSA level but always seem to get bizarre results. I would appreciate if someone could point out where my mistakes are happening or what I'm missing.
library(rgeos)
library(maptools)
library(ggplot2)
fsas = readShapeSpatial('./Resources/FSA/gfsa000a11a_e.shp')
data = fortify(fsas, region = 'CFSAUID')
data$fsa = factor(data$id)
data$id = NULL
df$fsa = substr(df$Postal, 1, 3)
prvdr_cts = data.frame(table(df$fsa)) ; names(prvdr_cts) = c('fsa', 'ct')
plot.data = merge(data, prvdr_cts, by = 'fsa')
ggplot(plot.data, aes(x = long, y = lat, group = group, fill = ct)) +
geom_polygon() +
coord_equal()
This is my resulting plot
I got my map file from http://www12.statcan.gc.ca/census-recensement/2011/geo/bound-limit/bound-limit-2011-eng.cfm under 'Forward sortation areas'. df has two columns Person ID and FSA.

I've seen similar problems before when I forgot group = group (as #r.bot points out), but as you have that I wonder if it's because the shapefile you're using is highly detailed.
I suggest trying the sf package to load shapefiles which has superseded using readShapePoly. This has the advantages of being faster and you don't need to fortify(). I've also simplified the shapefile somewhat to make plotting faster. Finally, you need to development version of ggplot2 to use the new geom_sf() (ATOW):
install.packages(c("rmapshaper", "sf", "devtools"))
devtools::install_github("tidyverse/ggplot2")
library("ggplot2")
fsas = sf::read_sf("gfsa000a11a_e.shp")
fsas = rmapshaper::ms_simplify(fsas, keep = 0.05)
ggplot(fsas) + geom_sf()

Related

Map FAO fishing areas in R

I would like to make a map in R that colours in the FAO Fishing Areas according to a data set (in my case, length data of shark species).
I would prefer to do a choropleth map in ggplot but other types of maps are also fine. Worst case scenario a base map of FAO areas that I can add bubbles to. Even just an existing base map of FAO areas would be great. Any suggestions welcome!
I went to this page and clicked through to find this link to retrieve a GeoJSON file:
download.file("http://www.fao.org/fishery/geoserver/fifao/ows?service=WFS&request=GetFeature&version=1.0.0&typeName=fifao:FAO_AREAS_CWP&outputFormat=json", dest="FAO.json")
From here on, I was following this example from the R graph gallery, with a little help from this SO question and these notes:
library(geojsonio)
library(sp)
library(broom)
library(ggplot2)
library(dplyr) ## for joining values to map
spdf <- geojson_read("FAO.json", what = "sp")
At this point, plot(spdf) will bring up a plain (base-R) plot of the regions.
spdf_fortified <- tidy(spdf)
## make up some data to go with ...
fake_fish <- data.frame(id = as.character(1:324), value = rnorm(324))
spdf2 <- spdf_fortified %>% left_join(fake_fish, by = "id")
ggplot() +
geom_polygon(data = spdf2, aes( x = long, y = lat, group = group,
fill = value), color="grey") +
scale_fill_viridis_c() +
theme_void() +
theme(plot.background = element_rect(fill = 'lightgray', colour = NA)) +
coord_map() +
coord_sf(crs = "+proj=cea +lon_0=0 +lat_ts=45") ## Gall projection
ggsave("FAO.png")
notes
some of the steps are slow, it might be worth looking up how to coarsen/lower resolution of a spatial polygons object (if you just want to show the picture, the level of resolution might be overkill)
to be honest the default sequential colour scheme might be better but all the cool kids seem to like "viridis" these days so ...
There are probably better ways to do a lot of these pieces (e.g. set map projection, fill in background colour for land masses, ... ?)

R crashed when using Geom_Point for large data frame

Background: I have a large data frame data_2014, containing ~ 1,000,000 rows like this
library(tidyverse)
tibble(
date_time = "4/1/2014 0:11:00",
Lat = 40.7690,
Lon = -73.9549,
Base = "B02512"
)
Problem: I want to create a plot like this
This is what I've attempted to do:
library(tidyverse)
library(ggthemes)
library(scales)
min_lat <- 40.5774
max_lat <- 40.9176
min_long <- -74.15
max_long <- -73.7004
ggplot(data_2014, aes(Lon, Lat)) +
geom_point(size = 1, color = "chocolate") +
scale_x_continuous(limits = c(min_long, max_long)) +
scale_y_continuous(limits = c(min_lat, max_lat)) +
theme_map() +
ggtitle("NYC Map Based on Uber Rides Data (April-September 2014)")
However, when I ran this code, Rstudio crashed. I'm not particularly sure how to fix or improve this. Is there any suggestion?
A million points is a lot for ggplot2, but do-able if your computer is good enough. Yours may or may not be. Short of getting a bigger computer here's what you should do.
This is spatial data, so use the sf package.
library(sf)
data_2014 <- st_as_sf(data_2014, coords = c('Lon', 'Lat')) %>%
st_set_crs(4326)
If you're only plotting the points, get rid of the columns of data you don't need. I'm guessing they might include trip distance, time, borough, etc. Use dplyr's select, or whatever other method you're familiar with.
Try plotting some of the data, and then a little more. See where your computer slows down & stop there. You can plot the data from row 1:n, or sample x number of rows.
# try starting with 100,000 and go up from there.
n <- 100000
ggplot(data_2014[1:n,]) +
geom_sf()
# Alternatively sample a fraction of the data.
# Start with ~10% and go up until R crashes again.
data_2015 %>%
sample_frac(.1) %>%
ggplot() +
geom_sf()

R:How to display city level data on map and how to ZOOM it on one specific region of the map

I am using usmap and ggplot to plot population on a map. My data has two columns - population and zipcodes.
Question: How can I display data on city level using the same libraries or if you know of other libraries that can do the job.
Question: I am plotting California map and I want to zoom on LA county and nearby counties.
Below code gives me a nice California map and population as a color.
library(usmap)
library(ggplot2)
usmap::plot_usmap("counties",
include = ("CA") )
plot_usmap(data = data, values = "pop_2015", include = c("CA"), color = "grey") +
theme(legend.position = "right")+scale_fill_gradient(trans = "log10")
The tigris package makes downloading zip code tabulation areas fairly simple. You can download as a simple features dataframe so joining your data by zip code using dplyr functions is fairly easy. Here is a quick example:
library(tigris)
library(dplyr)
library(ggplot2)
df <- zctas(cb = TRUE,
starts_with = c("778"),
class = "sf")
## generate some sample data that
## can be joined to the downloaded data
sample_data <- tibble(zips = df$ZCTA5CE10,
values = rnorm(n = df$ZCTA5CE10))
## left join the sample data to the downloaded data
df <- df %>%
left_join(sample_data,
by = c("ZCTA5CE10" = "zips"))
## plot something
ggplot(df) +
geom_sf(aes(fill = values))

How to properly join data and geometry using ggmap

An image is worth a thousand words:
Observed behaviour: As can be seen from the image above, countries' names do not match with their actual geometries.
Expected behaviour: I would like to properly join a data frame with its geometry and display the result in ggmap.
I've previously joined different data frames, but things get wrong by the fact that apparently ggmap needs to "fortify" (actually I don't know what really means) data frame in order to display results.
This is what I've done so far:
library(rgdal)
library(dplyr)
library(broom)
library(ggmap)
# Load GeoJSON file with countries.
countries = readOGR(dsn = "https://gist.githubusercontent.com/ccamara/fc26d8bb7e777488b446fbaad1e6ea63/raw/a6f69b6c3b4a75b02858e966b9d36c85982cbd32/countries.geojson")
# Load dataframe.
df = read.csv("https://gist.githubusercontent.com/ccamara/fc26d8bb7e777488b446fbaad1e6ea63/raw/a6f69b6c3b4a75b02858e966b9d36c85982cbd32/sample-dataframe.csv")
# Join geometry with dataframe.
countries$iso_a2 = as.factor(countries$iso_a2)
countries#data = left_join(countries#data, df, by = c('iso_a2' = 'country_code'))
# Convert to dataframe so it can be used by ggmap.
countries.t = tidy(countries)
# Here's where the problem starts, as by doing so, data has been lost!
# Recover attributes' table that was destroyed after using broom::tidy.
countries#data$id = rownames(countries#data) # Adding a new id variable.
countries.t = left_join(countries.t, countries#data, by = "id")
ggplot(data = countries.t,
aes(long, lat, fill = country_name, group = group)) +
geom_polygon() +
geom_path(colour="black", lwd=0.05) + # polygon borders
coord_equal() +
ggtitle("Data and geometry have been messed!") +
theme(axis.text = element_blank(), # change the theme options
axis.title = element_blank(), # remove axis titles
axis.ticks = element_blank()) # remove axis ticks
While your work is a reasonable approach - I would like to rethink your design, mainly because of two simple reasons:
1) while GeoJSON is the future, R still heavily relies on the sp package and its correspondent sp* objects - very soon you wish you had switched early on. It`s just about the packages and most of them (if not all) rely on sp* objects.
2) ggplot has great plotting capabilities combined with ggmap - but its still quite limited compared to sp* in combination with leaflet for R etc.
probably the fastest way to go is simple as:
library(sp)
library(dplyr)
library(geojsonio)
library(dplyr)
library(tmap)
#get sp* object instead of geojson
countries <- geojsonio::geojson_read("foo.geojson",what = "sp")
#match sp* object with your data.frame
countries#data <- dplyr::left_join(countries#data, your_df, by =
c("identifier_1" = "identifier_2"))
#creates a fast and nice looking plot / lots of configuration available
p1 <- tm_shape(countries) +
tm_polygons()
p1
#optional interactive leaflet plot
tmap_leaflet(p1)
It is written out of my head / bear with me if there are minor issues.
It is a different approach but its at least in my eyes a faster and more concise approach in R right now (hopefully geojson will receive more support in the future).
There is a reason for the messed up behaviour.
countries starts out as a large SpatialPolygonsDataFrame with 177 elements (and correspondingly 177 rows in countries#data). When you perform left_join on countries#data and df, the number of elements in countries isn't affected, but the number of rows in countries#data grows to 210.
Fortifying countries using broom::tidy converts countries, with its 177 elements, into a data frame with id running from 0 to 176. (I'm not sure why it's zero-indexed, but I usually prefer to specify the regions explicitly anyway).
Adding id to countries#data based on rownames(countries#data), on the other hand, results in id values running from 1 to 210, since that's the number of rows in countries#data after the earlier join with df. Consequently, everything is out of sync.
Try the following instead:
# (we start out right after loading countries & df)
# no need to join geometry with df first
# convert countries to data frame, specifying the regions explicitly
# (note I'm using the name column rather than the iso_a2 column from countries#data;
# this is because there are some repeat -99 values in iso_a2, and we want
# one-to-one matching.)
countries.t = tidy(countries, region = "name")
# join with the original file's data
countries.t = left_join(countries.t, countries#data, by = c("id" = "name"))
# join with df
countries.t = left_join(countries.t, df, by = c("iso_a2" = "country_code"))
# no change to the plot's code, except for ggtitle
ggplot(data = countries.t,
aes(long, lat, fill = country_name, group = group)) +
geom_polygon() +
geom_path(colour="black", lwd = 0.05) +
coord_equal() +
ggtitle("Data and geometry are fine") +
theme(axis.text = element_blank(),
axis.title = element_blank(),
axis.ticks = element_blank())
p.s. You don't actually need the ggmap package for this. Just the ggplot2 package that it loads.

How to use cshapes and ggplot2 to make a choropleth map in R?

I'm having trouble doing something very basic. I've done this hundreds of times with no problem with other maps but I can't get a cshapes shapefile to map properly using ggplot2 (as an example I'm trying to map "AREA" as the fill, which is a variable that comes with the cshapes shapefile). Here is the code I'm using:
library(cshapes)
library(ggplot2)
world <- cshp(date=as.Date("2009-1-1"))
world#data$id <- rownames(world#data)
world.df = fortify(world, region="COWCODE")
world.df <- join(world.df, world#data, by="id")
ggplot() + geom_polygon(data=world.df,
aes(x = long, y = lat, group = group,fill = AREA))
+coord_equal()
What I end up with is the following:, which as you can see is missing data for the eastern hemisphere. Not sure what's going on, any assistance is much appreciated.
The id you created did not match the id in world.df, thus NAs were introduced with joining by id.
If you set region to and join by SP_ID it works:
world <- cshp(date=as.Date("2009-1-1"))
world.df = fortify(world, region="SP_ID")
names(world.df)[6] <- "SP_ID"
world.df <- join(world.df, world#data)
Ok so I figured out the problem. When I was inspecting the data frame created by fortify() and then remerged with the original data, I noticed that NA's were produced in the merge. Not sure why. So I decided to use the ?help function for fortify() to see if I was missing an argument and lo and behold it says "Rather than using this function, I now recomend using the broom package, which implements a much wider range of methods. fortify may be deprecated in the future." -I had never seen this before and likely explains why I never had trouble in the past. So I checked out library(broom) and the equivalent function is tidy(), which works just fine, like so:
library(broom)
library(cshapes)
library(ggplot2)
library(dplyr)
world <- cshp(date=as.Date("2009-1-1"))
world#data$id <- rownames(world#data)
world.df = tidy(world)
world.df$arrange<-1:192609 ###Needs be reordered (something fortify did automatically)###
world.df <- join(world.df, world#data, by="id")
world.df<-arrange(world.df, arrange)
ggplot() + geom_polygon(data=world.df,
aes(x = long, y = lat, group = group,fill = AREA))
+coord_equal()
Which produces the following:

Resources