ggplot2 annotate using a measure unit other than the axis variable - r

Hi I've got the following code
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12-31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line()
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
But annotating the graph is really clunky. I'd like to be able to specify the x axis using percentage of axis (for example) or inches or something else. Is this possible and how would I go about it?
Thanks

Your only option, I think, is to post-process the graph using grid. You'll need to expose the viewports and navigate to the plot panel, and there you have access to all grid units. Following Paul Murrell's example:
library(ggplot2)
library(grid)
qplot(1:10, rnorm(10))
# grid.force() # doesn't seem necessary?
# grid.ls()
downViewport("panel.3-4-3-4")
grid.text(label = "Some text", x = unit(0,"inch"),hjust=0)
grid.text(label = "Some text", x = unit(0.5,"npc"),hjust=0.5)
upViewport(0)

The package 'scales' includs a ton of formatter options: e.g. to format the y-axis in your example to percent use "scale_y_continuous(labels = percent)"
require(ggplot2)
require(scales)
d1=data.frame(a=c(4,5,6,7),b=as.Date(c('2005-12-31','2006-12-31','2007-12-31','2008-12- 31'),"%Y-%m-%d"))
a = ggplot(d1,aes(x=b,y=a)) + geom_line() + scale_y_continuous(labels = percent)
a + annotate('text',x=as.Date('2006-12-31','%Y-%m-%d'),y=5.5,label='blah')
Have a look at the ggplot docs as well.

Related

Override aesthetics of custom plot

