Does anyone know how to make the the value of the grid line appear on the grid line in ggplot2?
I have a very large plot that I want to display the grid line value on intermittently throughout the plot, so when you are zoomed in you can easily tell what the value is. The only way I can currently think to do that is to add the labels with another data frame or something with the label and the x, y position. I was wondering if there was a more efficient way or an actual built in mechanism to do this.
Thanks!
There is no option built-in to do this specifically, but it is pretty straightforward using expand.grid to create the grid points and labels, then simply draw the result on with geom_text:
library(ggplot2)
gridlines <- expand.grid(Petal.Width = seq(0, 2, 0.5),
Sepal.Length = seq(4, 8),
Species = "setosa")
ggplot(iris, aes(Sepal.Length, Petal.Width, color = Species)) +
geom_point() +
geom_text(data = gridlines, color = "gray50", aes(label = Sepal.Length),
angle = 90, vjust = -0.5, nudge_y = 0.25) +
geom_text(data = gridlines, color = "gray50", aes(label = Petal.Width),
vjust = -0.5, nudge_x = 0.5) +
theme_bw(base_size = 20) +
theme(panel.grid.minor = element_blank())
Created on 2022-09-05 with reprex v2.0.2
Related
so I've had this question for quite a while now and I have not found a solution to this. Whenever I want to label a parameter of interest, such as central tendencies (mean, median etc.), for example, I create a vertical and/or horizontal line and use annotate for the labeling. However, I have not been able to use LaTeX and non-hardcoded values concurrently. A MRE is shown below:
data <- data.frame(stat = rnorm(1000))
plot <-
ggplot(data, aes(x = stat)) +
geom_histogram(aes(y=..density..), fill="aquamarine", color="black") +
geom_density(color="black", size=1) +
geom_vline(xintercept = 0, color = "blue", linetype="dashed", size = 1) +
annotate("text", x=-0.2, y=0.2,
label=paste0("Mean = ", round(mean(data$stat), 4)),
angle=90, size=7, color="black")
plot
See figure here: https://i.stack.imgur.com/QzojQ.png
In the example above, I generate some random normal data, save it to a dataframe and plot it. I add a dashed vertical line corresponding to the mean of the data and then label it via annotate. Note, however, that I explicitly write Mean in the label since the LaTeX operators don't work and random letters are not ideal for me.
A solution I found is by using expression instead of paste0 when labeling as shown below:
data <- data.frame(stat = rnorm(1000))
plot <-
ggplot(data, aes(x = stat)) +
geom_histogram(aes(y=..density..), fill="aquamarine", color="black") +
geom_density(color="black", size=1) +
geom_vline(xintercept = 0, color = "blue", linetype="dashed", size = 1) +
annotate("text", x=-0.25, y=0.2, label = expression(mu ~ "= -0.0403"),
angle=90, size = 7, color = "black")
plot
See figure here: https://i.stack.imgur.com/rnW5i.png
This allows us to get the LaTeX characters, such as $\mu$ in our case, to show in the plot. But now the downside is that I have to hardcode the mean value in the expression field. This is not ideal and I don't want to do this. I want to be able to do something as follows but this doesn't work:
annotate("text", x=-0.25, y=0.2,
label = expression(mu ~ round(mean(data$stat), 4)),
angle=90, size = 7, color = "black")
I have not been able to find a solution to this so any help is greatly appreciated.
One option to fix that would be to create your expression as a character string instead of using expression and add parse=TRUE to annotate:
library(ggplot2)
set.seed(123)
data <- data.frame(stat = rnorm(1000))
ggplot(data, aes(x = stat)) +
geom_histogram(aes(y = after_stat(density)), fill = "aquamarine", color = "black") +
geom_density(color = "black", size = 1) +
geom_vline(xintercept = 0, color = "blue", linetype = "dashed", size = 1) +
annotate("text",
x = -0.2, y = 0.2,
label = paste0("mu ==", round(mean(data$stat), 4)),
angle = 90, size = 7, color = "black", parse = TRUE
)
#> Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
#> ℹ Please use `linewidth` instead.
#> `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
I'm currently prototyping custom ggplot themes for use within my organisation. Currently, the theme looks like this (using mtcars data):
I would like to add a solid-colored bar (in the org's primary color) underlining the chart title region to act as an easily scalable type of branding (rather than a jpeg logo which restricts the aspect ratio and size of the charts). I made a mockup of the effect I'm trying to achieve in paint:
I'm aware of annotate() but as far as I understand it that function only accepts arguments corresponding to x and y coordinates in the plotting area, so I don't know how to create an annotation that is bound to a point outside of the plotting area
I would use annotation_custom using a grid::linesGrob here. This allows you to place the line relative to the panel without having to use the plot limits, which can produce inconsistent results. It also allows the line to extend beyond the left and right limits of the plotting area.
Suppose your plot is created a bit like this:
library(ggplot2)
p <- ggplot(mpg, aes(displ, hwy, col = class)) +
geom_point() +
labs(title = "Test 1, theme 1", subtitle = "R default dataset",
caption = "Organization caption here",
y = "Fuel efficiency (mpg)",
x = "Engine displacement (litres)") +
scale_color_brewer(palette = "Set2", name = NULL) +
theme(panel.grid = element_line(color = "gray50"),
panel.border = element_rect(fill = NA),
legend.position = "top")
p
To add the line you can do:
p + coord_cartesian(clip = "off") +
annotation_custom(grid::linesGrob(
x = unit(c(-1, 2), "npc"), y = unit(c(1.2, 1.2), "npc"),
gp = grid::gpar(col = "orange2", lwd = 5)))
It is a bit hard without reproducible example, but you can use annotate with "segment" and define the x-values with Inf and the y-values with max(y) + some value depending on your theme layout like this:
library(ggplot2)
library(dplyr)
mtcars %>%
ggplot(aes(x = mpg, y = wt)) +
geom_point() +
annotate("segment", x = -Inf, xend = Inf, y = max(mtcars$wt) + 0.5, yend = max(mtcars$wt) + 0.5, colour = "orange", size = 2) +
coord_cartesian(clip = "off", ylim = c(min(mtcars$wt), max(mtcars$wt))) +
theme(plot.margin = unit(c(3,3,1,1), "lines"))
Created on 2022-07-27 by the reprex package (v2.0.1)
I would like to create a box/rectangle around a single level of a category and include the axis category text and the bar itself:
As you can see in the photo, the rectangle extends beyond the grid and into the plot area to encompass the axis text. I'm hoping for something customizable so I can draw rounded corners or not, change the color, and specify where it goes.
Here's some generic code I used to produce a plot:
ggplot(mtcars, aes(x=factor(cyl)))+
geom_bar(stat="count", width=0.7, fill="steelblue")+
theme_minimal()
Hopefully, this isn't answered somewhere already!
For rectangle use annotate with "rect"
to go over the x axis you can set the x axis to blank
then add new quasi axis with geom_text setting y to 0 or -1. play around to fit:
p <- ggplot(mtcars, aes(x=factor(cyl)))+
geom_bar(stat="count", width=0.7, fill="steelblue")+
theme_minimal()
p + annotate("rect", xmin = 0.5, xmax = 1.5, ymin = -1, ymax = 12,
alpha = 0, color= "green") +
theme(axis.text.x = element_blank(),
axis.line.x = element_blank(),
axis.ticks.x = element_blank()) +
geom_text(aes(y = -0.5, x = factor(cyl),
label = cyl)) +
labs(title="Rectangle over x axis!",
x ="cyl", y = "count")
That's what the ggforce package is great for. Here with a semi-programmatic approach to define x/y coordinates of your shape. If you intend to mark specific areas / data points, you might also want to look into ggforce::geom_mark_rect
I have also un-factorised the x.
library(tidyverse)
library(ggforce)
cyl <- 4
n_cyl4 <- table(mtcars$cyl)[1]
df_rect <- data.frame(x = c(cyl - .5, rep(cyl + .5, 2), cyl - .5), y = c(rep(-2, 2), rep(n_cyl4 + .5, 2)))
ggplot(mtcars, aes(x = cyl)) +
geom_shape(data = df_rect, aes(x, y), fill = NA, color = "black", radius = .01) +
geom_bar(stat = "count", width = 0.7, fill = "steelblue") +
scale_x_continuous(breaks = seq(4, 8, 2)) +
coord_cartesian(ylim = c(0, NA), clip = "off") +
theme_minimal()
Created on 2021-08-03 by the reprex package (v2.0.0)
I would like to add a strip plot to the bottom of a geom_density plot... I could do something like :
ggplot(data = iris, aes(x = Sepal.Length)) +
geom_density(fill = "#2D708EFF", alpha = .2) +
geom_point(aes(y = 0), alpha = .4, shape = 73, size = 6)
But is there a more elegant way of doing this with ggplot2? My keywords might be off, but so far I haven't been able to find another ggplot2 solution.
You must be looking for geom_rug()
library(ggplot2)
ggplot(data = iris, aes(x = Sepal.Length)) +
geom_density(fill = "#2D708EFF", alpha = .2) +
# geom_point(aes(y = 0), alpha = .4, shape = 73, size = 6) +
geom_rug()
A rug plot is a compact visualisation designed to supplement a 2d display with the two 1d marginal distributions. Rug plots display individual cases so are best used with smaller datasets.
An example using ggplot2 to graph groups of data points and lines connecting the means for each group, mapped with the same aes for shape and for linetype:
p <- ggplot(mtcars, aes(gear, mpg, shape = factor(cyl), linetype = factor(cyl))) +
geom_point(size = 2) +
stat_summary(fun.y = mean, geom = "line", size = 1) +
scale_shape_manual(values = c(1, 4, 19))
Problem is that point symbols in the legend appear a bit too small to see, relative to the line symbols:
Trying to enlarge point size in legend also enlarges lineweight, so that is not useful here.
p1 <- p + guides(shape = guide_legend(override.aes = list(size = 4)))
It would be nice if lineweight were a distinct aesthetic from size.
I tried adding
+ guides(linetype = guide_legend(override.aes = list(size = 1)))
which just gives a warning.
> Warning message:
In guide_merge.legend(init, x[[i]]) : Duplicated override.aes is ignored.
It seems to make no difference either if I move the linetype aes out of ggplot() and into stat_summary(). If I wanted only the point symbols, I could eliminate lines from the legend this way.
p2 <- p + guides(shape = guide_legend(override.aes = list(size = 4, linetype = 0)))
Instead, (keeping small point symbols in the graph itself) I want one single legend with both big point symbols as in this last image and thin line symbols as in the first image. Is there a way to do this?
It sure does seem to be difficult to set those properties independently. I was only kind of able to come up with a hack. If your real data is much different it will likely have to be adjusted. But what i did was used the override.aes to set the size of the point. Then I went in and built the plot, and then manually changed the line width settings in the actual low-level grid objects. Here's the code
pp<-ggplot(mtcars, aes(gear, mpg, shape = factor(cyl), linetype = factor(cyl))) +
geom_point(size = 3) +
stat_summary(fun.y = mean, geom = "line", size = 1) +
scale_shape_manual(values = c(1, 4, 19)) +
guides(shape=guide_legend(override.aes=list(size=5)))
build <- ggplot_build(pp)
gt <- ggplot_gtable(build)
segs <- grepl("geom_path.segments", sapply(gt$grobs[[8]][[1]][[1]]$grobs, '[[', "name"))
gt$grobs[[8]][[1]][[1]]$grobs[segs]<-lapply(gt$grobs[[8]][[1]][[1]]$grobs[segs],
function(x) {x$gp$lwd<-2; x})
grid.draw(gt)
The magic number "8" was where gt$grobs[[8]]$name=="guide-box" so i knew I was working the legend. I'm not the best with grid graphics and gtables yet, so perhaps someone might be able to suggest a more elegant way.
Using the grid function grid.force(), all the grobs in the ggplot become visible to grid's editing functions, including the legend keys. Thus, grid.gedit can be applied, and the required edit to the plot can be achieved using one line of code. In addition, I increase the width of the legend keys so that the different line types for line segments are clear.
library(ggplot2)
library(grid)
p <- ggplot(mtcars, aes(gear, mpg, shape = factor(cyl), linetype = factor(cyl))) +
geom_point(size = 2) +
stat_summary(fun.y = mean, geom = "line", size = 1) +
scale_shape_manual(values = c(1, 4, 19)) +
theme(legend.key.width = unit(1, "cm"))
p
grid.ls(grid.force()) # To get the names of all the grobs in the ggplot
# The edit - to set the size of the point in the legend to 4 mm
grid.gedit("key-[-0-9]-1-1", size = unit(4, "mm"))
To save the modified plot
g <- grid.grab()
ggsave(plot=g, file="test.pdf")
I see what you mean. Here is a solution that fits what you're looking for, I think. It keeps both of the legends separate, but places them side by side. The labels and title of the shape are left out, so that the labels to the far right correspond to both the shapes and linetypes.
I'm posting this as a separate answer because I think both methods will be valid for future readers.
p2 <- ggplot(mtcars, aes(gear, mpg, shape = factor(cyl),
linetype = factor(cyl))) +
geom_point(size = 2) +
stat_summary(fun.y = mean, geom = "line", size = 1) +
# blank labels for the shapes
scale_shape_manual(name="", values = c(1, 4, 19),
labels=rep("", length(factor(mtcars$cyl))))+
scale_linetype_discrete(name="Cylinders")+
# legends arranged horizontally
theme(legend.box = "horizontal")+
# ensure that shapes are to the left of the lines
guides(shape = guide_legend(order = 1),
linetype = guide_legend(order = 2))
p2
One way to ensure separate legends is to give them different names (or other differences that would preclude them being grouped together).
Here's an example based on the code you supplied:
p <- ggplot(mtcars, aes(gear, mpg, shape = factor(cyl), linetype = factor(cyl))) +
geom_point(size = 2) +
stat_summary(fun.y = mean, geom = "line", size = 1) +
scale_shape_manual(name="Name 1", values = c(1, 4, 19))+
scale_linetype_discrete(name="Name2")
p