R: Bar Plot with Two Groups, of Which One Is Stacked

Stacked barplot with two categories in R

You can calculate percentage first, then use those values to add as labels in geom_text.

library(tidyverse)

df %>%
count(User, A) %>%
group_by(User) %>%
mutate(pct = n / sum(n)) %>%
ggplot(aes(x = User, y = pct, fill = A)) +
geom_col(width = 0.7) +
geom_text(aes(label = paste0(round(pct * 100), '%')),
position = position_stack(vjust = 0.5)) +
scale_fill_brewer(palette = "BrBG") +
labs(y = "Percent")

Output

Sample Image

Data

df <- structure(list(A = c("ABC", "DEF", "GHI", "XYZ", "JKL", "ABC", 
"XYZ", "XYZ"), User = c("Male", "Female", "Female", "Female",
"Male", "Male", "Male", "Female")), class = "data.frame", row.names = c(NA,
-8L))

Connect stack bar charts with multiple groups with lines or segments using ggplot 2

I don't think there is an easy way of doing this, you'd have to (semi)-manually add these lines yourself. What I'm proposing below comes from this answer, but applied to your case. In essence, it exploits the fact that geom_area() is also stackable like the bar chart is. The downside is that you'll manually have to punch in coordinates for the positions where bars start and end, and you have to do it for each pair of stacked bars.

library(tidyverse)

# mrs <- tibble(...) %>% mutate(...) # omitted for brevity, same as question

mrs %>% ggplot(aes(x= value, y= timepoint, fill= Score))+
geom_bar(color= "black", width = 0.6, stat= "identity") +
geom_area(
# Last two stacked bars
data = ~ subset(.x, timepoint %in% c("pMRS", "dMRS")),
# These exact values depend on the 'width' of the bars
aes(y = c("pMRS" = 2.7, "dMRS" = 2.3)[as.character(timepoint)]),
position = "stack", outline.type = "both",
# Alpha set to 0 to hide the fill colour
alpha = 0, colour = "black",
orientation = "y"
) +
geom_area(
# First two stacked bars
data = ~ subset(.x, timepoint %in% c("dMRS", "fMRS")),
aes(y = c("dMRS" = 1.7, "fMRS" = 1.3)[as.character(timepoint)]),
position = "stack", outline.type = "both", alpha = 0, colour = "black",
orientation = "y"
) +
scale_fill_manual(name= NULL,
breaks = c("6","5","4","3","2","1","0"),
values= c("#000000","#294e63", "#496a80","#7c98ac", "#b3c4d2","#d9e0e6","#ffffff"))+
scale_y_discrete(breaks=c("pMRS",
"dMRS",
"fMRS"),
labels=c("Pre-mRS, (N=21)",
"Discharge mRS, (N=21)",
"Followup mRS, (N=21)"))+
theme_classic()

Sample Image

Arguably, making a separate data.frame for the lines is more straightforward, but also a bit messier.

Plotting a bar chart with multiple groups

