R ggplot modifying the legend to have custom elements - r

I have the following code:
library(ggplot2)
library(RColorBrewer)
colours <-brewer.pal(n = 3, name = 'Paired')
ids <- c("TestA", "TestB", "TestC")
bg <-c(23, 13, 15)
s1 <- c(21,15,17)
s2 <- c(27,25,11)
s3 <- c(24,14,18)
df <- data.frame(ids, bg, s1,s2,s3)
colors <- c("bgs"= "grey", "tid1"=colours[1], "tid2"=colours[2], "tid3"=colours[3])
ggplot(df, aes(x = ids)) +
geom_col(aes(y = bg, color = 'bgs'), fill = 'grey', size = 1, ) +
geom_point(aes(y = s1, color= 'tid1'), size = 10, group = 1) +
geom_point(aes(y = s2, color= 'tid2'), size = 10, group = 1) +
geom_point(aes(y = s3, color='tid3'), size = 10, group = 1)+
labs(x = "Year",
y = "(%)",
color = "Legend") +
scale_color_manual(values = colors)+
theme(legend.position="bottom")
This code produces the following graph:
The issue here is with the legend, i have used the fill = 'grey' in order to fill the bar chart in geom_col(). However you can see that this now produces a grey box over which the other elements in the legend are placed. Is there a way to show just the gray box for the bg data, and show colored circles only for the other data in the legend

Correct me if I'm wrong but I think you just want to remove the graph box in the legend that shows nothing? I'd ask in a comment but I don't have enough reputation yet :(
This is happening because you are specifying color = inside the aes() argument. The easiest way to get rid of this is to get rid of color = bgs.
Since you are working with a barchart, what color = does is add a 'border' of specified color around your graph. Whereas fill = is the color that actually fills in the color of the bars. So here is what the code and the graph would look like:
library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 3.6.3
library(RColorBrewer)
colours <-brewer.pal(n = 3, name = 'Paired')
ids <- c("TestA", "TestB", "TestC")
bg <-c(23, 13, 15)
s1 <- c(21,15,17)
s2 <- c(27,25,11)
s3 <- c(24,14,18)
df <- data.frame(ids, bg, s1,s2,s3)
colors <- c("bgs"= "grey", "tid1"=colours[1], "tid2"=colours[2], "tid3"=colours[3])
ggplot(df, aes(x = ids)) +
geom_col(aes(y = bg), fill = 'grey', size = 1) +
geom_point(aes(y = s1, color = 'tid1'), size = 10, group = 1) +
geom_point(aes(y = s2, color = 'tid2'), size = 10, group = 1) +
geom_point(aes(y = s3, color = 'tid3'), size = 10, group = 1)+
labs(x = "Year",
y = "(%)",
color = "Legend") +
scale_color_manual(values = colors)+
theme(legend.position="bottom")
Created on 2020-09-16 by the reprex package (v0.3.0)
There are other ways to do this too, including override.aes() (I think). If you are curious take a look at Hadley Wickham's book on ggplot2. There is a free pdf here: ggplot2 book
I think you would want to look at chapter 6 which covers the legends

Related

how to change / specify fill color which exceeds the limits of a gradient bar?

