overlaying points in specific manner in ggplot scatter plots - r

The colors are added to the ggplot scatter plot based on interaction of two variables : choice and flag (each has two values, therefore, total four combinations). I used faceting based on z value.
library(tidyverse)
x <- runif(10000)
y <- runif(10000)
z <- c(rep(0, 5000), rep(1, 5000))
flag <- c(rep(0, 500), rep(1, 4500), rep(0, 4500), rep(1, 500))
choice <- rep(c(0, 1), 5000)
tbl <- tibble(x, y, z, flag, choice)
scatterplot <- ggplot(tbl,
aes(x = x,
y = y,
color = factor(interaction(choice, flag)))
) +
geom_point(alpha = 0.7,
size = 2) +
scale_color_manual(values = c("blue3", "cyan1", "red3", "orange")) +
facet_grid(z ~ .) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
ggtitle("Scatter plot")
scatterplot
But I have the following requirement -
z is used for facetting. For z = i, I want points with flag = i to be above, i.e. in the figure below,
for z = 0, blue points (flag = 0) should be over red/orange points.
for z = 1, red/orange points (flag = 1) should be over blue points (as shown)

If I understand you correctly, you are happy with the lower panel, but you need the blue dots in the top panel to be overlaid on the orange dots (at the moment the orange dots are overlaid on the blue dots in both panels).
If this is the case, then calling geom_point a second time with a subsetted data frame where z == 0 & flag == 0 will overlay the appropriate blue points on the top panel without affecting the lower panel.
tbl <- tbl %>%
mutate(col = interaction(choice, flag))
ggplot(tbl, aes(x, y, color = col)) +
geom_point(alpha = 0.7, size = 2) +
geom_point(data = subset(tbl, z == 0 & flag == 0),
alpha = 0.7, size = 2) +
scale_color_manual(values = c("blue3", "cyan1", "red3", "orange")) +
facet_grid(z ~ .) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
ggtitle("Scatter plot")

Consider this as an option for you. With facets it was complex to set specific order but you can do the same plot using patchwork:
library(tidyverse)
library(patchwork)
#Data
x <- runif(10000)
y <- runif(10000)
z <- c(rep(0, 5000), rep(1, 5000))
flag <- c(rep(0, 500), rep(1, 4500), rep(0, 4500), rep(1, 500))
choice <- rep(c(0, 1), 5000)
tbl <- tibble(x, y, z, flag, choice)
Plots:
#Plot
G1 <- ggplot(subset(tbl,z==0),aes(x = x,y = y,
color = factor(interaction(choice, flag),
levels = rev(unique(interaction(choice, flag))),
ordered = T))) +
geom_point(alpha = 0.7,
size = 2) +
scale_color_manual(values = c("blue3", "cyan1", "red3", "orange")) +
facet_grid(z ~ .) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
ggtitle("Scatter plot")+
labs(color='Color',x='')+theme(legend.position = 'none')
#Plot 2
G2 <- ggplot(subset(tbl,z==1),aes(x = x,y = y,
color = factor(interaction(choice, flag)))) +
geom_point(alpha = 0.7,
size = 2) +
scale_color_manual(values = c("blue3", "cyan1", "red3", "orange")) +
facet_grid(z ~ .) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
labs(color='Color')
Final arrange:
#Final plot
G <- G1/G2
G <- G+plot_layout(guides = 'collect')
Output:

Related

Two density plots in ggplot