Styling always involves a bit of fiddling and trial (and sometimes error (;). But generally you could probably get quite close to your desired result like so:

library(ggplot2)

ggplot(example, aes(categorical_var, n)) +
geom_bar(position="dodge",stat="identity") +
# Add some more space between groups
scale_x_discrete(expand = expansion(add = .9)) +
# Make axis start at zero
scale_y_continuous(expand = expansion(mult = c(0, .05))) +
# Put facet label to bottom
facet_wrap(~treatment, strip.position = "bottom") +
theme_minimal() +
# Styling via various theme options
theme(panel.spacing.x = unit(0, "pt"),
strip.placement = "outside",
strip.background.x = element_blank(),
axis.line.x = element_line(size = .1),
panel.grid.major.y = element_line(linetype = "dotted"),
panel.grid.major.x = element_blank(),
panel.grid.minor = element_blank())

Sample Image

R: bar plot with two groups, of which one is stacked

Doing this requires thinking about how barplot draws stacked bars. Basically, you need to feed it some data with 0 values in appropriate places. With your data:

mydat <- cbind(rbind(a,b,0),rbind(0,0,c))[,c(1,6,2,7,3,8,4,9,5,10)]
barplot(mydat,space=c(.75,.25))

barplot

To see what's going on under the hood, take a look at mydat:

> mydat
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
a 3 0 3 0 2 0 1 0 0 0
b 3 0 2 0 2 0 2 0 2 0
0 0 0 1 0 2 0 3 0 4

Here, you're plotting each bar with three values (the value of a, the value of b, the value of c). Each column of the mydat matrix is a bar, sorted so that the ab bars are appropriately interspersed with the c bars. You may want to play around with spacing and color.

Apparently versions of this have been discussed on R-help various times without great solutions, so hopefully this is helpful.

Stacked bar plot with multiple or different legend for each group

Try this. Simply reorder the factor and use scale_fill_manual to set the fill colors.

library(tidyverse)

df$trt <- factor(df$trt,levels=unique(as.character(df$trt)))
df$gene <- factor(df$gene,levels = unique(as.character(df$gene)))
# Reorder factor
df$gene <- forcats::fct_relevel(df$gene, "Others", after = 0)
df$gene <- forcats::fct_rev(df$gene)

# named vector of fill colors
cols <- select(df, gene, cols) %>%
distinct() %>%
deframe()

p <- ggplot(df, aes(x = trt, y = freq, fill = gene)) +
geom_bar(stat = "identity", color = "black") +
scale_fill_manual(values = cols) +
theme(axis.text.x = element_text(angle = 45, hjust = 1,size = 4))

Sample Image

Created on 2020-06-05 by the reprex package (v0.3.0)

EDIT Separate legends for the single groups can be achieved via ggnewscale::new_scale_fill. To get the correct order along the x-axis I make use of facetting. Try this:

library(tidyverse)
library(ggnewscale)

df$trt <- factor(df$trt,levels=unique(as.character(df$trt)))
df$gene <- factor(df$gene,levels = unique(as.character(df$gene)))
# Reorder factor
df$gene <- forcats::fct_relevel(df$gene, "Others", after = 0)
df$gene <- forcats::fct_rev(df$gene)

# named vector of fill colors
cols <- select(df, gene, cols) %>%
distinct() %>%
deframe()

p <- ggplot() +
geom_bar(mapping = aes(x = trt, y = freq, fill = gene), data = filter(df, trt == "M6"), stat = "identity", color = "black") +
scale_fill_manual(values = cols, guide = guide_legend(title = "M6", ncol = 2, title.position = "top")) +
new_scale_fill() + # Define scales before initiating a new one
geom_bar(mapping = aes(x = trt, y = freq, fill = gene), data = filter(df, trt == "M12"), stat = "identity", color = "black") +
scale_fill_manual(values = cols, guide = guide_legend(title = "M12", ncol = 2, title.position = "top")) +
new_scale_fill() + # Define scales before initiating a new one
geom_bar(mapping = aes(x = trt, y = freq, fill = gene), data = filter(df, trt == "M18"), stat = "identity", color = "black") +
scale_fill_manual(values = cols, guide = guide_legend(title = "M18", ncol = 2, title.position = "top")) +
theme(axis.text.x = element_text(angle = 45, hjust = 1,size = 4), legend.position = "bottom", legend.justification = 0) +
facet_wrap(~ trt, scales = "free_x")
p

Sample Image

Created on 2020-06-05 by the reprex package (v0.3.0)

EDIT 2

  1. To simplify the code you can use a loop. I make use of some helper functions and purrr::reduce but a simple for loop will also do the job.

  2. The reordering of the x-axis however requires a bit of a hack. The problem is that by splitting the data we lose the order of the categories. As a solution I use facetting to bring the order back in but get rid of the striptext and spacing between facets.

library(dplyr)
library(tidyverse)
library(ggnewscale)

g <- unique(as.character(df$gene))
i <- which(g == "Others")
g <- c(g[-i], g[i])

# Order and trim trt
df$trt <- stringr::str_trim(df$trt)
df$trt <- forcats::fct_inorder(df$trt)
tr <- levels(df$trt)

col_vec <- dplyr::select(df, gene, cols) %>%
distinct() %>%
deframe()

# Helper functions
make_df <- function(d, x) {
filter(d, trt == tr[x]) %>%
mutate(gene = forcats::fct_inorder(gene),
gene = forcats::fct_relevel(gene, "Others", after = length(levels(gene)) - 1)) %>%
arrange(gene) %>%
mutate(gene_order = as.numeric(gene))
}

# geom
help_geom <- function(x) {
geom_bar(aes(x = trt, y = freq, fill = gene), data = df_list[[x]], stat = "identity", color = "black")
}
# scale
help_scale <- function(x) {
scale_fill_manual(values = col_vec,
guide = guide_legend(order = x, title = tr[x], ncol = 1,
title.position = "top", title.theme = element_text(size = 4)))
}
# help for the loop
help_reduce <- function(p, x) {
p + new_scale_fill() + help_geom(x) + help_scale(x)
}

# List of df
df_list <- map(1:12, ~ make_df(df, .x))
# Init plot
p <- ggplot() + help_geom(1) + help_scale(1)
# Loop over trt
p <- reduce(c(2:12), help_reduce, .init = p)

# Add theme and wrap
p +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 4),
legend.text = element_text(size = 6),
legend.position = "bottom", legend.justification = 0,
strip.text = element_blank(),
panel.spacing.x = unit(0, "pt")) +
facet_wrap(~trt, scales = "free_x", nrow = 1)

