ggplot2 geom_pointline doesn't link centers of points - r

I would like to create a plot with points and lines between them, but with spaces, in ggplot2, R. I have a shaded area in the plot, so some parts of points has gray and white background. I found lemon library with geom_pointline function.
ggplot(data = dt, aes(x = x, y = y)) +
geom_ribbon(aes(ymin = min, ymax = max), fill = "gray", alpha = 0.35) +
geom_pointline(shape = 19, linecolor = "black", size = 4, color = "blue", distance = 2)
The result I get is shown below. As one can notice, the lines don't start and end in the middle of points, but rather at the top right and bottom left of the point. It gets even worse when I shorten the lines. I tried with many parameters but couldn't solve it. I would like the lines to start and end closer to the middle than it is now.
Thanks in advance!

If switching to an other package is an option for you then one option to achieve your desired result would be ggh4x::geom_pointpath whichs similar to geom_pointline adds some padding around points along a line or path. One drawback is that TBMK it has no option to set different colors for the points and the lines. A hack would be to draw the lines via ggh4x::geom_pointpath then add a geom_point on top of it.
Using some fake example data:
set.seed(123)
dt <- data.frame(
x = seq(20, 160, 20),
y = 1:8,
min = 1:8 - runif(8),
max = 1:8 + runif(8)
)
library(ggplot2)
library(ggh4x)
ggplot(data = dt, aes(x = x, y = y)) +
geom_ribbon(aes(ymin = min, ymax = max), fill = "gray", alpha = 0.35) +
geom_pointpath(shape = 19, size = 4, color = "black", mult = .25) +
geom_point(shape = 19, size = 4, color = "blue")

Related

Maintaining Aspect Ratio of Shapes and Images in ggplot/ ggimage

I'm trying to build a visualisation with both drawn shapes (e.g. using geom_circle) and images. In both cases, I want to be able to position them on the page specifically with coordinates, rather than using one of the built in chart types.
See update further down...
However, I can either get the circles with the correct aspect ratio (i.e. round not oval) or the images, but not both. In the example below, you can see the image is not shown as square when it is.
I have tried various things including coord_fixed, scale_size_identity and coord_cartesian, but to no avail. The overall output will be landscape, which is why I have set the cartesian limits as I have.
This is a simplified version. In the full version, I'll get the coordinates from the data (which I'm fine with).
images <-data.frame(url = c("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"))
ggplot(mpg) +
ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty)) +
#scale_size_identity() +
# Add Image
ggimage::geom_image(data = images,
aes(x = 4, y = 20, image=url),
size = 0.4,
hjust = 0.0,
by="height"
) +
coord_cartesian(
xlim = c(0, 120),
ylim = c(0, 80),
expand = FALSE,
clip = "on"
)
Update following really helpful input from #tjebo and further investigation.
I have now found there are at least 4 ways to add images to plots, each with their own advantages and disadvantages. I've listed these below to help others on this search.
Draw basic shapes to which images can be added
g <- ggplot(mpg) +
ggforce::geom_circle(aes(x0 = displ * 50 - 60, y0 = hwy, r=cty))
Plot with ggtexture - multiple images - aspect defined by x and y max - min
https://rdrr.io/github/clauswilke/ggtextures/man/geom_textured_rect.html
g + ggtextures::geom_textured_rect(data = images,
aes(xmin = 20, xmax = 60,
ymin = 20, ymax = 60, image = url),
lty = "blank", # line type of blank to remove border
fill="white", # used to fill transparent areas of image
nrow = 1,
ncol = 1,
img_width = unit(1, "null"),
img_height = unit(1, "null"),
position = "identity") +
coord_equal() # A fixed scale coordinate system forces a specified ratio between the physical representation of data units on the axes.
Plot with ggimage - multiple images - aspect defined by device
g + ggimage::geom_image(data = images,
aes(x = 20, y = 40, image=url),
size = 0.2,
by="height"
)
Plot with cowplot - single image - freedom over aspect
Independent drawing surface and scale (0-1)
cowplot::ggdraw(g) +
cowplot::draw_image(images[[1, "url"]],
x = .5, y = .3, width = 0.5, height = 0.5)
Plot with annotation_custom (ggplot) - original aspect
Seems to use widest of width of height and centre on mid coordinates
image <- magick::image_read(images[[1, "url"]])
rasterImg <- grid::rasterGrob(image, interpolate = FALSE)
g + annotation_custom(rasterImg, xmin = 00, xmax =200, ymin = 10, ymax = 50)
I strongly feel you may have seen this thread: Can geom_image() from the ggimage package be made to preserve the image aspect ratio? - this was asked and answered by ggplot2 gurus such as Claus Wilke, Baptiste and Marco Sandri - it seems that ggimage is scaling to the device. Thus if you would like a square, you need to save to a device of square dimensions. Or, if you don't have a square image, of course, dimensions relative to your image used.
I used see::geom_point2 (no weird border), because I got the strong feeling that you have not used ggforce::geom_circle. Also mild changes where I added the aes call.
library(ggimage)
#> Loading required package: ggplot2
images <-data.frame(url = c("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"))
# g <-
ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
scale_size_identity() +
# Add Image
geom_image(data = images,
aes(x = 4, y = 20, image=url),
size = 0.4,
hjust = 0.0,
by = "height"
)
Using reprex, with both fig width and height set to 3 inches
Created on 2021-02-13 by the reprex package (v1.0.0)
A different approach would be to not use ggimage - e.g., use cowplot for custom annotation, makes it super simple to add an image.
library(ggplot2)
library(cowplot)
p <- ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
scale_size_identity()
ggdraw(p) +
draw_image("https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG",
x = .5, y = .3, width = 0.5, height = 0.5)
Created on 2021-02-13 by the reprex package (v1.0.0)
Or, use the ggtextures package, with a little tweak of the coordinate system
this discussion seems relevant
library(ggtextures)
library(ggplot2)
library(tibble)
img_df <- tibble(
xmin = 1, ymin = 1, xmax = 4, ymax = 4,
image = "https://upload.wikimedia.org/wikipedia/commons/d/de/Windows_live_square.JPG"
)
ggplot(mpg) +
see::geom_point2(aes(x = displ, y = hwy, size = cty), alpha = 0.2) +
geom_textured_rect(data = img_df,
aes(xmin = xmin, xmax = xmax,
ymin = ymin, ymax = ymax, image = image),
nrow = 1,
ncol = 1,
img_width = unit(1, "null"),
img_height = unit(1, "null"),
position = "identity") +
coord_equal() # this is then necessary...
Created on 2021-02-13 by the reprex package (v1.0.0)