This is really basic. Still hope I can get your help. I need to superimpose two density plots. The first is a generated normal density plot given mean and sd of AAPL. >
x <- seq(-20, 20, length.out = 5113)
normAAPL<-data.frame(x, f = dnorm(x,mean = meanAAPL, sd = sdAAPL)) %>%
ggplot(aes(x, f)) +
geom_line() +
stat_function(fun=dnorm, geom="line", col=2, lty=2)+
ylim(0,0.2)
> meanAAPL
[1] 0.101133
> sdAAPL
[1] 2.461525
The next is the actual distribution
dAAPL <-density(oldandnew$AAPL)
Where the 20 first AAPL data is
c(-8.810021, 1.45281, -9.051401, 4.628075, -1.774445, -5.25055,
-6.181806, 10.40407, 3.74302, 3.425328, 2.48944, 6.309463, -1.948374,
-4.652429, 5.493372, -1.852238, -0.1725783, -7.924, 2.074379,
-3.431709)
Do I need to combine the data in one data frame to plot them in the same ggplot?
Hope you can help me out.
df <- data.frame(x = seq(-20, 20, length.out = 5113),
f = dnorm(x))
df2 <- data.frame(x = c(-8.810021, 1.45281, -9.051401, 4.628075, -1.774445, -5.25055,
-6.181806, 10.40407, 3.74302, 3.425328, 2.48944, 6.309463, -1.948374,
-4.652429, 5.493372, -1.852238, -0.1725783, -7.924, 2.074379,
-3.431709))
ggplot() +
geom_line(data = df, aes(x, f, colour = "Normal")) +
geom_density(data = df2, aes(x, colour = "Actual")) +
ylim(0,0.2) +
scale_color_manual(name = "Distribution", values = c("Normal" = "Blue", "Actual" = "Red")) +
theme_minimal() + theme(legend.position = "top", aspect.ratio = 1)
Produces:

Generate two plots with same x axis breaks

I have two plots I want the x axes being broken by the same way.
This is the code for plot 1:
m <- read.csv('Finalfor1lowergreaterthan1.csv', header=T, row.names=1)
m <- m[m$SVM.Count >= 40,]
boxOdds = m$Odd
df <- data.frame(
yAxis = length(boxOdds):1,
boxnucleotide = m$Position,
boxCILow = m$lower,
boxCIHigh = m$upper,
Mutation = m$Resistance)
ticksy <- c(seq(0,0.3,by=.1), seq(0, 1, by =.5), seq(0, 20, by =5), seq(0, 150, by =50))
ticksx <- c(seq(0,300,by=25))
p <- ggplot(df,
aes(x = boxnucleotide, y = boxOdds, colour=Mutation, label=rownames(m)))
p1 <- p +
geom_errorbar(aes(ymax = boxCIHigh, ymin = boxCILow), size = .5, height = .01) +
geom_point(size = 1) +
theme_bw() +
theme(panel.grid.minor = element_blank()) +
scale_y_continuous(breaks=ticksy, labels = ticksy) +
scale_x_continuous(breaks=ticksx, labels = ticksx) +
coord_trans(y = "log10") +
ylab("Odds ratio (log scale)") +
scale_color_manual(values=c("#00BFC4","#F8766D","#619CFF")) +
xlab("Integrase nucleotide position") +
geom_text(size=2,hjust=0, vjust=0)
Then I have another plot:
m <- read.csv('Finalfor20lowergreaterthan1.csv', header=T, row.names=1)
#m <- m[m$SVM.Count >= 40, ]
boxOdds = m$Odd
df <- data.frame(
yAxis = length(boxOdds):1,
boxnucleotide = m$Position,
boxCILow = m$lower,
boxCIHigh = m$upper,
Mutation = m$Resistance)
ticksy <- c(seq(0,0.3,by=.1), seq(0, 1, by =.5), seq(0, 20, by =5), seq(0, 150, by =50))
ticksx <- c(seq(0,300,by=25))
p <- ggplot(df,
aes(x = boxnucleotide, y = boxOdds, colour=Mutation, label=rownames(m)))
p1 <- p +
geom_errorbar(aes(ymax = boxCIHigh, ymin = boxCILow), size = .5, height = .01) +
geom_point(size = 1) +
theme_bw() +
theme(panel.grid.minor = element_blank()) +
scale_y_continuous(breaks=ticksy, labels = ticksy) +
scale_x_continuous(breaks=ticksx, labels = ticksx) +
coord_trans(y = "log10") +
ylab("Odds ratio (log scale)") +
scale_color_manual(values=c("#00BFC4","#F8766D","#619CFF")) +
xlab("Integrase nucleotide position") +
geom_text(size=2,hjust=0, vjust=0)
Why is plot 1 starting from 75 on x axis and plot 2 starting at 100...how can plot2 start at 75 as well and being scaled like plot 1.
The two codes get the same piece of: ticksx <- c(seq(0, 300, by=25))
A good technique to align the axis range on different plots is to use expand_limits.
You can simply use p1 + expand_limits(x=c(0, 300)). This will ensure the x-axis contains at least 0 and 300 on all your plots. You can also control the y-axis range by using the y argument.
From ?expand_limits:
Sometimes you may want to ensure limits include a single value, for all panels or all plots. This function is a thin wrapper around geom_blank() that makes it easy to add such values.

