ggplot, geom_density_ridges2 and row height - r

With ggplot2 and geom_density_ridges2, I try to plot two graphs. One with 2 rows and one with 9 rows.
On the two graphs I would like to keep the same height for each row. So the second graph should have the same width but it should be more than 4 times taller.
Unfortunately, Rstudio or ggsave give my graphs withs the same scale (same width, same height).
Code
data_df = data.frame(text = character(), position = numeric())
# Plot
theme_set(theme_bw())
g = data_df %>%
ggplot( aes(y=text, x=position, fill=text) ) +
coord_cartesian(xlim = c(0, max_position)) +
geom_density_ridges2(alpha=1, stat="binline", scale=0.95, bins=200, show.legend = FALSE) +
theme_ridges(font_size = 8, grid = TRUE, font_family = "",line_size = 0.5) +
labs(x = "positions", y = author)
# Save image
image = paste0(author, ".png")
unlink(image)
ggsave(
image,
plot = g,
device = "png",
path = "graphs/",
units = "mm",
width = 100,
scale = 1,
dpi = 320,
limitsize = FALSE
)
Is it possible to fix the height of the rows ?

Maybe this or another approach with patchwork could solve the problem?
library(patchwork)
p1 = ggplot(mtcars, aes(x = mpg, y =cyl)) +
geom_point()
p1 + (p1 / plot_spacer() / plot_spacer() / plot_spacer())

Related

How can I add annotation in ggplotly animation?