R - Contour plot from raster dataset with country borders overlaid

I have a fairly simple and probably common task, plotting a raster dataset with countour lines and adding country borders together in one plot, however I did not find a solution anywhere. There are a a few hints available (such as this one), but no raster dataset is used there and I can't get it to work.
The dataset I am using is actually in netcdf format and available here (15mb in size) and contains about 40 years of gridded precipitation data.
Here is my line of code:
setwd("...netcdf Data/GPCP")
library("raster")
library("maps")
nc_brick79_17 <- brick("precip.mon.mean.nc") # load in the ncdf data as a
raster brick
newextent <- c(85, 125, -20, 20) # specify region of interest
SEA_brick <- crop(nc_brick79_17, newextent) # crop the region
day1 <- SEA_brick[[1]] # select very first day as example
colfunc<-colorRampPalette(c("white","lightblue","yellow","red","purple")) # colorscale for plotting
So it works of course when I just plot the raster data together with a map overlaid:
plot(day1, col=(colfunc(100)), interpolate=F, main="day1",legend.args=list(text='mm/hr', side=4,font=1, line=2.5, cex=1.1))
map("world", add=TRUE, lwd=0.5, interior = FALSE, col = "black")
We get this plot (Raster Plot with country borders added)
Now the code I use to generate the contour plot is the following:
filledContour(day1,zlim=c(0,20),color=colorRampPalette(c("white","lightblue","yellow","red","purple")),
xlab = "Longitude (°)", ylab = "Latitude (°)")
map("world", add=TRUE, lwd=0.5, interior = FALSE, col = "black") # add map overlay
I end up with a plot where obviously the country borders do not align and are even covering the colorbar.
Contour plot with map overlay shifted
In this last part I am trying to add the country boundaries to the contour plot, but it does not work, even though it should I assume. The map is simply not there, no error though:
filledContour(day1, zlim=c(0,20),
color.palette = colorRampPalette(c("white","lightblue","yellow","red","purple")),
xlab = "Longitude (°)", ylab = "Latitude (°)",
xlim = c(90, 120), ylim = c(-20, 20), nlevels = 25,
plot.axes = {axis(1); axis(2);
map('world', xlim = c(90, 120), ylim = c(-20, 20), add = TRUE, lwd=0.5, col = "black")})
From that line of code I get this plot.
Contour plot but no country borders added
What could I improve or is there any mistake somewhere? Thank you!
I chose to use ggplot here. I leave two maps for you. The first one is the one you created. This is a replication with ggplot. The second one is the one you could not produce. There are many things to explain. But I am afraid I do not have enough time to write all. But I left some comments in my code below. Please check this question to learn more about the second graphic. Finally, I'd like to give credit to hrbrmstr who wrote a great answer in the linked question.
library(maptools)
library(akima)
library(raster)
library(ggplot2)
# This is a data set from the maptools package
data(wrld_simpl)
# Create a data.frame object for ggplot. ggplot requires a data frame.
mymap <- fortify(wrld_simpl)
# This part is your code.
nc_brick79_17 <- brick("precip.mon.mean.nc")
newextent <- c(85, 125, -20, 20)
SEA_brick <- crop(nc_brick79_17, newextent)
day1 <- SEA_brick[[1]]
# Create a data frame with a raster object. This is a spatial class
# data frame, not a regular data frame. Then, convert it to a data frame.
spdf <- as(day1, "SpatialPixelsDataFrame")
mydf <- as.data.frame(spdf)
colnames(mydf) <- c("value", "x", "y")
# This part creates the first graphic that you drew. You draw a map.
# Then, you add tiles on it. Then, you add colors as you wish.
# Since we have a world map data set, we trim it at the end.
ggplot() +
geom_map(data = mymap, map = mymap, aes(x = long, y = lat, map_id = id), fill = "white", color = "black") +
geom_tile(data = mydf, aes(x = x, y = y, fill = value), alpha = 0.4) +
scale_fill_gradientn(colors = c("white", "lightblue", "yellow", "red", "purple")) +
scale_x_continuous(limits = c(85, 125), expand = c(0, 0)) +
scale_y_continuous(limits = c( -20, 20), expand = c(0, 0)) +
coord_equal()
ggplot version of filled.contour()
# As I mentioned above, you want to study the linked question for this part.
mydf2 <- with(mydf, interp(x = x,
y = y,
z = value,
xo = seq(min(x), max(x), length = 400),
duplicate = "mean"))
gdat <- interp2xyz(mydf2, data.frame = TRUE)
# You need to draw countries as lines here. You gotta do that after you draw
# the contours. Otherwise, you will not see the map.
ggplot(data = gdat, aes(x = x, y = y, z = z)) +
geom_tile(aes(fill = z)) +
stat_contour(aes(fill = ..level..), geom = "polygon", binwidth = 0.007) +
geom_contour(color = "white") +
geom_path(data = mymap, aes(x = long, y = lat, group = group), inherit.aes = FALSE) +
scale_x_continuous(limits = c(85, 125), expand = c(0, 0)) +
scale_y_continuous(limits = c(-20, 20), expand = c(0, 0)) +
scale_fill_gradientn(colors = c("white", "lightblue", "yellow", "red", "purple")) +
coord_equal() +
theme_bw()

