Convert ggplot to greyscale with stat_summary - r

I have an R package as a back-end engine for a GUI (JASP). I want to be able to have people convert their images to greyscale (for publication printing). For most plots, I can use scale_colour_grey(), but that doesn't work when colors are specified within stats_summary. For example:
# simulate data
set.seed(1212)
y = rnorm(100)
g = sample(c("a", "b"), 100, T)
d = data.frame(y=y, g=g)
### create ggplot
plot = ggplot(data=d, aes(x=g, y=y)) +
geom_jitter() +
stat_summary(fun="mean", geom="point", size=3, color="red") +
stat_summary(geom="errorbar", size=3, color="red") +
theme_bw()
### converting to greyscale doesn't work
plot + scale_colour_grey()
After some research, I learned you can dissect the ggplot object then rebuilt it:
### can rebuilt ggplot object, but not ideal
q <- ggplot_build(plot)
q$data[[2]]$colour <- "black"
q$data[[3]]$colour <- "black"
q <- ggplot_gtable(q)
plot = ggplotify::as.ggplot(q)
plot
That works fine, but I try to include as few dependencies in my R packages as possible. Do I really have to resort to another package (ggplotify) in order to modify the color coming from stat_summary?
A few notes: I don't want to have to modify the original stat_summary statement. Let's just pretend that cannot be modified. Also, let's assume I cannot add another package (aside from ggplot2, which is already loaded). Remember, I'm a "guest" in the JASP framework and I don't want to have to add another package to the list of packages they have to store.

You can actually get at the ggplot object before it is built, reach into the layer that has the coloured object and change it. Here's a full reprex:
library(ggplot2)
# simulate data
set.seed(1212)
y = rnorm(100)
g = sample(c("a", "b"), 100, T)
d = data.frame(y=y, g=g)
### create ggplot
p <- ggplot(data=d, aes(x=g, y=y)) +
geom_jitter() +
stat_summary(fun="mean", geom="point", size=3, color="red") +
stat_summary(geom="errorbar", size=3, color="red") +
theme_bw()
p
p$layers[[3]]$aes_params$colour <- "gray50"
p
If you want a more general approach to change a particular geom's colour, you could do something like this function:
recolour_geom <- function(gg_plot, geom, colour = "gray50")
{
ss <- which(sapply(p$layers, function(l) {
paste(gsub("GEOM|GG|PROTO", "", toupper(class(l$geom))),
collapse = "")}) %in% toupper(geom))
if (length(ss) > 0)
{
for (i in ss)
{
p$layers[[i]]$aes_params$colour <- colour
}
}
p
}
Which allows you to do, for example
recolour_geom(p, "errorbar", "blue")
recolour_geom(p, "point", "green")
Be aware though that this changes the ggplot by reference, so p is changed as a side effect of the function.
Created on 2020-08-14 by the reprex package (v0.3.0)

Related

Correlation plot between two variables with line and person r value in graph - seeking alternate example

Would just like some clarity here and a different example if someone has one.
Initially I wanted to use this example because it has the graph, the mean line, and the r value all presented in the graph: http://www.sthda.com/english/wiki/correlation-test-between-two-variables-in-r
However, I'm using r studio server and creating a shiny app.
Library ggpubr will simply not install.
I've tried several ways to get this library to install.
So, does anyone have an alternate example that might work?
Cheers ~!
How about this:
library(ggplot2)
data(mtcars)
r <- round(cor(mtcars$wt, mtcars$mpg), 2)
p <- cor.test(mtcars$wt, mtcars$mpg)$p.value
ggplot(mtcars, aes(y=wt, x=mpg)) +
geom_point() +
geom_smooth(method="lm", col="black") +
annotate("text", x=20, y=4.5, label=paste0("r = ", r), hjust=0) +
annotate("text", x=20, y=4.25, label=paste0("p = ", round(p, 3)), hjust=0) +
theme_classic()
You could use the geom_smooth function from ggplot2 and implement the correlation coefficient aswell as the p-value as follows:
library(ggplot2)
my_data <- mtcars
cor_coefs <- cor.test(my_data$mpg, my_data$wt)
ggplot(data = my_data, aes(x = mpg, y = wt)) +
geom_point() +
geom_smooth(method=lm , color="red", fill="#69b3a2", se=TRUE) +
annotate("text", x = 30, y = 4, label = paste0("R: ", round(cor_coefs$estimate, 2))) +
annotate("text", x = 30, y = 3.5, label = paste0("p-value: ", round(cor_coefs$p.value, 10)))
cor_coefs safes the correlation test and you can use it to get the desired values. With annotate from ggplot2, you need to specify the x and y position of your text. You could implement that dynamically based on your needs (since you have not provided any data).

