Function that simply returns the plot - r

The function I'm asking for is just for convenience during programming.
Adding layers in ggplot2 with the "+" operator is great. Especially adding layers in the middle amounts to just adding another line of code.
However, if I want to try to add a layer after the last line, I have to append a "+" to the last row and if I want to remove this layer again, I also have to remove the "+" again:
ggplot(df, aes(x,y,...)) +
geom_X(...) + # after this line, I can easily add layers
... +
layer_Z(...) # to add a layer after here, I have to modify also this line
I'm searching for a function ggidentity() which just returns the plot itself to use it as a default last line so I can easily add more lines, as in
ggplot(df, aes(x,y,...)) +
geom_X(...) + # after this line, I can easily add layers
... +
layer_Z(...) + # now it's easy to add layers after this line
ggidentity() # this doesn't change anything in the plot
I tried it with a simple function
identity <- function(x) x
which works well with the magrittr-package (and improves my workflow in exploratory data analysis), but not with ggplot2.

I think we need geom_blank(), example:
library(ggplot2) # ggplot2_2.2.1
ggplot(mtcars, aes(wt, mpg)) +
geom_point() +
geom_blank() # The blank geom draws nothing

Related

Unable to correctly add labels to a list of ggplot graphs

I am trying to assemble a multipanel boxplot with ggplot.
To have a general structure I am generating a list of plots and plotting them. I also want to add letters reporting significance groups for each boxplot.
Everything works fine, except for the fact that all the boxplots show the letters computed during the last iteration of the loop.
I post below an example in which I just try to add letters reporting the loop iteration number, and as you can see instead of reporting "Plot 1" for the first loop and "Plot 2" for the second it always plots the second.
The code I used is the following:
library(ggplot2)
library(gridExtra)
mydata<-data.frame(values=c(1,4,5,6,4,2,4,7,3,4,5,6,4,4,2,1,3,6,4,1,2,5,4,3,4,2,1,3,4,2),group=c(rep("A",15),rep("B",15)))
mydata2<-data.frame(values=c(2,6,5,6,7,2,5,7,3,4,5,6,4,4,2,1,3,6,4,1,2,5,4,3,1,2,3,3,4,7),group=c(rep("A",15),rep("B",15)))
myp<-list()
for(aaa in 1:2)
{
if(aaa==1) mydata<-mydata else mydata<-mydata2
myp[[aaa]]<-ggplot(mydata, aes(x=group, y=values)) +
geom_boxplot(outlier.shape=NA) + #avoid plotting outliers twice
geom_jitter(position=position_jitter(width=.1, height=0)) +
geom_text(aes(x=1, y=max(values)-0.05*max(values),label=paste("Plot",aaa))) +
geom_text(aes(x=2, y=max(values)-0.05*max(values),label=paste("Plot",aaa)))
}
do.call(grid.arrange,myp)
What am I doing wrong? It looks like the used of do.call with grid.arrange creates problems with the geom_text (but not with the plot, which is different in the two loops).
I would prefer NOT to manually write all the plot functions, since I have at lest three multipanel plots each on with 4 boxplots.
I'm not entirely sure what goes wrong with geom_text, but everything works if you use annotate instead (which should be used exactly for this purpose).
for(aaa in 1:2){
print(aaa)
if(aaa==1) df<-mydata else df<-mydata2
myp[[aaa]]<-ggplot(df, aes(x=group, y=values)) +
geom_boxplot(outlier.shape=NA) + #avoid plotting outliers twice
geom_jitter(position=position_jitter(width=.1, height=0)) +
annotate("text", x=1, y=max(df$values)-0.05*max(df$values),label=paste("Plot",aaa)) +
annotate("text", x=2, y=max(df$values)-0.05*max(df$values),label=paste("Plot",aaa))
}

Conditionally modify ggplot theme based on presence of facets?