Showing geom_abline in legend without bar

I want to show the added line via geom_abline in the legend since the bar chart is denoted in the x axis labels.
How embarrassing, not sure how i forgot toy data. I also cleaned up the example making sure i was running the most up to date version of R and ggplot (and reshape!) I forgot how it can make a difference sometimes
The end product is a bar chart with the added line (indicating the average) with this information showing in the legend, so a red dotted line that says "County Average".
library(ggplot2)
DataToPlot.. <- data.frame(UGB = c("EUG","SPR","COB","VEN"),
Rate = c( 782, 798,858,902))
ggplot(DataToPlot.. ,y = Rate, x = UGB) +
geom_bar(aes(x=UGB,y=Rate, fill = UGB),stat="identity",show.legend = FALSE) +
scale_fill_brewer(palette="Set3") +
geom_abline(aes(intercept = 777, slope = 0), colour = "red",
size = 1.25, linetype="dashed",show.legend = TRUE)
After playing around for awhile (it was not as easy as I expected) I used this:
library(ggplot2)
DataToPlot.. <- data.frame(UGB = c("EUG","SPR","COB","VEN"),
Rate = c( 782, 798,858,902))
x <- c(0.5,nrow(DataToPlot..)+0.5)
AvgLine.. <- data.frame(UGB=x,Rate=777,avg="777")
ggplot(DataToPlot.. ,y = Rate, x = UGB) +
geom_bar(aes(x=UGB,y=Rate, fill = UGB),stat="identity",show.legend=TRUE ) +
scale_fill_brewer(palette="Set3") +
geom_line(data=AvgLine..,aes(x=UGB,y=Rate,linetype=avg),
colour = "red", size = 1.25) +
scale_linetype_manual(values=c("777"="dashed")) +
# make the guide wider and specify the order
guides(linetype=guide_legend(title="Country Average",order=1,keywidth = 3),
color=guide_legend(title="UGB",order=2))
Note I couldn't coerce geom_abline to make its own guide. I had to create a dataframe. The x-coordinates for that line are basically the factor values, and I adjusted them to reach beyond the edges of the plot.
To get this:

