Overlay Bar Graphs in Ggplot2

Overlay bar graphs in ggplot2

Try adding position = "identity" to your geom_bar call. You'll note from ?geom_bar that the default position is stack which is the behavior you're seeing here.

When I do that, I get:

print(ggplot(melted,aes(x=x,y=value,fill=variable)) + 
geom_bar(stat="identity",position = "identity", alpha=.3))

Sample Image

And, as noted below, probably position = "dodge" would be a nicer alternative:

Sample Image

Overlay two bar plots with geom_bar()

If you don't need a legend, Solution 1 might work for you. It is simpler because it keeps your data in wide format.

If you need a legend, consider Solution 2. It requires your data to be converted from wide format to long format.

Solution 1: Without legend (keeping wide format)

You can refine your aesthetics specification on the level of individual geoms (here, geom_bar):

ggplot(data=my_data, aes(x=Block)) +
geom_bar(aes(y=Start), stat="identity", position ="identity", alpha=.3, fill='lightblue', color='lightblue4') +
geom_bar(aes(y=End), stat="identity", position="identity", alpha=.8, fill='pink', color='red')

plot without legend

Solution 2: Adding a legend (converting to long format)

To add a legend, first use reshape2::melt to convert your data frame from wide format into long format.
This gives you two columns,

  • the variable column ("Start" vs. "End"),
  • and the value column

Now use the variable column to define your legend:

library(reshape2)
my_data_long <- melt(my_data, id.vars = c("Block"))
ggplot(data=my_data_long, aes(x=Block, y=value, fill=variable, color=variable, alpha=variable)) +
geom_bar(stat="identity", position ="identity") +
scale_colour_manual(values=c("lightblue4", "red")) +
scale_fill_manual(values=c("lightblue", "pink")) +
scale_alpha_manual(values=c(.3, .8))

Sample Image

How do I overlay a bar charts in ggplot2?

At the moment your data is in wide format. It is going to be difficult to plot it without reshaping.

Your current data looks like this:

agrisum
#> agri_work Male_ratio_02 Female_ratio_02 Male_ratio_04 Female_ratio_04
#> 1 0 25.72 25.77 28.78 27.92
#> 2 1 23.13 26.58 20.36 22.95

In order to plot this easily, we should have one observation per row, and one variable per column. At the moment, the gender and year are distributed between columns. We can fix this using dplyr and tidyr from the tidyverse:

library(dplyr)
library(tidyr)

agrisum <- agrisum %>%
pivot_longer(2:5) %>%
separate(name, sep = "_", into = c("Gender", NA, "Year")) %>%
mutate(Work = c("Agricultural", "Non-Agricultural")[agri_work + 1])

agrisum
#> # A tibble: 8 x 5
#> agri_work Gender Year value Work
#> <dbl> <chr> <chr> <dbl> <chr>
#> 1 0 Male 02 25.7 Agricultural
#> 2 0 Female 02 25.8 Agricultural
#> 3 0 Male 04 28.8 Agricultural
#> 4 0 Female 04 27.9 Agricultural
#> 5 1 Male 02 23.1 Non-Agricultural
#> 6 1 Female 02 26.6 Non-Agricultural
#> 7 1 Male 04 20.4 Non-Agricultural
#> 8 1 Female 04 23.0 Non-Agricultural

So now we can do:

ggplot(agrisum, aes(Work, value, fill = Gender)) +
geom_col(position = position_dodge(width = 0.8), width = 0.6,
color = "black", size = 0.1) +
scale_fill_manual(values = c(Female = "orange", Male = "deepskyblue3")) +
facet_grid(. ~ paste0("20", Year)) +
theme_bw() +
theme(strip.background = element_blank(),
panel.border = element_blank(),
axis.line.x = element_line())

Sample Image

How to position overlapping bar plot and bar width manually?

How about something like this:

d1 <- c(1.0, 2.0, 3.0, 4.0, 4.8)
d2 <- c(0.15, 0.5, 1.0, 1.5, 3.3)
d3 <- c(0.16,0.7,0.7,1,2.5)

df <- data.frame(d1, d2, d3)

library(dplyr)
library(ggplot2)