In ggplot2/geom_tile, how to change fill color whice exceed the limits?
As the image, Region_4/5 are out of limis(1,11) , so the fill color is default grey, how to change 'Region_4' to 'darkblue', 'Region_5' to 'black' . Thanks!
library(tidyverse)
library(RColorBrewer)
tile_data <- data.frame(category=letters[1:5],
region=paste0('region_',1:5),
sales=c(1,2,5,0.1,300))
tile_data %>% ggplot(aes(x=category,
y=region,
fill=sales))+
geom_tile()+
scale_fill_gradientn(limits=c(1,11),
colors=brewer.pal(12,'Spectral'))+
theme_minimal()
If you want to keep the gradient scale and have two additional discrete values for off limits above and below, I think the easiest way would be to have separate fill scales for "in-limit" and "off-limit" values. This can be done with separate calls to geom_tile on subsets of your data and with packages such as {ggnewscale}.
I think it then would make sense to place the discrete "off-limits" at the respective extremes of your gradient color bar. You need then three geom_tile calls and three scale_fill calls, and you will need to specify the guide order within each scale_fill call. You will then need to play around with the legend margins, but it's not a big problem to make it look OK.
library(tidyverse)
library(RColorBrewer)
tile_data <- data.frame(
category = letters[1:5],
region = paste0("region_", 1:5),
sales = c(1, 2, 5, 0.1, 300)
)
ggplot(tile_data, aes(
x = category,
y = region,
fill = sales
)) +
geom_tile(data = filter(tile_data, sales <= 11 & sales >=1)) +
scale_fill_gradientn(NULL,
limits = c(1, 11),
colors = brewer.pal(11, "Spectral"),
guide = guide_colorbar(order = 2)
) +
ggnewscale::new_scale_fill() +
geom_tile(data = filter(tile_data, sales > 11), mapping = aes(fill = sales > 11)) +
scale_fill_manual("Sales", values = "black", labels = "> 11", guide = guide_legend(order = 1)) +
ggnewscale::new_scale_fill() +
geom_tile(data = filter(tile_data, sales < 1), mapping = aes(fill = sales < 1)) +
scale_fill_manual(NULL, values = "darkblue", labels = "< 1", guide = guide_legend(order = 3)) +
theme_minimal() +
theme(legend.spacing.y = unit(-6, "pt"),
legend.title = element_text(margin = margin(b = 10)))
Created on 2021-11-22 by the reprex package (v2.0.1)
You can try scales::squish, define the limits, and put the out of bound (oob) values into the scalw:
p = tile_data %>% ggplot(aes(x=category,y=region,fill=sales))+ geom_tile()
p + scale_fill_gradientn(colors = brewer.pal(11,"Spectral"),
limit = c(1,11),oob=scales::squish)

geom_point legend colors have inverted shape overlays instead of actual shape

