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:
Related
I have a set of code that produces multiple plots using facet_wrap:
ggplot(summ,aes(x=depth,y=expr,colour=bank,group=bank)) +
geom_errorbar(aes(ymin=expr-se,ymax=expr+se),lwd=0.4,width=0.3,position=pd) +
geom_line(aes(group=bank,linetype=bank),position=pd) +
geom_point(aes(group=bank,pch=bank),position=pd,size=2.5) +
scale_colour_manual(values=c("coral","cyan3", "blue")) +
facet_wrap(~gene,scales="free_y") +
theme_bw()
With the reference datasets, this code produces figures like this:
I am trying to accomplish two goals here:
Keep the auto scaling of the y axis, but make sure only 1 decimal place is displayed across all the plots. I have tried creating a new column of the rounded expr values, but it causes the error bars to not line up properly.
I would like to wrap the titles. I have tried changing the font size as in Change plot title sizes in a facet_wrap multiplot, but some of the gene names are too long and will end up being too small to read if I cram them on a single line. Is there a way to wrap the text, using code within the facet_wrap statement?
Probably cannot serve as definite answer, but here are some pointers regarding your questions:
Formatting the y-axis scale labels.
First, let's try the direct solution using format function. Here we format all y-axis scale labels to have 1 decimal value, after rounding it with round.
formatter <- function(...){
function(x) format(round(x, 1), ...)
}
mtcars2 <- mtcars
sp <- ggplot(mtcars2, aes(x = mpg, y = qsec)) + geom_point() + facet_wrap(~cyl, scales = "free_y")
sp <- sp + scale_y_continuous(labels = formatter(nsmall = 1))
The issue is, sometimes this approach is not practical. Take the leftmost plot from your figure, for example. Using the same formatting, all y-axis scale labels would be rounded up to -0.3, which is not preferable.
The other solution is to modify the breaks for each plot into a set of rounded values. But again, taking the leftmost plot of your figure as an example, it'll end up with just one label point, -0.3
Yet another solution is to format the labels into scientific form. For simplicity, you can modify the formatter function as follow:
formatter <- function(...){
function(x) format(x, ..., scientific = T, digit = 2)
}
Now you can have a uniform format for all of plots' y-axis. My suggestion, though, is to set the label with 2 decimal places after rounding.
Wrap facet titles
This can be done using labeller argument in facet_wrap.
# Modify cyl into factors
mtcars2$cyl <- c("Four Cylinder", "Six Cylinder", "Eight Cylinder")[match(mtcars2$cyl, c(4,6,8))]
# Redraw the graph
sp <- ggplot(mtcars2, aes(x = mpg, y = qsec)) + geom_point() +
facet_wrap(~cyl, scales = "free_y", labeller = labeller(cyl = label_wrap_gen(width = 10)))
sp <- sp + scale_y_continuous(labels = formatter(nsmall = 2))
It must be noted that the wrap function detects space to separate labels into lines. So, in your case, you might need to modify your variables.
This only solved the first part of the question. You can create a function to format your axis and use scale_y_continous to adjust it.
df <- data.frame(x=rnorm(11), y1=seq(2, 3, 0.1) + 10, y2=rnorm(11))
library(ggplot2)
library(reshape2)
df <- melt(df, 'x')
# Before
ggplot(df, aes(x=x, y=value)) + geom_point() +
facet_wrap(~ variable, scale="free")
# label function
f <- function(x){
format(round(x, 1), nsmall=1)
}
# After
ggplot(df, aes(x=x, y=value)) + geom_point() +
facet_wrap(~ variable, scale="free") +
scale_y_continuous(labels=f)
scale_*_continuous(..., labels = function(x) sprintf("%0.0f", x)) worked in my case.
I am using ggplot2 to produce a plot that has 3 facets. Because I am comparing two different data sets, I would like to then be able to plot a second data set using the same y scale for the facets as in the first plot. However, I cannot find a simple way to save the settings of the first plot to then re-use them with the second plot. Since each facet has its own y scale, it will be a pain to specify them by hand for the second plot. Does anyone know of a quick way of re-using scales? To make this concrete, here is how I am generating first my plot:
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p + facet_wrap(~ cyl, scales = "free_y")
EDIT
When applying one of the suggestions below, I found out that my problem was more specific than described in the original post, and it had to do specifically with scaling of the error bars. Concretely, the error bars look weird when I rescale the second plot as suggested. Does anyone have any suggestions on how to keep the same scale for both plots and dtill display the error bars correctly? I am attaching example below for concreteness:
#Create sample data
d1 <- data.frame(fixtype=c('ff','ff','fp','fp'), detype=c('det','pro','det','pro'),
diffscore=c(-1,-15,3,-17),se=c(2,3,1,2))
d2 <- data.frame(fixtype=c('ff','ff','fp','fp'), detype=c('det','pro','det','pro'),
diffscore=c(-1,-3,-2,-1),se=c(4,3,5,3))
#Plot for data frame 1, this is the scale I want to keep
lim_d1 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_point(aes(size=1), shape=15) +
geom_errorbar(lim_d1, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#Plot for data frame 2 original scale
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d2, aes(colour=detype, y=diffscore, x=detype)) +
geom_point(aes(size=1), shape=15) +
geom_errorbar(lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#Plot for data frame 2 adjusted scale. This is where things go wrong!
#As suggested below, first I plot the first plot, then I draw a blank screen and try
#to plot the second data frame on top.
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_blank() +
geom_point(data=d2, aes(size=1), shape=15) +
geom_errorbar(lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
#If the error bars are fixed, by adding data=d2 to geom_errorbar(), then
#the error bars are displayed correctly but the scale gets distorted again
lim_d2 <- aes(ymax = diffscore + se, ymin=diffscore - se)
ggplot(d1, aes(colour=detype, y=diffscore, x=detype)) +
geom_blank() +
geom_point(data=d2, aes(size=1), shape=15) +
geom_errorbar(data=d2,lim_d2, width=0.2,size=1) +
facet_wrap(~fixtype, nrow=2, ncol=2, scales = "free_y")
You may first call ggplot on your original data where you add a geom_blank as a first layer. This sets up a plot area, with axes and legends based on the data provided in ggplot.
Then add geoms which use data other than the original data. In the example, I use a simple subset of the original data.
From ?geom_blank: "The blank geom draws nothing, but can be a useful way of ensuring common scales between different plots.".
ggplot(data = mtcars, aes(mpg, wt)) +
geom_blank() +
geom_point(data = subset(mtcars, wt < 3)) +
facet_wrap(~ cyl, scales = "free_y")
Here is an ugly hack that assumes you have an identical facetting layout in both plots.
It replaces the panel element of the ggplot build.
p <- ggplot(mtcars, aes(mpg, wt)) + geom_point()
p1 <- p + facet_wrap(~ cyl, scales = "free_y") + labs(title = 'original')
# create "other" data.frame
n <- nrow(mtcars)
set.seed(201405)
mtcars2 <- mtcars[sample(seq_len(n ),n-15),]
# create this second plot
p2 <- p1 %+% mtcars2 + labs(title = 'new data')
# and a copy so we can attempt to fix
p3 <- p2 + labs(title = 'new data original scale')
# use ggplot_build to construct the plots for rendering
p1b <- ggplot_build(p1)
p3b <- ggplot_build(p3)
# replace the 'panel' information in plot 2 with that
# from plot 1
p3b[['panel']] <- p1b[['panel']]
# render the revised plot
# for comparison
library(gridExtra)
grid.arrange(p1 , p2, ggplot_gtable(p3b))
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.
I have the following problem: I would like to visualize a discrete and a continuous variable on a boxplot in which the latter has a few extreme high values. This makes the boxplot meaningless (the points and even the "body" of the chart is too small), that is why I would like to show this on a log10 scale. I am aware that I could leave out the extreme values from the visualization, but I am not intended to.
Let's see a simple example with diamonds data:
m <- ggplot(diamonds, aes(y = price, x = color))
The problem is not serious here, but I hope you could imagine why I would like to see the values at a log10 scale. Let's try it:
m + geom_boxplot() + coord_trans(y = "log10")
As you can see the y axis is log10 scaled and looks fine but there is a problem with the x axis, which makes the plot very strange.
The problem do not occur with scale_log, but this is not an option for me, as I cannot use a custom formatter this way. E.g.:
m + geom_boxplot() + scale_y_log10()
My question: does anyone know a solution to plot the boxplot with log10 scale on y axis which labels could be freely formatted with a formatter function like in this thread?
Editing the question to help answerers based on answers and comments:
What I am really after: one log10 transformed axis (y) with not scientific labels. I would like to label it like dollar (formatter=dollar) or any custom format.
If I try #hadley's suggestion I get the following warnings:
> m + geom_boxplot() + scale_y_log10(formatter=dollar)
Warning messages:
1: In max(x) : no non-missing arguments to max; returning -Inf
2: In max(x) : no non-missing arguments to max; returning -Inf
3: In max(x) : no non-missing arguments to max; returning -Inf
With an unchanged y axis labels:
The simplest is to just give the 'trans' (formerly 'formatter') argument of either the scale_x_continuous or the scale_y_continuous the name of the desired log function:
library(ggplot2) # which formerly required pkg:plyr
m + geom_boxplot() + scale_y_continuous(trans='log10')
EDIT:
Or if you don't like that, then either of these appears to give different but useful results:
m <- ggplot(diamonds, aes(y = price, x = color), log="y")
m + geom_boxplot()
m <- ggplot(diamonds, aes(y = price, x = color), log10="y")
m + geom_boxplot()
EDIT2 & 3:
Further experiments (after discarding the one that attempted successfully to put "$" signs in front of logged values):
# Need a function that accepts an x argument
# wrap desired formatting around numeric result
fmtExpLg10 <- function(x) paste(plyr::round_any(10^x/1000, 0.01) , "K $", sep="")
ggplot(diamonds, aes(color, log10(price))) +
geom_boxplot() +
scale_y_continuous("Price, log10-scaling", trans = fmtExpLg10)
Note added mid 2017 in comment about package syntax change:
scale_y_continuous(formatter = 'log10') is now scale_y_continuous(trans = 'log10') (ggplot2 v2.2.1)
I had a similar problem and this scale worked for me like a charm:
breaks = 10**(1:10)
scale_y_log10(breaks = breaks, labels = comma(breaks))
as you want the intermediate levels, too (10^3.5), you need to tweak the formatting:
breaks = 10**(1:10 * 0.5)
m <- ggplot(diamonds, aes(y = price, x = color)) + geom_boxplot()
m + scale_y_log10(breaks = breaks, labels = comma(breaks, digits = 1))
After executing::
Another solution using scale_y_log10 with trans_breaks, trans_format and annotation_logticks()
library(ggplot2)
m <- ggplot(diamonds, aes(y = price, x = color))
m + geom_boxplot() +
scale_y_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10", scales::math_format(10^.x))
) +
theme_bw() +
annotation_logticks(sides = 'lr') +
theme(panel.grid.minor = element_blank())
I think I got it at last by doing some manual transformations with the data before visualization:
d <- diamonds
# computing logarithm of prices
d$price <- log10(d$price)
And work out a formatter to later compute 'back' the logarithmic data:
formatBack <- function(x) 10^x
# or with special formatter (here: "dollar")
formatBack <- function(x) paste(round(10^x, 2), "$", sep=' ')
And draw the plot with given formatter:
m <- ggplot(d, aes(y = price, x = color))
m + geom_boxplot() + scale_y_continuous(formatter='formatBack')
Sorry to the community to bother you with a question I could have solved before! The funny part is: I was working hard to make this plot work a month ago but did not succeed. After asking here, I got it.
Anyway, thanks to #DWin for motivation!