df %>%
mutate(index = seq_along(d1)) %>%
ggplot() +
geom_col(
aes(x = index, y = d1),
width = 0.8,
col = "black",
fill = "forestgreen"
) +
geom_col(
aes(x = index, y = d2),
width = 0.4,
col = "black",
fill = "red",
position = ggplot2::position_nudge(x = 0.2)
) +
geom_col(
aes(x = index, y = d3),
width = 0.4,
col = "black",
fill = "blue",
position = ggplot2::position_nudge(x = -0.2)
) +
labs(y = NULL)

Created on 2022-03-30 by the reprex package (v2.0.1)

Update: patterns instead of colors

This is actually not straightforward with ggplot2 itself. If you're OK with downloading packages outside CRAN then you can download ggpattern and do something like this:

df %>%
mutate(index = seq_along(d1)) %>%
ggplot() +
geom_col(
aes(x = index, y = d1),
width = 0.8,
col = "black",
fill = "forestgreen"
) +
ggpattern::geom_col_pattern(
aes(x = index, y = d2),
width = 0.4,
color = "black",
fill = "white",
pattern = 'stripe',
pattern_size = 0.1,
pattern_fill = "black",
position = ggplot2::position_nudge(x = 0.2)
) +
ggpattern::geom_col_pattern(
aes(x = index, y = d3),
width = 0.4,
color = "black",
fill = "lightgray",
pattern = 'crosshatch',
pattern_fill = "black",
pattern_angle = 0,
pattern_size = 0.1,
position = ggplot2::position_nudge(x = -0.2)
) +
labs(y = NULL)

Created on 2022-03-31 by the reprex package (v2.0.1)

ggpattern is well documented, so you should be able to adjust the above example to your needs.

If you don't want to rely on packages outside CRAN then you can take a look at any of these old questions for possible workarounds:

  • How to add texture to fill colors in ggplot2
  • Adding hatches or patterns to ggplot bars
  • ggplot2: Add Different Textures to Colored Barplot and Legend
  • How can I add hatches, stripes or another pattern or texture to a barplot in ggplot?

Overlaying ggplot bar plots

I have to options for you, but I like the second one better (although it might be a little bit longer).

library(ggplot2)

ggplot(csv_total) +
geom_col(aes(x = habitates, y = obs_flore, fill = "obs_flore"), alpha = 0.5) +
geom_col(aes(x = habitates, y = obs_faune, fill = "obs_faune"), alpha = 0.5) +
theme(axis.text.x = element_text(angle = 50, hjust = 1))

Sample Image

This looks ok, but with the following, we don't have to artifically create a fill legend like above and we can use the option "dodge" to shoe the columns side by side.
First we have to transform the data into the long format (using gather from tidyr):

library(tidyr)

csv_total_long <- gather(csv_total, flore_faune, obs, obs_flore, obs_faune)

csv_total_long
# A tibble: 12 x 4
# habitates surf_ha flore_faune obs
# <chr> <dbl> <chr> <dbl>
# 1 A 0.4 obs_flore 0
# 2 B 0.7 obs_flore 0
# 3 C 385. obs_flore 1.1
# 4 D 2941. obs_flore 2.1
# 5 E 45.9 obs_flore 2.3
# 6 F 306. obs_flore 2.5
# 7 A 0.4 obs_faune 2.4
# 8 B 0.7 obs_faune 15.6
# 9 C 385. obs_faune 0
# 10 D 2941. obs_faune 1
# 11 E 45.9 obs_faune 0.3
# 12 F 306. obs_faune 1

Now we have an extra row for each faune and flore observation. Then we can plot the columns next to each other. You would get the same plot as above without the position = "dodge".

ggplot(csv_total_long, aes(x = habitates, y = obs, fill = flore_faune)) +
geom_col(alpha = 0.5, position = "dodge") +
scale_fill_brewer(palette = "Dark2") +
theme(axis.text.x = element_text(angle = 50, hjust = 1))

Sample Image

I used geom_col here as it's the same as geom_bar with stat = "identity".

Data
I used letters for the habitates, as the original once weren't properly recognized by my system and that was not the point here.

csv_total <- structure(list(habitates = c("A", "B", "C", "D", "E", "F"), 
surf_ha = c(0.4, 0.7, 384.8, 2940.8, 45.9, 306.4),
obs_flore = c(0.0, 0.0, 1.1, 2.1, 2.3, 2.5),
obs_faune = c(2.4, 15.6, 0.0, 1.0, 0.3, 1.0)),
row.names = c(NA, -6L),
class = c("tbl_df", "tbl", "data.frame"))

overlay/superimpose grouped bar plots in ggplot2

