I've been plotting two sets of positional data with geom_point from different data sets (MR + MRTag).
MR
detect_date Latitude Longitude species
12/04/2016 11:08 -6.6524 71.3475 Manta Ray
12/04/2016 11:09 -6.6524 71.3475 Manta Ray
12/04/2016 11:10 -6.6524 71.3475 Manta Ray
16/04/2016 21:27 -6.6524 71.3475 Manta Ray
MRTag
species taggingdate deploy_lat deploy_lon
Manta Ray 3/24/2016 -5.4191 71.83855
Manta Ray 02/05/2013 -5.2568 71.65768333
Manta Ray 02/07/2013 -5.33448 71.9812
Manta Ray 02/08/2013 -5.3046 71.94231667
I then used the code below to make a base map
library(ggmap)
MR_bbox <- make_bbox(lon = MR$Longitude, lat = MR$Latitude, f=1)
MR_map <- get_map(location = MR_bbox, source = "google", maptype = "satellite")
Then plotted my location data from Mr + MRTag onto the map
MR_plot <-
ggmap(MR_map) +
geom_point(data = MR, mapping = aes(x = Longitude, y = Latitude),
shape = 1, color = "white") +
geom_point(data = MRTag, mapping = aes(x = deploy_lon, y = deploy_lat),
shape = 25, fill = "#00FF00", color = "#00FF00") +
xlab("Longitude") +
ylab("Latitude")
This gave me this plot
I'd like to add a legend for the two shapes to the map but I'm not sure of the best way to do it, and methods so far haven't worked
The solution is actually really simple: just move the relevant aesthetic arguments into the aes().
MR_plot <- ggmap(MR_map) +
geom_point(data = MR, aes(x = Longitude, y = Latitude,
color = 'detect', shape = 'detect', fill = 'detect')) +
geom_point(data = MRTag, aes(x = deploy_lon, y = deploy_lat,
color = 'tag', shape = 'tag', fill = 'tag')) +
xlab("Longitude") +
ylab("Latitude") +
scale_color_manual(name = 'legend', values = c(detect = 'white', tag = '#00FF00')) +
scale_fill_manual(name = 'legend', values = c(detect = NA, tag = '#00FF00')) +
scale_shape_manual(name = 'legend', values = c(detect = 1, tag = 25))
It's actually slightly confusing: if you use color= outside the aes(), then you set the actual color of those points and there is no legend because it's purely an aesthetic choice.
If you use color= (or fill= or shape=) inside the aes(), however, what you're doing is defining another variable that controls the color of the points. If you wanted to color those points based on species, for example, your aes() would be: aes(x = Longitude, y = Latitude, color = species). This is same thing: except by passing a single string you set the color variable for all points in that geom_point to a single value.
You then have to use a scale_color_* function (and corresponding functions for fill and shape) to set those color variables to the specific colors you want. By default, each scale defined in the aes() will get it's own legend. But if you name the legends and give them the same name, then they will be combined into one.
Related
Using this dataset: https://www.kaggle.com/datasets/syuzai/perth-house-prices. Is there a way of prettying it up (besides changing the colour scale) by turning it into a choropleth? Each individual point represents the price of a single house; the original data does organise the house prices by suburb level but the ozmaps package only gives boundaries up to LGA/district level.
library(ozmaps)
library(sf)
library(ggplot2)
ozmaps <- ozmaps::ozmap_states
p<- ggplot(ozmaps) +
geom_sf() +
geom_point(data = PerthReg2005, aes(x = LONGITUDE, y = LATITUDE, colour = PRICE),
size = 1, shape = 23,) +
coord_sf(xlim = c(115.5827,116.3432), ylim = c(-31.45745,-32.47298))
p + scale_colour_gradientn(colours = terrain.colors(10))
I'm trying to use geom_path and geom_text to create a labeled map, but it is not going well. I can use each geom_path and geom_text separately, but I can't seem to get them to work together. I think it has something to do with geom_polygon, but I'm not sure what.
Here is how I prepared my shapefile for mapping:
meck.hiv <- as(meck.hiv, "Spatial")
meck.hiv#data$seq_id <- seq(1:nrow(meck.hiv#data)) #create unique id for each polygon
meck.hiv#data$id <- rownames(meck.hiv#data)
meck.hivdf <- fortify(meck.hiv) #fortify the data
zipcodedf <- merge(meck.hivdf, meck.hiv#data,
by = "id")# merge the "fortified" data with the data from our spatial object
cnames <- aggregate(cbind(long, lat) ~ zip, data=zipcodedf, FUN=function(x)mean(range(x))) #get the names of our zipcode (and center the coordinates for labels)
With this, I was able to get the following maps:
With labels, no paths:
p2 <- ggplot(data = zipcodedf, aes(x = long, y = lat)) +
geom_polygon(aes(group=group, fill=hispanic.dis)) +
geom_text(data=cnames, aes(long, lat, label = zip), size=3, fontface='bold', color="black")+ #This put in zip code names
scale_fill_brewer(breaks=c(1, 2, 3, 4, 5, 6, 7), labels=c("<5", "5-10", "11-15", "16-20", "21-25", "26-30", "31+"), palette="Reds",
na.value="darkgrey") +
coord_equal() +
theme(panel.background= element_rect(color="black")) +
theme(axis.title = element_blank(), axis.text = element_blank()) +
labs(title = "Hispanic Population by Zip Code", fill="Hispanic Population (% of Total)")
Resulting map with labels but no path
Paths, but no labels:
p3 <- ggplot(data = zipcodedf, aes(x = long, y = lat, group = group, fill = hispanic.dis)) +
geom_polygon() +
geom_path(color = "black", size = 0.2)+ #Oddly, use of geom_path with the above leads to weird stuff, but we can customize map lines without labels here
scale_fill_brewer(breaks=c(1, 2, 3, 4, 5, 6, 7), labels=c("<5", "5-10", "11-15", "16-20", "21-25", "26-30", "31+"), palette="Reds",
na.value="darkgrey") +
coord_equal() +
theme(panel.background=element_blank())+
theme(panel.background= element_rect(color="black")) +
theme(axis.title = element_blank(), axis.text = element_blank()) +
labs(title = "Hispanic Population by Zip Code", fill="Hispanic Population(% of Total)")
Resulting path with paths but no labels
Trying to combine
p4 <- ggplot(data = zipcodedf, aes(x = long, y = lat)) +
geom_polygon(aes(group=group, fill=hispanic.dis)) +
geom_text(data=cnames, aes(long, lat, label = zip), size=3, fontface='bold', color="black")+
geom_path(color = "black", size = 0.2)+ #Oddly, use of geom_path with the above leads to weird stuff, but we can customize map lines without labels here
scale_fill_brewer(breaks=c(1, 2, 3, 4, 5, 6, 7), labels=c("<5", "5-10", "11-15", "16-20", "21-25", "26-30", "31+"), palette="Reds",
na.value="darkgrey") +
coord_equal() +
theme(panel.background=element_blank())+
theme(panel.background= element_rect(color="black")) +
theme(axis.title = element_blank(), axis.text = element_blank()) +
labs(title = "Hispanic Population by Zip Code", fill="Hispanic Population(% of Total)")
Resulting screwed up plot with paths and labels
My trouble-shooting suggests that there's something about how geom_path is interacting with geom_polygon combined with ggplot(), but if I move all the instructions into geom_polygon, path doesn't show up at all (although a plot will generate), and if I move all the instructions into ggplot(), I get an error: "Error in FUN(X[[i]], ...): object 'group' not found" (below).
p5 <- ggplot(data = zipcodedf, aes(x = long, y = lat, group = group, fill = hispanic.dis)) +
geom_polygon() +
geom_path(color = "black", size = 0.2)+
geom_text(data=cnames, mapping=aes(x=long, y=lat))+#Oddly, use of geom_path with the above leads to weird stuff, but we can customize map lines without labels here
scale_fill_brewer(breaks=c(1, 2, 3, 4, 5, 6, 7), labels=c("<5", "5-10", "11-15", "16-20", "21-25", "26-30", "31+"), palette="Reds",
na.value="darkgrey") +
coord_equal() +
theme(panel.background=element_blank())+
theme(panel.background= element_rect(color="black")) +
theme(axis.title = element_blank(), axis.text = element_blank()) +
labs(title = "Hispanic Population by Zip Code", fill="Hispanic Population(% of Total)")
Any help is appreciated. Please let me know if I need to provide more information on anything.
{ggplot2}'s layering and inheritance can be confusing at times. I hope the following helps. Also check out whether you really want an "additional" geom_path() layer. It feels like you want to depict/highlight the county boundaries. Please also note the use of color for setting the boundary(line) color for a layer in the example.
I have to emulate your problem, as you did not provide a reproducible example. The initial code is to get me the map data for North Carolina. I construct a cnames dataframe for the county names. You should be able to apply this to your zip-code problem at the county level ... :)
library(dplyr)
library(ggplot)
library(maps) # helper package to get some map data
library(mapdata) # helper for some map data
counties <- map_data("county") %>% filter(region == "north carolina")
head(counties)
yields a data frame
long lat group order region subregion
1 -79.53800 35.84424 1857 54915 north carolina alamance
2 -79.54372 35.89008 1857 54916 north carolina alamance
3 -79.53800 35.98175 1857 54917 north carolina alamance
4 -79.52081 36.23385 1857 54918 north carolina alamance
5 -79.26298 36.23385 1857 54919 north carolina alamance
6 -79.27444 35.90726 1857 54920 north carolina alamance
Plotting the counties only (note: this would be your zip-code level).
To explain the principle of layer inheritance, I "set" all parameters in the layer call (i.e. geom_polygon()):
ggplot() +
geom_polygon(data = counties
, aes(x = long, y = lat, group = group) # aesthetic mapping
, color = "white" # fixed value for "line"
, fill = "lightblue") # fixed value for "fill"
If you now would add the geom_path() layer without giving it aesthetics, the plot will not change. Check that I set the color to blue and the linesize to 2 for demo purposes.
ggplot() +
geom_polygon(data = counties, aes(x = long, y = lat, group = group), colour = "white", fill = "lightblue") +
# ---------------- adding a layer with no aesthetics -----------
geom_path(color = "blue", size = 2) # ... no joy!
If you now move the data and aesthetics to ggplot() "base"-layer, also path will inherit the aesthetics. In this case path will draw the "outlines" of the of the grouped lat/lon positions. The layer order and color/size of geom_path() will "overwrite" the white coloured polygon lines.
ggplot(data = counties, aes(x = long, y = lat, group = group)) +
geom_polygon( colour = "white", fill = "lightblue") +
#------------ path layer with inherited "polygon" grouping
geom_path(color = "blue", size = 2)
Next let's create the (zipcodes :) ) aka text labels by averaging the lat/lon values for the different polygon segment points.
cnames <- counties %>%
group_by(subregion) %>%
summarise(long = mean(long), lat = mean(lat) # averaging for "mid"-point
)
> cnames
# A tibble: 100 x 3
subregion long lat
<chr> <dbl> <dbl>
1 alamance -79.4 36.0
2 alexander -81.2 35.9
3 alleghany -81.1 36.5
Now add a geom_text() layer to show the (zip codes) aka subregion names.
ggplot(data = counties, aes(x = long, y = lat, group = group)) +
geom_polygon( colour = "white", fill = "lightblue") +
geom_path(color = "blue", size = 2) +
# --------------- adding a geom_text() layer
geom_text(data = cnames, aes(x = long, y = lat), color = "green")
## ------- uuummmppfff throws an error
Error in FUN(X[[i]], ...) : object 'group' not found
This throws an error. So why is that? Implicitly the group aesthetic in the ggplot() call is understood by geom_polygon() and geom_path() ... however geom_text() has issues with.
Moving the group aesthetics to the polygon layer ...
ggplot(data = counties, aes(x = long, y = lat)) +
geom_polygon( aes(group=group), colour = "white", fill = "lightblue") +
geom_path(color = "blue", size = 2) +
geom_text(data = cnames, aes(x = long, y = lat, label = "subregion"), color = "green")
does the trick but corrupts the geom_path() layer.
What happens here is that the data points (i.e. lat/lon) are no longer grouped, and ggplot connects the end points in the order they appear in the counties data frame. These are the jig-jag lines you see across your plot.
Accordingly, you would need to put another aes(group=group) for the geom_path layer! ... assuming you really want the path for the outlines.
ggplot(data = counties, aes(x = long, y = lat)) +
geom_polygon( aes(group=group), colour = "white", fill = "lightblue") +
geom_path(aes(group=group), color = "blue", size = 2) +
geom_text(data = cnames, aes(x = long, y = lat, label = "subregion"), color = "green")
Obviously the color = "white" is overwritten by the geom_path() call. You may skip one or the other.
As a rule of thumb, ggplot works well with "long" data tables. The moment you add a 2nd (or more other data objects) make sure to track which aesthetics are required and/or inherited from one layer to the other. In your original example, you could move the geom_path() upwards to have the group = group aesthetics from the geom_polygon() call.
In case of doubt, always populate the data = ... and aes() for each layer before combining (and inheriting) parameters across layers.
Good luck!
I’m currently trying to display my data on a map as points. In fact I got two different data sets of two different studies which I’d like to combine on the map. But my first problem was where to find a map to use for my purpose.
#In the R graphics Cookbook I read about this code to create a map using the ggplot2 package
library(ggplot2)
library(maps)
states_map <- mapd_data(“state”)
ggplot(states_map, aes(x=long, y=lat, group=group)) +
geom_polygon(fill=”white”, colour=”black”) +
#Then I added the data of my two data sets and it worked perfectly fine.
geom_point(data = dat1, aes(x = longitude, y = latitude, group= NA), shape=21, col = "black", fill=”red”, size = dat1$var1) +
geom_point(data = dat2, aes(x = longitude, y = latitude, group= NA), shape=21, col = "black", fill=”blue”, size = dat2$var2)
Now I want to add two legends, one to explain the difference between the point colors and the other to explain the point size. Both data sets can share the same legend for the point size because Var1 and Var2 are on the same scale. But I don’t know how to add custom legends to a ggplot (I even don’t achieve to add any legend to the ggplot). It would be nice to have anything like legend()-function which adds a legend to a normal plot, but I think ggplot wasn’t programmed to solve problems like this on your own. Another approach to solve the problem could be to plot the map without using ggplot.
If you want a legend then you have to map on an aesthetic, i.e. move fill=... and size=... inside aes(). After doing so you could set your desired fill colors via scale_fill_manual:
library(ggplot2)
library(maps)
states_map <- map_data("state")
dat1 <- data.frame(longitude = -110, latitude = 35, var1 = 100)
dat2 <- data.frame(longitude = -90, latitude = 40, var2 = 200)
ggplot(states_map, aes(x=long, y=lat)) +
geom_polygon(aes(group=group), fill="white", colour="black") +
geom_point(data = dat1, aes(x = longitude, y = latitude, fill = "dat1", size = var1), shape=21, col = "black") +
geom_point(data = dat2, aes(x = longitude, y = latitude, fill = "dat2", size = var2), shape=21, col = "black") +
scale_fill_manual(values = c(dat1 = "red", dat2 ="blue"), labels = c(dat1 = "Data 1", dat2 ="Data 2"))
I am making a map that needs to 2 legends. 1 legend shows the temperature, and another legend shows if points are labelled as zone 1:4. When I try to add the second color scale, for the second legend I get the error
Scale for 'fill' is already present. Adding another scale for
'fill', which will replace the existing scale.
Error: Continuous value supplied to discrete scale
When I don't specify the second color scale I get the map I want however, the points are not the colour I want them to be. I tried using new_scale_color() in the code but that doesn't work either, it's the same error.
Here is some sample data
#Dataframe for Ocean Temperature
mapoc_temp = expand.grid(data.frame(Longitude= seq(-64.5,-62.4,0.1),
Latitude= seq(42.7,44.8,0.1),
year = sample(c(2016,2017,2018), 22, replace = T),
month = sample(month.abb, 22, replace = T)))
mapoc_temp$Ave = runif(nrow(mapoc_temp))
#Dataframe for the points
individual_dets = data.frame(longitude= seq(-64.5,-62.4,0.1),
latitude= seq(42.7,44.8,0.1),
year = sample(c(2016, 2017, 2018), 22, replace = T),
Zone = sample(c(1:4), 22, replace = T),
month = sample(month.abb, 22, replace = T))
This is the code I use to map it, you will need this entire code to see my error.
library(mapdata)
#Import Canada map
canada = map_data("worldHires", "Canada")
## Map of temperature across Scotian Shelf
ggplot(mapoc_temp, aes(x=Longitude, y=Latitude)) +
#plotting the temperature as color
geom_raster(aes(fill = Ave,
x = Longitude), interpolate = TRUE) +
#getting the map of Scotian shelf
geom_polygon(data = canada,
aes(x=long, y=lat, group=group),
colour="grey50", fill = 'grey55')+
#Coordinates I'm interested in looking at
coord_sf(xlim=c(-64.5,-62.8), ylim=c(42.7,45)) +
scale_fill_viridis_c(name = "Temp.\n(°C)", option = "C") +
#Use this to get second legend?
new_scale_color() +
#to try and get zones overtop
geom_point(data = individual_dets,
aes(x = longitude,
y = latitude,
color = as.numeric(Zone),
fill = as.numeric(Zone)),
pch = 21, size = 2) +
#fill the zones with second legend.... Does not work
scale_fill_manual(values=c("#01579B", "#4FC3F7", "#ffa600", "#ff6361")) +
scale_color_manual(values=c("#01579B", "#4FC3F7", "#ffa600", "#ff6361"))
If I don't use the last chunk of code
#fill the zones with second legend.... Does not work
scale_fill_manual(values=c("#01579B", "#4FC3F7", "#ffa600", "#ff6361")) +
scale_color_manual(values=c("#01579B", "#4FC3F7", "#ffa600", "#ff6361"))
I get this picture
But I need the points to be those exact colors I have in the chunk of code. Does anyone know how to add the second legend scale to the ggplot map?
If you don't use a pch = 21, ggplot will use a point with color being the same fill (I guess it is pch = 16 by default), so, you don't need twice scale_fill but instead can manipulate points using scale_color_manual.
Also, you are converting Zone in a numeric format so ggplot2 is plotting it as a continuous scale. So, you can't pass only 4 color values on it. Either, you have to plot Zone as a factor and use scale_color_manual to set the color for each zone (which makes sense) or you can plot Zone as a gradient as you are doing but you need to use scale_color_gradient then to set the color scheme.
library(mapdata)
library(ggplot2)
ggplot(mapoc_temp, aes(x=Longitude, y=Latitude)) +
geom_raster(aes(fill = Ave,
x = Longitude), interpolate = TRUE) +
geom_polygon(data = canada,
aes(x=long, y=lat, group=group),
colour="grey50", fill = 'grey55')+
coord_sf(xlim=c(-64.5,-62.8), ylim=c(42.7,45)) +
scale_fill_viridis_c(name = "Temp.\n(°C)", option = "C") +
geom_point(data = individual_dets,
aes(x = longitude,
y = latitude,
color = as.factor(Zone)),
#fill = as.numeric(Zone)),
size = 2) +
scale_color_manual(values=c("#01579B", "#4FC3F7", "#ffa600", "#ff6361"), name = "Zone")
Does it look what you are trying to achieve ?
As I know gganimate has been in version 1.0.3,we can use transition_* function to plot a dynamic graph. But when I run following code ,there is an error :
Error in `$<-.data.frame`(`*tmp*`, "group", value = "") :
replacement has 1 row, data has 0
code:
library(ggmap)
library(gganimate)
world <- map_data("world")
world <- world[world$region!="Antarctica",]
data <- data.frame(state = c("Alabama","Alaska","Alberta","Alberta","Arizona"),
lon = c(-86.55,-149.52,-114.05,-113.25,-112.05),
lat = c(33.30,61.13,51.05,53.34,33.30)
)
ggplot()+
geom_map(data = world,
map = world,
aes(long,lat,map_id = region),
color = '#333300',
fill = '#663300') +
geom_point(data = data,
aes(x = lon, y = lat),
size = 2.5) +
geom_jitter(width = 0.1) +
transition_states(states = state)
You have no data defined in the top level ggplot() line, so state in transition_* comes out of nowhere.
It's also unclear to me why you have a geom_jitter level in your code. Like transition_*, it has no top level data / aesthetic mappings to inherit, so it would have thrown an error too, had transition_* not triggered an error first. In addition, even if we add the mappings, given the range of lat/lon coordinates in your data, jittering by 0.1 is hardly going to matter visually.
You can try the following:
# put data in top level ggplot()
ggplot(data,
aes(x = lon, y = lat))+
geom_map(data = world,
map = world,
aes(long,lat,map_id = region),
color = '#333300', fill = '#663300',
# lighter background for better visibility
alpha = 0.5) +
geom_point(size = 2.5) +
# limit coordinates to relevant range
coord_quickmap(x = c(-180, -50), y = c(25, 85)) +
transition_states(states = state)