How do you add a legend of geometries in ggplot2? - r

Let's say I've got a set of data and I want to add a legend to each geometry that I plot it with. For example:
x <- rnorm(100, 1)
qplot(x = x, y = 1:100, geom = c("point", "smooth"))
And it would look something like this:
Now, I want to add a legend so it would say something like:
Legend title
* points [in black]
--- smoothed [in blue]
Where I specify the "Legend title", "points", and "smoothed" names.
How would I go about that?

The easiest way to add extra information is with annotation rather than a legend.
(I know it's a toy example, but ggplot is being sensible by not including a legend when there is only one kind of point and one kind of line. You could make a legend, but it will by default take up more space and ink than necessary and be more work. When there is only one kind of point its meaning should be clear from labels on the x and y axes and from the general context of the graph. Lacking other information, the reader will then infer that the line is the result of fitting some function to the points. The only things they won't know are the specific function and the meaning of the grey error region. That can be a simple title, annotation, or text outside the plot.)
#Sample data in a dataframe since that works best with ggplot
set.seed(13013)
testdf <- data.frame(x <- rnorm(100, 1),y <- 1:100)
One option is a title:
ggplot(testdf , aes(x = x, y = y)) + geom_point()+
stat_smooth(method="loess")+
xlab("buckshot hole distance(from sign edge)")+
ylab("speed of car (mph)")+
ggtitle("Individual Points fit with LOESS (± 1 SD)")
Another option is an annotation layer. Here I used the mean and max functions to guess a reasonable location for the text, but one could do a better job with real data and maybe use an argument like size=3 to make the text size smaller.
ggplot(testdf , aes(x = x, y = y)) + geom_point()+
stat_smooth(method="loess")+
xlab("buckshot hole distance (from sign edge)")+
ylab("speed of car (mph)")+
annotate("text", x = max(testdf$x)-1, y = mean(testdf$y),
label = "LOESS fit with 68% CI region", colour="blue")

A fast way to annotate a ggplot plot , is to use geom_text
x <- rnorm(100, 1)
y = 1:100
library(ggplot2)
dat <- data.frame(x=x,y=y)
bp <- ggplot(data =dat,aes(x = x, y = y))+
geom_point()+ geom_smooth(group=1)
bp <- bp +geom_text(x = -1, y = 3, label = "* points ", parse=F)
bp <- bp +geom_text(x = -1, y = -1, label = "--- smoothed ", parse=F,color='blue')
bp

Related

Plotting pixel by pixel with R

For some unfortunate technical reasons, I need to have a pixel plot.
For example, let's take this simple plot:
plot(0,0)
Points(x=1:10, y=rep(0,10), cex=1)
I need each dot to be exactly a pixel wide. The size parameter cex does not seem to allow such precision.
You could use ggplot with the option geom_point(shape = ".").
For example
# generate random dataframe
df <- data.frame(x = runif(100), y = runif(100))
# make the figure
ggplot(df) + aes(x = x, y = y) + geom_point(shape = ".") + theme_void()
This will create something like:

How to plot histograms of raw data on the margins of a plot of interpolated data

