Broken polygons in levelplot using ggplot stat_density_2d - r

Creating a levelplot using ggplot's stat_density_2d I get "broken" polygons. For example, the outer one in the example below.
How can I fix this, to get a smooth form?
set.seed(0)
n <- 50
d <- data.frame(x = rnorm(n, -.7, .5),
y = rnorm(n, 0, .8))
ggplot(d, aes(x, y)) +
geom_point() +
stat_density_2d(aes(fill = ..level..), alpha=.1, geom = "polygon")

To build on #hrbrmstr's answer (which, at least on my machine, lops off one data point because the x scale isn't sufficiently wide), a slightly more involved approach would be to get the limits of the data, set the scale limits, then reset the plot limits back to the original range:
g <- ggplot(d, aes(x, y)) +
geom_point() +
stat_density_2d(aes(fill = ..level..), alpha=.1, geom = "polygon")
dat_lims <- lapply(d, function(v) c(min(v), max(v)))
plot_lims <- ggplot_build(g)$panel$ranges[[1]][c("x.range", "y.range")]
g +
scale_x_continuous(limits = dat_lims$x * 1.1) +
scale_y_continuous(limits = dat_lims$y * 1.1) +
coord_cartesian(xlim = plot_lims$x.range, ylim = plot_lims$y.range)
Output:

Related

ggplot2 and log scale don't show values = 1

I want to plot a diagram with a log scales y axis:
data <- data.frame(x = seq_len(5), y = c(2,1,100,500,30))
ggplot(data) + aes(x,y) + geom_col() + scale_y_log10()
But because the horizontal axis always crosses the vertical axis at y = 1, the bar for y = 1 is never drawn.
I know that log(1) = 0, but can I set the plot base to 0.1 to let the bar start below 1?
Can't you just multiply the values of y by 10 but divide the labels by the same amount?
ggplot(data) + aes(x, y * 10) + geom_col() +
scale_y_log10(labels = function(x) x/10, name = "y")
You could do the same with a histogram:
set.seed(2)
ggplot(data.frame(x = rnorm(1000)), aes(x)) +
geom_histogram(aes(y = ..count.. * 10), color = "black", fill = "gold") +
scale_y_log10(labels = function(x) x/10, name = "count")
An alternative solution is to place the bottom of the bars at -Inf, so that the log10(1) still shows up. This is a bit tricky, because you have to reparameterise the bars as rectangles and get the geom to understand that the -Inf is after scale transformation.
library(ggplot2)
data <- data.frame(x = seq_len(5), y = c(2,1,100,500,30))
ggplot(data) +
geom_rect(
aes(xmin = x - 0.45, xmax = x + 0.45,
ymin = stage(1, after_scale = -Inf), ymax = y)
) +
scale_y_log10()
Created on 2020-11-03 by the reprex package (v0.3.0)

Draw a box around the legend graphic in ggplot?