I'm working on a custom ggplot2 theme and was thinking it could be nifty to automatically modify elements of the theme depending on certain characteristics of the the plot object. For instance, is there a way to specify that if the plot contains facets, add a border to each panel?
I guess the question is really, can I access the current gg object from within a custom theme() call and then conditionally apply certain theme elements? In my head I would define my theme function to be something like this:
theme_custom <- function() {
if (plot$facet$params > 0) {
theme_minimal() +
theme(panel.border = element_rect(color = "gray 50", fill = NA))
}
else {
theme_minimal()
}
}
If this is possible, it would look like this in use:
library(ggplot2)
# plot with facets automatically adds panel borders
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
facet_wrap(vars(cyl)) +
theme_custom()
# plot without facets no panel border
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
theme_custom()
NOTE: This was originally posted on RStudio Community and did not receive an answer.
I think Oliver was thinking in the correct direction.
I don't think the theme_custom function is the correct place to check the plot for conditional theming, because theme functions are mostly agnostic about the precise plot that they are added to.
Instead, I think the appropriate place to check the plot is when the theme is added to the plot. You could write a theme function like the following, which sets a different class to the output.
theme_custom <- function() {
out <- theme_minimal()
class(out) <- c("conditional_theme", class(out))
out
}
Now everytime a theme is added to a plot, this is done through the ggplot_add.theme function, which we can rewrite for the conditional_theme class. In my opinion, the correct way to check if a plot is facetted, is to check the class of the plot$facet slot, which can be FacetGrid, FacetWrap etc when a proper facet is added and defaults to FacetNull when no facet is set.
ggplot_add.conditional_theme <- function(object, plot, object_name) {
if (!inherits(plot$facet, "FacetNull")) {
object <- object + theme(panel.border = element_rect(colour = "grey50", fill = NA))
}
plot$theme <- ggplot2:::add_theme(plot$theme, object, object_name)
plot
}
And now the use cases should work as intended:
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
facet_wrap(vars(cyl)) +
theme_custom()
ggplot(mtcars, aes(mpg, wt)) +
geom_point() +
theme_custom()
The only downside is that you would literally have to add the theme to the plot every time and you can't use the theme_set(theme_custom()) to have this apply to any plot in the session.
This requires a bit more knowledge than my current level of expertise in ggproto and ggproto_method objects. So this is not a complete answer, but a possible direction.
If you can gain access to the plot ggproto object, this object contains a ggproto_method in stored in the ggproto$facet$compute_layout. Depending on whether the plot contains a call to geom_facet, this will have a varying function length, as illustrated below
data(mtcars)
library(ggplot2)
p <- ggplot(mtcars, mapping = aes(x = hp, y = mpg)) +
geom_point()
pfacet <- p + facet_wrap(.~cyl)
nchar(format(p$facet$compute_layout))
[1] 139
nchar(format(pfacet$facet$compute_layout))
[1] 1107
(Note that 139 seems to be standard for any ggproto not containing a facet)
This assumes you can gain access to the proto object every time the plot is called or that you place your method as the a call after facet_wrap or similar methods are called, and is indeed just a hacky method due to my lack of knowledge of the intricates of gg, ggproto and ggproto_method objects.
From a related post about conditionally adding ggplot elements it transpires one can add elements using {if(cond)expr}+ formatting, i.e. put the whole element in {} then follow with the +.
One can combine this with theme element replacement formatting, e.g.
theme_minimal() %+replace% theme(axis.title.y.right = element_text(angle = 90)) +
To give:
{if(cond) theme() %+replace% theme(element = value)} +
So, shamelessly stealing from (/standing on the gigantic shoulders of) #teunbrand 's answer:
{if (!inherits(plot$facet, "FacetNull")) theme() %+replace% theme(panel.border = element_rect(colour = "grey50", fill = NA))} +
This works for my code but I'm not 100% sure about your example, apologies for not testing, in the middle of a huge function edit, but wanted to share this approach for its general applicability.
One nice thing about this approach is that it's easy to chain element edits within the same condition, and have different conditions in their own {if}.

Removing the NA element from the legend for extra data element in facets

In general I know that I can use breaks in my scale_color_manual command to remove a specific label. But for some reason this doesn't work in my case and I don't see the error. If I try to set the breaks (uncommenting the breaks line) it removes the whole legend.
Did I overlook something? Is it maybe it is not the overall data frame? The Thing is that I want to have thresholds in only one of the facets.
library(ggplot2)
threshold <- log2(c(1.5,2))
ggplot() +
geom_hline(data=data.frame(category=c('contains virus IDs',
rep('enriched',4)),
threshold=c(NA, threshold, -threshold),
color=c(NA, rep(2^threshold, 2))),
aes(yintercept=threshold, color=as.factor(color)),
na.rm=TRUE) +
scale_color_manual('threshold',
values=c(`2`='red', `1.5`='orange')
# , breaks=c(`2`='2 fold', `1.5`='1.5 fold')
) +
facet_grid(~ category, scales='free_x', space='free_x')

Why does this ggplot only plot the grid without the values?