I think the clue is to set the width of the "after" bars, but to dodge them as if their width are 0.9 (i.e. the same (default) width as the "before" bars). In addition, because we don't map fill of the "after" bars, we need to use the group aesthetic instead to achieve the dodging.

I prefer to have only one data set and just subset it in each call to geom_col.

ggplot(mapping = aes(x = question, y = n, fill = factor(ans))) +
geom_col(data = d[d$t == "before", ], position = "dodge") +
geom_col(data = d[d$t == "after", ], aes(group = ans),
fill = "black", width = 0.5, position = position_dodge(width = 0.9))

Sample Image

Data:

set.seed(2)
d <- data.frame(t = rep(c("before", "after"), each = 6),
question = rep(c("pain", "fear"), each = 3),
ans = 1:3, n = sample(12))

Alternative data preparation using data.table, starting with your original 'df':

library(data.table)
d <- melt(setDT(df), measure.vars = names(df), value.name = "ans")
d[ , c("t", "question") := tstrsplit(variable, "_")]

Either pre-calculate the counts and proceed as above with geom_col

# d2 <- d[ , .N, by = .(question, ans)]

Or let geom_bar do the counting:

ggplot(mapping = aes(x = question, fill = factor(ans))) +
geom_bar(data = d[d$t == "before", ], position = "dodge") +
geom_bar(data = d[d$t == "after", ], aes(group = ans),
fill = "black", width = 0.5, position = position_dodge(width = 0.9))

Sample Image

Data:

df <- data.frame(before_fear = c(1,1,1,2,3), before_pain = c(2,2,1,3,1),
after_fear = c(1,3,3,2,3),after_pain = c(1,1,2,3,1))

Overlaying a Bar chart with multiple bars with a line graph with ggplot2 in R

The main problem here is the fill in the ggplot call. Because you have it in ggplot(aes()), it is propagating to both geom_bar and geom_line. If you just set fill = variable inside ggplot(aes()), you wouldn't get the error, but you would have an Total_1 in the legend, which is not what I think you want.

You also don't need to use the df.1.1$ and df.1.2$ inside aes(). You want to put your data in the data argument and then the variables in aes() without calling the data frame again.

Here is a solution.

ggplot(data = df.1.1, aes(x = Month, y = value)) + 
geom_bar(aes(fill = variable), position = position_dodge(),stat = 'identity') +
geom_line(data = df.1.2, aes(x=Month, y=value, group=1))

One other note is that you can use geom_col instead of geom_bar with stat='identity'.

ggplot(data = df.1.1, aes(x = Month, y = value)) + 
geom_col(aes(fill = variable), position = position_dodge()) +
geom_line(data = df.1.2, aes(x=Month, y=value, group=1))

Reorder Overlaid Bars in Plot so Longer Bars are in back -R

I would recommend you switch to geom_col if you are going to be plotting bars, but supplying x= and y= aesthetics. See here for the documentation explanation. With that being said, it will work either way. Basically:

  • Tidy Data: I cannot confirm, but it seems that your dataset df2 is not organized following Tidy Data Principles, which makes things much easier with ggplot2 and many other methods of data analysis. Instead of having your y values split among df2$Mar-20 and df2$Apr-20, you should have a column for the category (let's call it df2$date) and a column for the actual value df2$y. You would then have one call to geom_bar giving aes(x=id, y=y). You can do this via dplyr::gather() or melt from the reshape2 package.

  • Arrange Data: Outside of any other influence (such as ordering of the levels of a factor), the plotting function in ggplot2 will plot data according to the arrangement of the actual dataframe for the x= and y= aesthetic. This means if you order the data beforehand on a particular, non-factor value, this will dictate the order in which ggplot2 will plot. So, you should order the plot based on descending df2$y prior to plotting to have the largest bars plotted first and smallest last, which means the smallest will be in front.

Here's a complete example with dummy data:

library(ggplot2)
library(dplyr)

set.seed(1234)
df <- data.frame(
x=rep(LETTERS[1:10],3),
y=sample(1:100,30,replace=TRUE),
id=c(rep('Group1',10),rep('Group2',10),rep('Group3',10))
)

df %>%
arrange(-y) %>%
ggplot(aes(x,y)) + theme_bw() +
geom_bar(aes(fill=id),stat='identity',position=position_identity())

Sample Image

Try that ggplot() function without the arrange() function and you'll see the effect is what you're looking to do.



Related Topics



Leave a reply



Submit