I am new to ggplot2 and I am struggling since hours to add a second legend in my plot.
I am using two data.frames (df_1 and df_2) and two geom_point calls for them. I managed to create a legend for df_2 but I was not able to add a second legend for df_1.
Here a code example with also plot:
########## Create sample data
set.seed(69)
df_1 = data.frame(lat = rnorm(20),
lon = rnorm(20),
cor = c(rep('positive', 12), rep('negative', 8)),
sign = 0)
df_2 = data.frame(lat = rnorm(20),
lon = rnorm(20),
cor = c(rep('positive', 7), rep('negative', 13)),
sign = c(rep(99, 5), rep(95, 6), rep(90,9)))
#### Plot data
library(ggplot2)
p = ggplot() +
# geom_point for df_1
geom_point(data=df_1, aes(x=lon, y=lat),
alpha=0.7, color = 'darkgrey', size = 3) +
# geom_point for df_2
geom_point(data=df_2, aes(x=lon, y=lat, size=sign, colour = cor), alpha = 0.5) +
scale_color_manual(values=c("red", "blue"),
name='cor',
labels = c('neg', 'pos'),
guide = guide_legend(override.aes = list(alpha = 1, size = 3))) +
scale_size(range = c(1,3),
breaks = c(90, 95, 99),
labels = c(0.1, 0.05, 0.01),
name = 'sign',
guide = guide_legend(override.aes = list(colour = 'black',
alpha = 1)))
print(p)
How can I add a legend for the geom_point call of df_1?
It would be enough to add a 3rd darkgrey point to cor (right legend) with label 'not sign'.
I guess the straightforward solution is too do what you ask add 3rd darkgrey point to cor legend. To do this you have to:
Change cor values in df_1 to be all the same.
Specify color in df_1 aes.
Add information for the third point in scale_color_manual.
Code:
# Change values so we would have single color for them
df_1$cor <- "foo"
library(ggplot2)
ggplot() +
geom_point(aes(lon, lat, color = cor), df_1,
alpha = 0.7, size = 3) +
geom_point(aes(lon, lat, size = sign, colour = cor), df_2,
alpha = 0.5) +
scale_color_manual(values = c("darkgrey", "red", "blue"),
labels = c("not sign", "neg", "pos"),
guide = guide_legend(override.aes = list(alpha = 1, size = 3))) +
scale_size(range = c(1, 3),
breaks = c(90, 95, 99),
labels = c(0.1, 0.05, 0.01),
guide = guide_legend(override.aes = list(colour = "black", alpha = 1)))
Result:
Related
I have some data from a range of tests that I'm calculating STEN scores for. I'm aiming to visualise this data in the form of a circular bar plot and would like to set the colour gradient based on a STEN score range. For example, a score of 0-2 would be a very light colour, 2.1-4 light, 4.1-6 moderate, 6.1-8 dark and 8.1-10 very dark. My code below uses the RColorBrewer package and the "YlGn" palette, but I'm stuck on how I can predefine the colour scheme based on the example mentioned above and set this in the plot legend. The example below produces a circular bar plot containing a lowest STEN score of 4.8, so I would like this to be reflected as the moderate colour, where currently its the lightest. I essentially want the legend to show all five STEN score ranges irrespective of whether someone's data scores within each range. Hope this makes sense.
library(tidyverse)
library(RColorBrewer)
set.seed(50)
dat <- data.frame(
par = paste("par", 1:15),
test_1 = round(rnorm(15, mean = 30, sd = 5), 1),
test_2 = round(rnorm(15, mean = 30, sd = 5), 1),
test_3 = round_any(rnorm(15, mean = 90, sd = 5), 2.5),
test_4 = round(rnorm(15, mean = 5.4, sd = 0.3), 1),
test_5 = round(rnorm(15, mean = 17, sd = 1.5), 1)
)
sten_dat <- dat %>%
mutate_if(is.numeric, scale) %>%
mutate(across(c(2:6), ~ . * 2 + 5.5)) %>%
mutate(across(where(is.numeric), round, 1)) %>%
pivot_longer(!par, names_to = "test", values_to = "sten") %>%
filter(par == "par 1")
ggplot(sten_dat) +
geom_col(aes(x = str_wrap(test), y = sten, fill = sten),
position = "dodge2", alpha = 0.7, show.legend = TRUE) +
coord_polar() +
scale_y_continuous(limits = c(-1, 11), breaks = seq(0, 10, 2)) +
scale_fill_gradientn(colours = brewer.pal(name = "YlGn", n = 5))`
Simply add limits to your fill scale:
ggplot(sten_dat) +
geom_col(aes(x = str_wrap(test), y = sten, fill = sten),
position = "dodge2", alpha = 0.7, show.legend = TRUE) +
coord_polar() +
scale_y_continuous(limits = c(-1, 11), breaks = seq(0, 10, 2)) +
scale_fill_gradientn(colours = brewer.pal(name = "YlGn", n = 5),
limits = c(0, 10))
If you want the colors to be clearly "binned" in the way you describe, you can use scale_fill_stepn instead of scale_fill_gradientn
ggplot(sten_dat) +
geom_col(aes(x = str_wrap(test), y = sten, fill = sten),
position = "dodge2", alpha = 0.7, show.legend = TRUE) +
scale_y_continuous(limits = c(-1, 11), breaks = seq(0, 10, 2)) +
scale_fill_stepsn(colours = brewer.pal(name = "YlGn", n = 5),
limits = c(0, 10), breaks = 0:5 * 2) +
geomtextpath::coord_curvedpolar() +
theme_minimal() +
theme(axis.text.x = element_text(size = 16, face = 2),
panel.grid.major.x = element_blank())
I generated a heatmap with ggplot, and order the samples by using hclust, However, I still need more reordering to get all the similar values corespondent with one of the samples in the ordered cluster. Here I generate a samples data to explain better.
set.seed(99)
M <- data.frame(names = paste0("g", seq(1,30)), S1 = runif(30, 0 , 8), S2 = runif(30, -4, 5), S3 = runif(30, -5, 5))
M.mat <- M %>%
tibble::column_to_rownames('names') %>%
as.matrix()
M.dendro <- as.dendrogram(hclust(d = dist(x = M.mat)))
dendro.plot <- ggdendrogram(data = M.dendro, rotate = TRUE) +
theme(axis.text.y = element_text(size = 6))
print(dendro.plot)
str(M.dendro)
dend.order <- order.dendrogram(M.dendro)
df <- melt(M, id.vars = "names")
df$names <- factor(x = df$names,
levels = M$names[dend.order],
ordered = TRUE)
ggplot(df, aes(x = names, y = variable, fill = value)) +
geom_tile(color = "black") +
scale_fill_gradient2(low = muted("steelblue"), mid = "white", high = muted("red3"),
midpoint = 0, space = "Lab", na.value = "grey50",
guide = "colourbar", aesthetics = "fill"
) +
theme(axis.text.x = element_text(angle = 90, hjust=1), legend.key.size = unit(0.4, "cm")) +
coord_fixed()
For the generated heatmap, I need reorder it such that all the dark blue be on the bottom, the middle color and then the red on the top based on samples S3. Thank you
I recently asked this question. However, I am asking a separate question now as the scope of my new question falls outside the range of the last question.
I am trying to create a heatmap in ggplot... however, outside of the axis I am trying to plot geom_tile. The issue is I cannot find a consistent way to get it to work. For example, the code I am using to plot is:
library(colorspace)
library(ggplot2)
library(ggnewscale)
library(tidyverse)
asd <- expand_grid(paste0("a", 1:9), paste0("b", 1:9))
df <- data.frame(
a = asd$`paste0("a", 1:9)`,
b = asd$`paste0("b", 1:9)`,
c = sample(20, 81, replace = T)
)
# From discrete to continuous
df$a <- match(df$a, sort(unique(df$a)))
df$b <- match(df$b, sort(unique(df$b)))
z <- sample(10, 18, T)
# set color palettes
pal <- rev(diverging_hcl(palette = "Blue-Red", n = 11))
palEdge <- rev(sequential_hcl(palette = "Plasma", n = 11))
# plot
ggplot(df, aes(a, b)) +
geom_tile(aes(fill = c)) +
scale_fill_gradientn(
colors = pal,
guide = guide_colorbar(
frame.colour = "black",
ticks.colour = "black"
),
name = "C"
) +
theme_classic() +
labs(x = "A axis", y = "B axis") +
new_scale_fill() +
geom_tile(data = tibble(a = 1:9,
z = z[1:9]),
aes(x = a, y = 0, fill = z, height = 0.3)) +
geom_tile(data = tibble(b = 1:9,
z = z[10:18]),
aes(x = 0, y = b, fill = z, width = 0.3)) +
scale_fill_gradientn(
colors = palEdge,
guide = guide_colorbar(
frame.colour = "black",
ticks.colour = "black"
),
name = "Z"
)+
coord_cartesian(clip = "off", xlim = c(0.5, NA), ylim = c(0.5, NA)) +
theme(aspect.ratio = 1,
plot.margin = margin(10, 15.5, 25, 25, "pt")
)
This produces something like this:
However, I am trying to find a consistent way to plot something more like this (which I quickly made in photoshop):
The main issue im having is being able to manipulate the coordinates of the new scale 'outside' of the plotting area. Is there a way to move the tiles that are outside so I can position them in an area that makes sense?
There are always the two classic options when plotting outside the plot area:
annotate/ plot with coord_...(clip = "off")
make different plots and combine them.
The latter option usually gives much more flexibility and way less headaches, in my humble opinion.
library(colorspace)
library(tidyverse)
library(patchwork)
asd <- expand_grid(paste0("a", 1:9), paste0("b", 1:9))
df <- data.frame(
a = asd$`paste0("a", 1:9)`,
b = asd$`paste0("b", 1:9)`,
c = sample(20, 81, replace = T)
)
# From discrete to continuous
df$a <- match(df$a, sort(unique(df$a)))
df$b <- match(df$b, sort(unique(df$b)))
z <- sample(10, 18, T)
# set color palettes
pal <- rev(diverging_hcl(palette = "Blue-Red", n = 11))
palEdge <- rev(sequential_hcl(palette = "Plasma", n = 11))
# plot
p_main <- ggplot(df, aes(a, b)) +
geom_tile(aes(fill = c)) +
scale_fill_gradientn("C",colors = pal,
guide = guide_colorbar(frame.colour = "black",
ticks.colour = "black")) +
theme_classic() +
labs(x = "A axis", y = "B axis")
p_bottom <- ggplot() +
geom_tile(data = tibble(a = 1:9, z = z[1:9]),
aes(x = a, y = 0, fill = z, height = 0.3)) +
theme_void() +
scale_fill_gradientn("Z",limits = c(0,10),
colors = palEdge,
guide = guide_colorbar(
frame.colour = "black", ticks.colour = "black"))
p_left <- ggplot() +
theme_void()+
geom_tile(data = tibble(b = 1:9, z = z[10:18]),
aes(x = 0, y = b, fill = z, width = 0.3)) +
scale_fill_gradientn("Z",limits = c(0,10),
colors = palEdge,
guide = guide_colorbar( frame.colour = "black", ticks.colour = "black"))
p_left + p_main +plot_spacer()+ p_bottom +
plot_layout(guides = "collect",
heights = c(1, .1),
widths = c(.1, 1))
Created on 2021-02-21 by the reprex package (v1.0.0)
I am plotting (mapping) sf objects with ggplot2. My understanding is that since version 2.2.1 ggplot2 contains the geom geom_sf, for simple feature objects.
I can produce the exact map that I want by doing the following:
library(sf)
library(ggplot2)
# some points to start with
a <- st_as_sf(data.frame(lon = c(1,4,6), lat = c(0,0,-3)), coords = c('lon', 'lat'))
b <- st_as_sf(data.frame(lon = c(2.5,4), lat = c(-4.5,-5)), coords = c('lon', 'lat'))
# circles around those points
buf.a <- st_buffer(a, 1)
buf.b <- st_buffer(b, 1)
# colors to mark the points
sol.a = rgb(1,0,0)
sol.b = rgb(0,0,1)
# colors to fill the circles
fil.a = adjustcolor(sol.a, alpha.f = .25)
fil.b = adjustcolor(sol.b, alpha.f = .25)
# the plot I want
g = ggplot() +
geom_sf(data = buf.a, fill = fil.a, color = NA) +
geom_sf(data = buf.b, fill = fil.b, color = NA) +
geom_sf(data = a, color = sol.a, shape = 20, size = 3) +
geom_sf(data = b, color = sol.b, shape = 20, size = 3)
g
which produces
This is what I want except that it is missing a legend. For that, I am doing
cols.fill = c("GROUP A" = fil.a, "GROUP B" = fil.b)
cols.sol = c("GROUP A" = sol.a, "GROUP B" = sol.b)
g = ggplot() +
geom_sf(data = buf.a, color = NA, aes(fill = 'GROUP A')) +
geom_sf(data = buf.b, color = NA, aes(fill = 'GROUP B')) +
geom_sf(data = a, shape = 20, size = 3, aes(color = 'GROUP A')) +
geom_sf(data = b, shape = 20, size = 3, aes(color = 'GROUP B')) +
scale_fill_manual(name = "circles", values = cols.fill) +
scale_color_manual(name = "points", values = cols.sol)
g
which gives
That's not what I want, because in the legend:
'points' should be points (not squares); and
'circles' should be, well, circles (again, not squares)
Would be nice if the legend could respect the transparency of my colors (which it did in this example).
I tried to change the last couple of lines of the above to something like
scale_fill_manual(name = "circles", values = cols.fill,
guide = guide_legend(override.aes = list(shape = c(19, 19)))) +
scale_color_manual(name = "points", values = cols.sol,
guide = guide_legend(override.aes = list(shape = c(20, 20))))
but that didn't do anything to my plot.
Ideas?
Note: If it ends up being simpler for the plot, I could change the structure of the data, e.g., by combining objects a and b in the same simple feature object and add a column indicating the group (same for buf.a and buf.b).
Here's how far I managed to get to.
g = ggplot() +
geom_sf(data = buf.a, color = NA, aes(fill = 'GROUP A'), show.legend = "point") +
geom_sf(data = buf.b, color = NA, aes(fill = 'GROUP B'), show.legend = "point") +
geom_sf(data = a, shape = 20, size = 3, aes(color = 'GROUP A'), show.legend = "point") +
geom_sf(data = b, shape = 20, size = 3, aes(color = 'GROUP B'), show.legend = "point") +
scale_color_manual(name = "points", values = cols.sol,
guide = guide_legend(override.aes = list(shape = c(20, 20)))) +
scale_fill_manual(name = "circles", values = cols.fill,
guide = guide_legend(override.aes = list(shape = c(20, 20), color = cols.fill, size = 8)))
g
To get rid of the gray background in the legend symbols,
g + theme(legend.key = element_rect(fill = "white"))
The only issue here is that the circles do not have the transparency I wanted. This is odd.
To get the transparency in the legend need to add it to the override.aes, try:
guide = guide_legend(override.aes = list(alpha = 0.5, shape = c(20, 20), color = cols.fill, size = 8, )))
Has been now a few months I am working with ggplot2 but I still get stuck very easily on basic things, as the options out here are close to infinite.
Let's assume I have a simple plot created as follows:
set.seed(100)
df_1 = data.frame(lat = rnorm(20),
lon = rnorm(20),
x = rnorm(20))
library(ggplot2)
p = ggplot() +
geom_point(data = df_1,
aes(x=lon, y=lat, fill = x),
size = 5, colour = 'black', pch = 21) +
scale_fill_gradient2(low = "green", mid = 'white', high = "yellow",
breaks = c(-1, 0, 1),
labels = c('-1', '0', '1'),
limits = c(-1,1))
print(p)
How can I add a second legend with title (e.g. y) showing only one of those circles with white background and black contour?
To add extra element to legend, you have to add it to a plot. You can do this with:
geom_point(aes(alpha = ""), head(df_1, 1),
size = 5, fill = "white", pch = 21) +
Here we are adding first point in your dataset, setting it's fill and dummy alpha value (we need to set something within aes to add it to legend). I'm using "" so we won't have any text next to a point.
Also, it's important to add this point before main geom_point because it will cover original point (with white fill). You also need to reset alpha values from "" to 1 and to set wanted legend name for alpha in labs().
library(ggplot2)
ggplot(df_1, aes(lon, lat, fill = x)) +
geom_point(aes(alpha = ""), head(df_1, 1),
size = 5, fill = "white", pch = 21) +
geom_point(size = 5, pch = 21) +
scale_fill_gradient2(low = "green", mid = "white", high = "yellow",
breaks = c(-1, 0, 1),
labels = c("-1", "0", "1"),
limits = c(-1, 1)) +
scale_alpha_manual(values = 1) +
labs(alpha = "y")
PS. I have made some changes in your ggplot2 code:
You can specify data and aes within first ggplot call.
In geom layers aes is first argument, data is second. So instead of geom_point(data = df_1, aes(...). You have use geom_point(aes(...), df_1).
color = "black" is a default setting - you don't need to specify it.
You could add a factor with one level and use scale_color_manual:
set.seed(100)
df_1 = data.frame(lat = rnorm(20),
lon = rnorm(20),
x = rnorm(20),
new = rep('Coordinates', 20))
library(ggplot2)
p = ggplot() +
geom_point(data = df_1,
aes(x=lon, y=lat, fill = x, colour = new),
size = 5, pch = 21) +
scale_fill_gradient2(low = "green", mid = 'white', high = "yellow",
breaks = c(-1, 0, 1),
labels = c('-1', '0', '1'),
limits = c(-1,1)) +
scale_color_manual(name = "", values = "black")
print(p)