I would like to show in the same plot interpolated data and a histogram of the raw data of each predictor. I have seen in other threads like this one, people explain how to do marginal histograms of the same data shown in a scatter plot, in this case, the histogram is however based on other data (the raw data).
Suppose we see how price is related to carat and table in the diamonds dataset:
library(ggplot2)
p = ggplot(diamonds, aes(x = carat, y = table, color = price)) + geom_point()
We can add a marginal frequency plot e.g. with ggMarginal
library(ggExtra)
ggMarginal(p)
How do we add something similar to a tile plot of predicted diamond prices?
library(mgcv)
model = gam(price ~ s(table, carat), data = diamonds)
newdat = expand.grid(seq(55,75, 5), c(1:4))
names(newdat) = c("table", "carat")
newdat$predicted_price = predict(model, newdat)
ggplot(newdat,aes(x = carat, y = table, fill = predicted_price)) +
geom_tile()
Ideally, the histograms go even beyond the margins of the tileplot, as these data points also influence the predictions. I would, however, be already very happy to know how to plot a histogram for the range that is shown in the tileplot. (Maybe the values that are outside the range could just be added to the extreme values in different color.)
PS. I managed to more or less align histograms to the margins of the sides of a tile plot, using the method of the accepted answer in the linked thread, but only if I removed all kind of labels. It would be particularly good to keep the color legend, if possible.
EDIT:
eipi10 provided an excellent solution. I tried to modify it slightly to add the sample size in numbers and to graphically show values outside the plotted range since they also affect the interpolated values.
I intended to include them in a different color in the histograms at the side. I hereby attempted to count them towards the lower and upper end of the plotted range. I also attempted to plot the sample size in numbers somewhere on the plot. However, I failed with both.
This was my attempt to graphically illustrate the sample size beyond the plotted area:
plot_data = diamonds
plot_data <- transform(plot_data, carat_range = ifelse(carat < 1 | carat > 4, "outside", "within"))
plot_data <- within(plot_data, carat[carat < 1] <- 1)
plot_data <- within(plot_data, carat[carat > 4] <- 4)
plot_data$carat_range = as.factor(plot_data$carat_range)
p2 = ggplot(plot_data, aes(carat, fill = carat_range)) +
geom_histogram() +
thm +
coord_cartesian(xlim=xrng)
I tried to add the sample size in numbers with geom_text. I tried fitting it in the far right panel but it was difficult (/impossible for me) to adjust. I tried to put it on the main graph (which would anyway probably not be the best solution), but it didn’t work either (it removed the histogram and legend, on the right side and it did not plot all geom_texts). I also tried to add a third row of plots and writing it there. My attempt:
n_table_above = nrow(subset(diamonds, table > 75))
n_table_below = nrow(subset(diamonds, table < 55))
n_table_within = nrow(subset(diamonds, table >= 55 & table <= 75))
text_p = ggplot()+
geom_text(aes(x = 0.9, y = 2, label = paste0("N(>75) = ", n_table_above)))+
geom_text(aes(x = 1, y = 2, label = paste0("N = ", n_table_within)))+
geom_text(aes(x = 1.1, y = 2, label = paste0("N(<55) = ", n_table_below)))+
thm
library(egg)
pobj = ggarrange(p2, ggplot(), p1, p3,
ncol=2, widths=c(4,1), heights=c(1,4))
grid.arrange(pobj, leg, text_p, ggplot(), widths=c(6,1), heights =c(6,1))
I would be very happy to receive help on either or both tasks (adding sample size as text & adding values outside plotted range in a different color).
Based on your comment, maybe the best approach is to roll your own layout. Below is an example. We create the marginal plots as separate ggplot objects and lay them out with the main plot. We also extract the legend and put it outside the marginal plots.
Set-up
library(ggplot2)
library(cowplot)
# Function to extract legend
#https://github.com/hadley/ggplot2/wiki/Share-a-legend-between-two-ggplot2-graphs
g_legend<-function(a.gplot){
tmp <- ggplot_gtable(ggplot_build(a.gplot))
leg <- which(sapply(tmp$grobs, function(x) x$name) == "guide-box")
legend <- tmp$grobs[[leg]]
return(legend) }
thm = list(theme_void(),
guides(fill=FALSE),
theme(plot.margin=unit(rep(0,4), "lines")))
xrng = c(0.6,4.4)
yrng = c(53,77)
Plots
p1 = ggplot(newdat, aes(x = carat, y = table, fill = predicted_price)) +
geom_tile() +
theme_classic() +
coord_cartesian(xlim=xrng, ylim=yrng)
leg = g_legend(p1)
p1 = p1 + thm[-1]
p2 = ggplot(diamonds, aes(carat)) +
geom_line(stat="density") +
thm +
coord_cartesian(xlim=xrng)
p3 = ggplot(diamonds, aes(table)) +
geom_line(stat="density") +
thm +
coord_flip(xlim=yrng)
plot_grid(
plot_grid(plotlist=list(p2, ggplot(), p1, p3), ncol=2,
rel_widths=c(4,1), rel_heights=c(1,4), align="hv", scale=1.1),
leg, rel_widths=c(5,1))
UPDATE: Regarding your comment about the space between the plots: This is an Achilles heel of plot_grid and I don't know if there's a way to fix it. Another option is ggarrange from the experimental egg package, which doesn't add so much space between plots. Also, you need to save the output of ggarrange first and then lay out the saved object with the legend. If you run ggarrange inside grid.arrange you get two overlapping copies of the plot:
# devtools::install_github('baptiste/egg')
library(egg)
pobj = ggarrange(p2, ggplot(), p1, p3,
ncol=2, widths=c(4,1), heights=c(1,4))
grid.arrange(pobj, leg, widths=c(6,1))