ggplot: how to get legend linetype group-dependent while errorbar linetype solid

I am trying to plot lines representing observations from two groups, y1 and y2 in a way that:
the two groups have different line color (marked on legend)
the two groups have different linetype (marked on legend)
the plot has errorbar, and error bar is solid line in both groups
Code generating some data:
## generate data
x.grid <- seq(0, 1, length.out = 6)
y1.func <- function(x) 1/(x+1)
y2.func <- function(x) 2/(x+3)
set.seed(1)
x.vec <- numeric()
y.vec <- numeric()
group.vec <- numeric()
for (x in x.grid){
x.vec <- c(x.vec, rep(x, 2*10))
y.vec <- c(y.vec,
rep(y1.func(x), 10) + rnorm(10, sd = 0.1),
rep(y2.func(x), 10) + rnorm(10, sd = 0.1))
group.vec <- c(group.vec, rep("y1", 10), rep("y2", 10))
}
plt.df <- data.frame(x = x.vec, y = y.vec, group = group.vec)
## summarize data
plt.df.se <- Rmisc::summarySE(plt.df, measurevar = "y", groupvars=c("x", "group"))
Approach 1:
ggplot2::ggplot(plt.df.se,
aes(x = x,
y = y,
color = group,
linetype = group)) +
geom_line(position=pd, size = 0.5) +
geom_errorbar(aes(ymin=y-se, ymax=y+se), width=.05,
position=position_dodge(0.05), linetype = 1)
bad: legend blue not dashed
Approach 2:
ggplot2::ggplot(plt.df.se,
aes(x = x,
y = y,
color = group,
linetype = group)) +
geom_line(position=pd, size = 0.5) +
geom_errorbar(aes(ymin=y-se, ymax=y+se), width=.05,
position=position_dodge(0.05))
bad: blue error bars are dashed (I want them solid)
First off, you only want the linetype aesthetic to apply to your lines, so don't include it in the top-level aesthetic mapping, only in geom_line(). Then use show.legend = FALSE in geom_errorbar() so it won't affect the legends:
ggplot(plt.df.se,
aes(x = x,
y = y,
color = group)) +
geom_line(aes(linetype = group), position=position_dodge(0.05), size = 0.5) +
geom_errorbar(aes(ymin=y-se, ymax=y+se), width=.05,
position=position_dodge(0.05),
show.legend = FALSE)
Result:

Getting a bar plot with error bars in ggplot2 to expand above error bars whilst also keeping the origin at y=0 [duplicate]

