Related
I am trying to create some ggplots automaticly. Here is my working code example for adding stat_functions:
require(ggplot2)
p1 <- ggplot(data.frame(x = c(-2.5, 7.5)), aes(x = x)) + theme_minimal()+
stat_function(fun= function(x){1*x},lwd=1.25, colour = "navyblue") +
stat_function(fun= function(x){2*x},lwd=1.25, colour = "navyblue") +
stat_function(fun= function(x){3*-x},lwd=1.25, colour = "red")
p1
As you can see the stat_functions all use (nearly) the same function just with a different parameter.
Here is what i have tried to write:
f <- function(plot,list){
for (i in 1:length(list)){
plot <- plot + stat_function(fun= function(x){x*list[i]})
}
return(plot)
}
p1 <- ggplot(data.frame(x = c(-2.5, 7.5)), aes(x = x)) + theme_minimal()
p2 <- f(p1,c(1,2,3))
p2
This however doesnt return 3 lines, but only one. Why?
Your question is a bit confusing, because the first plot actually contains some other variable bits, but in your function you have a single stat_summary call for only one variable element.
Anyways. Keep the ggplot main object separate and create a list of additional objects, very easy for example with lapply. Add this list to your main plot as usual.
Check also https://ggplot2-book.org/programming.html
library(ggplot2)
p <- ggplot(data.frame(x = c(-2.5, 7.5)), aes(x = x)) + theme_minimal()
ls_sumfun <- lapply(1:3, function(y){
stat_function(fun= function(x){y*x}, lwd=1.25, colour = "navyblue")
}
)
p + ls_sumfun
Created on 2021-04-26 by the reprex package (v2.0.0)
In R, you can pass functions as arguments. You can also return functions from functions. This might make your code simpler and cleaner.
Here's an example:
p1 <- ggplot(data.frame(x = c(-2.5, 7.5)), aes(x = x))
add_stat_fun <- function (ggp, f) {
ggp + stat_function(fun = f)
}
make_multiply_fun <- function (multiplier) {
force(multiplier) # not sure if this is required...
f <- function (x) {multiplier * x}
return(f)
}
my_funs <- lapply(1:3, make_multiply_fun)
# my_funs is now a list of functions
add_stat_fun(p1, my_funs[[1]])
add_stat_fun(p1, my_funs[[2]])
add_stat_fun(p1, my_funs[[3]])
I have put together a simple for loop to generate a series of plots and then use grid.arrange to plot them. I have two problems:
The axes of the plots change correctly to the column names, but the same data is plotted on each graph. Having put in a breakpoint and stepped through the code it appears to be incrementing correctly so I'm not sure why.
I have set the plot aesthetic to group on year, however this produces intermediate .5 years that appear in the legend. This hasn't happened to me before.
Should all be reproducible using mtcars.
library(ggplot2)
library(gridExtra)
result <- mtcars
for(i in 1:2) {
nam <- paste("p", i, sep = "")
assign(
nam, ggplot(result, aes(x = disp, y = results[i+4], group = gear, color = gear)) +
geom_line() +
geom_point() +
scale_colour_distiller(palette = "Dark2", direction = -1, guide = "legend") +
scale_y_continuous(name = colnames(results[i+4])) +
scale_x_continuous(name = "x")
)
}
plist <- mget(paste0("p", 1:2))
do.call(grid.arrange, plist)
I think trying to access the columns by their number in the aes mapping is confusing ggplot. This works:
for(i in 1:2) {
nam <- paste("p", i, sep = "")
assign(
nam, ggplot(result,aes_string(x="disp",y=colnames(result)[i+4], group="gear", color="gear")) +
geom_line() +
geom_point() +
scale_colour_distiller(palette = "Dark2", direction=-1, guide="legend") +
scale_y_continuous(name=colnames(result[i+4])) +
scale_x_continuous(name="x")
)
}
I would suggest iterating over the names though; this makes the code much clearer. Here's a version that does this and skips the detour around the environment:
plots <- lapply(c("drat", "wt"), function(column) {
ggplot(result,aes_string(x="disp",y=column, group="gear", color="gear")) +
geom_line() + geom_point() +
scale_colour_distiller(palette = "Dark2", direction=-1, guide="legend") +
scale_y_continuous(name=column) +
scale_x_continuous(name="x")}) %>%
do.call(grid.arrange, .)
do.call(grid.arrange, plots)
Your using results and result. And you should use aes_string and then refer to the variables by string name:
You should also avoid to make tons of assignments. Just put it all into a list()
library(ggplot2)
library(gridExtra)
result<-mtcars
for(i in 1:2) {
nam <- paste("p", i, sep = "")
assign(
nam, ggplot(result,aes_string(x="disp",y=names(result)[i+4], group="gear", color="gear")) +
geom_line() +
geom_point() +
scale_colour_distiller(palette = "Dark2", direction=-1, guide="legend") +
scale_y_continuous(name=colnames(result[i+4])) +
scale_x_continuous(name="x")
)
}
plist <- mget(paste0("p", 1:2))
do.call(grid.arrange, plist)
The problem is that the plot is generated in the for loop, but evaluated in the do.call. Since i has changed in the for loop, both are evaluated with i = 2. You can confirm this with:
i <- 3
do.call(grid.arrange, plist)
A small adjustment to your code fixes the issue:
for(i in 1:2) {
nam <- paste("p", i, sep = "")
coln <- colnames(result[i+4])
assign(
nam, ggplot(result,aes_(x=~disp,y=as.name(coln), group=~gear, color=~gear)) +
geom_line() +
geom_point() +
scale_colour_distiller(palette = "Dark2", direction=-1, guide="legend") +
scale_y_continuous(name=coln) +
scale_x_continuous(name="x")
)
}
plist <- mget(paste0("p", 1:2))
do.call(grid.arrange, plist)
You should take full advantage of ggplot::facet_wrap
This means tidying your data to a single data frame that's interpretable to ggplot
Data
temp <- mtcars
Tidy data
library(purrr)
library(dplyr)
Names <- map_chr(1:2, ~names(temp)[.x+4])
# "drat" "wt"
data <- map_df(1:2, ~temp[,c("cyl", names(temp)[.x+4])] %>% setNames(c("cyl", "value")), .id="iteration") %>%
mutate(iteration = Names[as.numeric(iteration)])
plot with facet_wrap
ggplot(data=data, aes(x=cyl, y=value, label=iteration)) +
geom_line() +
geom_point() +
facet_wrap(~iteration)
Say I have these data:
set.seed(100)
mydf<-
data.frame(
day = rep(1:5, each=20),
id = rep(LETTERS[1:4],25),
x = runif(100),
y = sample(1:2,100,T)
)
If I just want to plot all five days of id=="A" using facet_wrap(), we do like this:
ggplot(mydf[mydf$id=="A",], aes(x,y)) +
geom_tile() +
facet_wrap(~day,ncol=1)
Gives:
But, if I want to plot four of these next to each other automatically in a 2x2 grid (i.e. showing A,B,C,D), is that possible using a nested facet? I tried doing multiple variables in the function like this:
ggplot(mydf, aes(x,y)) +
geom_tile() +
facet_wrap(~ day+id)
but this gives this:
I'm looking for a nested approach. Five faceted rows by day in each panel with each plot in columns/rows by id. Obviously for a small number of plots I could save individually and arrange with grid.arrange etc., but in the real data I have many plots so want to automate if possible.
EDIT:
In response to comment - this is the sort of desired output:
try this,
p <- ggplot(mydf, aes(x,y)) +
geom_tile() +
facet_wrap(~ day, ncol=1)
library(plyr)
lp <- dlply(mydf, "id", function(d) p %+% d + ggtitle(unique(d$id)))
library(gridExtra)
grid.arrange(grobs=lp, ncol=2)
Here is a quick attempt using the multiplot function found here
ids = levels(as.factor(mydf$id))
p = vector("list", length(ids))
names(p) = ids
for(i in 1:length(ids)){
p[[i]] = ggplot(mydf[mydf$id == ids[i],], aes(x,y)) + geom_tile() + ggtitle(paste(ids[i])) + facet_wrap(~day, ncol=1)
}
multiplot(p$A, p$B, p$C, p$D, cols = 2)
This question already has answers here:
Align multiple plots in ggplot2 when some have legends and others don't
(6 answers)
Closed 5 years ago.
I'm trying to use ggplot to draw a graph comparing the absolute values of two variables, and also show the ratio between them. Since the ratio is unitless and the values are not, I can't show them on the same y-axis, so I'd like to stack vertically as two separate graphs with aligned x-axes.
Here's what I've got so far:
library(ggplot2)
library(dplyr)
library(gridExtra)
# Prepare some sample data.
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# Plot absolute values
plot_values <- ggplot(results, aes(x=index)) +
geom_point(aes(y=value, color="value")) +
geom_point(aes(y=control, color="control"))
# Plot ratios between values
plot_ratios <- ggplot(results, aes(x=index, y=ratio)) +
geom_point()
# Arrange the two plots above each other
grid.arrange(plot_values, plot_ratios, ncol=1, nrow=2)
The big problem is that the legend on the right of the first plot makes it a different size. A minor problem is that I'd rather not show the x-axis name and tick marks on the top plot, to avoid clutter and make it clear that they share the same axis.
I've looked at this question and its answers:
Align plot areas in ggplot
Unfortunately, neither answer there works well for me. Faceting doesn't seem a good fit, since I want to have completely different y scales for my two graphs. Manipulating the dimensions returned by ggplot_gtable seems more promising, but I don't know how to get around the fact that the two graphs have a different number of cells. Naively copying that code doesn't seem to change the resulting graph dimensions for my case.
Here's another similar question:
The perils of aligning plots in ggplot
The question itself seems to suggest a good option, but rbind.gtable complains if the tables have different numbers of columns, which is the case here due to the legend. Perhaps there's a way to slot in an extra empty column in the second table? Or a way to suppress the legend in the first graph and then re-add it to the combined graph?
Here's a solution that doesn't require explicit use of grid graphics. It uses facets, and hides the legend entry for "ratio" (using a technique from https://stackoverflow.com/a/21802022).
library(reshape2)
results_long <- melt(results, id.vars="index")
results_long$facet <- ifelse(results_long$variable=="ratio", "ratio", "values")
results_long$facet <- factor(results_long$facet, levels=c("values", "ratio"))
ggplot(results_long, aes(x=index, y=value, colour=variable)) +
geom_point() +
facet_grid(facet ~ ., scales="free_y") +
scale_colour_manual(breaks=c("control","value"),
values=c("#1B9E77", "#D95F02", "#7570B3")) +
theme(legend.justification=c(0,1), legend.position=c(0,1)) +
guides(colour=guide_legend(title=NULL)) +
theme(axis.title.y = element_blank())
Try this:
library(ggplot2)
library(gtable)
library(gridExtra)
AlignPlots <- function(...) {
LegendWidth <- function(x) x$grobs[[8]]$grobs[[1]]$widths[[4]]
plots.grobs <- lapply(list(...), ggplotGrob)
max.widths <- do.call(unit.pmax, lapply(plots.grobs, "[[", "widths"))
plots.grobs.eq.widths <- lapply(plots.grobs, function(x) {
x$widths <- max.widths
x
})
legends.widths <- lapply(plots.grobs, LegendWidth)
max.legends.width <- do.call(max, legends.widths)
plots.grobs.eq.widths.aligned <- lapply(plots.grobs.eq.widths, function(x) {
if (is.gtable(x$grobs[[8]])) {
x$grobs[[8]] <- gtable_add_cols(x$grobs[[8]],
unit(abs(diff(c(LegendWidth(x),
max.legends.width))),
"mm"))
}
x
})
plots.grobs.eq.widths.aligned
}
df <- data.frame(x = c(1:5, 1:5),
y = c(1:5, seq.int(5,1)),
type = factor(c(rep_len("t1", 5), rep_len("t2", 5))))
p1.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p1.2 <- ggplot(df, aes(x = x, y = y, colour = type)) + geom_line()
plots1 <- AlignPlots(p1.1, p1.2)
do.call(grid.arrange, plots1)
p2.1 <- ggplot(diamonds, aes(clarity, fill = cut)) + geom_bar()
p2.2 <- ggplot(df, aes(x = x, y = y)) + geom_line()
plots2 <- AlignPlots(p2.1, p2.2)
do.call(grid.arrange, plots2)
Produces this:
// Based on multiple baptiste's answers
Encouraged by baptiste's comment, here's what I did in the end:
library(ggplot2)
library(dplyr)
library(gridExtra)
# Prepare some sample data.
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# Plot ratios between values
plot_ratios <- ggplot(results, aes(x=index, y=ratio)) +
geom_point()
# Plot absolute values
remove_x_axis =
theme(
axis.ticks.x = element_blank(),
axis.text.x = element_blank(),
axis.title.x = element_blank())
plot_values <- ggplot(results, aes(x=index)) +
geom_point(aes(y=value, color="value")) +
geom_point(aes(y=control, color="control")) +
remove_x_axis
# Arrange the two plots above each other
grob_ratios <- ggplotGrob(plot_ratios)
grob_values <- ggplotGrob(plot_values)
legend_column <- 5
legend_width <- grob_values$widths[legend_column]
grob_ratios <- gtable_add_cols(grob_ratios, legend_width, legend_column-1)
grob_combined <- gtable:::rbind_gtable(grob_values, grob_ratios, "first")
grob_combined <- gtable_add_rows(
grob_combined,unit(-1.2,"cm"), pos=nrow(grob_values))
grid.draw(grob_combined)
(I later realised I didn't even need to extract the legend width, since the size="first" argument to rbind tells it just to have that one override the other.)
It feels a bit messy, but it is exactly the layout I was hoping for.
An alternative & quite easy solution is as follows:
# loading needed packages
library(ggplot2)
library(dplyr)
library(tidyr)
# Prepare some sample data
results <- data.frame(index=(1:20))
results$control <- 50 * results$index
results$value <- results$index * 50 + 2.5*results$index^2 - results$index^3 / 8
results$ratio <- results$value / results$control
# reshape into long format
long <- results %>%
gather(variable, value, -index) %>%
mutate(facet = ifelse(variable=="ratio", "ratio", "values"))
long$facet <- factor(long$facet, levels=c("values", "ratio"))
# create the plot & remove facet labels with theme() elements
ggplot(long, aes(x=index, y=value, colour=variable)) +
geom_point() +
facet_grid(facet ~ ., scales="free_y") +
scale_colour_manual(breaks=c("control","value"), values=c("green", "red", "blue")) +
theme(axis.title.y=element_blank(), strip.text=element_blank(), strip.background=element_blank())
which gives:
Let's say we have a simple plot of the following kind.
library(ggplot2)
df = data.frame(y=c(0,1.1,2.3,3.1,2.9,5.8,6,7.4,8.2,9.1),x=seq(1,100, length.out=10))
ggplot(df,aes(x=x,y=y)) + geom_point()
x perfectly correlates with z. The relation is: Constant=x^2*z=1.23
therefore I could rewrite the data.frame like this:
df = cbind(df,1.23/df$x^2)
The question is:
How can I display both variables xand zone the x-axis? It could be one at the bottom and one at the top of the graph or both at the bottom.
Here's a dangerous attempt. Previous version with a log-scale was just wrong.
library(ggplot2)
df = data.frame(y=c(0,1.1,2.3,3.1,2.9,5.8,6,7.4,8.2,9.1),
x=seq(1,100, length.out=10))
df$z = 1.23/df$x^2
## let's at least remove the gridlines
p1 <- ggplot(df,aes(x=x,y=y)) + geom_point() +
scale_x_continuous(expand=c(0,0)) +
theme(panel.grid.major=element_blank(),
panel.grid.minor = element_blank())
## make sure both plots have expand = c(0,0)
## otherwise data and top-axis won't necessarily be aligned...
p2 <- ggplot(df,aes(x=z,y=y)) + geom_point() +
scale_x_continuous(expand=c(0,0))
library(gtable)
g1 <- ggplotGrob(p1)
g2 <- ggplotGrob(p2)
tmp <- gtable_filter(g2, pattern="axis-b")
## ugly tricks to extract and reshape the axis
axis <- tmp[["grobs"]][[1]][["children"]][["axis"]] # corrupt the children
axis$layout <- axis$layout[2:1,]
axis$grobs[[1]][["y"]] <- axis$grobs[[1]][["y"]] - unit(1,"npc") + unit(0.15,"cm")
## back to "normality"
g1 <- gtable_add_rows(g1, sum(tmp$heights), 2)
gtableAddGrobs <- gtable_add_grob # alias, making sure #!hadley doesn't see this
g1 <- gtableAddGrobs(g1,
grobs=list(gtable_filter(g2, pattern="xlab"),axis),
t=c(1,3), l=4)
grid.newpage()
grid.draw(g1)
A both-on-the-bottom approach can be done with the excellent cowplot library.
library(ggplot2)
library(cowplot)
data <- data.frame(temp_c=runif(100, min=-5, max=30), outcome=runif(100))
plot <- ggplot(data) +
geom_point(aes(x=temp_c, y=outcome)) +
theme_classic() +
labs(x='Temperature (Celsius)')
x2plot <- ggplot(data) +
geom_point(aes(x=temp_c, y=outcome)) +
theme_classic() +
scale_x_continuous(label=function(x){round(x*(9/5) + 32)}) +
labs(x='Temperature (Fahrenehit)')
x <- get_x_axis(x2plot)
xl <- get_plot_component(x2plot, "xlab-b")
plot_grid(plot, ggdraw(x), ggdraw(xl), align='v', axis='rl', ncol=1,
rel_heights=c(0.8, 0.05, 0.05))