I am creating animated plotly graph for my assignment in r, where I am comparing several models with various number of observations. I would like to add annotation showing what is the RMSE of the current model - this means I would like to have text that changes together with slider. Is there any easy way how to do that?
Here is my dataset stored on GitHub. There already is created variable with RMSE: data
The base ggplot graphic is as follows:
library(tidyverse)
library(plotly)
p <- ggplot(values_predictions, aes(x = x)) +
geom_line(aes(y = preds_BLR, frame = n, colour = "BLR")) +
geom_line(aes(y = preds_RLS, frame = n, colour = "RLS")) +
geom_point(aes(x = x, y = target, frame = n, colour = "target"), alpha = 0.3) +
geom_line(aes(x = x, y = sin(2 * pi * x), colour = "sin(2*pi*x)"), alpha = 0.3) +
ggtitle("Comparison of performance) +
labs(y = "predictions and targets", colour = "colours")
This is converted to plotly, and I have added an animation to the Plotly graph:
plot <- ggplotly(p) %>%
animation_opts(easing = "linear",redraw = FALSE)
plot
Thanks!
You can add annotations to a ggplot graph using the annotate function: http://ggplot2.tidyverse.org/reference/annotate.html
df <- data.frame(x = rnorm(100, mean = 10), y = rnorm(100, mean = 10))
# Build model
fit <- lm(x ~ y, data = df)
# function finds RMSE
RMSE <- function(error) { sqrt(mean(error^2)) }
library(ggplot2)
ggplot(df, aes(x, y)) +
geom_point() +
annotate("text", x = Inf, y = Inf, hjust = 1.1, vjust = 2,
label = paste("RMSE", RMSE(fit$residuals)) )
There seems to be a bit of a problem converting between ggplot and plotly. However this workaround here shows a workaround which can be used:
ggplotly(plot) %>%
layout(annotations = list(x = 12, y = 13, text = paste("RMSE",
RMSE(fit$residuals)), showarrow = F))
Here's an example of adding data dependent text using the built in iris dataset with correlation as text to ggplotly.
library(plotly)
library(ggplot2)
library(dplyr)
mydata = iris %>% rename(variable1=Sepal.Length, variable2= Sepal.Width)
shift_right = 0.1 # number from 0-1 where higher = more right
shift_down = 0.02 # number from 0-1 where higher = more down
p = ggplot(mydata, aes(variable1,variable2))+
annotate(geom = "text",
label = paste0("Cor = ",as.character(round(cor.test(mydata$variable1,mydata$variable2)$estimate,2))),
x = min(mydata$variable1)+abs(shift_right*(min(mydata$variable1)-max(mydata$variable1))),
y = max(mydata$variable2)-abs(shift_down*(min(mydata$variable2)-max(mydata$variable2))), size=4)+
geom_point()
ggplotly(p) %>% style(hoverinfo = "none", traces = 1) # remove hover on text

Programmatically position ggplot labels

I am creating a lot of charts programmatically in R using ggplot2 and have everything working perfectly except the position of bar labels.
This requires inputs of the plot height, y axis scale and text size.
Example (stripped down) plot code:
testInput <- data.frame("xAxis" = c("first", "second", "third"), "yAxis" = c(20, 200, 60))
# Changeable variables
yMax <- 220
plotHeight <- 5
textSize <- 4
# Set up labels
geomTextList <- {
textHeightRatio <- textSize / height
maxHeightRatio <- yMax / height
values <- testInput[["yAxis"]]
### THIS IS THE FORMULA NEEDING UPDATING
testInput[["labelPositions"]] <- values + 5 # # Should instead be a formula eg. (x * height) + (y * textSize) + (z * yMax)?
list(
ggplot2::geom_text(data = testInput, ggplot2::aes_string(x = "xAxis", y = "labelPositions", label = "yAxis"), hjust = 0.5, size = textSize)
)
}
# Create plot
outputPlot <- ggplot2::ggplot(testInput) +
ggplot2::geom_bar(data = testInput, ggplot2::aes_string(x = "xAxis", y = "yAxis"), stat = "identity", position = "dodge", width = 0.5) +
geomTextList +
ggplot2::scale_y_continuous(breaks = seq(0, yMax, yInterval), limits = c(0, yMax))
ggplot2::ggsave(filename = "test.png", plot = outputPlot, width = 4, height = plotHeight, device = "png")
I have tried various combinations of coefficients for the formula, but suspect that at leat one of the factors isn't linear. If this is purely a statistical problem, I could take it to Cross-Validation, but I wondered whether anyone had already solved this?
If your problem is with offsetting the text to not overlap the bar while dealing with varying text sizes, just use vjust which is already proportional to the text size. A value of 0 will make the bottom of the text touch the bar, and a small negative value will give you some space between them:
testInput <- data.frame("xAxis" = c("first", "second", "third"), "yAxis" = c(20, 200, 60))
# Changeable variables
yMax <- 220
plotHeight <- 5
textSize <- 4
# Set up labels
geomTextList <- {
values <- testInput[["yAxis"]]
testInput[["labelPositions"]] <- values # Use the exact value
list(
ggplot2::geom_text(
data = testInput,
# vjust provides proportional offset
ggplot2::aes_string(x = "xAxis", y = "labelPositions", label = "yAxis"),
hjust = 0.5, vjust = -0.15, size = textSize
)
)
}
# Create plot
outputPlot <- ggplot2::ggplot(testInput) +
ggplot2::geom_bar(data = testInput, ggplot2::aes_string(x = "xAxis", y = "yAxis"), stat = "identity", position = "dodge", width = 0.5) +
geomTextList +
ggplot2::scale_y_continuous(limits = c(0, yMax))
ggplot2::ggsave(filename = "test.png", plot = outputPlot, width = 4, height = plotHeight, device = "png")

How is the line width (size) defined in ggplot2?

The line width (size) aesthetics in ggplot2 seems to print approximately 2.13 pt wider lines to a pdf (the experiment was done in Adobe Illustrator with a Mac):
library(ggplot2)
dt <- data.frame(id = rep(letters[1:5], each = 3), x = rep(seq(1:3), 5), y = rep(seq(1:5), each = 3), s = rep(c(0.05, 0.1, 0.5, 1, 72.27/96*0.5), each = 3))
lns <- split(dt, dt$id)
ggplot() + geom_line(data = lns[[1]], aes(x = x, y = y), size = unique(lns[[1]]$s)) +
geom_text(data = lns[[1]], y = unique(lns[[1]]$y), x = 3.5, label = paste("Width in ggplot =", unique(lns[[1]]$s))) +
geom_line(data = lns[[2]], aes(x = x, y = y), size = unique(lns[[2]]$s)) +
geom_text(data = lns[[2]], y = unique(lns[[2]]$y), x = 3.5, label = paste("Width in ggplot =", unique(lns[[2]]$s))) +
geom_line(data = lns[[3]], aes(x = x, y = y), size = unique(lns[[3]]$s)) +
geom_text(data = lns[[3]], y = unique(lns[[3]]$y), x = 3.5, label = paste("Width in ggplot =", unique(lns[[3]]$s))) +
geom_line(data = lns[[4]], aes(x = x, y = y), size = unique(lns[[4]]$s)) +
geom_text(data = lns[[4]], y = unique(lns[[4]]$y), x = 3.5, label = paste("Width in ggplot =", unique(lns[[4]]$s))) +
geom_line(data = lns[[5]], aes(x = x, y = y), size = unique(lns[[5]]$s)) +
geom_text(data = lns[[5]], y = unique(lns[[5]]$y), x = 3.5, label = paste("Width in ggplot =", unique(lns[[5]]$s))) +
xlim(1,4) + theme_void()
ggsave("linetest.pdf", width = 8, height = 2)
# Device size does not affect line width:
ggsave("linetest2.pdf", width = 10, height = 6)
I read that one should multiply the line width by 72.27/96 to get a line width in pt, but the experiment above gives me a line width of 0.8 pt, when I try to get 0.5 pt.
As #Pascal points out, the line width does not seem to follow the pt to mm conversion that works for fonts and was defined by #hadley in one of the comments. I.e. the line width does not appear to be defined by "the magic number" 1/0.352777778.
What is the equation behind line width for ggplot2?
You had all the pieces in your post already. First, ggplot2 multiplies the size setting by ggplot2::.pt, which is defined as 72.27/25.4 = 2.845276 (line 165 in geom-.r):
> ggplot2::.pt
[1] 2.845276
Then, as you state, you need to multiply the resulting value by 72.27/96 to convert from R pixels to points. Thus the conversion factor is:
> ggplot2::.pt*72.27/96
[1] 2.141959
As you can see, ggplot2 size = 1 corresponds to approximately 2.14pt, and similarly 0.8 pt corresponds to 0.8/2.141959 = 0.3734899 in ggplot2 size units.

how do i control geom_errorbar width by symbol size?

In the below example i have a simple plot of mean values with standard deviation error bars for both X and Y axis. I would like to control the error bar width so both axis always plot the same size.
Ideally I would like the bar width/height to be the same size as the symbols (i.e. in this case cex = 3) irrelevant of the final plot dimensions. Is there a way to do this?
# Load required packages:
library(ggplot2)
library(plyr)
# Create dataset:
DF <- data.frame(
group = rep(c("a", "b", "c", "d"),each=10),
Ydata = c(seq(1,10,1),seq(5,50,5),seq(20,11,-1),seq(0.3,3,0.3)),
Xdata = c(seq(1,10,1),seq(5,50,5),seq(20,11,-1),seq(0.3,3,0.3)))
# Summarise data:
subDF <- ddply(DF, .(group), summarise,
X = mean(Xdata), Y = mean(Ydata),
X_sd = sd(Xdata, na.rm = T), Y_sd = sd(Ydata))
# Plot data with error bars:
ggplot(subDF, aes(x = X, y = Y)) +
geom_errorbar(aes(x = X,
ymin = (Y-Y_sd),
ymax = (Y+Y_sd)),
width = 1, size = 0.5) +
geom_errorbarh(aes(x = X,
xmin = (X-X_sd),
xmax = (X+X_sd)),
height = 1, size = 0.5) +
geom_point(cex = 3)
Looks fine when plotted at 1:1 ratio (500x500):
but the errorbar width/heigh look different when plotted at 600x200
I'd use a size-variable so you can control all of the 3 plot elements at the same time
geom_size <- 3
# Plot data with error bars:
ggplot(subDF, aes(x = X, y = Y)) +
geom_errorbar(aes(x = X,
ymin = (Y-Y_sd),
ymax = (Y+Y_sd)),
width = 1, size = geom_size) +
geom_errorbarh(aes(x = X,
xmin = (X-X_sd),
xmax = (X+X_sd)),
height = 1, size = geom_size) +
geom_point(cex = geom_size)
This is just building on brettljausn earlier answer. You can control the ratio of your plot with a variable as well. This will only work when you actually save the file with ggsave() not in any preview. I also used size to control the size of the point. It scaled nicer with the error bar ends.
plotheight = 100
plotratio = 3
geomsize = 3
plot = ggplot(subDF, aes(x = X, y = Y)) +
geom_errorbar(aes(x = X,
ymin = (Y-Y_sd),
ymax = (Y+Y_sd)),
width = .5 * geomsize / plotratio, size = 0.5) +
geom_errorbarh(aes(x = X,
xmin = (X-X_sd),
xmax = (X+X_sd)),
height = .5 * geomsize, size = 0.5) +
geom_point(size = geomsize)
ggsave(filename = "~/Desktop/plot.png", plot = plot,
width = plotheight * plotratio, height = plotheight, units = "mm")
Change the plotheight, plotratio, and geomsize to whatever you need it to be to look good. You will have to change the filename in the one but last line to get the file in the folder of your choice.

textGrob placement relative to changing plot size

I'm producing a whole pile of graphs of changing sizes. I want each graph to display a symbol (say, asterisk) at a specific point on the graph margin (top y-axis value), regardless of plot size. Right now I do it manually by defining x/y for each textGrob, but there has got to be a better way.
Plot size is determined by number of categories in the dataset (toy data below). Ideally, the output plots would have identical panel sizes (I'm assuming that can be controlled through defining margin sizes in inches and adding that value to the height parameter?). Widths don't usually change, but it would be nice to automate both x and y placements based on the defined device width (and plot margins).
Thanks so much!
library(ggplot2)
library(gridExtra)
set.seed(123)
df <- data.frame(x = rnorm(20, 0, 1), y = rnorm(20, 0, 1), category = rep(c("a", "b"), each = 10))
## plot 1
sub <- df[df$category == "a",]
height = 2*length(unique(sub$category))
p <- ggplot(sub) +
geom_point(aes(x = x, y = y)) +
facet_grid(category ~ .)
jpeg(filename = "fig1.jpg",
width = 6, height = height, units = "in", pointsize = 12, res = 900,
quality = 100)
g <- arrangeGrob(p, sub = textGrob("*", x = 0.07, y = 10.15, hjust = 0, vjust=0, #### puts the top discharge value; might need to be adjusted manually in following years
gp = gpar(fontsize = 15)))
grid.draw(g)
dev.off()
## plot 2
height = 2*length(unique(df$category))
p <- ggplot(df) +
geom_point(aes(x = x, y = y)) +
facet_grid(category ~ .)
jpeg(filename = "fig2.jpg",
width = 6, height = height, units = "in", pointsize = 12, res = 900,
quality = 100)
g <- arrangeGrob(p, sub = textGrob("*", x = 0.07, y = 23.1, hjust = 0, vjust=0, #### puts the top discharge value; might need to be adjusted manually in following years
gp = gpar(fontsize = 15)))
grid.draw(g)
dev.off()

Resources