How do you adjust the expansion of limits asymmetrically in ggplot? For example,
library(ggplot2)
ggplot(mtcars) +
geom_bar(aes(x = cyl), width = 1)
I would like the bottom of the bars flush with the bottom of the panel background, but would still like space at the top. I can achieve this with a blank annotation:
ggplot(mtcars) +
geom_bar(aes(x = cyl), width = 1) +
annotate("blank", x = 4, y = 16) +
scale_y_continuous(expand = c(0.0,0))
In previous versions of ggplot, however, I could use the solution provided by Rosen Matev:
library("scales")
scale_dimension.custom_expand <- function(scale, expand = ggplot2:::scale_expand(scale)) {
expand_range(ggplot2:::scale_limits(scale), expand[[1]], expand[[2]])
}
scale_y_continuous <- function(...) {
s <- ggplot2::scale_y_continuous(...)
class(s) <- c('custom_expand', class(s))
s
}
and then use scale_y_continuous(expand = list(c(0,0.1), c(0,0))) which would add a consistently addition to the top of the chart. In the current version, however, I get an error
ggplot(mtcars) +
geom_bar(aes(x = cyl), width = 1) +
scale_y_continuous(expand = list(c(0,0.1), c(0,0)))
# Error in diff(range) * mul : non-numeric argument to binary operator
Is there an effective solution for ggplot2 2.0?
A solution should include the ability to work flexibly with facets, and free_xy scale options. For example,
ggplot(mtcars) +
geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
facet_grid(vs ~ ., scales = "free_y")
A solution should provide something like:
ggplot(mtcars) +
geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
facet_grid(vs ~ ., scales = "free_y") +
scale_y_continuous(expand = c(0,0)) +
geom_blank(data = data.frame(cyl = c(5,5), y = c(12, 16), vs = c(1,0)), aes(x = cyl, y = y))
ggplot2 v3.0.0 released in July 2018 has expand_scale() option (w/ mult argument) to achieve OP's goal.
Edit: expand_scale() will be deprecated in the future release in favor of expansion(). See News for more information.
library(ggplot2)
### ggplot <= 3.2.1
ggplot(mtcars) +
geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
facet_grid(vs ~ ., scales = "free_y") +
scale_y_continuous(expand = expand_scale(mult = c(0, .2)))
### ggplot >= 3.2.1.9000
ggplot(mtcars) +
geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
facet_grid(vs ~ ., scales = "free_y") +
scale_y_continuous(expand = expansion(mult = c(0, .2)))
I have now tried to add code for this to ggplot2; see issue #1669 and the corresponding pull request. If it is accepted, the syntax for the expand argument will been changed from c(m, a) to c(m_lower, a_lower, m_uppper, a_upper), for specifying separate expansion values for the lower and upper range limits. (The old syntax will still continue to work, though, as the first two elements will be reused if elements three and/or four are missing.)
With this new syntax, you can use
ggplot(mtcars) +
geom_bar(aes(x = cyl), width = 1) +
scale_y_continuous(expand = c(0, 0, 0.05, 0))
The result looks like this:
It also works with facetting:
ggplot(mtcars) +
geom_bar(aes(x = cyl, fill = factor(vs)), width = 1) +
facet_grid(vs ~ ., scales = "free_y") +
scale_y_continuous(expand = c(0, 0, 0.05, 0))
I used Rosen Matev's solution often, and was disappointed when it broke with ggplot version 2.0. I offer a solution, though not nearly as elegant as Rosen's, but will work on plots with no facetting, facet_wrap, and facet_grid, and with one-way and two-way facet_grid. However, it will not work with more complicated facet grids, nor will it work with coord_flip. There are two functions: one for asymmetric expansion along the y-axis, and one for expansion along the x-axis. The functions perform multiplicative and additive expansions.
The functions gather information from the plot, calculate new limits for the y (or x) axis, then use geom_blank to construct new plots with the desired expansion factors.
First, a function to perform asymmetric expansion along the y-axis.
# Function takes two parameters
# 'p' is the plot
# 'expand' is a list of two vectors:
# First vector contains the multiplicative factors;
# Second vector contains the additive parts.
# First element in each vector refers to the lower boundary;
# Second element refers to the upper boundary.
asymmY = function(p, expand = list(mult = c(0, .2), add = c(0, 0))) {
np = p + coord_cartesian(expand = FALSE) # No expand
gb <- ggplot_build(np)
limits <- sapply(gb$panel$ranges, "[[", "y.range")
range = apply(limits, 2, function(x) max(x) - min(x))
rangeU = range*expand[[1]][2]
rangeL = range*expand[[1]][1]
limits <- limits + rbind(-rangeL, rangeU) # Multiplicative expand
limits[1,] = limits[1,] - expand[[2]][1] # Additive expand
limits[2,] = limits[2,] + expand[[2]][2]
limits = as.vector(limits)
df = facet_type(np, gb, "y", limits) # df with new limits - depends on facet type
np = np + geom_blank(data = df, inherit.aes = FALSE, aes(x = Inf, y = y)) # new plot
# But the x axis expansions were set to false. Put back the default expand
gb <- ggplot_build(np)
if(any(grepl("Discrete", class(gb$panel$x_scale[[1]])))) {
limits <- sapply(gb$panel$ranges, "[[", "x.range")
limits[1,] = ceiling(limits[1,]) - .6
limits[2,] = trunc(limits[2,]) + .6
limits = as.vector(limits)
} else {
limits <- sapply(gb$panel$ranges, "[[", "x.range")
range = apply(limits, 2, function(x) max(x) - min(x))
rangeU = range*.05
rangeL = range*.05
limits <- limits + rbind(-rangeL, rangeU)
limits = as.vector(limits)
}
df = facet_type(np, gb, "x", limits)
np + geom_blank(data = df, inherit.aes = FALSE, aes(x = x, y = Inf))
}
# Function to determine type of facetting
# and to get data frame of new limits.
facet_type = function(np, gb, axis, limits) {
if(class(np$facet)[1] == "null") {
setNames(data.frame(y = limits), axis)
} else
if(class(np$facet)[1] == "wrap") {
facetvar <- as.character(np$facet$facets)
facetlev <- gb$panel$layout[[facetvar]]
setNames(data.frame(rep(facetlev, each = 2), limits), c(facetvar, axis))
} else {
facetvar <- as.character(np$facet$cols)
if(length(facetvar) == 0) facetvar <- as.character(np$facet$rows)
facetlev <- gb$panel$layout[[facetvar]]
setNames(data.frame(rep(facetlev, each = 2), limits), c(facetvar, axis))
}
}
Try it out with some facet wrap and facet grid plots.
# Try asymmetric expand along y-axis
library(ggplot2)
p1 <- ggplot(mtcars) +
geom_bar(aes(x = factor(cyl))) +
facet_grid(am ~ vs , scales = "free_y")
p2 <- ggplot(mtcars) +
geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) +
facet_grid(vs ~ ., scales = "free_y")
p3 <- ggplot(mtcars) +
geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) +
facet_grid(. ~ vs)
p4 <- ggplot(mtcars) +
geom_bar(aes(x = factor(cyl), fill = factor(vs)), width = .5) +
facet_wrap(~vs, scales = "free_y")
asymmY(p1, list(c(0, 0.1), c(0, 0)))
asymmY(p2, list(c(0, 0.1), c(0, 0)))
asymmY(p3, list(c(0, 0.1), c(0, 0)))
asymmY(p4, list(c(0, 0.1), c(0, 0)))
Second, a function to perform asymmetric expansion along the x-axis.
asymmX = function(p, expand = list(mult = c(0, .2), add = c(0, 0))) {
np = p + coord_cartesian(expand = FALSE) # No expand
gb <- ggplot_build(np)
limits <- sapply(gb$panel$ranges, "[[", "x.range")
range = apply(limits, 2, function(x) max(x) - min(x))
rangeU = range*expand[[1]][2]
rangeL = range*expand[[1]][1]
limits <- limits + rbind(-rangeL, rangeU) # Mult expand
limits[1,] = limits[1,] - expand[[2]][1]
limits[2,] = limits[2,] + expand[[2]][2] # Add expand
limits = as.vector(limits)
df = facet_type(np, gb, "x", limits) # df with new limits - depends on facet type
np = np + geom_blank(data = df, inherit.aes = FALSE, aes(x = x, y = Inf)) # new plot
# But the y axis expansions were set to false. Put back the default expand
gb <- ggplot_build(np)
if(any(grepl("Discrete", class(gb$panel$y_scale[[1]])))) {
limits <- sapply(gb$panel$ranges, "[[", "y.range")
limits[1,] = ceiling(limits[1,]) - .6
limits[2,] = trunc(limits[2,]) + .6
limits = as.vector(limits)
} else {
limits <- sapply(gb$panel$ranges, "[[", "y.range")
range = apply(limits, 2, function(x) max(x) - min(x))
rangeU = range*.05
rangeL = range*.05
limits <- limits + rbind(-rangeL, rangeU)
limits = as.vector(limits)
}
df = facet_type(np, gb, "y", limits)
np + geom_blank(data = df, inherit.aes = FALSE, aes(x = Inf, y = y))
}
Try it out.
# Try asymmetric expand along x-axis
df = data.frame(x = c(20, 15, 25, 23, 12, 14),
y = rep(c("a", "b", "c"), 2),
z = rep(c("aaa", "bbb"), each = 3),
w = rep(c("ccc", "ddd", "eee"), each = 2))
p1 = ggplot(df[,-4]) + geom_point(aes(x, y)) +
geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
geom_text(aes(x = x, y = y, label = x), hjust = -1) +
facet_grid(. ~ z, scales = "free_x")
p2 = ggplot(df[, -4]) + geom_point(aes(x, y)) +
geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
geom_text(aes(x = x, y = y, label = x), hjust = -1) +
facet_grid(z ~ .)
p3 = ggplot(df) + geom_point(aes(x, y)) +
geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
geom_text(aes(x = x, y = y, label = x), hjust = -1) +
facet_grid(w ~ z)
p4 = ggplot(df[,-4]) + geom_point(aes(x, y)) +
geom_segment(aes(x = 0, xend = x, y = y, yend = y)) +
geom_text(aes(x = x, y = y, label = x), hjust = -1) +
facet_wrap(~ z)
asymmX(p1, list(c(0, .15), c(0, 0)))
asymmX(p2, list(c(0, 0), c(0, 5)))
asymmX(p3, list(c(0, .2), c(0, 0)))
asymmX(p4, list(c(0, 0), c(9, 5)))