I am not sure exactly how to override aesthetic properties of a custom plot made with ggplot. The only way I could think of right now was using the functionality of the grid package, though is really hackish. Maybe there is a easier way, like using guides or so from ggplot2, though I could't manage to make it work?
Below is an example where I just want to adjust the line width in the graph. Of course, I would like that to trickle down in the legend as well. So, below are my steps with grid, but any simpler solution is greatly appreciated (ideally something that doesn't need grid but just ggplot2, if possible).
library(iNEXT)
library(ggplot2)
library(grid)
# Some custom plot from the iNEXT package
data(spider)
out <- iNEXT(spider, q=0, datatype="abundance")
custom_plot <- ggiNEXT(out)
custom_plot
# Get the grobs
g <- grid.force(ggplotGrob(custom_plot))
# Check the list of names of grobs:
# grid.ls(g)
# View(g$grobs)
# Get an idea about the grob paths
gpaths <- paste(gsub(pattern = "layout::",
replacement = "",
x = grid.ls(g, print = FALSE)$gPath),
grid.ls(g, print = FALSE)$name,
sep = "::")
gpaths[grepl("polyline", gpaths)]
#> [1] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.y..polyline.107"
#> [2] "panel.7-5-7-5::grill.gTree.114::panel.grid.minor.x..polyline.109"
#> [3] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.y..polyline.111"
#> [4] "panel.7-5-7-5::grill.gTree.114::panel.grid.major.x..polyline.113"
#> [5] "panel.7-5-7-5::GRID.polyline.91"
#> [6] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.95::GRID.polyline.93"
#> [7] "panel.7-5-7-5::geom_ribbon.gTree.101::geom_ribbon.gTree.99::GRID.polyline.97"
# Edit the width of the lines
g <- editGrob(grob = g,
gPath = gpaths[grepl("panel.7-5-7-5::GRID.polyline", gpaths)],
gp = gpar(lwd = c(1,1,1,1)))
plot(g)
Created on 2020-07-22 by the reprex package (v0.3.0)
The answer you are looking for is under "Draw R/E curves by yourself" at
https://cran.r-project.org/web/packages/iNEXT/vignettes/Introduction.html.
Fortunately, the authors of the package have provided the function fortify() along with some code to copy verbatim, to achieve what you desire.
You should copy the following from that section and change the lwd (line width) parameter in the geom_line() function call to your liking.
df <- fortify(out, type=1) # Note the type parameter!
df.point <- df[which(df$method=="observed"),]
df.line <- df[which(df$method!="observed"),]
df.line$method <- factor(df.line$method,
c("interpolated", "extrapolated"),
c("interpolation", "extrapolation"))
ggplot(df, aes(x=x, y=y, colour=site)) +
geom_point(aes(shape=site), size=5, data=df.point) +
geom_line(aes(linetype=method), lwd=1.5, data=df.line) +
geom_ribbon(aes(ymin=y.lwr, ymax=y.upr,
fill=site, colour=NULL), alpha=0.2) +
labs(x="Number of individuals", y="Species diversity") +
theme(legend.position = "bottom",
legend.title=element_blank(),
text=element_text(size=18),
legend.box = "vertical")
I think you're making life overly complicated. Does this approach gives you what you need?
Generate a plot
plot <- mtcars %>% ggplot() + geom_line(aes(x=mpg, y=cyl, colour=as.factor(gear)))
plot
Modify the plot
plot + aes(size=5) + guides(size=FALSE)
The guides call suppresses the legend for size. Obviously, you can delete it if you do want the legend to appear.
Update
Responding to OP's question in the comments. I agree: my suggestion does not modify the ggiNEXT plot as I predicted.
I've done some digging. The diversity curves in the plot are produced by the following statement in the ggiNEXT.iNEXT function
g <- g + geom_line(aes_string(linetype = "lty"), lwd = 1.5) + ...
I find this strange. As far as I know, lwd is not an aesthetic in ggplot2. (And "lty" is not a valid value for the linetype aesthetic. However, lty and lwd are the base R equivalents of ggplot2's linetype and size respectively.)
In case lwd was an undocumented feature, I tried
custom_plot + aes(lwd=3)
But this had no effect.
I then copied the body of the ggiNEXT.iNEXT function into my own function and changed the call to geom_line to read
g <- g + geom_line(aes_string(linetype = "lty"), size = 1.5)
Calling my new function produced a plot identical (to my eye at least) to that produced by the original ggiNEXT.iNEXT call. Then
custom_plot <- myPlot(out)
custom_plot
custom_plot + aes(size=3) + guides(size=FALSE)
Produced the predicted changes. So my best suggestion is either (1) to create a local version of ggiNEXT.iNEXT and load it whenever you need to make this modification [Of course, you then need to make sure you update your local copy in line with any changes to the "official" version] or (2) to create the graph from scratch. Looking at the source code for ggiNEXT.iNEXT, it's not that complicated.
This might be worth raising as an issue with the authors of iNEXT.

ggplot query or change plot limits

I have a ggplot object returned by a function in an R package. I want to add some elements to this plot before plotting it. But, I do not know the plot limits. Is there a way to query the ggplot object to find the plot limits? Actually, what I'd really like to do is simply set new limits for subsequent plotting, but I understand this is not possible, based on discussions of the impossibility of plotting data against two different y-axes.
For example, say I want to plot a small rectangle in lower-left corner of plot, but not knowing the plot limits, I don't know where to put it:
p = function() return(ggplot() + xlim(-2, 5) + ylim(-3, 5) +
geom_rect(mapping=aes(xmin=1, xmax=2, ymin=1, ymax=2)))
gp = p()
gp = gp + geom_rect(mapping=aes(xmin=0, ymin=0, xmax=0.5, ymax=0.5))
print(gp)
In ggplot2 3.0.0:
ggplot_build(gp)$layout$panel_params[[1]][c("x.range","y.range")]
ggplot_build(p)$layout$panel_ranges[[1]][c("x.range","y.range")]

How to plot stacked point histograms?

What's the ggplot2 equivalent of "dotplot" histograms? With stacked points instead of bars? Similar to this solution in R:
Plot Histogram with Points Instead of Bars
Is it possible to do this in ggplot2? Ideally with the points shown as stacks and a faint line showing the smoothed line "fit" to these points (which would make a histogram shape.)
ggplot2 does dotplots Link to the manual.
Here is an example:
library(ggplot2)
set.seed(789); x <- data.frame(y = sample(1:20, 100, replace = TRUE))
ggplot(x, aes(y)) + geom_dotplot()
In order to make it behave like a simple dotplot, we should do this:
ggplot(x, aes(y)) + geom_dotplot(binwidth=1, method='histodot')
You should get this:
To address the density issue, you'll have to add another term, ylim(), so that your plot call will have the form ggplot() + geom_dotplot() + ylim()
More specifically, you'll write ylim(0, A), where A will be the number of stacked dots necessary to count 1.00 density. In the example above, the best you can do is see that 7.5 dots reach the 0.50 density mark. From there, you can infer that 15 dots will reach 1.00.
So your new call looks like this:
ggplot(x, aes(y)) + geom_dotplot(binwidth=1, method='histodot') + ylim(0, 15)
Which will give you this:
Usually, this kind of eyeball estimate will work for dotplots, but of course you can try other values to fine-tune your scale.
Notice how changing the ylim values doesn't affect how the data is displayed, it just changes the labels in the y-axis.
As #joran pointed out, we can use geom_dotplot
require(ggplot2)
ggplot(mtcars, aes(x = mpg)) + geom_dotplot()
Edit: (moved useful comments into the post):
The label "count" it's misleading because this is actually a density estimate may be you could suggest we changed this label to "density" by default. The ggplot implementation of dotplot follow the original one of Leland Wilkinson, so if you want to understand clearly how it works take a look at this paper.
An easy transformation to make the y axis actually be counts, i.e. "number of observations". From the help page it is written that:
When binning along the x axis and stacking along the y axis, the numbers on y axis are not meaningful, due to technical limitations of ggplot2. You can hide the y axis, as in one of the examples, or manually scale it to match the number of dots.
So you can use this code to hide y axis:
ggplot(mtcars, aes(x = mpg)) +
geom_dotplot(binwidth = 1.5) +
scale_y_continuous(name = "", breaks = NULL)
I introduce an exact approach using #Waldir Leoncio's latter method.
library(ggplot2); library(grid)
set.seed(789)
x <- data.frame(y = sample(1:20, 100, replace = TRUE))
g <- ggplot(x, aes(y)) + geom_dotplot(binwidth=0.8)
g # output to read parameter
### calculation of width and height of panel
grid.ls(view=TRUE, grob=FALSE)
real_width <- convertWidth(unit(1,'npc'), 'inch', TRUE)
real_height <- convertHeight(unit(1,'npc'), 'inch', TRUE)
### calculation of other values
width_coordinate_range <- diff(ggplot_build(g)$panel$ranges[[1]]$x.range)
real_binwidth <- real_width / width_coordinate_range * 0.8 # 0.8 is the argument binwidth
num_balls <- real_height / 1.1 / real_binwidth # the number of stacked balls. 1.1 is expanding value.
# num_balls is the value of A
g + ylim(0, num_balls)
Apologies : I don't have enough reputation to 'comment'.
I like cuttlefish44's "exact approach", but to make it work (with ggplot2 [2.2.1]) I had to change the following line from :
### calculation of other values
width_coordinate_range <- diff(ggplot_build(g)$panel$ranges[[1]]$x.range)
to
### calculation of other values
width_coordinate_range <- diff(ggplot_build(g)$layout$panel_ranges[[1]]$x.range)

rdata & ggplot: specifying plot initial plot size?

I'm using ggplot2 and attempting to create an empty plot with some basic dimensions, like I might do w/ the stock plot function like so:
plot(x = c(0, 10), y=c(-7, 7))
Then I'd plot the points with geom_point() (or, stock point() function)
How can I set that basic plot up using ggplot? I'm only able to draw a plot using like:
ggplot() + layer(data=data, mapping = aes(x=side, y=height), geom = "point")
But this has max x/y values based on the data.
There are two ways to approach this:
Basically the same approach as with base graphics; the first layer put down has the limits you want, using geom_blank()
ggplot() +
geom_blank(data=data.frame(x=c(0,10),y=c(-7,7)), mapping=aes(x=x,y=y))
Using expand_limits()
ggplot() +
expand_limits(x=c(0,10), y=c(-7,7))
In both cases, if your data extends beyond this, the axes will be further expanded.
You can set the overall plotting region limits using xlim and ylim:
ggplot(data = data) +
geom_point(aes(x = side, y = height) +
xlim(c(0,10)) +
ylim(c(-7,7))
Also see coord_cartesian which zooms in and out rather than hard coding the axis limits.
Edit Since #Brian clarified the differences between his answer and mine well, I thought I should mention it as well in my answer, so no one misses it. Using xlim and ylim will set the limits of the plotting region no matter what data you add in subsequent layers. Brian's method using expand_limits is a way to set the minimum ranges.

How do I include plotting symbol glyphs in a text annotation in ggplot2?

I'd like to include the glyph used in a scatter plot directly in a text annotation.
E.g., Suppose I have:
g <- g + geom_text(aes(x=14, y = 17, label="GOOD"))
where g is a scatter plot w/ a linear trend line, labeled by "GOOD" at (14,17). Instead of
"GOOD", I'd like to have "X = GOOD" where X is a plotting glyph (e.g., a hollow triangle, circle etc.) used in the scatter plot. This way I can dispense w/ the legend.
To expand upon Harlan's answer, here's a plot of the car's dataset
p <- ggplot(mtcars, aes(wt, mpg)) + geom_point()
You need to add two annotations, and the trick is to set the horizontal justification of the text to make the positions line up.
x <- 4
y <- 30
p + annotate("point", x, y) + annotate("text", x, y, label = " = GOOD", hjust = 0)
The alternative is to use opts(legend.position = ??) to place your legend inside the plot, which has much the same effect.
You could probably do it by plotting a single point of the right type using annotate(), with the text you have above next to it as a separate annotate().
Personally, though, it sounds like you're trying to make what they used to call a camera-ready image. In that case, I'd recommend exporting to PDF and using something like Inkspace to custom-create your in-graph labels. It'll be simpler and you have a lot more flexibility, plus a WYSIWYG interface.

Resources