Home > database >  Adding a horizonal line to separate band of bars in geom_col in ggplot2
Adding a horizonal line to separate band of bars in geom_col in ggplot2

Time:01-13

I guess my question is very simple for someone who knows ggplot very well but I spent a lot of time trying different ways. I want to draw a horizontal line that pass-through y axis to separate each band of the bars produced using geom_col. For example, I want to draw a horizontal line that separate bars of meat from maize. Here is my code, example data and the design of the figure I want to produce.

library(tidyverse)
library(ggplot2)

#  sample data 
Food = c("meat", "meat", "meat", "meat", "wheat","wheat","wheat", "wheat", "maize","maize","maize","maize")
Subgroup = c("Male", "Female", "Urban", "Rural", "Male",  "Female", "Urban", "Rural",  "Male",  "Female","Urban", "Rural")
mean = c(8.66, 10.45,  9.88,  7.32, 21.04, 19.65, 20.26, 20.87, 51.06 , 44.51,  47.60, 48.40)
df <- data.frame(Food, Subgroup,  mean)

#Color code
colorPanel = c('#083c5d','#2d004b','#106d8e','#7d103d')

# Plot
Plot_FBGDS <-  ggplot(df, aes(x = Food, y = mean,  fill = Subgroup))   
  geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82)   
  
  scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
                     limits = c(0,100), 
                     labels = function(x) paste0(x, "%"))  
  coord_flip()    
  scale_fill_manual(values =  colorPanel)   
  labs( x= " ", 
        y = " ") 
    

CodePudding user response:

Try this, using geom_vline with manually specified xintercept.

# Plot
ggplot(df, aes(x = Food, y = mean,  fill = Subgroup))   
  geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82)   
  
  scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
                     limits = c(0,100), 
                     labels = function(x) paste0(x, "%"))  
  geom_vline(xintercept = c(0.5, 1.5, 2.5, 3.5))   
  coord_flip()    
  scale_fill_manual(values =  colorPanel)   
  labs( x= " ", 
        y = " ") 

Note that geom_vline typically produces a vertical line, but since you have coord_flip it becomes horizontal. Without the coord_flip, you would use geom_hline and set the yintercept parameter instead.

Also, if I may suggest an alternative way to visualize this using facet_wrap instead, I would say the option below looks a lot better and you can style the facets using the strip_ properties in plot_theme

# Plot
ggplot(df, aes(x = Subgroup, y = mean,  fill = Subgroup))   
  geom_col(stat = "identity", position = position_dodge(-0.9), width = 0.82)   
  
  scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
                     limits = c(0,100), 
                     labels = function(x) paste0(x, "%"))  
  coord_flip()    
  scale_fill_manual(values =  colorPanel)   
  facet_wrap(~Food, ncol=1)
  labs( x= " ", 
        y = " ")  
  theme(
    legend.position = "none"
  )

CodePudding user response:

I'm going to build off enter image description here

Note that I had to include Subgroup as a column in the linesdata data frame. The way to avoid having to do this would be to specify the fill= aesthetic inside geom_col instead of globally... but it works this way too.

The reason why geom_hline and vline always clip

Interestingly, you'll note that even though yend=Inf, the line does not extend beyond the panel area in the positive direction! I had no idea this works this way, but It seems Inf is specially designed to clip to the panel area no matter what. I'm pretty sure that geom_vline() and geom_hline() are using values of Inf and -Inf under the hood. If I change the value to y=-Inf inside geom_segment() you can see it doesn't extend the same way as specifying a number:

ggplot(df, aes(x = Food, y = mean,  fill = Subgroup))   
  geom_col(position = position_dodge(-0.9), width = 0.82)   
  
  scale_y_continuous(breaks = c(0,20, 40, 60,80), expand = c(0,0),
                     limits = c(0,100), 
                     labels = function(x) paste0(x, "%"))  
  coord_flip(clip="off")   
  geom_segment(
    data=linesdata, y=-Inf, yend=Inf,
    aes(x=xvals, xend=xvals)
  )  
  scale_fill_manual(values =  colorPanel)   
  labs( x= " ", 
        y = " ")

enter image description here

The only problem here is that you have to play around a bit to find the right value for the starting y value via trial and error. I found -5 did the trick pretty well.

  •  Tags:  
  • Related