how to combine in ggplot line / points with special values?

I'm quite new to ggplot but I like the systematic way how you build your plots. Still, I'm struggeling to achieve desired results. I can replicate plots where you have categorical data. However, for my use I often need to fit a model to certain observations and then highlight them in a combined plot. With the usual plot function I would do:
library(splines)
set.seed(10)
x <- seq(-1,1,0.01)
y <- x^2
s <- interpSpline(x,y)
y <- y+rnorm(length(y),mean=0,sd=0.1)
plot(x,predict(s,x)$y,type="l",col="black",xlab="x",ylab="y")
points(x,y,col="red",pch=4)
points(0,0,col="blue",pch=1)
legend("top",legend=c("True Values","Model values","Special Value"),text.col=c("red","black","blue"),lty=c(NA,1,NA),pch=c(4,NA,1),col=c("red","black","blue"),cex = 0.7)
My biggest problem is how to build the data frame for ggplot which automatically then draws the legend? In this example, how would I translate this into ggplot to get a similar plot? Or is ggplot not made for this kind of plots?
Note this is just a toy example. Usually the model values are derived from a more complex model, just in case you wante to use a stat in ggplot.
The key part here is that you can map colors in aes by giving a string, which will produce a legend. In this case, there is no need to include the special value in the data.frame.
df <- data.frame(x = x, y = y, fit = predict(s, x)$y)
ggplot(df, aes(x, y)) +
geom_line(aes(y = fit, col = 'Model values')) +
geom_point(aes(col = 'True values')) +
geom_point(aes(col = 'Special value'), x = 0, y = 0) +
scale_color_manual(values = c('True values' = "red",
'Special value' = "blue",
'Model values' = "black"))

ggplot2: Reading maximum bar height from plot object containing geom_histogram

Like this previous poster, I am also using geom_text to annotate plots in gglot2. And I want to position those annotations in relative coordinates (proportion of facet H & W) rather than data coordinates. Easy enough for most plots, but in my case I'm dealing with histograms. I'm sure the relevant information as to the y scale must be lurking in the plot object somewhere (after adding geom_histogram), but I don't see where.
My question: How do I read maximum bar height from a faceted ggplot2 object containing geom_histogram? Can anyone help?
Try this:
library(plyr)
library(scales)
p <- ggplot(mtcars, aes(mpg)) + geom_histogram(aes(y = ..density..)) + facet_wrap(~am)
r <- print(p)
# in data coordinate
(dc <- dlply(r$data[[1]], .(PANEL), function(x) max(x$density)))
(mx <- dlply(r$data[[1]], .(PANEL), function(x) x[which.max(x$density), ]$x))
# add annotation (see figure below)
p + geom_text(aes(x, y, label = text),
data = data.frame(x = unlist(mx), y = unlist(dc), text = LETTERS[1:2], am = 0:1),
colour = "red", vjust = 0)
# scale range
(yr <- llply(r$panel$ranges, "[[", "y.range"))
# in relative coordinates
(rc <- mapply(function(d, y) rescale(d, from = y), dc, yr))

Line plot that changes color over "time"

I have a data frame that contains x and y coordinates for a random walk that moves in discrete steps (1 step up, down, left, or right). I'd like to plot the path---the points connected by a line. This is easy, of course. The difficulty is that the path crosses over itself and becomes difficult to interpret. I add jitter to the points to avoid overplotting, but it doesn't help distinguish the ordering of the walk.
I'd like to connect the points using a line that changes color over "time" (steps) according to a thermometer-like color scale.
My random walk is stored in its own class and I'm writing a specific plot method for it, so if you have suggestions for how I can do this using plot, that would be great. Thanks!
This is pretty easy to do in ggplot2:
so <- data.frame(x = 1:10,y = 1:10,col = 1:10)
ggplot(so,aes(x = x, y = y)) +
geom_line(aes(group = 1,colour = col))
If you prefer not to use ggplot, then ?segments will do what you want. -- I'm assuming here that x and y are both functions of time, as implied in your example.
If you use ggplot, you can set the colour aesthetic:
library(ggplot2)
walk <-cumsum(rnorm(n=100, mean=0))
dat <- data.frame(x = seq_len(length(walk)), y = walk)
ggplot(dat, aes(x,y, colour = x)) + geom_line()

Resources