Bar graph: Combine one stacked bar with one dodged bar - r

I'm trying to recreate a bar graph found on page 4 of the following report:
The figure has three bars with the first two stacked and the third dodged next to it. I've seen iterations of this question but none that recreate the figure in this exact way.
Here is the data:
a <- rep(c('RHB', 'FERS', 'CSRS'), 3)
b <- c(rep('Assets', 3), rep('Amount Past Due', 3),
rep('Actuarial Liability', 3))
c <- c(45.0, 122.5, 152.3, 47.2, 3.4, 4.8, 114.4, 143.4, 181.3)
df <- data.frame(a,b,c)
names(df) <- c('Fund', 'Condition', 'Value')
And what I've managed so far:
p <- ggplot(subset_data, aes(fill=Condition, y=Value, x=Fund)) +
geom_bar(position="stack", stat="identity") +
coord_flip()
I'm not partial to ggplot so if there's another tool that works better I'm ok using another package.

Taking some ideas from the link #aosmith posted.
You can call geom_bar twice, once with Assets and Amounts Past Due stacked, and again with just Actuarial Liability.
You can use width to make the bars thinner, then nudge one set of bars so the two geom_bar calls are not overlapping. I chose to make the width 0.3 and nudge by 0.3 so the edges just line up. If you nudge by more you will see a gap between the two bars.
Edit: add some more formatting and numeric labels
library(tidyverse)
library(scales)
df_al <- filter(df, Condition == 'Actuarial Liability')
df_xal <- filter(df, Condition != 'Actuarial Liability')
bar_width <- 0.3
hjust_lab <- 1.1
hjust_lab_small <- -0.2 # hjust for labels on small bars
ggplot() +
theme_classic() +
geom_bar(data = df_al,
aes(fill=Condition, y=Value, x=Fund),
position = position_nudge(x = -bar_width),
width = bar_width,
stat="identity") +
geom_bar(data = df_xal,
aes(fill=Condition, y=Value, x=Fund),
position="stack",
stat="identity",
width = bar_width) +
geom_text(data = df_al,
aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
position = position_nudge(x = -bar_width),
hjust = hjust_lab) +
geom_text(data = df_xal,
aes(label= dollar(Value, drop0trailing = TRUE), y=Value, x=Fund),
position="stack",
hjust = ifelse(df_xal$Value < 5, hjust_lab_small, hjust_lab)) +
scale_fill_manual(values = c('firebrick3', 'lightsalmon', 'dodgerblue')) +
scale_y_continuous(breaks = seq(0,180, by = 20), labels = dollar) +
coord_flip() +
labs(x = NULL, y = NULL, fill = NULL) +
theme(legend.position = "bottom")

I think I would use the "sneaky facet" method, after adding a dummy variable to dodge the columns and making Fund a factor with the correct order:
df$not_liability <-df$Condition != "Actuarial Liability"
df$Fund <- factor(df$Fund, levels = c('RHB', 'FERS', 'CSRS'))
Most of the plotting code is then an attempt to copy the look of the supplied plot:
ggplot(df, aes(fill=Condition, y=Value, x=not_liability)) +
geom_bar(position = "stack", stat = "identity") +
scale_x_discrete(expand = c(0.5, 0.5)) +
scale_y_continuous(breaks = 0:10 * 20, labels = scales::dollar) +
coord_flip() +
facet_grid(Fund~., switch = "y") +
scale_fill_manual(values = c("#c00000", "#f7c290", "#0071bf"), name = "") +
theme_classic() +
theme(panel.spacing = unit(0, "points"),
strip.background = element_blank(),
axis.text.y = element_blank(),
axis.ticks.length.y = unit(0, "points"),
axis.title = element_blank(),
strip.placement = "outside",
strip.text = element_text(),
legend.position = "bottom",
panel.grid.major.x = element_line())

Related

R ggplot2: remove panel spacing from strip text