Sample Image

Created on 2020-06-06 by the reprex package (v0.3.0)

How to draw bar plot including different groups in R with ggplot2?

Your code throws an error, because evaluation_4model is not defined.

However, the answer to your problem is likely to make a faceted plot and hence melt the data to a long format. To do this, I usually make use of the reshape library. Tweaking your code looks like this

library(ggplot2)
library(reshape2)

compare_data = data.frame(model=c("lr","rf","gbm","xgboost"),
precision=c(0.6593,0.7588,0.6510,0.7344),
recall=c(0.5808,0.6306,0.4897,0.6416),
f1=c(0.6176,0.6888,0.5589,0.6848),
acuracy=c(0.6766,0.7393,0.6453,0.7328))

plotdata <- melt(compare_data,id.vars = "model")

compare2 <- ggplot(plotdata, aes(x=model, y=value)) +
geom_bar(aes(fill = model), stat="identity")+
facet_grid(~variable)

compare2

Sample Image

does that help?

GGPLOT2: Stacked bar plot for two discrete variable columns

Your problem here is that you haven't fixed your tibble from Wide to Long.

FixedData <- sampleData %>%
pivot_longer(cols = c("var_1", "var_2"), names_prefix = "var_",
names_to = "Variable Number", values_to = "ValueName")

Once you do this, the problem becomes much easier to solve. You only need to change a few things, most notably the y, fill, and position variables to make it work.

p2 <- ggplot(FixedData, aes(x = grp, y = ValueName, fill = `Variable Number`)) +
geom_bar(stat="identity", position = "stack")+
coord_flip()+ theme_bw()

p2

Creating a grouped barplot with two Y axes in R

How about this:

dat <- data.frame(Week = c(1, 2, 3, 4, 5, 6, 7, 8), 
SPH = c(2.676, 2.660, 4.175, 2.134, 3.742, 1.395, 4.739, 2.756),
CPH = c(75.35, 29.58, 20.51, 80.43, 97.94, 85.39, 168.61, 142.19))

library(tidyr)
library(dplyr)

## Find minimum and maximum values for each variable
tmp <- dat %>%
summarise(across(c("SPH", "CPH"), ~list(min = min(.x), max=max(.x)))) %>%
unnest(everything())