Draw circles on dotplot to mark optimal regions in ggplot2

I have a point plot with power plants that have the properties x and y. The best power plants are those were x and y are both high. I now want to visualize which regions of my plot are desirable and less desirable for a power plant to be in
What i need to produce is something like this:
Any ideas? i tried drawing huge dots with geom_point but they change size depending on the export resolution of my plot I also tried drawing circles like here but ggplot doesn't draw the circles correctly if parts of it fall outside the actual plot
When using #joran's circle function here, this seems to work:
# prepare data for circles
green <- circleFun(center = c(5, 5), diameter = 1.5, npoints = 100)
yellow <- circleFun(center = c(5, 5), diameter = 3, npoints = 100)
orange <- circleFun(center = c(5, 5), diameter = 4.5, npoints = 100)
red <- circleFun(center = c(5, 5), diameter = 6, npoints = 100)
dat <- rbind(green, yellow, orange, red)
# specify levels to get the order of colours right,
# and set the plotting order from large (red) to small (green) circle
dat$fill <- factor(rep(c("green", "yellow", "orange", "red"), each = 100),
levels = rev(c("green", "yellow", "orange", "red")))
# dummy data for points
dat2 <- data.frame(x = rnorm(100, mean = 3), y = rnorm(100, mean = 3))
ggplot(data = dat, aes(x = x, y = y)) +
geom_polygon(aes(fill = fill)) +
geom_point(data = dat2, aes(x = x, y = y)) +
coord_cartesian(xlim = c(0, 5), ylim = c(0, 5)) +
scale_fill_manual(values = rev(c("green", "yellow", "orange", "red")))
Things that did not work so well:
For some reason circles are distorted when (1) the legend is turned off using theme(legend.position = "none"), and (2) when scale_fill_identity() is used, instead of scale_fill_manual(), to pick colours from 'fill' variable in 'dat'. I have no clue why.

Place annotation at the top of a series of histograms in ggplot2 using a for loop

I am creating a number of histograms and I want to add annotations towards the top of the graph. I am plotting these using a for loop so I need a way to place the annotations at the top even though my ylims change from graph to graph. If I could store the ylim for each graph within the loop I could cause the y coordinates for my annotation to vary based on the current graph. The y value I include in my annotation must change dynamically as the loop proceeds across iterations. Here is some sample code to demonstrate my issue (Notice how the annotation moves around. I need it to change based on the ylim for each graph):
library(ggplot2)
cuts <- levels(as.factor(diamonds$cut))
pdf(file = "Annotation Example.pdf", width = 11, height = 8,
family = "Helvetica", bg = "white")
for (i in 1:length(cuts)) {
by.cut<-subset(diamonds, diamonds$cut == cuts[[i]])
print(ggplot(by.cut, aes(price)) +
geom_histogram(fill = "steelblue", alpha = .55) +
annotate ("text", label = "My annotation goes at the top", x = 10000 ,hjust = 0, y = 220, color = "darkred"))
}
dev.off()
ggplot uses Inf in its positions to represent the extremes of the plot range, without changing the plot range. So the y value of the annotation can be set to Inf, and the vjust parameter can also be adjusted to get a better alignment.
...
print(ggplot(by.cut, aes(price)) +
geom_histogram(fill = "steelblue", alpha = .55) +
annotate("text", label = "My annotation goes at the top",
x = 10000, hjust = 0, y = Inf, vjust = 2, color = "darkred"))
...
For i<-2, this looks as:
There may be a neater way, but you can get the max count and use that to set y in the annotate call:
for (i in 1:length(cuts)) {
by.cut<-subset(diamonds, diamonds$cut == cuts[[i]])
## get the cut points that ggplot will use. defaults to 30 bins and thus 29 cuts
by.cut$cuts <- cut(by.cut$price, seq(min(by.cut$price), max(by.cut$price), length.out=29))
## get the highest count of prices in a given cut.
y.max <- max(tapply(by.cut$price, by.cut$cuts, length))
print(ggplot(by.cut, aes(price)) +
geom_histogram(fill = "steelblue", alpha = .55) +
## change y = 220 to y = y.max as defined above
annotate ("text", label = "My annotation goes at the top", x = 10000 ,hjust = 0, y = y.max, color = "darkred"))
}

Resources