How do you force Rmarkdown plots to be Square instead of Rectangle?

I have a Generalized Linear Model (GLM) that I'm plotting diagnostics for using the glm.diag.plots function in the MASS package. But it tends to plot rectangular instead of square, which is very ugly for publication.
Below is some sample code that shows the problem in an .Rmd file. In Rstudio, you can just drag the window around until it's square, but not possible in Rmarkdown documents, and I'd like to enforce square manually.
I checked in the ggplot documentation for ways to enforce square plotting, but could not find anything. glm.diag.plot() appears to use split.screen(), which doesn't provide any documentation for enforcing aspect ratios, either.
#rawr's comment is spot-on; this is a knitr/markdown issue, not glm.diag or ggplot or anything else. All you need to do is specify the desired height and width of the output (in inches, by default) using fig.width and fig.height.
It looks like you are using glm.diag.plots from package boot to acquire plots.
You could recreate them using ggplot if you wish. Here is an example:
some model:
data(anorexia, package = "MASS")
anorex.1 <- glm(Postwt ~ Prewt + Treat + offset(Prewt),
family = gaussian, data = anorexia)
the glm.diag.plots output
library(boot)
glm.diag.plots(anorex.1)
To create each plot in ggplot first get an object from glm.diag.plots
z <- glm.diag.plots(anorex.1, ret = T)
then plot each plot:
library(ggplot2)
plot1 <- ggplot(data.frame(x = predict(anorex.1),
y = z$res))+
geom_point(aes(x, y)) +
xlab("Linear predictor") +
ylab("Residuals") +
theme_bw()+
theme(aspect.ratio=1)
plot2 <- ggplot(data.frame(x = qnorm(ppoints(length(z$rd)))[rank(z$rd)],
y = z$rd)) +
geom_point(aes(x, y)) +
xlab("Ordered deviance residuals") +
ylab("Quantiles of standard normal") +
geom_abline(intercept = 0, slope = 1, lty =2) +
theme_bw()+
theme(aspect.ratio=1)
plot3 <- ggplot(data.frame(x = z$h/(1-z$h),
y = z$cook)) +
geom_point(aes(x, y)) +
xlab("h/(h-1)") +
ylab("Cook statistic") +
theme_bw()+
theme(aspect.ratio=1)
plot4 <- ggplot(data.frame(x = 1:length(z$cook),
y = z$cook)) +
geom_point(aes(x, y)) +
xlab("Case") +
ylab("Cook statistic") +
theme_bw()+
theme(aspect.ratio=1)
then combine them
library(cowplot)
plot_grid(plot1, plot2, plot3, plot4, ncol = 2)
Now you can customize each plot the way you wish.

Create a function in R to make all the ggplot components more transparent

