Search code examples
rggplot2

Pyramid plot text alignment


I created the plot below, but I'm not sure how to adjust the figures to ensure they are positioned inside the plot area. Additionally, I would like to display the percentage savings, calculated as the difference between the "blue" (2024) and "red" (2023) values, prominently in the middle with larger font sizes.

Plot: enter image description here

Code:

energy_data_pyramid <- energy_data %>%
  select(Year, Location, Month, energy_use_kWh) %>%
  spread(key = Year, value = energy_use_kWh) %>%
  mutate(
    energy_use_kWh_2023 = ifelse(!is.na(`2023`), -`2023`, 0),  # Negative for 2023
    energy_use_kWh_2024 = ifelse(!is.na(`2024`), `2024`, 0)    # Positive for 2024
  ) %>%
  gather(key = "Year", value = "energy_use_kWh", energy_use_kWh_2023, energy_use_kWh_2024) %>%
  mutate(
    Year = recode(Year, "energy_use_kWh_2023" = "2023", "energy_use_kWh_2024" = "2024"),
    Month = factor(Month, levels = c("January", "February", "March", "April", "May"))
  )

# Check the transformed data
print(head(energy_data_pyramid))

# Create the pyramid plot
ggplot(energy_data_pyramid, aes(x = Month, y = energy_use_kWh, fill = Year)) +
  geom_bar(stat = "identity", position = "stack") +
  coord_flip() +  # Flip coordinates to create the pyramid effect
  scale_y_continuous(labels = abs) +  # Ensure the y-axis labels are positive
  geom_text(aes(label = round(abs(energy_use_kWh), 2)))+
  facet_wrap(~Location, scales = "free_y") +
  labs(title = "Bar sockets energy consumption: 2023 vs 2024",
       x = "Month",
       y = "Energy Use (kWh)",
       fill = "Year") +
  theme_minimal() +
  theme(axis.text.y = element_text(size = 10),
        axis.text.x = element_text(size = 10))

Data:

energy_data <- structure(list(Location = c("DG2-100-0-048", "DG2-100-0-048", 
 "DG2-100-0-048", "DG2-100-0-048", "DG2-100-0-048", "DG2-100-0-133", 
 "DG2-100-0-133", "DG2-100-0-133", "DG2-100-0-133", "DG2-100-0-133", 
 "DG2-100-0-210", "DG2-100-0-210", "DG2-100-0-210", "DG2-100-0-210", 
 "DG2-100-0-210", "DG2-100-0-048", "DG2-100-0-048", "DG2-100-0-048", 
 "DG2-100-0-048", "DG2-100-0-048"), Month = structure(c(4L, 2L,  1L,
 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L, 3L, 5L, 4L, 2L, 1L,  3L, 5L),
 levels = c("January", "February", "March", "April",  "May"), class =
 "factor"), `2023` = c(12.79, 11.81, 13.31, 13.15, 
 11.82, 11.99, 10.61, 12.28, 12.3, 11.43, 3.31, 3.1, 3.41, 3.54, 
 3.38, 12.79, 11.81, 13.31, 13.15, 11.82), `2024` = c(9.56, 9.06, 
 9.62, 9.77, 9.78, 8.93, 8.62, 9.01, 9.24, 9.08, 1.81, 2.24, 2.52, 
 2.67, 2.34, 9.56, 9.06, 9.62, 9.77, 9.78), Year = c("2023", "2023",  "2023", "2023", "2023", "2023", "2023", "2023", "2023", "2023", 
 "2023", "2023", "2023", "2023", "2023", "2024", "2024", "2024", 
 "2024", "2024"), energy_use_kWh = c(-12.79, -11.81, -13.31, -13.15, 
 -11.82, -11.99, -10.61, -12.28, -12.3, -11.43, -3.31, -3.1, -3.41, 
 -3.54, -3.38, 9.56, 9.06, 9.62, 9.77, 9.78)), row.names = c(NA, 
 -20L), class = c("tbl_df", "tbl", "data.frame"))

Solution

  • You can conditionally set the alignment of the labels based on the Year using an ifelse. In the code below I additionally use a second ifelse to put the labels outside of the bars for smaller values, but you can drop that line if it is not needed. To add the difference use a second geom_text.

    Note: I pre-processed your example data to get a proper wide and long dataset.

    library(tidyverse)
    
    energy_data_wide <- energy_data_pyramid |>
      select(-energy_use_kWh, -Year) |>
      distinct(Location, Month, .keep_all = TRUE)
    
    energy_data_long <- energy_data_wide |>
      pivot_longer(-c(Location, Month),
        names_to = "Year",
        values_to = "energy_use_kWh"
      ) |>
      mutate(
        energy_use_kWh = if_else(Year == 2023, -energy_use_kWh, energy_use_kWh),
        hjust = ifelse(Year == 2023, 0, 1),
        hjust = ifelse(abs(energy_use_kWh) < 5, 1 - hjust, hjust)
      )
    
    ggplot(energy_data_long, aes(x = energy_use_kWh, y = Month)) +
      geom_col(aes(fill = Year)) +
      scale_x_continuous(labels = abs) + # Ensure the y-axis labels are positive
      geom_label(
        aes(
          hjust = hjust,
          label = round(abs(energy_use_kWh), 2)
        ),
        fill = NA, label.size = 0
      ) +
      geom_text(
        data = energy_data_wide,
        aes(
          x = 0,
          label = round(`2024` - `2023`, 2),
        ),
        fontface = "bold",
        size = 14 / .pt
      ) +
      facet_wrap(~Location, scales = "free_y") +
      labs(
        title = "Bar sockets energy consumption: 2023 vs 2024",
        y = "Month",
        x = "Energy Use (kWh)",
        fill = "Year"
      ) +
      theme_minimal() +
      theme(
        axis.text = element_text(size = 10)
      )
    

    enter image description here