Change geom default aesthetics as part of theme component only - r

For a custom ggplot2 theme I'd like to change the default aesthetic of some geom, say I want red dots instead of black dots.
From this answer I know we can change defaults for a geom using the function update_geom_default but I wonder if it is possible to change the colour only when we call theme_red_dots?
Example of my naive attempt:
library(ggplot2)
theme_red_dots <- function(...) {
update_geom_defaults("point", list(colour = "red"))
theme_minimal() +
theme(...)
}
Looks good here:
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
theme_red_dots()
But I'd like the points to be black again when I call
ggplot(mtcars, aes(mpg, disp)) +
geom_point()
Thanks in advance!
Below is an example of why I thought this could be useful. We can change panel.background to be black fairly easy but this would make it impossible to see the points if we don't map an aesthetic to colour. (The usefulness of this theme_black can certainly be discussed, but I would like to avoid an argument about that.)
theme_black <- function(...) {
theme_minimal() +
theme(panel.background = element_rect(fill = "black")) +
theme(...)
}
# update_geom_defaults("point", list(colour = "black"))
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
theme_black()
Changing the colour of the points inside geom_point() is an option here (see #zx8754 answer), but this requires the user of theme_black() to change it, while I am wondering if there is a way to do this right inside theme_*.

Another solution is to detach and reattach ggplot2 (apparently you can do this within custom ggplot2 theme function).
library(ggplot2)
theme_red_dots <- function(...) {
# wanted theme
update_geom_defaults("point", list(colour = "red"))
# Plot
p <- theme_minimal() +
theme(...)
# Detach and reattach ggplot2
detach("package:ggplot2", unload = TRUE); library(ggplot2)
# Return wanted plot
return(p)
}
# red dots
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
theme_red_dots()
# black (default) dots
ggplot(mtcars, aes(mpg, disp)) +
geom_point()
Works with wanted theme_black too:
theme_black <- function(...) {
update_geom_defaults("point", list(colour = "red"))
p <- theme_minimal() +
theme(panel.background = element_rect(fill = "black")) +
theme(...)
detach("package:ggplot2", unload = TRUE); library(ggplot2)
return(p)
}
# Plots with black background
ggplot(mtcars, aes(mpg, disp)) +
geom_point() +
theme_black()
# Plots with default background
ggplot(mtcars, aes(mpg, disp)) +
geom_point()

The released version of ggplot2 doesn't currently offer a way to do this. However, this is a fairly old feature request and has been under development since the summer of 2018.

Instead of changing defaults, make custom geom_point:
library(ggplot2)
# make custom geom with red as default
geom_point_red <- function()geom_point(col = "red")
ggplot(mtcars, aes(mpg, disp)) +
geom_point_red()

As my reputation won't allow me to comment:
#PoGibas:
An issue about detaching and re-attaching ggplot2 is that if other packages are loaded that require it, e.g. rstan, it won't work and return an error.

Related

How to align grob with ggplot using ggplotGrob and annotation_custom?

I am creating a grob from a ggplot using ggplotGrob and then adding it as a background layer in a complex ggplot construction using annotation_custom since the performance is much improved in a facet_wrap plot with a large dataset. However I am unable to align the underlying grob correctly with the ggplot.
This simple example shows the issue I am trying to solve.
library(ggplot2)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point(color = "red")
ggplot(mtcars, aes(wt, mpg)) +
annotation_custom(grob = ggplotGrob(p)) +
geom_point()
I want the plot with red points to be perfectly underneath the plot with black points.
You'd probably want to grab just the panel without axis, margins etc. before adding it as a custom annotation then. In example below, I made the red points larger so you can see that they overlap.
library(ggplot2)
p <- ggplot(mtcars, aes(wt, mpg)) +
geom_point(color = "red", size = 3)
grab_panel <- function(p) {
gt <- ggplotGrob(p)
layout <- gt$layout
is_panel <- which(layout$name == "panel")[[1]]
i <- layout$t[is_panel]
j <- layout$l[is_panel]
gt[i,j]
}
ggplot(mtcars, aes(wt, mpg)) +
annotation_custom(grob = grab_panel(p)) +
geom_point()
Created on 2021-03-20 by the reprex package (v1.0.0)

Theme(position.legend="none") doesn't work with coord_flip()

I want to have a horizontal bar chart, but without legend. When I run the script below without coord_flip(), no legend shows. But when I run it with the coord_flip() argument, the legend appaers. I think this might be a bug. Does anyone know a different code with which I can skip the legend?
df <- aggregate(formula=mpg~cyl, data=mtcars, FUN=sum)
fig <- ggplot(df, aes(x=cyl, y=mpg, fill=cyl, label=mpg)) +
geom_col() +
coord_flip() +
theme(legend.position="none") +
theme_bw()
fig

incorrect linetype in geom_vline with legend in r ggplot2

I am trying to display the xvar median as a dotted line & show it in the legend. Here's my code:
require(ggplot2)
require(scales)
medians_mtcars <- data.frame("wt.median"=median(mtcars$wt))
# legend shows but linetype is wrong (solid)
p <- ggplot(mtcars, aes(wt, mpg))
p <- p + geom_point()
p <- p + geom_vline(aes(xintercept=wt.median, linetype="dotted"),
data=medians_mtcars, show_guide=TRUE)
p
I also tried:
# linetype is correct but legend does not show
p <- ggplot(mtcars, aes(wt, mpg))
p <- p + geom_point()
p <- p + geom_vline(aes(xintercept=wt.median),
data=medians_mtcars, show_guide=TRUE, linetype="dotted")
p
Would have liked to post the plot images, but haven't crossed the reputation threshold yet.
There were 2 other posts on this forum that comes close to this topic but does not offer a solution to this problem:
Add vline to existing plot and have it appear in ggplot2 legend?
;
Incorrect linetype in legend, ggplot2 in R
I am using ggplot2 version 1.0.0
What am I doing wrong ?
Thanks in advance
If you need to show linetype in legend and also change it then inside aes() you can just write name for that linetype (as you have only one line) and then change linetype with scale_linetype_manual().
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
geom_vline(aes(xintercept=wt.median, linetype="media"),
data=medians_mtcars, show_guide=TRUE)+
scale_linetype_manual(values="dotted")
If you really want to type linetype in aes() and also get correct legend then you should use scale_linetype_identity() with argument guide="legend".
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
geom_vline(aes(xintercept=wt.median, linetype="dotted"),
data=medians_mtcars,show_guide=TRUE)+
scale_linetype_identity(guide="legend")

Separate y-axis labels by facet OR remove legend but keep the space

Ok, I'm stumped on a home-brew ggplot.
What I would like to do is have a three row, one column faceted plot with a different y-axis label for each facet. The units of the y-axis are all the same. This would be the most convenient, but googling tells me it may not be possible.
Alternatively, I found this solution using grid.arrange, which seems like it will work. However, I want to keep a legend only for one plot and remove it from the other two, but maintain the spacing as if it were still there so that everything lines up nice. Someone had the same problem a few years ago, but the suggested solution is depreciated and I can't sort out how to make it work in modern ggplot.
Any help is appreciated! Using facets would be easiest!
Edited to add copy of plot after using user20560's gridArrange solution below. Very nearly there, just would like to get back the box around the top and bottom facet panels!
I have assumed (possibly wrongly) that you are wanting to add separate y-axis titles rather than axis labels. [If it is the labels you want different you can use the scales argument in facet_grid]
There will be a ggplot way to do this but here are a couple of ways you could tweak the grobs yourself.
So using mtcars dataset as example
library(ggplot2)
library(grid)
library(gridExtra)
One way
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ .)
# change the y axis labels manually
g <- ggplotGrob(p)
yax <- which(g$layout$name=="ylab")
# define y-axis labels
g[["grobs"]][[yax]]$label <- c("aa","bb", "cc")
# position of labels (ive just manually specified)
g[["grobs"]][[yax]]$y <- grid::unit(seq(0.15, 0.85, length=3),"npc")
grid::grid.draw(g)
Or using grid.arrange
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# remove legends from two of the plots
g1 <- ggplotGrob(p1)
g1[["grobs"]][[which(g1$layout$name=="guide-box")]][["grobs"]] <- NULL
g3 <- ggplotGrob(p3)
g3[["grobs"]][[which(g3$layout$name=="guide-box")]][["grobs"]] <- NULL
gridExtra::grid.arrange(g1,p2,g3)
If it is the axis titles you want to add I should ask why you want a different titles - can the facet strip text not do?
Following the comments by Axeman and aosmith (thank you), here's a way to do this using the facet labels using ggplot2 version 2.2.0
library(ggplot2) # From sessionInfo(): ggplot2_2.2.0
ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() +
facet_grid(gear ~ ., switch = 'y') +
theme( axis.title.y = element_blank(), # remove the default y-axis title, "wt"
strip.background = element_rect(fill = 'transparent'), # replace the strip backgrounds with transparent
strip.placement = 'outside', # put the facet strips on the outside
strip.text.y = element_text(angle=180)) # rotate the y-axis text (optional)
# (see ?ggplot2::theme for a list of theme elements (args to theme()))
I know this is an old post, but after finding it, I could not get #user20560's response to work.
I've edited #user20560's grid.extra approach as follows:
library(ggplot2)
library(gridExtra)
library(grid)
# Create a plot for each level of grouping variable and y-axis label
p1 <- ggplot(mtcars[mtcars$gear==3, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="aa") + theme_bw()
p2 <- ggplot(mtcars[mtcars$gear==4, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="bb") + theme_bw()
p3 <- ggplot(mtcars[mtcars$gear==5, ], aes(mpg, wt, col=factor(vs))) +
geom_point() + labs(y="cc") + theme_bw()
# get the legend as a grob
legend <- ggplotGrob(p1)
legend <- legend$grobs[[which(legend$layout$name=="guide-box")]]
lheight <- sum(legend$height)
lwidth <- sum(legend$width)
# remove the legend from all the plots
p1 <- p1 + theme(legend.position = 'none')
p2 <- p2 + theme(legend.position = 'none')
p3 <- p3 + theme(legend.position = 'none')
# force the layout to the right side
layoutMat <- matrix(c(1,2,3,4,4,4),ncol = 2)
grid.arrange(p1,p2,p3,legend, layout_matrix = layoutMat, ncol = 2,
widths = grid::unit.c(unit(1,'npc') - lwidth, lwidth))
This example is somewhat specific to this particular layout. There is a more general approach on the ggplot2 wiki.
I too had trouble getting the first approach in the answer of user20560 (above) to work. This is probably because the internals of ggplot2 have evolved, and there is no guarantee that these internals should stay the same. In any case, here is a version that currently works:
library(ggplot2) # From sessionInfo(): ggplot2_2.1.0
library(grid)
p <- ggplot(mtcars, aes(mpg, wt, col=factor(vs))) + geom_point() + facet_grid(gear ~ .)
g <- ggplotGrob(p)
yax <- which(g$layout$name == "ylab")
g[["grobs"]][[yax]]$children[[1]]$label <- c('fo','bar','foobar')
g[["grobs"]][[yax]]$children[[1]]$y <- grid::unit(seq(0.15, 0.85, length=3), "npc")
grid.draw(g)
Note that this is the approach that keeps the facets and does not repeat the x-axes.

How to change default aesthetics in ggplot?

Let's say that I would prefer geom_point to use circles (pch=1) instead of solid dots (pch=16) by default. You can change the shape of the markers by passing a shape argument to geom_point, e.g.
ggplot(diamonds, aes(depth, carat, colour=cut)) + geom_point(shape=1)
ggplot(diamonds, aes(depth, carat, colour=cut)) + geom_point(shape=16)
but I can't figure out how to change the default behaviour.
Geom (and stat) default can be updated directly:
update_geom_defaults("point", list(shape = 1))
ggplot(mtcars, aes(x=wt, y=mpg)) + geom_point()
One way to do it (although I don't really like it) is to make your own geom_point function. E.g.
geom_point2 <- function(...) geom_point(shape = 1, ...)
Then just use as normal:
ggplot(diamonds, aes(depth, carat, colour=cut)) + geom_point2()
Or if you want you can override the function geom_point():
geom_point <- function(...) {
ggplot2::geom_point(shape = 1, ...)
}
This might be considered bad practice but it works. Then you don't have to change how you plot:
ggplot(diamonds, aes(depth, carat, colour=cut)) + geom_point()

Resources