A similar sounding question is asked here. However, in the linked question, they put a bounding box around the legend and legend title. I was wondering if it's possible to put a bounding box around the legend graphic. For example,
library(ggplot2)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile()+
scale_fill_gradient(low = "white" ,high = "red")
The above code creates this plot:
Whereas, I am trying to create something like this:
I tried playing with legend.background function from ggplot but I can't get it to work for me.
Any suggestions as to how I would do this?
I don't think you can do this with theme() - you can do it with guides() though, e.g.
library(ggplot2)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap
ggplot(data, aes(X, Y, fill= Z)) +
geom_tile()+
scale_fill_gradient(low = "white" ,high = "red") +
guides(fill = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
Edit per comment
Looking at the source code for new_scale() there should be a way to apply this solution to two of the same scales, but I can't figure it out. I reckon you should post another question and see if someone can solve it. Until then, maybe this workaround based on cowplot will work for you e.g.
## (I changed 'fill' to 'color' here, but the concept is the same)
library(ggplot2)
library(ggnewscale)
library(cowplot)
# Dummy data
x <- LETTERS[1:20]
y <- paste0("var", seq(1,20))
data <- expand.grid(X=x, Y=y)
data$Z <- runif(400, 0, 5)
# Heatmap with both scales but legends aren't plotted
p1 <- ggplot(data, aes(X, Y, color = Z)) +
geom_tile() +
scale_color_gradient(low = "white", high = "red") +
new_scale_color() +
geom_point(aes(color = Z)) +
theme(legend.position = "none")
# Heatmap with only the first scale
p2 <- ggplot(data, aes(X, Y, color = Z)) +
geom_tile() +
scale_color_gradient(low = "white", high = "red") +
guides(color = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
# Heatmap with only the second scale
p3 <- ggplot(data, aes(X, Y, color = Z)) +
geom_point(aes(color = Z)) +
guides(color = guide_colorbar(frame.colour = "black", frame.linewidth = 1.5))
# Grab the legends using cowplot::get_legend()
p2_legend <- get_legend(p2)
p3_legend <- get_legend(p3)
# Combine the legends one on top of the other
legends <- plot_grid(p2_legend, p3_legend, ncol = 1, nrow = 2)
# Combine the heatmap with the legends
plot_grid(p1, legends, ncol = 2, align = "h", rel_widths = c(0.9, 0.1))
## You may need to tinker with spacing/scale/rel_widths/rel_heights to get it looking right, but it should work out ok with some effort

Pass changed geom from object to other ggplot

I first make a plot
df <- data.frame(x = c(1:40, rep(1:20, 3), 15:40))
p <- ggplot(df, aes(x=x, y = x)) +
stat_density2d(aes(fill='red',alpha=..level..),geom='polygon', show.legend = F)
Then I want to change the geom_density values and use these in another plot.
# build plot
q <- ggplot_build(p)
# Change density
dens <- q$data[[1]]
dens$y <- dens$y - dens$x
Build the other plot using the changed densities, something like this:
# Built another plot
ggplot(df, aes(x=x, y =1)) +
geom_point(alpha = 0.3) +
geom_density2d(dens)
This does not work however is there a way of doing this?
EDIT: doing it when there are multiple groups:
df <- data.frame(x = c(1:40, rep(1:20, 3), 15:40), group = c(rep('A',40), rep('B',60), rep('C',26)))
p <- ggplot(df, aes(x=x, y = x)) +
stat_density2d(aes(fill=group,alpha=..level..),geom='polygon', show.legend = F)
q <- ggplot_build(p)
dens <- q$data[[1]]
dens$y <- dens$y - dens$x
ggplot(df, aes(x=x, y =1)) +
geom_point(aes(col = group), alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = fill, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F)
Results when applied to my own dataset
Although this is exactly what I'm looking for the fill colors seem not to correspond to the initial colors (linked to A, B and C):
Like this? It is possible to plot a transformation of the shapes plotted by geom_density. But that's not quite the same as manipulating the underlying density...
ggplot(df, aes(x=x, y =1)) +
geom_point(alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = fill, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F)
Edit - OP now has multiple groups. We can plot those with the code below, which produces an artistic plot of questionably utility. It does what you propose, but I would suggest it would be more fruitful to transform the underlying data and summarize that, if you are looking for representative output.
ggplot(df, aes(x=x, y =1)) +
geom_point(aes(col = group), alpha = 0.3) +
geom_polygon(data = dens, aes(x, y, fill = group, group = piece, alpha = alpha)) +
scale_alpha_identity() +
guides(fill = F, alpha = F) +
theme_minimal()

overlaying plots from different dataframes in ggplot without messing with legend

I want to overlay two plots: one is a simple point plot where a variable is used to control the dot size; and another is a simple curve.
Here is a dummy example for the first plot;
library(ggplot2)
x <- seq(from = 1, to = 10, by = 1)
df = data.frame(x=x, y=x^2, v=2*x)
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla")
Now lets overlay a curve to this plot with data from another dataframe:
df2 = data.frame(x=x, y=x^2-x+2)
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla") + geom_line(data=df2, aes(x, y), color = "blue") + scale_color_discrete(name = "other", labels = c("nanana"))
It produces the error:
Error in FUN(X[[i]], ...) : object 'v' not found
The value in v is not used to draw the intended curse, but anyway, I added a dummy v to df2.
df2 = data.frame(x=x, y=x^2-x+2, v=replicate(length(x),0)) # add a dummy v
ggplot(df, aes(x, y, size = v)) + geom_point() + theme_classic() + scale_size("blabla") + geom_line(data=df2, aes(x, y), color = "blue") + scale_color_discrete(name = "other", labels = c("nanana"))
An the result has a messed legend:
What is the right way to achieve the desired plot?
You can put the size aes in the geom_point() call to make it so that you don't need the dummy v in df2.
Not sure exactly what you want regarding the legend. If you replace the above, then the blue portion goes away. If you want to have a legend for the line color, then you have to place color inside the geom_line aes call.
x <- seq(from = 1, to = 10, by = 1)
df = data.frame(x=x, y=x^2, v=2*x)
df2 = data.frame(x=x, y=x^2-x+2)
ggplot(df, aes(x, y)) +
geom_point(aes(size = v)) +
theme_classic() +
scale_size("blabla") +
geom_line(data=df2, aes(x, y, color = "blue")) +
scale_color_manual(values = "blue", labels = "nanana", name = "other")

Combining a map and a XY ggplot chart in R

I need to combine a map, which represents an archaeological site, with an XY ggplot chart of different archaeological objects. The map is in a tiff file and must respect its proportion.
First, this is the map, highlighting the reference scales in red (in X axis, from -6000 to -4000 there are 20 meters of distance, for instance; in Y axis, from 900 to 2100 there are 12 meters).
My ggplot chart is obtained by running this code:
archaeo <- ggplot() +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2+250, b = Diameter_N.S/2+250, angle = 0),
lwd=0, col="darkgray", fill="gray", alpha=0.15) +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2+120, b = Diameter_N.S/2+120, angle = 0),
lwd=0, col="darkgray", fill="gray", alpha=0.25) +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2, b = Diameter_N.S/2, angle = 0),
lwd=0.5, col="darkgray", fill="gray", alpha=0.75) +
geom_point(data=Unit_H, aes(X, Y), size = 0.5) +
geom_point(data=Refits_H_trans, aes(x,y,group=sample, colour=factor(sample))) +
geom_line(data=Refits_H_trans, lwd=0.2, lty=1, aes(x,y, group=sample, colour=factor(sample))) +
coord_fixed() +
theme_bw() +
theme(legend.position="none") +
ggtitle("Unit H") +
xlim(-6600,-3800) +
ylim(400,2400)
The resulting chart is:
Now, my problem, which deals with the inclusion of the map as background of the ggplot. I used background_image() from ggpubr, with this result:
map_levelH <- readPNG("Planta H-I.png")
Map.archaeo <- ggplot() +
background_image(map_levelH) +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2+250, b = Diameter_N.S/2+250, angle = 0),
lwd=0, col="darkgray", fill="gray", alpha=0.15) +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2+120, b = Diameter_N.S/2+120, angle = 0),
lwd=0, col="darkgray", fill="gray", alpha=0.25) +
geom_ellipsis(data=Unit_H,
aes(x0 = X, y0 = Y, a = Diameter_E.W/2, b = Diameter_N.S/2, angle = 0),
lwd=0.5, col="darkgray", fill="gray", alpha=0.75) +
geom_point(data=Unit_H, aes(X, Y), size = 0.5) +
geom_point(data=Refits_H_trans, aes(x,y,group=sample, colour=factor(sample))) +
geom_line(data=Refits_H_trans, lwd=0.2, lty=1, aes(x,y, group=sample, colour=factor(sample))) +
coord_fixed() +
theme_bw() +
theme(legend.position="none") +
ggtitle("Unit H") +
xlim(-6600,-3800) +
ylim(400,2400)
As you can see, scales of the ggplot and the map don't match. So, my questions are:
How can I georeference the map with the values of the ggplot X and Y axes?
I need to keep the proportion of the image, in order not to distort it. How can I do it? I am asking this because if I change the xlim values, the image also change and its proportion changes.
I've recreated a simple example where this problem exists:
library(tidyverse)
# create the background
bck_square <- data.frame(x=c(1,1,0,0),y=c(0,1,1,0))
p <- ggplot(bck_square, aes(x=x, y=y)) +
geom_point(size=10, color="red") +
theme(panel.border=element_blank(),
panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),
#panel.background=element_blank(), keep the background to see where image ends
axis.text=element_blank(),
axis.ticks=element_blank(),
axis.title=element_blank())
p
I will save the image to be used as a background for my figures:
ggsave("temp.png",p)
img <- readPNG("temp.png")
Setting the background using background_image from the ggpubr package, the old square does not line up with the new square, even though the data is the same. This is expected, as ggsave adds a thin border around the image
library(ggpubr)
ggplot(bck_square, aes(x, y)) +
background_image(img) +
geom_point()
However, by using annotation_custom instead (see this guide), you can adjust where the minimum and maximum for the image should be. Playing around with the border parameters I was able to get the image background and figure to line up.
library(png)
library(grid)
min_border <- .064
max_border <- .061
ggplot(bck_square, aes(x, y)) +
annotation_custom(g,xmin=-min_border, xmax=1+max_border, ymin=-min_border, ymax=1+max_border) +
geom_point()
This method should work with a tiff file. Another potential solution could be spatial data transformations using rspatial (see the documentation here) but this may over-complicate the issue.

Resources