You can see in the below picture that the 'colour' legend is not acting correctly. I am expecting a colored circle (For example color one should be a green circle), however the background is green and you can see the black overlay of what appears to be some type of bounding box.
Here is my code:
library(ggmap)
c_map <- c(left = -86.817417, bottom = 36.133247, right = -86.793141, top = 36.153650)
campus <- get_stamenmap(c_map, zoom = 15, maptype = "toner-lines")
some_data <- read.csv('https://gist.githubusercontent.com/pconwell/085c1413e418adaa7c1e203c9680a0f8/raw/c7a3e5f7aa900de6bc2bcccd5dc5d9b8f7e31b81/some_data.csv')
ggmap(campus, darken = c(0.33, "white"), extent = "device") +
stat_density2d(data = some_data,
aes(x = longitude,
y = latitude,
fill = stat(nlevel),
alpha = stat(nlevel)),
size = 2,
bins = 12,
geom = "polygon"
) +
scale_fill_gradientn(guide = "colorbar",
colours = c("#adddd1", "#3e98af", "#375980"),
breaks=c(0.2, 0.9),
labels=c("Least","Most"),
name = "Heat Map"
) +
geom_point(data = some_data,
aes(x = longitude,
y = latitude,
color = color,
shape = shape),
alpha = .75,
size = 4
) +
scale_alpha_continuous(range = c(0.33, 0.66),
guide = FALSE
)
How can I get the legend to show the colors correctly? The icons appear correctly in the map, but do not show correctly in the legend.
A simplified version of the problem (removing the requirement for ggmap and a Google API key) shows that it's mostly about superfluous arguments. Take the code apart layer by later, since those layers build up what gets included in the legend. The size argument in your stat_density2d doesn't do anything with the geom set to polygon; it would only make sense for contours, such as:
library(ggplot2)
some_data <- read.csv('https://gist.githubusercontent.com/pconwell/085c1413e418adaa7c1e203c9680a0f8/raw/c7a3e5f7aa900de6bc2bcccd5dc5d9b8f7e31b81/some_data.csv')
ggplot(some_data, aes(x = longitude, y = latitude)) +
geom_point(aes(color = color, shape = shape), alpha = 0.75, size = 4) +
stat_density2d(aes(alpha = stat(nlevel)),
size = 2) +
guides(alpha = "none",
color = guide_legend(override.aes = list(fill = NA)))
There you see the effect of size = 2 in both the density and the legend, where it shows as a thick line in the color legend.
Since this isn't necessary for the polygons, remove the size argument or set it to 0. You also had a fill element coming in from the density layer, making those darkened areas on the legend. Remove that by explicitly setting the fill to NA in the legend.
ggplot(some_data, aes(x = longitude, y = latitude)) +
geom_point(aes(color = color, shape = shape), alpha = 0.75, size = 4) +
stat_density2d(aes(fill = stat(nlevel), alpha = stat(nlevel)),
size = 0,
geom = "polygon") +
guides(alpha = "none",
color = guide_legend(override.aes = list(fill = NA)))
The background and the bounding box come from the legend for colour of a polygon. Here’s a little simpler reproducible example:
library(ggplot2)
#> Warning: package 'ggplot2' was built under R version 3.5.3
set.seed(42)
df <- as.data.frame(MASS::mvrnorm(100, c(0, 0), matrix(c(1, .6), 2, 2)))
ggplot(df, aes(V1, V2)) +
stat_density_2d(geom = "polygon") +
geom_point(aes(colour = cut_number(V1, 5)))
You can fix this by explicitly setting the colour of the polygon geom:
ggplot(df, aes(V1, V2)) +
stat_density_2d(geom = "polygon", colour = NA) +
geom_point(aes(colour = cut_number(V1, 5)))
However, I’m a bit surprised that the polygon legend shows up even though the associated layer doesn’t have anything mapped to colour. Perhaps this is a bug?
UPDATE: I couldn’t reproduce this behaviour with other geoms or stats, so I looked into density 2d a bit more: Strangely enough, it seems like the colour legend for the polygon appears because StatDensity2d has a default aesthetic value for colour:
StatDensity2d$default_aes
#> Aesthetic mapping:
#> * `colour` -> "#3366FF"
#> * `size` -> 0.5
StatDensity2d$default_aes <- aes()
ggplot(df, aes(V1, V2)) +
stat_density_2d(geom = "polygon") +
geom_point(aes(colour = cut_number(V1, 5)))
Created on 2019-07-04 by the reprex package (v0.3.0)

How do I add a legend to identify vertical lines in ggplot?

