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.
Related
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)
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.
I want to recreate an "image" plot in ggplot (because of some other aspects of the package). However, I'm facing a problem caused by my y-scale, which is defined by unequally but logically spaced values, e.g. I would have z values for y = 2,4,8,16,32. This causes the tiles to not be equally large, so I have these white bands in my figure. I can solve this by transforming the y values in a factor, but I don't want to do this because I'm also trying to plot other geom objects on the figure which require a numeric scale.
This clearifies my problem a bit:
# random data, with y scale numeric
d <- data.frame(Var1=rep(1901:2000,10),Var2=rep(c(2,4,8,16,32),each=100),value=rnorm(500,50,5))
line=data.frame(Var1=1901:2000,Var2=rnorm(50,1.5,0.5))
ggplot(d, aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=value)) +
geom_line(data=line)
# y as factor
d2 = d
d2$Var2=as.factor(d2$Var2) ggplot(d2, aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=value)) +
geom_line(data=line)
I tried attributing the line values to the value of the nearest factor level, but this introduces a big error. Also, I tried the size option in geom_tile, but this didn't work out either.
In the example the y data is log transformed, but this is just for the ease of making a fake dataset.
Thank you.
Something like this??
ggplot(d, aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=value)) +
geom_line(data=line)+
scale_y_continuous(trans="log2")
Note the addition of scale_y_continuous(trans="log2")
EDIT Based on OP's comment below.
There is no built-in "reverse log2 transform", but it is possible to create new transformations using the trans_new(...) function in package scales. And, naturally, someone has already thought of this: ggplot2 reverse log coordinate transform. The code below is based on the link.
library(scales)
reverselog2_trans <- function(base = 2) {
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))
}
ggplot(d, aes(x=Var1, y=Var2)) +
geom_tile(aes(fill=value)) +
geom_line(data=line)+
scale_y_continuous(trans="reverselog2")
Perhaps another approach using a discrete scale and facets might be a possibility:
d <- data.frame(Var1=rep(1901:2000,10),Var2=rep(c(2,4,8,16,32),each=100),value=rnorm(500,50,5), chart="tile" )
d$Var2 <- factor(d$Var2, levels=rev(unique(d$Var2)))
line <- data.frame(Var1=1901:2000,Var2=rnorm(50,1.5,0.5), chart="line")
ggplot(d, aes(x=Var1, y=Var2)) +
geom_tile(aes(y = Var2, fill=value) ) +
geom_line( data=line ) +
scale_y_discrete() +
facet_grid( chart ~ ., scale = "free_y", space="free_y")
which gives a chart like:
I would like to create a function that produce a ggplot graph.
data1 <- data.table(x=1:5, y=1:5, z=c(1,2,1,2,1))
data2 <- data.table(x=1:5, y=11:15, z=c(1,2,1,2,1))
myfun <- function(data){
ggplot(data, aes(x=x, y=y)) +
geom_point() +
geom_text(aes(label=y), y=3) +
facet_grid(z~.)
}
myfun(data2)
It is supposed to label some text on the graph. However, without knowing the data in advance I am unable to adjust the positions of text vertically manually. Especially I don't want the label to move positions with data: I want it always stays at about 1/4 vertically of the plots. (top-mid)
How can I do that?
Is there a function that returns the y.limit.up and y.limit.bottom then I can assign y = (y.limit.up + y.limit.bottm) / 2 or something.
Setting either x or y position in geom_text(...) relative to the plot scale in a facet is actually a pretty big problem. #agstudy's solution works if the y scale is the same for all facets. This is because, in calculating range (or max, or min, etc), ggplot uses the unsubsetted data, not the data subsetted for the appropriate facet (see this question).
You can achieve what you want using auxiliary tables, though.
data1 <- data.table(x=1:5, y=1:5, z=c(1,2,1,2,1))
data2 <- data.table(x=1:5, y=11:15, z=c(1,2,1,2,1))
myfun <- function(data){
label.pos <- data[,ypos:=min(y)+0.75*diff(range(y)),by=z] # 75% to the top...
ggplot(data, aes(x=x, y=y)) +
geom_point() +
# geom_text(aes(label=y), y=3) +
geom_text(data=label.pos, aes(y=ypos, label=y)) +
facet_grid(z~., scales="free") # note scales = "free"
}
myfun(data2)
Produces this.
If you want scales="fixed", then #agstudy's solution is the way to go.
You can do this for example:
ggplot(data2, aes(x=x)) +
geom_point(aes(y=y)) +
geom_text(aes(label=y, y=mean(range(y)))) +
facet_grid(z~.)
Or fix y limits manually:
scale_y_continuous(limits = c(10, 15))
#user890739 :
with geom_density you can estimate an ypos variable like this :
data<-dplyr::mutate(group_by(data, z), ypos=max(density(y)$y)*.75*nrow(data))
Then plot the result :
ggplot(data, aes(x=x)) +
stat_density(aes(y=..density..)) +
geom_text(aes(label=y, y=ypos)) +
facet_grid(z~., scales="free")
I've become quite fond of boxplots in which jittered points are overlain over the boxplots to represent the actual data, as below:
set.seed(7)
l1 <- gl(3, 1, length=102, labels=letters[1:3])
l2 <- gl(2, 51, length=102, labels=LETTERS[1:2]) # Will use this later
y <- runif(102)
d <- data.frame(l1, l2, y)
ggplot(d, aes(x=l1, y=y)) +
geom_point(position=position_jitter(width=0.2), alpha=0.5) +
geom_boxplot(fill=NA)
(These are particularly helpful when there are very different numbers of data points in each box.)
I'd like to use this technique when I am also (implicitly) using position_dodge to separate boxplots by a second variable, e.g.
ggplot(d, aes(x=l1, y=y, colour=l2)) +
geom_point(position=position_jitter(width=0.2), alpha=0.5) +
geom_boxplot(fill=NA)
However, I can't figure out how to dodge the points by the colour variable (here, l2) and also jitter them.
Here is an approach that manually performs the jittering and dodging.
# a plot with no dodging or jittering of the points
dp <- ggplot(d, aes(x=l1, y=y, colour=l2)) +
geom_point(alpha=0.5) +
geom_boxplot(fill=NA)
# build the plot for rendering
foo <- ggplot_build(dp)
# now replace the 'x' values in the data for layer 1 (unjittered and un-dodged points)
# with the appropriately dodged and jittered points
foo$data[[1]][['x']] <- jitter(foo$data[[2]][['x']][foo$data[[1]][['group']]],amount = 0.2)
# now draw the plot (need to explicitly load grid package)
library(grid)
grid.draw(ggplot_gtable(foo))
# note the following works without explicitly loading grid
plot(ggplot_gtable(foo))
I don't think you'll like it, but I've never found a way around this except to produce your own x values for the points. In this case:
d$l1.num <- as.numeric(d$l1)
d$l2.num <- (as.numeric(d$l2)/3)-(1/3 + 1/6)
d$x <- d$l1.num + d$l2.num
ggplot(d, aes(l1, y, colour = l2)) + geom_boxplot(fill = NA) +
geom_point(aes(x = x), position = position_jitter(width = 0.15), alpha = 0.5) + theme_bw()
It's certainly a long way from ideal, but becomes routine pretty quickly. If anyone has an alternative solution, I'd be very happy!
The new position_jitterdodge() works for this. However, it requires the fill aesthetic to tell it how to group points, so you have to specify a manual fill to get uncolored boxes:
ggplot(d, aes(x=l1, y=y, colour=l2, fill=l2)) +
geom_point(position=position_jitterdodge(width=0.2), alpha=0.5) +
geom_boxplot() + scale_fill_manual(values=rep('white', length(unique(l2))))
I'm using a newer version of ggplot2 (ggplot2_2.2.1.9000) and I was struggling to find an answer that worked for a similar plot of my own. #John Didon's answer produced an error for me; Error in position_jitterdodge(width = 0.2) : unused argument (width = 0.2). I had previous code that worked with geom_jitter that stopped working after downloading the newer version of ggplot2. This is how I solved it below - minimal-fuss code....
ggplot(d, aes(x=l1, y=y, colour=l2, fill=l2)) +
geom_point(position = position_jitterdodge(dodge.width = 1,
jitter.width = 0.5), alpha=0.5) +
geom_boxplot(position = position_dodge(width = 1), fill = NA)
Another option would be to use facets:
set.seed(7)
l1 <- gl(3, 1, length=102, labels=letters[1:3])
l2 <- gl(2, 51, length=102, labels=LETTERS[1:2]) # Will use this later
y <- runif(102)
d <- data.frame(l1, l2, y)
ggplot(d, aes(x=l1, y=y, colour=l2)) +
geom_point(position=position_jitter(width=0.2), alpha=0.5) +
geom_boxplot(fill=NA) +
facet_grid(.~l2) +
theme_bw()
Sorry, donĀ“t have enough points to post the resulting graph.