I am looking for a function that makes my ggplot graphs more transparent. Customizing a new theme I thing that it´s not all good because as the own docs say, 'Use theme() to modify individual components of a theme, allowing you to control the appearance of all non-data components of the plot.'
Supose that we have this simple graph:
ggplot(economics) +
aes(unemploy, psavert) +
geom_point() +
geom_smooth(se = F) +
ggtitle('Unemploy vs Personal Savings Rate')
It will render like this:
My idea is to build a function that modifies all elements and makes them more transparency with some ratio, something like this:
make.invisible <- function(graph, alpha=.75){
graph +
# Change all elements
}
Is it possible with ggplot2?
try this,
print(p, vp=viewport(gp=gpar(alpha=0.3)))
Here is a solution following this post. It uses the edit_colors() function from the colorblindr package. (Disclaimer: I'm an author of that package.)
p <- ggplot(economics) +
aes(unemploy, psavert) +
geom_point() +
geom_smooth(se = F) +
ggtitle('Unemploy vs Personal Savings Rate')
library(colorblindr) # devtools::install_github("clauswilke/colorblindr")
library(colorspace) # install.packages("colorspace", repos = "http://R-Forge.R-project.org") --- colorblindr requires the development version
# need also install cowplot; current version on CRAN is fine.
# modify alpha values using the alpha function from the scales package
p_alpha <- edit_colors(p, scales::alpha, alpha = .5)
# print
grid::grid.newpage()
grid::grid.draw(p_alpha)
One downside of the alpha() function from scales is that it replaces the alpha value, it doesn't combine alpha values. So here is a simple example of how you can combine alpha values, using your own alpha combination function:
mult_alpha <- function(color, alpha = .5)
{
col <- grDevices::col2rgb(color, TRUE)/255
new_col <- grDevices::rgb(col[1, ], col[2, ], col[3, ], alpha*col[4, ])
new_col[is.na(color)] <- NA
new_col
}
p2 <- ggplot(iris, aes(Sepal.Length, fill = Species)) +
geom_density(alpha = .3) + theme_bw()
p2_mult_alpha <- edit_colors(p2, mult_alpha, alpha = .7)
Comparison between the two approaches shows that they give very different results for plots that already have some transparency:
p2_alpha <- edit_colors(p2, scales::alpha, alpha = .7)
cowplot::plot_grid(p2_alpha, p2_mult_alpha)
(left image: using alpha from scales; right image: using mult_alpha as defined above.)

How do I create a categorical scatterplot in R like boxplots?

Does anyone know how to create a scatterplot in R to create plots like these in PRISM's graphpad:
I tried using boxplots but they don't display the data the way I want it. These column scatterplots that graphpad can generate show the data better for me.
Any suggestions would be appreciated.
As #smillig mentioned, you can achieve this using ggplot2. The code below reproduces the plot that you are after pretty well - warning it is quite tricky. First load the ggplot2 package and generate some data:
library(ggplot2)
dd = data.frame(values=runif(21), type = c("Control", "Treated", "Treated + A"))
Next change the default theme:
theme_set(theme_bw())
Now we build the plot.
Construct a base object - nothing is plotted:
g = ggplot(dd, aes(type, values))
Add on the points: adjust the default jitter and change glyph according to type:
g = g + geom_jitter(aes(pch=type), position=position_jitter(width=0.1))
Add on the "box": calculate where the box ends. In this case, I've chosen the average value. If you don't want the box, just omit this step.
g = g + stat_summary(fun.y = function(i) mean(i),
geom="bar", fill="white", colour="black")
Add on some error bars: calculate the upper/lower bounds and adjust the bar width:
g = g + stat_summary(
fun.ymax=function(i) mean(i) + qt(0.975, length(i))*sd(i)/length(i),
fun.ymin=function(i) mean(i) - qt(0.975, length(i)) *sd(i)/length(i),
geom="errorbar", width=0.2)
Display the plot
g
In my R code above I used stat_summary to calculate the values needed on the fly. You could also create separate data frames and use geom_errorbar and geom_bar.
To use base R, have a look at my answer to this question.
If you don't mind using the ggplot2 package, there's an easy way to make similar graphics with geom_boxplot and geom_jitter. Using the mtcars example data:
library(ggplot2)
p <- ggplot(mtcars, aes(factor(cyl), mpg))
p + geom_boxplot() + geom_jitter() + theme_bw()
which produces the following graphic:
The documentation can be seen here: http://had.co.nz/ggplot2/geom_boxplot.html
I recently faced the same problem and found my own solution, using ggplot2.
As an example, I created a subset of the chickwts dataset.
library(ggplot2)
library(dplyr)
data(chickwts)
Dataset <- chickwts %>%
filter(feed == "sunflower" | feed == "soybean")
Since in geom_dotplot() is not possible to change the dots to symbols, I used the geom_jitter() as follow:
Dataset %>%
ggplot(aes(feed, weight, fill = feed)) +
geom_jitter(aes(shape = feed, col = feed), size = 2.5, width = 0.1)+
stat_summary(fun = mean, geom = "crossbar", width = 0.7,
col = c("#9E0142","#3288BD")) +
scale_fill_manual(values = c("#9E0142","#3288BD")) +
scale_colour_manual(values = c("#9E0142","#3288BD")) +
theme_bw()
This is the final plot:
For more details, you can have a look at this post:
http://withheadintheclouds1.blogspot.com/2021/04/building-dot-plot-in-r-similar-to-those.html?m=1

How to get a reversed, log10 scale in ggplot2?

I'd like to make a plot with a reversed, log10 x scale using ggplot2:
require(ggplot2)
df <- data.frame(x=1:10, y=runif(10))
p <- ggplot(data=df, aes(x=x, y=y)) + geom_point()
However, it seems that I can either a log10 scale or a reversed scale:
p + scale_x_reverse() + scale_x_log10()
p + scale_x_reverse()
I guess this is logical, if a layer can only have one scale. And certainly I could hack it by doing the log transform on the dataframe myself, df$xLog <- log10(df$x)
but that solution is a seems contrary to the spirit of ggplot. Is there a way to get this kind of plot without doing data transformations external to the ggplot call?
[See #user236321's answer for a more modern (post April 2022) answer.]
The link that #joran gave in his comment gives the right idea (build your own transform), but is outdated with regard to the new scales package that ggplot2 uses now. Looking at log_trans and reverse_trans in the scales package for guidance and inspiration, a reverselog_trans function can be made:
library("scales")
reverselog_trans <- function(base = exp(1)) {
trans <- function(x) -log(x, base)
inv <- function(x) base^(-x)
trans_new(paste0("reverselog-", format(base)), trans, inv,
log_breaks(base = base),
domain = c(1e-100, Inf))
}
This can be used simply as:
p + scale_x_continuous(trans=reverselog_trans(10))
which gives the plot:
Using a slightly different data set to show that the axis is definitely reversed:
DF <- data.frame(x=1:10, y=1:10)
ggplot(DF, aes(x=x,y=y)) +
geom_point() +
scale_x_continuous(trans=reverselog_trans(10))
ggforce package has trans_reverser() function for this task.
library(ggplot2)
library(ggforce)
p <- ggplot() +
geom_line(aes(x = 1:100, y = 1:100))
p +
scale_x_continuous(trans = trans_reverser('log10')) +
annotation_logticks(sides = 'tb') +
theme_bw()
Edit: starting from v1.2.0 of the scales package, this will also work
library(scales)
p +
scale_x_continuous(
trans = compose_trans("log10", "reverse"),
breaks = c(100, 10, 1)
) +
annotation_logticks(sides = 'tb') +
theme_bw()
p +
scale_x_continuous(
trans = compose_trans("log10", "reverse"),
labels = label_log()
) +
annotation_logticks(sides = 'tb') +
theme_bw()
Created on 2020-11-14 by the reprex package (v0.3.0)
You can apply the logarithm directly inside the ggplot function, in the aes() specification:
require(ggplot2)
df <- data.frame(x=1:10, y=runif(10))
p <- ggplot(data=df, aes(x = log10(x), y=y)) + geom_point()
and then reverse the x axis
p + scale_x_reverse()
in this way your data is not altered, but you can scale the graph
Just thought I put updated answer to this question that does not require writing your own transformation. As of scales version 1.2.0 (released in April 2022), transformation composition is handled by the scales package directly. Use scale_x_continuous(), with the trans argument as a vector with both log10 and reverse. You need to put log10 before reverse or you'll get an error; the transformations are applied in the order specified.
p + scale_x_continuous(trans = c("log10", "reverse"))
The documentation for scales::compose_trans even has this usage as an example.

Resources