I have a chart that shows mobile usage by operating system. I'd like to add vertical lines to identify when those operating systems were released. I'll go through the chart and then the code.
The chart -
The code -
dev %>%
group_by(os) %>%
mutate(monthly_change = prop - lag(prop)) %>%
ggplot(aes(month, monthly_change, color = os)) +
geom_line() +
geom_vline(xintercept = as.numeric(ymd("2013-10-01"))) +
geom_text(label = "KitKat", x = as.numeric(ymd("2013-10-01")) + 80, y = -.5)
Instead of adding the text in the plot, I'd like to create a legend to identify each of the lines. I'd like to give each of them its own color and then have a legend to identify each. Something like this -
Can I make my own custom legend like that?
1) Define a data frame that contains the line data and then use geom_vline with it. Note that BOD is a data frame that comes with R.
line.data <- data.frame(xintercept = c(2, 4), Lines = c("lower", "upper"),
color = c("red", "blue"), stringsAsFactors = FALSE)
ggplot(BOD, aes( Time, demand ) ) +
geom_point() +
geom_vline(aes(xintercept = xintercept, color = Lines), line.data, size = 1) +
scale_colour_manual(values = line.data$color)
2) Alternately put the labels right on the plot itself to avoid an extra legend. Using the line.data frame above. This also has the advantage of avoiding possible multiple legends with the same aesthetic.
ggplot(BOD, aes( Time, demand ) ) +
geom_point() +
annotate("text", line.data$xintercept, max(BOD$demand), hjust = -.25,
label = line.data$Lines) +
geom_vline(aes(xintercept = xintercept), line.data, size = 1)
3) If the real problem is that you want two color legends then there are two packages that can help.
3a) ggnewscale Any color geom that appears after invoking new_scale_color will get its own scale.
library(ggnewscale)
BOD$g <- gl(2, 3, labels = c("group1", "group2"))
line.data <- data.frame(xintercept = c(2, 4), Lines = c("lower", "upper"),
color = c("red", "blue"), stringsAsFactors = FALSE)
ggplot(BOD, aes( Time, demand ) ) +
geom_point(aes(colour = g)) +
scale_colour_manual(values = c("red", "orange")) +
new_scale_color() +
geom_vline(aes(xintercept = xintercept, colour = line.data$color), line.data,
size = 1) +
scale_colour_manual(values = line.data$color)
3b) relayer The experimental relayer package (only on github) allows one to define two color aethetics, color and color2, say, and then have separate scales for each one.
library(dplyr)
library(relayer)
BOD$g <- gl(2, 3, labels = c("group1", "group2"))
ggplot(BOD, aes( Time, demand ) ) +
geom_point(aes(colour = g)) +
geom_vline(aes(xintercept = xintercept, colour2 = line.data$color), line.data,
size = 1) %>% rename_geom_aes(new_aes = c("colour" = "colour2")) +
scale_colour_manual(aesthetics = "colour", values = c("red", "orange")) +
scale_colour_manual(aesthetics = "colour2", values = line.data$color)
You can definitely make your own custom legend, but it is a bit complicated, so I'll take you through it step-by-step with some fake data.
The fake data contained 100 samples from a normal distribution (monthly_change for your data), 5 groupings (similar to the os variable in your data) and a sequence of dates from a random starting point.
library(tidyverse)
library(lubridate)
y <- rnorm(100)
df <- tibble(y) %>%
mutate(os = factor(rep_len(1:5, 100)),
date = seq(from = ymd('2013-01-01'), by = 1, length.out = 100))
You already use the colour aes for your call to geom_line, so you will need to choose a different aes to map onto the calls to geom_vline. Here, I use linetype and a call to scale_linetype_manual to manually edit the linetype legend to how I want it.
ggplot(df, aes(x = date, y = y, colour = os)) +
geom_line() +
# set `xintercept` to your date and `linetype` to the name of the os which starts
# at that date in your `aes` call; set colour outside of the `aes`
geom_vline(aes(xintercept = min(date),
linetype = 'os 1'), colour = 'red') +
geom_vline(aes(xintercept = median(date),
linetype = 'os 2'), colour = 'blue') +
# in the call to `scale_linetype_manual`, `name` will be the legend title;
# set `values` to 1 for each os to force a solid vertical line;
# use `guide_legend` and `override.aes` to change the colour of the lines in the
# legend to match the colours in the calls to `geom_vline`
scale_linetype_manual(name = 'lines',
values = c('os 1' = 1,
'os 2' = 1),
guide = guide_legend(override.aes = list(colour = c('red',
'blue'))))
And there you go, a nice custom legend. Please do remember next time that if you can provide your data, or a minimally reproducible example, we can better answer your question without having to generate fake data.

How can I add specific, labeled points to a ggtern plot?

