Home > Enterprise >  How to center value label within the bar of a diverging bar plot, using ggplot?
How to center value label within the bar of a diverging bar plot, using ggplot?

Time:11-03

I've used ggplot to create a number of diverging bar charts, using the following data and code:

Dataset

library(ggplot)
library(gridExtra)

# Import percentage change dataset
LC_Pct_Change <- read.csv("LC_%_Change_SA.csv", header=T)

# Create plots for relevant periods
PC_1930_1960 <- ggplot(LC_Pct_Change, aes(x=Land_Cover_Category, y=Change_1930_1960))  
  geom_bar(stat="identity", fill=ifelse(LC_Pct_Change$Change_1930_1960<0,"darksalmon", "darkseagreen2"), show.legend = FALSE)  
  geom_text(aes(label = round(Change_1930_1960, 1), hjust = 0.5, vjust = ifelse(Change_1930_1960 < 0, 1.5, -1)), size = 2.5)  
  ggtitle("1930-1960")  
  xlab("Land Cover")  
  ylab("% Change")  
  theme_bw()  
  scale_x_discrete(limits = c("W", "R", "G", "A", "U"))

# Repeated the above for each period

# Then combine into a single plot to export
PC_All <- grid.arrange(PC_1930_1960, PC_1960_1990, PC_1990_2000, PC_2000_2007,
                       PC_2007_2015, PC_2015_2020, PC_1930_2020, ncol=3)

The code I've got adds labels above and below the bars in the geom_text line, as below:Bar Chart example

Instead, I'd like them to be in the centre (horizontally and vertically) of the bars. All the examples online I've found have been for bar charts with positive values only - even so I've tried several methods, but the most common way I've seen online is to add in something along the lines of position = position_stack(vjust = .5) into geom_text. But I can't get it to work.

Edit: Adding dataset:

LC_Pct_Change <- structure(list(
  Land_Cover_Category = c("W", "R", "G", "A", "U"),
  Change_1930_1960 = c(1.984932164, -3.628139858, -1.446064637, 2.20879849, 0.880473841),
  Change_1960_1990 = c(1.56838857, -7.920867205, -1.967420952, 6.078769876, 2.241129711),
  Change_1990_2000 = c(
    1.54170708,
    0.909136716, -7.092356282, -2.215064057, 6.856576543
  ),
  Change_2000_2007 = c(-1.244727434, 5.104227969, -0.328826854, 3.025170368, -6.555844049),
  Change_2007_2015 = c(1.254510492, -6.750375486, 5.846903583, -2.837464028, 2.486425439),
  Change_2015_2020 = c(0.998194067, -0.084741498, -0.2985041, -1.250054436, 0.635105966),
  Change_1990_2020 = c(2.549684204, -0.821752298, -1.872783653, -3.277412153, 3.4222639),
  Change_1930_2020 = c(6.103004939, -12.37075936, -5.286269243, 5.010156213, 6.543867451)
), class = "data.frame", row.names = c(NA, -5L))

CodePudding user response:

Besides the option to place the labels in the middle of the bars via the y aesthetic as outlined by @lab_rat_kid you could achieve your desired using position_stack like so.

Using some fake example data:

library(ggplot2)

LC_Pct_Change <- data.frame(
  Land_Cover_Category = c("W", "R", "G", "A", "U"),
  Change_1930_1960 = seq(-2, 2, length.out = 5)
)

ggplot(LC_Pct_Change, aes(x = Land_Cover_Category, y = Change_1930_1960))  
  geom_bar(stat = "identity", fill = ifelse(LC_Pct_Change$Change_1930_1960 < 0, "darksalmon", "darkseagreen2"), show.legend = FALSE)  
  geom_text(aes(label = round(Change_1930_1960, 1)), 
            position = position_stack(vjust = .5), size = 2.5)  
  ggtitle("1930-1960")  
  xlab("Land Cover")  
  ylab("% Change")  
  theme_bw()  
  scale_x_discrete(limits = c("W", "R", "G", "A", "U"))

UPDATE Not sure what's the issue when using my code above with your real data. But based on your real data I simplified your code a bit, e.g. instead of copy and paste the same code which IMHO is error prone I have put the plotting code inside a function. Afterwards I use lapply to loop over the columns to be plotted to create a list of plots. This list could then be plotted via grid.arrange to create the final chart. Unfortunately grid.arrange does not allow to pass the plots as a list. That's why I had to use do.call to achieve this.

Note: Of course could you go on without lapply and do.call, e.g. you could do PC_1930_1960 <- plot_fun("Change_1930_1960") to create your plots one by one.

Additionally, instead of providing the fill colors as argument I use the fill aes and set the colors via scale_fill_manual. Once more doing so is in general less error prone.

library(ggplot2)

plot_fun <- function(col) {
  title <- gsub("^Change_", "", col)
  title <- gsub("_", "-", title)
  ggplot(LC_Pct_Change, aes(x = Land_Cover_Category, y = .data[[col]]))  
    geom_col(aes(fill = .data[[col]] < 0))  
    scale_fill_manual(values = c("FALSE" = "darkseagreen2", "TRUE" = "darksalmon"), guide = "none")  
    geom_text(aes(label = round(.data[[col]], 1)), 
              position = position_stack(vjust = .5), size = 2.5)  
    ggtitle(title)  
    xlab("Land Cover")  
    ylab("% Change")  
    theme_bw()  
    scale_x_discrete(limits = c("W", "R", "G", "A", "U"))  
}

cols_to_plot <- names(LC_Pct_Change)[grepl("^Change", names(LC_Pct_Change))]

plot_list <- lapply(cols_to_plot, plot_fun)
names(plot_list) <- cols_to_plot

library(gridExtra)

do.call("grid.arrange", c(plot_list, ncol = 3))

CodePudding user response:

geom_text(aes(label = round(Change_1930_1960, 1), hjust = 0.5, y= (Change_1930_1960/2)), size = 2.5)

This should work, but you haven't supplied your data in a usable form (don't do pictures please), so I can't test.

  • Related