stat_function and legends: create plot with two separate colour legends mapped to different variables

I would like to combine two different types of plots in one image with ggplot2. Here's the code I use:
fun.bar <- function(x, param = 4) {
return(((x + 1) ^ (1 - param)) / (1 - param))
}
plot.foo <- function(df, par = c(1.7, 2:8)) {
require(ggplot2)
require(reshape2)
require(RColorBrewer)
melt.df <- melt(df)
melt.df$ypos <- as.numeric(melt.df$variable)
p <- ggplot(data = melt.df, aes(x = value, y = ypos, colour = variable)) +
geom_point(position = "jitter", alpha = 0.2, size = 2) +
xlim(-1, 1) + ylim(-5, 5) +
guides(colour =
guide_legend("Type", override.aes = list(alpha = 1, size = 4)))
pal <- brewer.pal(length(par), "Set1")
for (i in seq_along(par)) {
p <- p + stat_function(fun = fun.bar,
arg = list(param = par[i]), colour = pal[i], size = 1.3)
}
p
}
df.foo <- data.frame(A=rnorm(1000, sd=0.25),
B=rnorm(1000, sd=0.25), C=rnorm(1000, sd=0.25))
plot.foo(df.foo)
As a result, I get the following picture.
However, I'd like to have another legend with colours from red to pink, displaying information about parameters of curves in the lower part of the plot. The problem is the key aesthetics for both parts is the colour, so manual overriding via scale_colour_manual() destroys the existing legend.
I understand there's a "one aesthetic -- one legend" concept, but how can I bypass this restriction in this specific case?
When looking at previous examples of stat_function and legend on SO, I got the impression that it is not very easy to make the two live happily together without some hard-coding of each curve generated by stat_summary (I would be happy to find that I am wrong). See e.g. here, here, and here. In the last answer #baptiste wrote: "you'll be better off building a data.frame before plotting". That's what I try in my answer: I pre-calculated data using the function, and then use geom_line instead of stat_summary in the plot.
# load relevant packages
library(ggplot2)
library(reshape2)
library(RColorBrewer)
library(gridExtra)
library(gtable)
library(plyr)
# create base data
df <- data.frame(A = rnorm(1000, sd = 0.25),
B = rnorm(1000, sd = 0.25),
C = rnorm(1000, sd = 0.25))
melt.df <- melt(df)
melt.df$ypos <- as.numeric(melt.df$variable)
# plot points only, to get a colour legend for points
p1 <- ggplot(data = melt.df, aes(x = value, y = ypos, colour = variable)) +
geom_point(position = "jitter", alpha = 0.2, size = 2) +
xlim(-1, 1) + ylim(-5, 5) +
guides(colour =
guide_legend("Type", override.aes = list(alpha = 1, size = 4)))
p1
# grab colour legend for points
legend_points <- gtable_filter(ggplot_gtable(ggplot_build(p1)), "guide-box")
# grab colours for points. To be used in final plot
point_cols <- unique(ggplot_build(p1)[["data"]][[1]]$colour)
# create data for lines
# define function for lines
fun.bar <- function(x, param = 4) {
return(((x + 1) ^ (1 - param)) / (1 - param))
}
# parameters for lines
pars = c(1.7, 2:8)
# for each value of parameters and x (i.e. x = melt.df$value),
# calculate ypos for lines
df2 <- ldply(.data = pars, .fun = function(pars){
ypos = fun.bar(melt.df$value, pars)
data.frame(pars = pars, value = melt.df$value, ypos)
})
# colour palette for lines
line_cols <- brewer.pal(length(pars), "Set1")
# plot lines only, to get a colour legends for lines
# please note that when using ylim:
# "Observations not in this range will be dropped completely and not passed to any other layers"
# thus the warnings
p2 <- ggplot(data = df2,
aes(x = value, y = ypos, group = pars, colour = as.factor(pars))) +
geom_line() +
xlim(-1, 1) + ylim(-5, 5) +
scale_colour_manual(name = "Param", values = line_cols, labels = as.character(pars))
p2
# grab colour legend for lines
legend_lines <- gtable_filter(ggplot_gtable(ggplot_build(p2)), "guide-box")
# plot both points and lines with legend suppressed
p3 <- ggplot(data = melt.df, aes(x = value, y = ypos)) +
geom_point(aes(colour = variable),
position = "jitter", alpha = 0.2, size = 2) +
geom_line(data = df2, aes(group = pars, colour = as.factor(pars))) +
xlim(-1, 1) + ylim(-5, 5) +
theme(legend.position = "none") +
scale_colour_manual(values = c(line_cols, point_cols))
# the colours in 'scale_colour_manual' are added in the order they appear in the legend
# line colour (2, 3) appear before point cols (A, B, C)
# slightly hard-coded
# see alternative below
p3
# arrange plot and legends for points and lines with viewports
# define plotting regions (viewports)
# some hard-coding of positions
grid.newpage()
vp_plot <- viewport(x = 0.45, y = 0.5,
width = 0.9, height = 1)
vp_legend_points <- viewport(x = 0.91, y = 0.7,
width = 0.1, height = 0.25)
vp_legend_lines <- viewport(x = 0.93, y = 0.35,
width = 0.1, height = 0.75)
# add plot
print(p3, vp = vp_plot)
# add legend for points
upViewport(0)
pushViewport(vp_legend_points)
grid.draw(legend_points)
# add legend for lines
upViewport(0)
pushViewport(vp_legend_lines)
grid.draw(legend_lines)
# A second alternative, with greater control over the colours
# First, plot both points and lines with colour legend suppressed
# let ggplot choose the colours
p3 <- ggplot(data = melt.df, aes(x = value, y = ypos)) +
geom_point(aes(colour = variable),
position = "jitter", alpha = 0.2, size = 2) +
geom_line(data = df2, aes(group = pars, colour = as.factor(pars))) +
xlim(-1, 1) + ylim(-5, 5) +
theme(legend.position = "none")
p3
# build p3 for rendering
# get a list of data frames (one for each layer) that can be manipulated
pp3 <- ggplot_build(p3)
# grab the whole vector of point colours from plot p1
point_cols_vec <- ggplot_build(p1)[["data"]][[1]]$colour
# grab the whole vector of line colours from plot p2
line_cols_vec <- ggplot_build(p2)[["data"]][[1]]$colour
# replace 'colour' values for points, with the colours from plot p1
# points are in the first layer -> first element in the 'data' list
pp3[["data"]][[1]]$colour <- point_cols_vec
# replace 'colour' values for lines, with the colours from plot p2
# lines are in the second layer -> second element in the 'data' list
pp3[["data"]][[2]]$colour <- line_cols_vec
# build a plot grob from the data generated by ggplot_build
# to be used in grid.draw below
grob3 <- ggplot_gtable(pp3)
# arrange plot and the two legends with viewports
# define plotting regions (viewports)
vp_plot <- viewport(x = 0.45, y = 0.5,
width = 0.9, height = 1)
vp_legend_points <- viewport(x = 0.91, y = 0.7,
width = 0.1, height = 0.25)
vp_legend_lines <- viewport(x = 0.92, y = 0.35,
width = 0.1, height = 0.75)
grid.newpage()
pushViewport(vp_plot)
grid.draw(grob3)
upViewport(0)
pushViewport(vp_legend_points)
grid.draw(legend_points)
upViewport(0)
pushViewport(vp_legend_lines)
grid.draw(legend_lines)
I'd like to share a quick hack I used while waiting for an answer to this question.
fun.bar <- function(x, param = 4) {
return(((x + 1) ^ (1 - param)) / (1 - param))
}
plot.foo <- function(df, par = c(1.7, 2:8)) {
require(ggplot2)
require(reshape2)
require(RColorBrewer)
melt.df <- melt(df)
melt.df$ypos <- as.numeric(melt.df$variable)
# the trick is to override factor levels
levels(melt.df$variable) <- 1:nlevels(melt.df$variable)
p <- ggplot(data = melt.df, aes(x = value, y = ypos, colour = variable)) +
geom_point(position = "jitter", alpha = 0.2, size = 2) +
xlim(-1, 1) + ylim(-5, 5) +
guides(colour =
guide_legend("Type", override.aes = list(alpha = 1, size = 4)))
pal <- brewer.pal(length(par), "Set1")
for (i in seq_along(par)) {
p <- p + stat_function(fun = fun.bar,
arg = list(param = par[i]), colour = pal[i], size = 1.3)
}
# points are displayed by supplying values for manual scale
p + scale_colour_manual(values = pal, limits = seq_along(par), labels = par) +
# this needs proper "for" cycle to remove hardcoded labels
annotate("text", x = 0.8, y = 1, label = "A", size = 8) +
annotate("text", x = 0.8, y = 2, label = "B", size = 8) +
annotate("text", x = 0.8, y = 3, label = "C", size = 8)
}
df.foo <- data.frame(A=rnorm(1000, sd=0.25),
B=rnorm(1000, sd=0.25), C=rnorm(1000, sd=0.25))
plot.foo(df.foo)
This workaround is not even close to being so awesome as the answer provided by #Henrik, but suited my one-time needs.

Resources