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, )))
Related
I have this data frame :
Raw.Score = c(0,1,2,3,4,5,6,7,8)
Severity = c(-3.56553994,-2.70296933,-1.63969850,-0.81321707,-0.04629182,
0.73721320,1.61278518,2.76647043,3.94804472)
x = data.frame(Raw.Score = Raw.Score, Severity = Severity)
Raw.score are raw numbers from 0 to 8 (let's consider them as the labels of the severity numbers)
Severity are relative numbres that represent the locations of the scores in the diagram
I want to graphically present the results as in the following example using ggplot (the example includes different numbers but I want something similar)
As a fun exercise in ggplot-ing here is one approach to achieve or come close to your desired result.
Raw.Score = c(0,1,2,3,4,5,6,7,8)
Severity = c(-3.56553994,-2.70296933,-1.63969850,-0.81321707,-0.04629182,
0.73721320,1.61278518,2.76647043,3.94804472)
dat <- data.frame(Raw.Score, Severity)
library(ggplot2)
dat_tile <- data.frame(
Severity = seq(-4.1, 4.1, .05)
)
dat_axis <- data.frame(
Severity = seq(-4, 4, 2)
)
tile_height = .15
ymax <- .5
ggplot(dat, aes(y = 0, x = Severity, fill = Severity)) +
# Axis line
geom_hline(yintercept = -tile_height / 2) +
# Colorbar
geom_tile(data = dat_tile, aes(color = Severity), height = tile_height) +
# Sgements connecting top and bottom labels
geom_segment(aes(xend = Severity, yend = -ymax, y = ymax), color = "orange") +
# Axis ticks aka dots
geom_point(data = dat_axis,
y = -tile_height / 2, shape = 21, stroke = 1, fill = "white") +
# ... and labels
geom_text(data = dat_axis, aes(label = Severity),
y = -tile_height / 2 - .1, vjust = 1, fontface = "bold") +
# Bottom labels
geom_label(aes(y = -ymax, label = scales::number(Severity, accuracy = .01))) +
# Top labels
geom_point(aes(y = ymax, color = Severity), size = 8) +
geom_text(aes(y = ymax, label = Raw.Score), fontface = "bold") +
# Colorbar annotations
annotate(geom = "text", fontface = "bold", label = "MILD", color = "black", x = -3.75, y = 0) +
annotate(geom = "text", fontface = "bold", label = "SEVERE", color = "white", x = 3.75, y = 0) +
# Fixing the scales
scale_x_continuous(expand = c(0, 0)) +
scale_y_continuous(limits = c(-ymax, ymax)) +
# Color gradient
scale_fill_gradient(low = "orange", high = "red", guide = "none") +
scale_color_gradient(low = "orange", high = "red", guide = "none") +
# Get rid of all non-data ink
theme_void() +
# Add some plot margin
theme(plot.margin = rep(unit(10, "pt"), 4)) +
coord_cartesian(clip = "off")
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 have a ggplot problem. Here is the example data:
df <- data.frame(x = rep(1:5,5),
type2 = c(rep(letters[1:2],each = 10),rep("c",5)),
type1 = rep(LETTERS[1:5],each = 5),
value = unlist(lapply(-2:2,function(a){rnorm(5,mean = a, sd = 1)})))
library(ggplot2)
plotcolor <- c( "#99d8c9","#2ca25f","#cbc9e2","#9e9ac8","#e34a33")
p <- ggplot(df,aes(x,value,color = type1,fill = type1,shape = type2))+
geom_point(size = 5)+
theme_light()+
labs(title = "",
color = "Method",
fill = "Method",
shape = "")+
geom_hline(yintercept = 0)+
guides(colour = guide_legend(override.aes = list(shape = c(21,21,24,24,22),
linetype = c(rep("blank",5)),
fill = plotcolor,
color = plotcolor)))+
scale_shape(guide = FALSE)+
scale_colour_manual(values = plotcolor)
p
which gives
Now I want to split the legend into two columns, for space reasons. I tried
p + guides(color=guide_legend(ncol=2))
but it remove the override part of my legend, letting just points:
p + guides(color=guide_legend(ncol=2),
fill =guide_legend(ncol=2) ,
shape = guide_legend(ncol=2))
didn't work either. Does anyone have an idea on how to deal with this particular problem?
You can specify ncol within the existing guide_legend (do not use it multiple times):
guides(colour = guide_legend(override.aes = list(shape = c(24,24,22,22,21),
linetype = c(rep("blank",5)),
fill = plotcolor,
color = plotcolor),
ncol = 2))+
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:
I am dealing with multiple sf geometries in ggplot and would like to display legend in the form of a point, a line, and a square (for the polygon). However, geom_sf legend combines my geometry features (i.e. combining line and point) displayed below:
library(ggplot2)
library(sf)
poly1 <- cbind(lon = c(5, 6, 7, 5), lat = c(52, 53, 51, 52))
poly <- st_sf(st_sfc(st_polygon(list(poly1))))
line <- st_sf(st_sfc(list(st_linestring(cbind(lon = c(5.5, 4.5), lat = c(53.5, 54.5))))))
point <- st_sf(st_sfc(st_point(cbind(lon = 5.5, lat = 52.7))))
ggplot() +
geom_sf(data = poly, aes(fill = "A")) +
geom_sf(data = point, aes(colour = "B"), show.legend = "point") +
geom_sf(data = line, aes(colour = "C"), show.legend = "line") +
scale_fill_manual(values = c("A" = "yellow")) +
scale_colour_manual(values = c("B" = "pink", "C" = "purple")) +
theme_minimal()
I would like three separate legends, a single yellow square, a pink point, and a purple line on the same image illustrated below. It is only the case when I plot individual geometry but not the combination of three.
I looked up similar topics, but none of them dealt with point geometries i.e. https://github.com/tidyverse/ggplot2/issues/2460
Would anyone offer any insight on this?
GitHub issue: https://github.com/tidyverse/ggplot2/issues/2763
Inspired by the comment from #Axeman, this issue and this post, the problem is solved using the override.aes argument in guide_legend():
library(ggplot2)
library(sf)
poly1 <- cbind(lon = c(5, 6, 7, 5), lat = c(52, 53, 51, 52))
poly <- st_sf(st_sfc(st_polygon(list(poly1))))
line <- st_sf(st_sfc(list(st_linestring(cbind(lon = c(5.5, 4.5), lat = c(53.5, 54.5))))))
point <- st_sf(st_sfc(st_point(cbind(lon = 5.5, lat = 52.7))))
ggplot() +
geom_sf(data = poly, aes(fill = "A")) +
geom_sf(data = point, aes(colour = "B"), show.legend = "point") +
geom_sf(data = line, aes(colour = "C"), show.legend = "line") +
scale_fill_manual(values = c("A" = "yellow"), name = NULL,
guide = guide_legend(override.aes = list(linetype = "blank", shape = NA))) +
scale_colour_manual(values = c("B" = "pink", "C" = "purple"), name = NULL,
guide = guide_legend(override.aes = list(linetype = c("blank", "solid"),
shape = c(16, NA)))) +
theme_minimal()
I know how to seperate the legends, they only get combined now because you are mapping color twice. By mapping shape to the points and setting the color, you can get around that:
ggplot() +
geom_sf(data = poly, aes(fill = "A")) +
geom_sf(data = point, aes(colour = "B"), show.legend = "point") +
geom_sf(data = line, aes(shape = "C"), show.legend = "line", color = 'purple') +
scale_fill_manual(name = NULL, values = c("A" = "yellow")) +
scale_colour_manual(name = NULL, values = c("B" = "pink")) +
scale_shape_discrete(
name = NULL,
guide = guide_legend(override.aes = list(color = 'purple'))) +
theme_minimal()
But: the point and line are still showing up in all three legends. I don't think they should! Perhaps you can fill a github issue.