With ggplot2 I normally expect to be able to add a data point like so,
ggtern(df, aes(X, Y, Z, value = VALUE), aes(x, y, z)) +
geom_point(aes(fill = VALUE), size = 2, stroke = 0, shape = 21) +
scale_fill_gradient(low = "red",high = "yellow", guide = F) +
scale_color_gradient(low = "red",high = "yellow", guide = F) +
geom_point(aes(x = 10, y = 10, z = 50), shape = 21)
However, when using the ggtern package to generate a ternary diagram they are being inserted into the wrong locations (see example image) with the following warning:
Warning: Ignoring unknown aesthetics: z
This implies that ggplot2 is likely attempting to render the point and not ggtern. How can I add specific, labeled points to the ggtern plot?
It appears that there are two main points for this. The first is to create an annotation, although that might not be ideal since it is not as precise as a point. An example would be,
ggtern() +
annotate(geom = 'text',
x = c(0.5,1/3,0.0),
y = c(0.5,1/3,0.0),
z = c(0.0,1/3,1.0),
angle = c(0,30,60),
vjust = c(1.5,0.5,-0.5),
label = paste("Point",c("A","B","C")),
color = c("green","red",'blue')) +
theme_dark() +
theme_nomask()
The second option would be to create a new data frame and add that to the plot. While this has the advantage of having more control over the point, the disadvantage is that labeling will require additional work.
One possibility is to have a column that identifies the points you wish to label, in this example the column 'lab' and say I want to label points one and three:
df <- data.frame(x=c(10,20,30), y=c(15,25,35), z=c(75,55,35), VALUE=c(1,2,3), lab=c("One", "", "Three"))
Then geom_text or geom_label can be used to label those specific points, for example:
ggtern(df, aes(x, y, z, value = VALUE)) +
geom_point(aes(fill = VALUE), size = 2, stroke = 0, shape = 21) +
scale_fill_gradient(low = "red",high = "yellow", guide = F) +
scale_color_gradient(low = "red",high = "yellow", guide = F) +
geom_text(aes(label = lab), vjust=1)

Multiple legends for a ggplot in R

I have seen existing treads but i couldnt correct my code. I have to divide the legend "Segmentation" into two different legends. One legend should be showing (Run,Walk) and other legend should be telling the StayPoint(Yes, No). Issue is that, all the legend values gets mixed up and comes under the same legend heading. Can anyone tell me about it ? Thank you !
ll_meanstt <- sapply(total_trajectory[1:2], mean)
sq_map2tt <- get_map(location = ll_meanstt, maptype = "roadmap", source
= "google", zoom = 21)
sisquoctt <-
setNames(data.frame(total_trajectory$tt_lon,total_trajectory$tt_lat,
total_trajectory$tt_ids, total_trajectory$Trajectory_Segmentation,
total_trajectory$tt_speed, Staypoint), c("lon", "lat", "LocationID",
"Segmentation", "SpeedMetersPerSecond", "Staypoint"));
ggmap(sq_map2tt) +
geom_point(data = sisquoctt, size = 12, aes(fill = Staypoint, shape =
Staypoint)) +
geom_point(data = sisquoctt, size = 8, aes(fill = Segmentation, shape =
Segmentation)) +
geom_line(data = sisquoctt, size = 3, aes(color =SpeedMetersPerSecond)) +
geom_label_repel (data = sisquoctt, aes(label = paste("",
as.character(LocationID), sep="")),
angle = 60, hjust = 2, color = "indianred3",size = 4)
lon lat LocationID Segmentation SpeedMetersPerSecond Staypoint
1 5.010633 47.29399 W5232 Walk 1.2 No
2 5.010643 47.29400 W5769 Walk 1.0 Yes
3 5.010716 47.29398 W5234 Run 1.5 No
The problem is you have called geom_point() twice for data in the same x, y location and assigned the same aesthetics (fill and shape) to both Staypoint and Segmentation, so ggplot is putting them in the same legend. If you specify fill = for one variable, and shape = for the other, they should go into different legends. Also, not all points have fill aesthetics, you need to either select shapes that do(shapes 21 - 25 have fill), or use color =, an aesthetic that all ggplot2 points have.
Example using color instead of fill
ggmap(sq_map2tt) +
geom_point(data = sisquoctt, size = 8, aes(color = Staypoint, shape = Segmentation)) +
geom_line(data = sisquoctt, size = 3, aes(color = SpeedMetersPerSecond))
another approach if you want to use fill instead of color
ggmap(sq_map2tt) +
geom_point(data = sisquoctt, size = 8, aes(fill = Staypoint, shape = Segmentation)) +
scale_shape_manual(values = c(21, 24) +
geom_line(data = sisquoctt, size = 3, aes(color = SpeedMetersPerSecond))

Resources