I would like to know how to use ggplot2 to create a side by side plots with one common legend. I have seen some similar questions but not sure how to directly apply it to my code. I have provided my code for the graphs with the legend and some data that can be used to recreate the graphs.
Stocks1<-c(2,1,0.8,0.7,0.6)
Bonds1<-c(1,0.8,0.7,0.6,0.5)
Cash1<-1-(Stocks1+Bonds1)
Stocks2<-c(0.6,0.5,0.4,0.3,0.2)
Bonds2<-c(0.3,0.2,0.2,0.15,0.1)
Cash2<-1-(Stocks2+Bonds2)
H<-length(Stocks1) #Change value to represent data
t <- seq(from = 0, to = H, 1) # time grid
And here are the two graphs
pi_F<- data.frame(cash = Cash1, bonds = Bonds1,
stocks= Stocks1,time=t[-1])
melted_F <- melt(pi_F, id.vars = 'time')
ggplot(melted_F, aes(x=time, y=value, group = variable)) +
geom_area(aes(fill=variable)) +
scale_fill_manual(values=c("#2E318F", "#DFAE41","#109FC6"),
name="Asset Type",
labels = c("Bank account","Bonds", "Stocks"))+
scale_x_continuous(name = 'Age',
breaks = seq(1,H,1)) +
scale_y_continuous(name = 'Asset allocation (in %)',
labels=scales::percent,
breaks = seq(0,1,0.1),
sec.axis = sec_axis(~.*1,breaks = seq(0,1,0.1),labels=scales::percent)) +
coord_cartesian(xlim = c(1,H), ylim = c(0,1), expand = TRUE) +
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
pi_F<- data.frame(cash = Cash2, bonds = Bonds2,
stocks= Stocks2,time=t[-1])
melted_F <- melt(pi_F, id.vars = 'time')
ggplot(melted_F, aes(x=time, y=value, group = variable)) +
geom_area(aes(fill=variable)) +
scale_fill_manual(values=c("#2E318F", "#DFAE41","#109FC6"),
name="Asset Type",
labels = c("Bank account","Bonds", "Stocks"))+
scale_x_continuous(name = 'Age',
breaks = seq(1,H,1)) +
scale_y_continuous(name = 'Asset allocation (in %)',
labels=scales::percent,
breaks = seq(0,1,0.1),
sec.axis = sec_axis(~.*1,breaks = seq(0,1,0.1),labels=scales::percent)) +
coord_cartesian(xlim = c(1,H), ylim = c(0,1), expand = TRUE) +
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank())
Idealy I would like these side by side with the legend in an appropriate place, probably to the right of both graphs. Thanks in advance for the help!
Put your data together and use facets:
## calling the first data `melted_F` and the second `melted_F2`
## put them in one data frame with a column named "data" to tell
## which is which
melted = dplyr::bind_rows(list(data1 = melted_F, data2 = melted_F2), .id = "data")
## exact same plot code until the last line
ggplot(melted, aes(x=time, y=value, group = variable)) +
geom_area(aes(fill=variable)) +
scale_fill_manual(values=c("#2E318F", "#DFAE41","#109FC6"),
name="Asset Type",
labels = c("Bank account","Bonds", "Stocks"))+
scale_x_continuous(name = 'Age',
breaks = seq(1,H,1)) +
scale_y_continuous(name = 'Asset allocation (in %)',
labels=scales::percent,
breaks = seq(0,1,0.1),
sec.axis = sec_axis(~.*1,breaks = seq(0,1,0.1),labels=scales::percent)) +
coord_cartesian(xlim = c(1,H), ylim = c(0,1), expand = TRUE) +
theme(panel.grid.major = element_blank(), panel.grid.minor = element_blank()) +
## facet by the column that identifies the data source
facet_wrap(~ data)
Related
I want to display both an annual value (school hours) and its cumulative (total school hours) in the same graph. This works, so now I want to tidy-up the legend a little bit. I want to drop the three groups associated with the cumulative values and only report a custom label for each state I use. Thus, the legend should only read "NW, G8", "NW, G9", and "CA", next to the colour they are associated with.
I have found other solutions to this problem which no longer seem to work with the current ggplot version (at least I believe this to be a version-issue, maybe I made another mistake):
https://community.rstudio.com/t/how-keep-aesthetic-mapping-but-remove-a-specific-item-from-legend-with-ggplot/52818/3
-> trying to replicate the solution with the provided code results in a gray barplot "c" and not a blue bar plot for me.
Remove legend entries for some factors levels
-> again, similar solution, but if I update my scale_color_manual to the following alternative, I again have gray lines for my cumulative values
scale_color_manual(breaks = c("hours_nw_G8", "hours_nw_G9", "hours_ca"),
values = c("#073B4C", "#118AB2", "#FFD166", "#073B4C", "#118AB2", "#FFD166")) +
Code:
require(tidyverse)
## school hours: comparison of US state (CA) and DE State (NW)
df_hours <- data.frame(year=c(1:13),
hours_nw_G8=c(21.5,22.5,25.5,26.5,31.5,31.5,32.5,32.5,33.5,34,34,34,NA),
hours_nw_G9=c(21.5,22.5,25.5,26.5,28,29,30,30,31,31,31.5,29.5,29.5),
hours_ca=c(840,840,840,900,900,900,900,900,1080,1080,1080,1080,NA)
)
df_hours$hours_nw_G8 <- df_hours$hours_nw_G8 * 38 * 0.75 # scaling by #weeks and accounting for German school hour
df_hours$hours_nw_G9 <- df_hours$hours_nw_G9 * 38 * 0.75
# cumulate
df_hours$c_hours_nw_G8 <- cumsum(df_hours$hours_nw_G8) / 8 # cummulate and divide by scaling factor
df_hours$c_hours_nw_G9 <- cumsum(df_hours$hours_nw_G9) / 8
df_hours$c_hours_ca <- cumsum(df_hours$hours_ca) / 8
# reshape & dummy for cumulative
df_hours <- gather(df_hours, state, hours, hours_nw_G8:c_hours_ca, factor_key=TRUE)
df_hours$cu <- c(rep("annual",13*3),rep("cumulative",13*3))
# Figure
ggplot(df_hours, aes(x=year)) +
geom_line(aes(y=hours, color=state, linetype=cu), linewidth = 1) +
scale_y_continuous(limits = c(0,1500), expand = c(0,0),
breaks = c(0,250,500,750,1000,1250,1500),
name="Hours (annual)",
sec.axis = sec_axis(~ .*8, name = "Hours (cummulative)",
breaks = c(0,2000,4000,6000,8000,10000,12000))
) +
scale_x_continuous(limits = c(0.5,13.5), expand = c(0,0), breaks = c(1:13)) +
labs(x="School Year", y="Hours") +
theme_tufte() +
scale_color_manual(values = c("#073B4C", "#118AB2", "#FFD166", "#073B4C", "#118AB2", "#FFD166")) +
theme(axis.line = element_line(linewidth = 0.75), text = element_text(size = 10, color = "black"),
legend.position = c(.3, .9), legend.title = element_blank()) +
guides(linetype = F)
One solution could be using some stringr function to modify the strings:
library(ggthemes)
library(tidyverse)
df_hours %>%
mutate(state_label = str_remove(state, "c_hours_|hours_"),
state_label = str_to_upper(state_label),
state_label = str_replace(state_label, "_", ", ")) %>%
ggplot(aes(x=year)) +
geom_line(aes(y=hours, color=state_label, linetype=cu), linewidth = 1) +
scale_y_continuous(limits = c(0,1500), expand = c(0,0),
breaks = c(0,250,500,750,1000,1250,1500),
name="Hours (annual)",
sec.axis = sec_axis(~ .*8, name = "Hours (cummulative)",
breaks = c(0,2000,4000,6000,8000,10000,12000))
) +
scale_x_continuous(limits = c(0.5,13.5), expand = c(0,0), breaks = c(1:13)) +
labs(x="School Year", y="Hours") +
theme_tufte() +
scale_color_manual(values = c("#073B4C", "#118AB2", "#FFD166", "#073B4C", "#118AB2", "#FFD166")) +
theme(axis.line = element_line(0.75), text = element_text(size = 10, color = "black"),
legend.position = c(.3, .9), legend.title = element_blank()) +
guides(linetype = F)
I am plotting a smooth to my data using geom_smooth and using geom_ribbon to plot shaded confidence intervals for this smooth. No matter what I try I cannot get a single legend that represents both the smooth and the ribbon correctly, i.e I am wanting a single legend that has the correct colours and labels for both the smooth and the ribbon. I have tried using + guides(fill = FALSE), guides(colour = FALSE), I also read that giving both colour and fill the same label inside labs() should produce a single unified legend.
Any help would be much appreciated.
Note that I have also tried to reset the legend labels and colours using scale_colour_manual()
The below code produces the below figure. Note that there are two curves here that are essentially overlapping. The relabelling and setting couours has worked for the geom_smooth legend but not the geom_ribbon legend and I still have two legends showing which is not what I want.
ggplot(pred.dat, aes(x = age.x, y = fit, colour = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci, fill = tagged), alpha = 0.2, colour = NA) +
theme_classic() +
labs(x = "Age (days since hatch)", y = "Body mass (g)", colour = "", fill = "") +
scale_colour_manual(labels = c("Untagged", "Tagged"), values = c("#3399FF", "#FF0033")) +
theme(axis.title.x = element_text(face = "bold", size = 14),
axis.title.y = element_text(face = "bold", size = 14),
axis.text.x = element_text(size = 12),
axis.text.y = element_text(size = 12),
legend.text = element_text(size = 12))
The problem is that you provide new labels for the color-aesthetic but not for the fill-aesthetic. Consequently ggplot shows two legends because the labels are different.
You can either also provide the same labels for the fill-aesthetic (code option #1 below) or you can set the labels for the levels of your grouping variable ("tagged") before calling ggplot (code option #2).
library(ggplot2)
#make some data
x = seq(0,2*pi, by = 0.01)
pred.dat <- data.frame(x = c(x,x),
y = c(sin(x), cos(x)) + rnorm(length(x) * 2, 0, 1),
tag = rep(0:1, each = length(x)))
pred.dat$lci <- c(sin(x), cos(x)) - 0.4
pred.dat$uci <- c(sin(x), cos(x)) + 0.4
#option 1: set labels within ggplot call
pred.dat$tagged <- as.factor(pred.dat$tag)
ggplot(pred.dat, aes(x = x, y = y, color = tagged, fill = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci), alpha = 0.2, color = NA) +
scale_color_manual(labels = c("untagged", "tagged"), values = c("#F8766D", "#00BFC4")) +
scale_fill_manual(labels = c("untagged", "tagged"), values = c("#F8766D", "#00BFC4")) +
theme_classic() + theme(legend.title = element_blank())
#option 2: set labels before ggplot call
pred.dat$tagged <- factor(pred.dat$tag, levels = 0:1, labels = c("untagged", "tagged"))
ggplot(pred.dat, aes(x = x, y = y, color = tagged, fill = tagged)) +
geom_smooth(size = 1.2) +
geom_ribbon(aes(ymin = lci, ymax = uci), alpha = 0.2, color = NA) +
theme_classic() + theme(legend.title = element_blank())
I am using the windrose function posted here: Wind rose with ggplot (R)?
I need to have the percents on the figure showing on the individual lines (rather than on the left side), but so far I have not been able to figure out how. (see figure below for depiction of goal)
Here is the code that makes the figure:
p.windrose <- ggplot(data = data,
aes(x = dir.binned,y = (..count..)/sum(..count..),
fill = spd.binned)) +
geom_bar()+
scale_y_continuous(breaks = ybreaks.prct,labels=percent)+
ylab("")+
scale_x_discrete(drop = FALSE,
labels = waiver()) +
xlab("")+
coord_polar(start = -((dirres/2)/360) * 2*pi) +
scale_fill_manual(name = "Wind Speed (m/s)",
values = spd.colors,
drop = FALSE)+
theme_bw(base_size = 12, base_family = "Helvetica")
I marked up the figure I have so far with what I am trying to do! It'd be neat if the labels either auto-picked the location with the least wind in that direction, or if it had a tag for the placement so that it could be changed.
I tried using geom_text, but I get an error saying that "aesthetics must be valid data columns".
Thanks for your help!
One of the things you could do is to make an extra data.frame that you use for the labels. Since the data isn't available from your question, I'll illustrate with mock data below:
library(ggplot2)
# Mock data
df <- data.frame(
x = 1:360,
y = runif(360, 0, 0.20)
)
labels <- data.frame(
x = 90,
y = scales::extended_breaks()(range(df$y))
)
ggplot(data = df,
aes(x = as.factor(x), y = y)) +
geom_point() +
geom_text(data = labels,
aes(label = scales::percent(y, 1))) +
scale_x_discrete(breaks = seq(0, 1, length.out = 9) * 360) +
coord_polar() +
theme(axis.ticks.y = element_blank(), # Disables default y-axis
axis.text.y = element_blank())
#teunbrand answer got me very close! I wanted to add the code I used to get everything just right in case anyone in the future has a similar problem.
# Create the labels:
x_location <- pi # x location of the labels
# Get the percentage
T_data <- data %>%
dplyr::group_by(dir.binned) %>%
dplyr::summarise(count= n()) %>%
dplyr::mutate(y = count/sum(count))
labels <- data.frame(x = x_location,
y = scales::extended_breaks()(range(T_data$y)))
# Create figure
p.windrose <- ggplot() +
geom_bar(data = data,
aes(x = dir.binned, y = (..count..)/sum(..count..),
fill = spd.binned))+
geom_text(data = labels,
aes(x=x, y=y, label = scales::percent(y, 1))) +
scale_y_continuous(breaks = waiver(),labels=NULL)+
scale_x_discrete(drop = FALSE,
labels = waiver()) +
ylab("")+xlab("")+
coord_polar(start = -((dirres/2)/360) * 2*pi) +
scale_fill_manual(name = "Wind Speed (m/s)",
values = spd.colors,
drop = FALSE)+
theme_bw(base_size = 12, base_family = "Helvetica") +
theme(axis.ticks.y = element_blank(), # Disables default y-axis
axis.text.y = element_blank())
I cannot seem to change the legend title without the legend splitting my shape and color into two separate legends. How can i change the combined legend title? The image is what the graph looks like.
ggplot(data = df, aes (x = factor(dminp,c("-3 to -1", "-1 to 1")), y = sum_diff,col = factor(dmin), shape = factor(dmin), group = factor(dmin)))+
xlab("Range of Difficulty Parameters for Screen Items") + ylab("Bias Due to Skip-Logic") +
stat_summary(geom = "point",fun.y = "mean",size = 8, aes(shape = factor(dmin)))+
stat_summary(geom = "point",fun.y = "mean",size = 8, aes(col = factor(dmin)))+
scale_shape_manual(values = c(8,5)) + theme_bw() + scale_colour_manual(values = c("orange","purple"))+
theme(panel.grid.major.x = element_blank(),
panel.grid.major = element_line(colour = "black",size=0.25))+ theme(legend.justification = "top")
I have tried using labs(col = "what i want it to be named") but this adds a 2nd legend and splits shape/color.
How about trying:
... +
scale_shape_manual(name="X",values = c(8,5)) +
scale_colour_manual(name="X",values = c("orange","purple"))+
..
Here's an example:
ggplot(iris,aes(x=Sepal.Width,y=Sepal.Length,shape=Species,col=Species)) +
geom_point()+
scale_color_manual(name="X",values=c("Blue","Orange","Red")) +
scale_shape_manual(name="X",values=c(17,18,19))
I want to build a ggplot graph for a given data.frame with one x-axis and multiple y.curves. Also, I want to do it within a customized function so i could call this function anytime I want to plot something with various dataframes.
The script I'm trying to develop is:
graph.date <- function(data, y.axis1, y.axis2, y.axis3, y.axis4, y.axis5, y.axis6, y.axis7, x.axis, y.lab, title, ...){
ggplot(data, aes_string(x = x.axis)) +
ylab(label = y.lab) + xlab(label = "Date") +
ggtitle(label = title) +
scale_x_date(breaks = "1 month", labels = date_format("%d-%b-%Y")) +
geom_line(aes(y = y.axis1, colour = y.axis1), size = 1) +
geom_line(aes(y = y.axis2, colour = y.axis2), size = 1) +
geom_line(aes(y = y.axis3, colour = y.axis3), size = 1) +
geom_line(aes(y = y.axis4, colour = y.axis4), size = 1) +
geom_line(aes(y = y.axis5, colour = y.axis5), size = 1) +
geom_line(aes(y = y.axis6, colour = y.axis6), size = 1) +
geom_line(aes(y = y.axis7, colour = y.axis7), size = 1) +
scale_fill_discrete() + scale_color_manual(values = c(brewer.pal(9, "Set1"), brewer.pal(9, "Set1"))) +
labs(colour = "") + theme(plot.title = element_text(size = rel(1.76))) +
guides(colour = guide_legend(override.aes = list(size=3))) +
theme(text = element_text(size=20), axis.title=element_text(size=34,face="bold"), axis.text.x = element_text(face="bold",
color="black", size=24, angle=25), axis.text.y = element_text(face="bold", color="black", size=24, angle=0))
}
Then I am calling the function:
graph.date(data = BelgiumMerged, y.axis1 = "Gen1", y.axis2 = "Gen2", y.axis3 = "Gen3",
x.axis = "Date", y.lab = "Capacity", title = "title")
The error I get is :
Error in eval(expr, envir, enclos) : object 'y.axis1' not found
The error you get is that df does not have a column called y.axis1. The easiest way to refer to the column that have the name that is stored in the variable y.axis1 is to use aes_string() instead of aes(). Also don't set the color in the call to aes()
So change all
geom_line(aes(y = y.axis1, colour = y.axis1), size = 1)
to
geom_line(aes_string(y = y.axis1), size = 1,color="red") # Or whatever color you want
However a better way to solve the problem is to reshape the dataframe to long formating so that all x coordinates lays in one column all y coordinates in one column and the grouping of these in a third column. Your function could then be defined as
graph.date <- function(df,y.axes,x.axis){
index <- which(names(df) %in% y.axes)
plotDF <- gather(df,y.type,y.data,index)
ggplot(plotDF,aes_string(x.axis)) +
geom_line(mapping=aes(y=y.data,color=y.type))
}
Here you will pass a vector of y axes instead of having one parameter for each y axis
Thanks a lot Nist - you're a RockStar
My final script looks as follows:
graph.date <- function(data,y.axes,x.axis, y.lab, x.lab, title){
index <- which(names(data) %in% y.axes)
plotDF <- gather(data,y.type,y.data,index)
ggplot(plotDF,aes_string(x.axis)) + ggtitle(label = title) + ylab("Capacity [MW]") + xlab("Date") +
geom_line(mapping=aes(y=y.data,color=y.type))+
scale_fill_discrete() + scale_x_date(breaks = "1 month", labels = date_format("%d-%b-%Y")) +
scale_color_manual(values = c(brewer.pal(9, "Set1"), brewer.pal(9, "Set1"))) +
labs(colour = "LegendTitle") + theme(plot.title = element_text(size = rel(1.76))) +
guides(colour = guide_legend(override.aes = list(size=3))) +
theme(text = element_text(size=20), axis.title=element_text(size=34,face="bold"),
axis.text.x = element_text(face="bold", color="black", size=24, angle=25),
axis.text.y = element_text(face="bold", color="black", size=24, angle=0))
}
#Calling the function
graph.date(df, y.axes = c("Gen1", "Gen2", "Gen3"), x.axis = "Date", title = "title")