I am trying to create a barplot with two x-axis (grouped x-axis):
# read data
tmp <- read.table(text = "label CNV_x CNV_Type
17p -1 Loss
9p -1 Loss
16q 1 Gain
10p 1 Gain
8q 1 Gain
13q 1 Gain", header = T)
tmp$CNV_Type <- relevel(tmp$CNV_Type, ref = 'Loss')
# plot
ggplot(tmp, aes(x = label, y = CNV_x)) +
geom_bar(stat = 'identity') +
theme_bw() +
geom_hline(yintercept = 0) +
coord_flip() +
facet_wrap(~CNV_Type, strip.position = "bottom", scales = "free_x") +
theme(panel.spacing = unit(0, "lines"),
strip.background = element_blank(),
strip.placement = "outside",
panel.border = element_rect(colour = NA))
This creates a plot like this:
This plot shows 0.00 twice on x-axis and I can't figure out a way to remove the spacing between the two vertical lines separating the strips (one is Gain and other is Loss).
Any help would be much appreciated. Thanks!
UPDATE: I added scale_y_continuous(expand = c(0, 0)) as suggested below:
ggplot(tmp, aes(x = label, y = CNV_x)) +
geom_bar(stat = 'identity') +
theme_bw() +
geom_hline(yintercept = 0) +
scale_y_continuous(expand = c(0, 0)) +
coord_flip() +
facet_wrap(~CNV_Type, strip.position = "bottom", scales = "free_x") +
theme(panel.spacing = unit(0, "lines"),
strip.background = element_blank(),
strip.placement = "outside",
panel.border = element_rect(colour = NA))
This creates a plot like this:
The only issue now is there is no spacing between the bars and the left and right margins of the plot - not sure why that happened.
I would not use facets here. A couple of options. You could indicate the type by colour:
tmp %>%
ggplot(aes(label, CNV_x)) +
geom_col(aes(fill = CNV_Type)) +
geom_hline(yintercept = 0) +
coord_flip() +
scale_fill_manual(values = c("darkorange", "skyblue3"))
And/or add the labels for type to the plot using annotate. That requires some manual fiddling with x, y and expand to get it right:
tmp %>%
ggplot(aes(label, CNV_x)) +
geom_col() +
geom_hline(yintercept = 0) +
coord_flip() +
annotate("text",
label = c("Loss", "Gain"),
x = c(7, 7),
y = c(-0.5, 0.5)) +
scale_x_discrete(expand = c(0.1, 0.1))

ggplot2: change strip.text position in facet_grid plot

you can set the position of the legend inside the plotting area, like
... + theme(legend.justification=c(1,0), legend.position=c(1,0))
Is there a similarly easy way to change the position of the strip text
(or factor levels in grouped plots)
library(reshape2); library(ggplot2)
sp <- ggplot(tips, aes(x=total_bill, y=tip/total_bill)) + geom_point() +
facet_grid(. ~ sex)
sp
(http://www.cookbook-r.com/Graphs/Facets_%28ggplot2%29/)
in lattice I would use something like strip.text = levels(dat$Y)[panel.number()]
and panel.text(...), but there may be a cleaner way too...
thx, Christof
Here's one approach:
ggplot(tips, aes(x = total_bill, y = tip / total_bill)) +
geom_point() +
facet_grid(. ~ sex) +
geom_text(aes(label = sex), x = Inf, y = Inf, hjust = 1.5, vjust = 1.5) +
theme(
strip.background = element_blank(),
strip.text = element_blank()
)
However, this is not moving the strip.text, rather, it's adding a geom_text element and turning off the strip.background and strip.text, but I think it achieves the desired outcome.
A slight addition to #JasonAizkalns is to add the check_overlap = T option in geom_text, to avoid the superposition of multiple identical labels.
ggplot(tips, aes(x = total_bill, y = tip / total_bill)) +
geom_point() +
facet_grid(. ~ sex) +
geom_text(aes(label = sex), x = Inf, y = Inf, hjust = 1.5, vjust = 1.5, check_overlap = TRUE) +
theme(
strip.background = element_blank(),
strip.text = element_blank()
)

putting two different legends in two columns in ggplot2

I have the following reproducible code which gets me the plot listed below:
require(ggplot2)
set.seed(123)
ChickWt <- data.frame(ChickWeight, AR = sample(c("p=0", "p=1", "hat(p)"), size = 578, replace = T))
exprvec <- expression( p==hat(p), p==0, p==1)
p1 <- ggplot(ChickWt, aes(x=Time, y=weight, colour=Diet, Group = Chick, linetype = AR)) + geom_line()
p1 <- p1 + scale_linetype_manual(values=c(2,4,1), labels = exprvec,name="AR order") + theme_bw() + theme(legend.justification=c(1,-0.2), legend.position=c(0.3,0.2), legend.text=element_text(size=10), legend.title=element_text(size=10), axis.title.x=element_text(size=10), axis.title.y=element_text(size = 10), legend.key = element_blank(), legend.background = element_rect(color="black",size = 0.1)) + ylim(c(0,400)) + guides(fill=guide_legend(ncol=2))
but I would like the legend on Diet and AR order in two separate columns. How do I get this to work? Clearly, the guides(fill=guide_legend(ncol=2)) has no effect, perhaps because these are two separate legends.
Thanks for suggestions!
The reason that guides(fill=guide_legend(ncol=2)) does not work is because it only refers to the fill-legend and not to the linetype-legend. You can position the legends next to each other by using legend.box = "horizontal":
ggplot(ChickWt, aes(x=Time, y=weight, colour=Diet, Group = Chick, linetype = AR)) +
geom_line() +
scale_linetype_manual(values=c(2,4,1), labels = exprvec,name="AR order") +
theme_bw() +
theme(legend.justification=c(1,-0.2),
legend.position=c(0.3,0.2),
legend.text=element_text(size=10),
legend.title=element_text(size=10),
axis.title.x=element_text(size=10),
axis.title.y=element_text(size = 10),
legend.key = element_blank(),
legend.background = element_rect(color="black",size = 0.1),
legend.box = "horizontal") +
ylim(c(0,400))
which gives:

Split parts of a stacked barplot into individual series

I want to create in R 3.2.2 a barplot with stacked bars but with each part of each bar splitted into an individual series.
Example data frame:
num_var_x = 14
num_var_y = 17
x = runif(num_var_x, 0.0, 1.0)
norm = x/sum(x)
data = data.frame(replicate(num_var_y,sample(norm)))
EDIT:
Thanks to Floo0 I have come up with this continuation of the code:
## preparing dataset for ggplot
require(ggplot2)
require(reshape2)
data$no <- seq_len(nrow(data))
data_molten <- melt(data, id.vars = "no")
data_molten_sort = data_molten[with(data_molten,order(no)),]
## removing elements from variable 'no' whose max. value is e.g. < 0.025
sequence = seq(from=1, to=(num_var_y*num_var_x-num_var_x)+1, by=num_var_x)
for(i in 1:length(sequence))
{
if(isTRUE((max(data_molten_sort$value[(sequence[i]):((num_var_x+sequence[i])-(1))])) < 0.025))
{
data_molten_sort$value[(sequence[i]):((num_var_x+sequence[i])-(1))] = NA
}
}
View(data_molten)
## preparing posterior exporting
#install.packages("Cairo"); "cairo" type in png() has a better quality
library("Cairo")
#preparing exporting
png(file="ggplot.png",type="cairo", width = 4, height = 5, units = 'in',pointsize=8,res=600)
## plotting
ggplot(data_molten[!is.na(data_molten$value),], aes(x = variable, y = value, fill = factor(no))) +
geom_bar(stat = "identity") +
scale_fill_hue(l=40) + facet_grid(no~., as.table=FALSE, scale="free_y", space = "free_y") + theme_minimal() +
geom_vline(xintercept=max(as.numeric(data_molten$variable)) + 0.586, size=0.3) +
theme(legend.position="none",
axis.text.x = element_text(angle = 90, colour="black", vjust = 0.4, hjust=1, size=8),
axis.title.x = element_blank(), axis.title.y = element_blank(),
axis.line.y=element_blank(), axis.text.y=element_blank(), axis.ticks.y=element_blank(),
strip.text.y=element_text(size = 8, colour="black", family="", angle=00,hjust = 0.1),
panel.grid=element_blank(),
axis.line=element_line(size = 0.3, colour = "black", linetype = "solid"),
axis.ticks.x=element_line(size = 0.3, colour = "black", linetype = "solid"),
panel.background=element_blank(), panel.margin = unit(0, "lines"))
## exporting barplot "ggplot.png" to directory
dev.off()
which produces the desired barplot:
http://i.imgur.com/C6h5fPg.png?1
You can use ggplot2 to do that as follows:
require(ggplot2)
require(reshape2)
data$no <- seq_len(nrow(data))
data_molten <- melt(data, id.vars = "no")
If you want the rows to have different hights, have a look at: Different y-Axis Labels facet_grid and sizes
I am not 100% sure in which direction you want the plot to be turned:
Version 1
ggplot(data_molten, aes(x = no, y = value, fill = variable)) + geom_bar(stat = "identity") +
facet_grid(variable~.) + theme(legend.position="none")
Version 2
Thx bergant fot the comment
ggplot(data_molten, aes(x = variable, y = value, fill = factor(no))) + geom_bar(stat = "identity") +
facet_grid(no~.) + theme(legend.position="none")
Original
ggplot(data_molten, aes(x = no, y = value, fill = variable)) + geom_bar(stat = "identity")

ggplot2, facet wrap, fixed y scale for each row, free scale between rows

I would like to produce a plot using facet_wrap that has a different y scale for each row of the wrap. In other words, with fixed scales on the same row, free scales on different rows, with a fixed x scale. Free scales doesn't give me exactly what I'm looking for, nor does facet_grid. If possible, I'd like to avoid creating 2 separate plots and then pasting them together. I'm looking for a result like the plot below, but with a y scale max of 300 for the first row, and an y scale max of 50 in the second row. Thanks for any help!
Here is my code:
library(ggplot2)
library(reshape)
# set up data frame
dat <- data.frame(jack = c(150,160,170),
surgeon = c(155,265,175),
snapper = c(10,15,12),
grouper = c(5,12,50))
dat$island<-c("Oahu","Hawaii","Maui")
df<-melt(dat)
# plot
ggplot(df, aes(fill=variable, y=value, x=island)) +
geom_bar(width = 0.85, position= position_dodge(width=0.5),stat="identity", colour="black") +
facet_wrap(~variable, scales = "free_y",ncol=2) +
theme_bw() +
theme(strip.text = element_text(size=15, face="bold"))+
theme(legend.position="none")+
theme(panel.grid.major = element_line(colour = "white", size = 0.2))+
theme(panel.grid.minor = element_line(colour = "white", size = 0.5))+
theme(axis.text.x = element_text(angle = 90, hjust =1, vjust =0.5, size=18))+
labs(y = expression(paste("Yearly catch (kg)")))
Drawing on one of the lower ranked answers from the link Eric commented, you can add a layer that blends into the background to enforce the axes.
Here I created a second data frame (df2) that puts a single point at "Hawaii" and the max value you wanted (300 or 50) for the four variable/fish types. By manually setting the color of the geom_point white, it fades into the background.
library(ggplot2)
library(reshape)
# set up data frame
dat <- data.frame(jack = c(150,160,170),
surgeon = c(155,265,175),
snapper = c(10,15,12),
grouper = c(5,12,50))
dat$island<-c("Oahu","Hawaii","Maui")
df<-melt(dat)
#> Using island as id variables
df2 <- data.frame(island = rep("Hawaii",4), variable = c("jack","surgeon","snapper","grouper"),value = c(300,300,50,50))
ggplot(df, aes(fill=variable, y=value, x=island)) +
geom_bar(width = 0.85, position= position_dodge(width=0.5),stat="identity", colour="black") +
geom_point(data = df2, aes(x = island, y = value), colour = "white") +
facet_wrap(~variable, scales = "free_y",ncol=2) +
theme_bw() +
theme(strip.text = element_text(size=15, face="bold"))+
theme(legend.position="none")+
theme(panel.grid.major = element_line(colour = "white", size = 0.2))+
theme(panel.grid.minor = element_line(colour = "white", size = 0.5))+
theme(axis.text.x = element_text(angle = 90, hjust =1, vjust =0.5, size=18))+
labs(y = expression(paste("Yearly catch (kg)")))

Resources