Home > OS >  How to properly align labels on top of bars when using positive and negative values
How to properly align labels on top of bars when using positive and negative values

Time:10-09

I am doing a plot using ggplot2 and a small dataframe df. My dataframe has a group variable Letter and two numeric variables X and Y (I include the dput() version of df at the end of this post).

My main issue appears when I try to align the labels of the bars on top. Because of my design, one variable needs to be showed to right side and the other to left side. That is why I multiply some values by -1. This is the code and output for my plot:

library(tidyverse)
library(ggplot2)
#Plot
df %>%
  pivot_longer(-c(Letter)) %>%
  mutate(value=ifelse(name=='X',value*-1,value)) %>%
  ggplot(aes(x=Letter,y=value,fill=name)) 
  geom_bar(stat = 'identity',color='black',alpha=0.7) 
  geom_text(aes(label=format(abs(value),big.mark = '.')),
            size=3,fontface='bold') 
  scale_x_discrete(limits = rev(unique(df$Letter))) 
  scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                     breaks = scales::pretty_breaks(10)) 
  coord_flip()

And the output:

enter image description here

As you can see the plot is fine but the problem is with the labels. In both left and right sides, a part of the label is inside the bar and the other is outside. I would like to have the labels in both sides at the top of each bar. I do not know if this is possible because some values are positive and other negatives. I have tried adding hjust to geom_text() and this only works for left side:

#Plot 1
df %>%
  pivot_longer(-c(Letter)) %>%
  mutate(value=ifelse(name=='X',value*-1,value)) %>%
  ggplot(aes(x=Letter,y=value,fill=name)) 
  geom_bar(stat = 'identity',color='black',alpha=0.7) 
  geom_text(aes(label=format(abs(value),big.mark = '.')),
            size=3,fontface='bold',
            hjust=1) 
  scale_x_discrete(limits = rev(unique(df$Letter))) 
  scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                     breaks = scales::pretty_breaks(10)) 
  coord_flip()

Output:

enter image description here

I would like to find a way to have the labels on both sides aligned to the top of their respective bars.

Many thanks for the help. The dput() of my data is next:

#Data
df <- structure(list(Letter = c("A", "B", "C", "D", "E", "F", "G", 
"H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S"), 
    X = c(3099, 14201, 18901, 17351, 29498, 28928, 32284, 31536, 
    25334, 20008, 39242, 35856, 43317, 53765, 44652, 32876, 26142, 
    18567, 8836), Y = c(9482, 22669, 11159, 9506, 12113, 12805, 
    10570, 10199, 9190, 9217, 10674, 10894, 11986, 15664, 12793, 
    10552, 8050, 8609, 7717)), row.names = c(NA, -19L), class = c("tbl_df", 
"tbl", "data.frame"))

CodePudding user response:

You can actually set the hjust as an aesthetic variable, instead of giving all labels the same hjust value. Just give it a 1 for negative values and a 0 for positive values by doing hjust = value < 0 inside the aes call - the resulting logical vector will be implicitly converted to 1s and 0s:

df %>%
    pivot_longer(-c(Letter)) %>%
    mutate(value = ifelse(name == 'X', value * -1, value)) %>%
    ggplot(aes(x = Letter, y = value, fill = name))  
    geom_bar(stat = 'identity', color = 'black', alpha = 0.7)  
    geom_text(aes(label = format(abs(value), big.mark = '.'), hjust = value < 0),
              size = 3, fontface = 'bold')  
    scale_x_discrete(limits = rev(unique(df$Letter)))  
    scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                       breaks = scales::pretty_breaks(10))  
    coord_flip()

enter image description here

CodePudding user response:

try to split geom_text by value type (>0,<0). see below:

df %>%
  pivot_longer(-c(Letter)) %>%
  mutate(value=ifelse(name=='X',value*-1,value)) %>%
  ggplot(aes(x=Letter,y=value,fill=name)) 
  geom_bar(stat = 'identity',color='black',alpha=0.7) 
  geom_text(data = . %>% filter(value>0), aes(label=format(abs(value),big.mark = '.')),
            size=3,fontface='bold',hjust=0) 
  geom_text(data = . %>% filter(value<0), aes(label=format(abs(value),big.mark = '.')),
            size=3,fontface='bold',hjust= 1) 
  scale_x_discrete(limits = rev(unique(df$Letter))) 
  scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                     breaks = scales::pretty_breaks(10)) 
  coord_flip()

enter image description here

CodePudding user response:

Here is an alternative approach, with an ifelse statement:

library(tidyverse)
#Plot
df1 <- df %>%
  pivot_longer(-c(Letter)) %>%
  mutate(value=ifelse(name=='X',value*-1,value)) 
  
ggplot(df1, aes(x=Letter,y=value,fill=name)) 
  geom_bar(stat = 'identity',color='black',alpha=0.7) 
  geom_text(aes(label = format(value, big.mark = '.'),
            vjust = ifelse(value >= 0, 0.5, 0.5),
            hjust = ifelse(value>= 0, -0.05, 1.05)),
            size=3,fontface='bold')  
  scale_x_discrete(limits = rev(unique(df$Letter))) 
  scale_y_continuous(labels = function(x) scales::comma(abs(x)),
                     breaks = scales::pretty_breaks(10)) 
  coord_flip()

enter image description here

  • Related