Related
I've been stuck on an issue and can't find a solution. I've tried many suggestions on Stack Overflow and elsewhere about manually ordering a stacked bar chart, since that should be a pretty simple fix, but those suggestions don't work with the huge complicated mess of code I plucked from many places. My only issue is y-axis item ordering.
I'm making a series of stacked bar charts, and ggplot2 changes the ordering of the items on the y-axis depending on which dataframe I am trying to plot. I'm trying to make 39 of these plots and want them to all have the same ordering. I think ggplot2 only wants to plot them in ascending order of their numeric mean or something, but I'd like all of the bar charts to first display the group "Bird Advocates" and then "Cat Advocates." (This is also the order they appear in my data frame, but that ordering is lost at the coord_flip() point in plotting.)
I think that taking the data frame through so many changes is why I can't just add something simple at the end or use the reorder() function. Adding things into aes() also doesn't work, since the stacked bar chart I'm creating seems to depend on those items being exactly a certain way.
Here's one of my data frames where ggplot2 is ordering my y-axis items incorrectly, plotting "Cat Advocates" before "Bird Advocates":
Group,Strongly Opposed,Opposed,Slightly Opposed,Neutral,Slightly Support,Support,Strongly Support
Bird Advocates,0.005473026,0.010946052,0.012509773,0.058639562,0.071149335,0.31118061,0.530101642
Cat Advocates,0.04491726,0.07013396,0.03624901,0.23719464,0.09141056,0.23404255,0.28605201
And here's all the code that takes that and turns it into a plot:
library(ggplot2)
library(reshape2)
library(plotly)
#Importing data from a .csv file
data <- read.csv("data.csv", header=TRUE)
data$s.Strongly.Opposed <- 0-data$Strongly.Opposed-data$Opposed-data$Slightly.Opposed-.5*data$Neutral
data$s.Opposed <- 0-data$Opposed-data$Slightly.Opposed-.5*data$Neutral
data$s.Slightly.Opposed <- 0-data$Slightly.Opposed-.5*data$Neutral
data$s.Neutral <- 0-.5*data$Neutral
data$s.Slightly.Support <- 0+.5*data$Neutral
data$s.Support <- 0+data$Slightly.Support+.5*data$Neutral
data$s.Strongly.Support <- 0+data$Support+data$Slightly.Support+.5*data$Neutral
#to percents
data[,2:15]<-data[,2:15]*100
#melting
mdfr <- melt(data, id=c("Group"))
mdfr<-cbind(mdfr[1:14,],mdfr[15:28,3])
colnames(mdfr)<-c("Group","variable","value","start")
#remove dot in level names
mylevels<-c("Strongly Opposed","Opposed","Slightly Opposed","Neutral","Slightly Support","Support","Strongly Support")
mdfr$variable<-droplevels(mdfr$variable)
levels(mdfr$variable)<-mylevels
pal<-c("#bd7523", "#e9aa61", "#f6d1a7", "#999999", "#c8cbc0", "#65806d", "#334e3b")
ggplot(data=mdfr) +
geom_segment(aes(x = Group, y = start, xend = Group, yend = start+value, colour = variable,
text=paste("Group: ",Group,"<br>Percent: ",value,"%")), size = 5) +
geom_hline(yintercept = 0, color =c("#646464")) +
coord_flip() +
theme(legend.position="top") +
theme(legend.key.width=unit(0.5,"cm")) +
guides(col = guide_legend(ncol = 12)) + #has 7 real columns, using to adjust legend position
scale_color_manual("Response", labels = mylevels, values = pal, guide="legend") +
theme(legend.title = element_blank()) +
theme(axis.title.x = element_blank()) +
theme(axis.title.y = element_blank()) +
theme(axis.ticks = element_blank()) +
theme(axis.text.x = element_blank()) +
theme(legend.key = element_rect(fill = "white")) +
scale_y_continuous(breaks=seq(-100,100,100), limits=c(-100,100)) +
theme(panel.background = element_rect(fill = "#ffffff"),
panel.grid.major = element_line(colour = "#CBCBCB"))
The plot:
I think this works, you may need to play around with the axis limits/breaks:
library(dplyr)
mdfr <- mdfr %>%
mutate(group_n = as.integer(case_when(Group == "Bird Advocates" ~ 2,
Group == "Cat Advocates" ~ 1)))
ggplot(data=mdfr) +
geom_segment(aes(x = group_n, y = start, xend = group_n, yend = start + value, colour = variable,
text=paste("Group: ",Group,"<br>Percent: ",value,"%")), size = 5) +
scale_x_continuous(limits = c(0,3), breaks = c(1, 2), labels = c("Cat", "Bird")) +
geom_hline(yintercept = 0, color =c("#646464")) +
theme(legend.position="top") +
theme(legend.key.width=unit(0.5,"cm")) +
coord_flip() +
guides(col = guide_legend(ncol = 12)) + #has 7 real columns, using to adjust legend position
scale_color_manual("Response", labels = mylevels, values = pal, guide="legend") +
theme(legend.title = element_blank()) +
theme(axis.title.x = element_blank()) +
theme(axis.title.y = element_blank()) +
theme(axis.ticks = element_blank()) +
theme(axis.text.x = element_blank()) +
theme(legend.key = element_rect(fill = "white"))+
scale_y_continuous(breaks=seq(-100,100,100), limits=c(-100,100)) +
theme(panel.background = element_rect(fill = "#ffffff"),
panel.grid.major = element_line(colour = "#CBCBCB"))
produces this plot:
You want to factor the 'Group' variable in the order by which you want the bars to appear.
mdfr$Group <- factor(mdfr$Group, levels = c("Bird Advocates", "Cat Advocates")
I made a figure using geom_point from ggplot2 (just showing part of it). Colors are representing 3 classes. Black bar is mean (not relevant for the question).
The data structure is the following (stored in a list):
V1 V2 V3
1 L. brevis 5 class1
3 L. sp. 13 class1
4 L. rhamnosus 14 class1
5 L. lindneri 17 class1
6 L. plantarum 17 class1
7 L. acidophilus 18 class1
8 L. acidophilus 18 class1
10 L. plantarum 18 class1
... ... .. ...
Where V2 is the position of the datapoints on the y-axis and V3 is the class (color).
Now I would like to show the percentages for each of the three classes on top of the figure (Or maybe even as pie charts :-) ). I made an example for "L. acidophilus" on the image (66.7% / 33.3%).
The legend explaining groups ideally is also produced by R but I can do it manually.
How do I do that?
Forgot to add the 0% for group three on top of column "L. acidophilus"... Sorry for that.
EDIT: Here the ggplot2 code:
p <- ggplot(myData, aes(x=V1, y=V2)) +
geom_point(aes(color=V3, fill=V3), size=2.5, cex=5, shape=21, stroke=1) +
scale_color_manual(values=colBorder, labels=c("Class I","Class II","Class III","This study")) +
scale_fill_manual(values=col, labels=c("Class I","Class II","Class III","This study")) +
theme_bw() +
theme(axis.text.x=element_text(angle=50,hjust=1,face="italic", color="black"), text = element_text(size=12),
axis.text.y=element_text(color="black"), panel.grid.major = element_line(color="gray85",size=.15), panel.grid.minor = element_blank(),
panel.grid.major.y = element_blank(), axis.ticks = element_line(size = 0.3), panel.border = element_rect(fill=NA, colour = "black", size=0.3)) +
stat_summary(aes(shape="mean"), fun.y=mean, size = 6, shape=95, colour="black", geom="point") +
guides(fill=guide_legend(title="Class", order=1), color=guide_legend(title="Class",order=1), shape=guide_legend(title="Blup", order=2))
Option A: Secondary Axis
You can do this using a secondary x axis (new to ggplot2 v2.2.0), but it's hard to do with a categorical variable on the x axis because it doesn't work with scale_x_discrete(), only scale_x_continuous(). So, you have to convert the factor to integer, plot based on that, and then overwrite the labels on the primary x axis.
For example:
set.seed(123)
df <- iris[sample.int(nrow(iris),size=300,replace=TRUE),]
# Assume we are grouping by species
# Some group-level stats -- how about count and mean/sdev of sepal length
library(dplyr)
df_stats <- df %>%
group_by(Species) %>%
summarize(stat_txt = paste0(c('N=','avg=','sdev='),
c(n(),round(mean(Sepal.Length),2),round(sd(Sepal.Length),3) ),
collapse='\n') )
library(ggplot2)
ggplot(data = df,
aes(x = as.integer(Species),
y = Sepal.Length)) +
geom_point() +
stat_summary(aes(shape="mean"), fun.y=mean, size = 6, shape=95,
colour="black", geom="point") +
theme_bw() +
scale_x_continuous(breaks=1:length(levels(df$Species)),
limits = c(0,length(levels(df$Species))+1),
labels = levels(df$Species),
minor_breaks=NULL,
sec.axis=sec_axis(~.,
breaks=1:length(levels(df$Species)),
labels=df_stats$stat_txt)) +
xlab('Species') +
theme(axis.text.x = element_text(hjust=0))
Option B: grid.arrange your statistics as a separate chart atop your main chart.
This is a little more straightforward, but the two charts don't quite perfectly line up, possibly because of the ticks and labels being suppressed on the axes of the top chart.
library(ggplot2)
library(gridExtra)
p <-
ggplot(data = df,
aes(x = Species,
y = Sepal.Length)) +
geom_point() +
stat_summary(aes(shape="mean"), fun.y=mean, size = 6, shape=95,
colour="black", geom="point") +
theme_bw() +
theme(axis.text.x = element_text(angle=45, hjust=1, vjust=1))
annot <-
ggplot(data=df_stats, aes(x=Species, y = 0)) +
geom_text(aes(label=stat_txt), hjust=0) +
theme_minimal() +
scale_x_discrete(breaks=NULL) +
scale_y_continuous(breaks=NULL) +
xlab(NULL) + ylab('')
grid.arrange(annot, p, heights=c(1,8))
I want to add the number of observations below each boxplot (as in the figure- no need for the red square). :)
However, I don't know how to annotate this type of boxplot (see figure below).multiple boxplot annotate number of observations
Does anyone know how to do it?
This is the code that I used to plot this figure.
ggplot(data=MIOT1, aes(stage, time, fill=resp)) +
geom_boxplot(color= "black", lwd=0.3) +
stat_summary(fun.y=mean, geom="point", shape=0, size=1, colour="black", position=position_dodge(width=0.75)) +
scale_fill_manual(values=c("grey25", "grey50", "grey67")) +
annotation_custom(mygrobA) +
scale_y_continuous(limits=c(-10,124)) +
theme(panel.grid.major = element_blank(),
panel.grid.minor = element_blank(),
strip.background = element_rect(colour="black"),
panel.border = element_rect(colour = "black", fill="transparent")) +
xlab(bquote(' ')) +
ylab(bquote('Minimum Consecutive Time (s)')) +
labs(title="SATIATION\n") +
theme(axis.title.y = element_text(colour="black",size=10,face="bold"),
axis.text.x = element_text(colour="black",size=8, face="plain"),
axis.text.y = element_text(colour="black",size=8, face="plain"),
axis.title.x = element_text(colour="black",size=10,face="bold")) +
theme(panel.background = element_rect(fill = "white")) +
theme(plot.title = element_text(lineheight=.8, size=10, face="bold")) +
theme(legend.title=element_blank(), legend.key = element_rect(fill = NA, colour = NA)) +
theme(legend.position="none") +
theme(legend.background = element_rect(fill=NA)) +
theme(plot.margin = unit(c(.25,.25,.0,.0), "cm"))<i>
EXAMPLE DATA
MIOT1 is a numeric variable (y-axis), and I am considering two grouping factors (development stage- x axis) and the response (unresponsive, coastal, lagoon).
Something like
stage resp time
pre U 100
pre U 80
pre U 50
pre C 20
flex U 80
flex U 90
flex C 10
flex C 20
post U 40
post U 30
post U 60
post C 80
post C 100
post L 50
post L 40
Thank you!
Pedro
Here's a simple example of how to do it, using the built-in mtcars data frame:
ggplot(mtcars, aes(factor(cyl), mpg)) +
geom_boxplot() +
geom_text(stat="count", aes(label=..count..), y=min(mtcars$mpg)- 0.6)
In your case, it will be something like
ggplot(data=MIOT1, aes(stage, time, fill=resp)) +
geom_boxplot(color= "black", lwd=0.3) +
geom_text(stat="count", aes(label=..count..), y=min(MIOT1$time))
where you may have to adjust the y location of the text labels and you might also need to adjust the range of the y-axis to make room for the labels.
UPDATE: I was able to reproduce the error you reported, but I'm not sure how to fix it. Instead, you can pre-summarize the data and then add it to the plot. Here's an example:
library(dplyr)
# Get counts by desired grouping variables
counts = mtcars %>% group_by(cyl, am) %>% tally
ggplot(mtcars, aes(factor(cyl), mpg, fill=factor(am))) +
geom_boxplot(position=position_dodge(0.9)) +
geom_text(data=counts, aes(label=n, y=min(mtcars$mpg) - 0.6),
position=position_dodge(0.9))
IN SUMMARY EIPI10 ANSWERED MY QUESTION:
library(dplyr)
# Get counts by desired grouping variables
counts = mtcars %>% group_by(cyl, am) %>% tally
ggplot(mtcars, aes(factor(cyl), mpg, fill=factor(am))) +
geom_boxplot(position=position_dodge(0.9)) +
geom_text(data=counts, aes(label=n, y=min(mtcars$mpg) - 0.6), position=position_dodge(0.9)
I am trying to demonstrate the soil type (soil column) at different depths in the ground using box plots. However, as the sampling interval is not consistent, there are also gaps in between the samples.
My questions are as follows:
Is it possible to put the box plots within the same column? i.e. all box plots in 1 straight column
Is it possible to remove the x-axis labels and ticks when using ggdraw? I tried to remove it when using plot, but appears again when I use ggdraw.
My code looks like this:
SampleID <- c("Rep-1", "Rep-2", "Rep-3", "Rep-4")
From <- c(0,2,4,9)
To <- c(1,4,8,10)
Mid <- (From+To)/2
ImaginaryVal <- c(1,1,1,1)
Soiltype <- c("organic", "silt","clay", "sand")
df <- data.frame(SampleID, From, To, Mid, ImaginaryVal, Soiltype)
plot <- ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype,
middle=`Mid`, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity") + scale_y_reverse(breaks = seq(0,10,0.5)) + xlab('Soiltype') + ylab('Depth (m)') + theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
ggdraw(switch_axis_position(plot + theme_bw(8), axis = 'x'))
In the image I have pointed out what I want, using the red arrows and lines.
You can use position = position_dodge() like so:
plot <- ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype, middle=Mid, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity", position = position_dodge(width=0)) +
scale_y_reverse(breaks = seq(0,10,0.5)) +
xlab('Soiltype') +
ylab('Depth (m)') +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank())
edit: I don't think you need cowplot at all, if this is what you want your plot to look like:
ggplot(df, aes(x=ImaginaryVal, ymin=From, lower=From,fill=Soiltype, middle=Mid, upper=To, ymax=To)) +
geom_boxplot(colour= "black", stat="identity", position = position_dodge(width=0)) +
scale_y_reverse(breaks = seq(0,10,0.5)) +
xlab('Soiltype') +
ylab('Depth (m)') +
theme_bw() +
theme(axis.text.x = element_blank(), axis.ticks.x = element_blank()) +
xlab("") +
ggtitle("Soiltype")
I am trying to create a stacked bar plot which will show the revenue of the company and various components of its cost of sales (operating expenses. other fixed costs etc.). Now I want the individual components of the cost of sales to be shown on top of the revenue bar so that it is clear what part of the revenue is cost of sales.
Right now, I am only able to create a stacked bar plot which lays everything on top of each other. In other words, cost of sales is displayed on top of revenue.
Ideally, I would want the individual components of cost of sales to be displayed as subset of revenue.
Here's a brief look at the molten data frame I have:
Time variable value
2013-01-01 A 84.32153
2013-02-01 A 91.41203
2013-01-01 B 1214.29960
2013-02-01 B 1224.21256
2013-01-01 C 312.78462
2013-02-01 C 175.58130
2013-01-01 D 321.12000
2013-02-01 D 298.82000
In the above scenario, I want B to be displayed as the super set and A,C and D should be components of B for the two months shown above.
I am using the following code:
stackbar <- ggplot(temp, aes_string(x = 'Time',y='value', fill = "variable")) +
geom_bar(stat='identity') +
ylab("Count") + theme(legend.title = element_blank()) +
theme(legend.direction = "horizontal") +
theme(legend.position = c(1, 1)) +
theme(legend.justification = c(1, 0)) +
theme(panel.grid.minor.x=element_blank(),
panel.grid.minor.y=element_blank(), panel.background=element_blank(),
panel.grid.major.x=element_line(color='grey90',linetype='dashed'),
panel.grid.major.y=element_line(color='grey90',linetype='dashed')) +
theme(axis.ticks.x=element_blank()) + theme(axis.ticks.y=element_blank()) +
scale_colour_discrete(limits = levels(temp$variable))
Any help in this regard would be much appreciated.
What you want to do is to subset your data and have two geom_bar layers. First, you want to draw bars with variable B. Then, you draw the rest in the second geom_bar(). I hope this will give you the figure you want.
stackbar <- ggplot(mydf, aes(x = Time, y = value, fill = variable)) +
geom_bar(data = mydf[mydf$variable == "B",], stat = "identity") +
geom_bar(data = mydf[!mydf$variable == "B",], stat = "identity")+
ylab("Count") +
theme(legend.title = element_blank()) +
theme(legend.direction = "horizontal") +
theme(legend.position = c(1, 1)) +
theme(legend.justification = c(1, 0)) +
theme(panel.grid.minor.x=element_blank(),
panel.grid.minor.y=element_blank(), panel.background=element_blank(),
panel.grid.major.x=element_line(color='grey90',linetype='dashed'),
panel.grid.major.y=element_line(color='grey90',linetype='dashed')) +
theme(axis.ticks.x=element_blank()) + theme(axis.ticks.y=element_blank()) +
scale_colour_discrete(limits = levels(mydf$variable))
stackbar