I am trying to plot a bar chart in ggplot but I am continuously getting only the grid. This is apparently a demonstration about the draw nothing here but I would like to understand how to get the values visible in the simplest way.
library(ggplot2)
testData<-data.frame(x=c("a","b","c","d","e","f"), y=c(10,6,9,28,10,17))
bar <- ggplot(data=testData, aes(x=c("a","b","c","d","e","f"), y=c(10,6,9,28,10,17), fill = "#FFCC00"))
One way I can get the plots is the geom_bar
bar <- ggplot(data=testData, aes(x=c("a","b","c","d","e","f"), y=c(10,6,9,28,10,17), fill = "#FFCC00")) + geom_bar(stat="identity")
Why are the values not plotted on the first bar chart and how to fix it the simplest way? What is the idea behind of this way of plotting with + and what is it called?
With the ggplot2 package, calling ggplot() is only meant to call the basic grid; it's like taking out a piece of graph paper before drawing a graph. In either case, having the grid ready has nothing to do with plotting the graph. That's why running the following command will result in the empty grid in your first example:
ggplot(data=testData, aes(x=x, y=y, fill = "#FFCC00"))
It's not the same as using a function like plot() or hist(), which prep the grid and plot the data at the same time:
plot(x=x,y=y,data=testData)
hist(x=x,data=testData)
The "+" in ggplot is just a way to say that there are more arguments related to the ggplot that we want included on top of the first blank grid. That's why each line separated by a "+" is typically called a layer.
So, if we want to make a simple scatterplot, we add points on top of a grid:
testData<-data.frame(x=c(1:6), y=c(10,6,9,28,10,17))
ggplot(data=testData,aes(x=x,y=y)) +
geom_point()
Output:
If we want to add lines to that scatterplot, we can just add one line of code:
ggplot(data=testData,aes(x=x,y=y)) +
geom_point() +
geom_line()
Output:
We can keep adding layers like this if we want. Just note that they will print in the order that you type them (i.e. the first few lines will be below the lines printed after them):
ggplot(data=testData,aes(x=x,y=y)) +
geom_bar(stat="identity",fill="#00BFC4") +
geom_point() +
geom_line()
Output:
Also, note that it's recommended not to call your data multiple times within a ggplot call; that can lead to errors.
Don't use:
ggplot(data=testData, aes(x=c("a","b","c","d","e","f"),
y=c(10,6,9,28,10,17), fill = "#FFCC00")) +
geom_bar(stat="identity")
#or
ggplot(data=testData, aes(x=testData$x, y=testData$x, fill = "#FFCC00")) +
geom_bar(stat="identity")
Instead use:
ggplot(data=testData, aes(x=x, y=y, fill="#FFCC00")) +
geom_bar(stat="identity")
If you want to plot data from a data frame(s) not called within the first ggplot() line, then simply add a data argument to the "layers" that use that different data frame, like this:
ggplot(data=testData,aes(x=x,y=y)) +
geom_bar(stat="identity",fill="#00BFC4") +
geom_point(data=differentDf, aes(x=x,y=y)) +
geom_line(data=differentDf, aes(x=x,y=y))

ggplot2 stacked barplots, formatting, and grids

In the data that I am attempting to plot, each sample belongs in one of several groups, that will be plotted on their own grids. I am plotting stacked bar plots for each sample that will be ordered in increasing number of sequences, which is an id attribute of each sample.
Currently, the plot (with some random data) looks like this:
(Since I don't have the required 10 rep for images, I am linking it here)
There are couple things I need to accomplish. And I don't know where to start.
I would like the bars not to be placed at its corresponding nseqs value, rather placed next to each other in ascending nseqs order.
I don't want each grid to have the same scale. Everything needs to fit snugly.
I have tried to set scales and size to for facet_grid to free_x, but this results in an unused argument error. I think this is related to the fact that I have not been able to get the scales library loaded properly (it keeps saying not available).
Code that deals with plotting:
ggfdata <- melt(fdata, id.var=c('group','nseqs','sample'))
p <- ggplot(ggfdata, aes(x=nseqs, y=value, fill = variable)) +
geom_bar(stat='identity') +
facet_grid(~group) +
scale_y_continuous() +
opts(title=paste('Taxonomic Distribution - grouped by',colnames(meta.frame)[i]))
Try this:
update.packages()
## I'm assuming your ggplot2 is out of date because you use opts()
## If the scales library is unavailable, you might need to update R
ggfdata <- melt(fdata, id.var=c('group','nseqs','sample'))
ggfdata$nseqs <- factor(ggfdata$nseqs)
## Making nseqs a factor will stop ggplot from treating it as a numeric,
## which sounds like what you want
p <- ggplot(ggfdata, aes(x=nseqs, y=value, fill = variable)) +
geom_bar(stat='identity') +
facet_wrap(~group, scales="free_x") + ## No need for facet_grid with only one variable
labs(title = paste('Taxonomic Distribution - grouped by',colnames(meta.frame)[i]))

Resources