## make mapping from CPH to SPH
m <- lm(SPH ~ CPH, data=tmp)
## make mapping from SPH to CPH
m_inv <- lm(CPH ~ SPH, data=tmp)

## transform CPH so it's on the same scale as SPH.
## to do this, you need to use the coefficients from model m above
dat <- dat %>%
mutate(CPH = coef(m)[1] + coef(m)[2]*CPH) %>%
## pivot the data so all plotting values are in a single column
pivot_longer(c("SPH", "CPH"),
names_to="var", values_to="vals") %>%
mutate(var = factor(var, levels=c("SPH", "CPH"),
labels=c("Sightings", "Clicks")))

ggplot(dat, aes(x=as.factor(Week), y=vals, fill=var)) +
geom_bar(position="dodge", stat="identity") +
## use model m_inv from above to identify the transformation from the tick values of SPH
## to the appropriate tick values of CPH
scale_y_continuous(sec.axis=sec_axis(trans = ~coef(m_inv)[1] + coef(m_inv)[2]*.x, name="Clicks/hour")) +
labs(y="Sightings/hour", x="Week", fill="") +
theme_bw() +
theme(legend.position="top")

Sample Image



Update - start both axes at zero

To start both axes at zero, you need to change have the values that are used in the linear map both start at zero. Here's an updated full example that does that:

dat <- data.frame(Week = c(1, 2, 3, 4, 5, 6, 7, 8), 
SPH = c(2.676, 2.660, 4.175, 2.134, 3.742, 1.395, 4.739, 2.756),
CPH = c(75.35, 29.58, 20.51, 80.43, 97.94, 85.39, 168.61, 142.19))

library(tidyr)
library(dplyr)

## Find minimum and maximum values for each variable
tmp <- dat %>%
summarise(across(c("SPH", "CPH"), ~list(min = min(.x), max=max(.x)))) %>%
unnest(everything())

## set lower bound of each to zero
tmp$SPH[1] <- 0
tmp$CPH[1] <- 0

## make mapping from CPH to SPH
m <- lm(SPH ~ CPH, data=tmp)
## make mapping from SPH to CPH
m_inv <- lm(CPH ~ SPH, data=tmp)

## transform CPH so it's on the same scale as SPH.
## to do this, you need to use the coefficients from model m above
dat <- dat %>%
mutate(CPH = coef(m)[1] + coef(m)[2]*CPH) %>%
## pivot the data so all plotting values are in a single column
pivot_longer(c("SPH", "CPH"),
names_to="var", values_to="vals") %>%
mutate(var = factor(var, levels=c("SPH", "CPH"),
labels=c("Sightings", "Clicks")))

ggplot(dat, aes(x=as.factor(Week), y=vals, fill=var)) +
geom_bar(position="dodge", stat="identity") +
## use model m_inv from above to identify the transformation from the tick values of SPH
## to the appropriate tick values of CPH
scale_y_continuous(sec.axis=sec_axis(trans = ~coef(m_inv)[1] + coef(m_inv)[2]*.x, name="Clicks/hour")) +
labs(y="Sightings/hour", x="Week", fill="") +
theme_bw() +
theme(legend.position="top")

Sample Image

How to group bars together in a stacked bar plot? ggplot R

Removed prior answers:

The key element is to use facet_grid(. ~ group, scales = "free", space = "free")

library(tidyverse)

df %>%
rownames_to_column(var = "id") %>%
filter(id != "miseq_pDNA") %>%
select(id, unmapped_multihit:mapped_correct) %>%
mutate(group = c(rep('KAPA', 4), rep('TAKARA', 4), 'plasmid_DNA')) %>%
pivot_longer(unmapped_multihit:mapped_correct) %>%
ggplot(aes(x = id, y = value, fill = name)) +
geom_col(position = position_fill(reverse = T)) +
scale_y_continuous(labels = scales::percent)+
facet_grid(. ~ group, scales = "free", space = "free")

Sample Image



Related Topics



Leave a reply



Submit