jacopoburelli
jacopoburelli

Reputation: 33

align geom_col to the "bottom" doesn't work

I have a problem regarding alignment when using geom_col. I attach a reproducible example and the plot. What I would like to get are bars on top of each other on different weapons but aligned to the bottom without empty space, to make the plot more compact, even if this ignore some kind of ordering of y-axis. Solutions like transforming missing or 0 values to NA didn't work (ggplot2 does ignore NA automatically), I think the problem is some kind of conflict with facet_grid2, since specifying one particular year and room, there is not this problem.

   library(dplyr)
   library(ggplot2)
   library(ggiraph)
   library(ggh4x)

    # Data with Cluedo theme
    set.seed(123)
    cluedo_data <- data.frame(
      SUSPECT_ID = 1:100,
      YEAR = sample(2018:2020, 100, replace = TRUE),
      ROOM = sample(c("Library", "Kitchen", "Conservatory"), 100, replace = TRUE),
      GENDER = sample(c("M", "F"), 100, replace = TRUE),
      CLUEDO_PATH = sample(c("New", "Old"), 100, replace = TRUE),
      WEAPON_ID = paste0("weapon_", sample(1:10, 100, replace = TRUE)),
      CLUE_TYPE = sample(c("OPEN", "MCQ"), 100, replace = TRUE),
      EVIDENCE = sample(c(-1, 0, 1), 100, replace = TRUE),
      SCORE = sample(0:10, 100, replace = TRUE)
    )
    
    # Filter and transform data
    open_scores <- cluedo_data %>%
      filter(CLUE_TYPE == "OPEN", CLUEDO_PATH == "New") %>%
      group_by(YEAR, SUSPECT_ID, ROOM, WEAPON_ID) %>%
      filter(sum(abs(EVIDENCE)) != 0) %>%
      distinct(SUSPECT_ID, .keep_all = TRUE) %>% 
      mutate(TOTAL = max(sum(SCORE), 0)) %>%
      ungroup() %>%
      group_by(YEAR, ROOM, WEAPON_ID) %>%
      top_n(1, TOTAL) %>%
      distinct(YEAR, WEAPON_ID, ROOM, GENDER, TOTAL) %>%
      ungroup()
    
    open_scores$WEAPON_ID <- gsub('weapon_', 'w', open_scores$WEAPON_ID)
    
    color_table <- tibble(gender = c("M", "F"), Color = c("#795695", "#00868b"))
    open_scores$GENDER <- factor(open_scores$GENDER, levels = color_table$gender)
    
    plt <- ggplot(open_scores) +
      geom_col(aes(TOTAL, WEAPON_ID, fill = GENDER), width = 0.6, position = "stack") +
      geom_text(data = open_scores, aes(x = 1, y = WEAPON_ID, label = WEAPON_ID), hjust = "inward", nudge_x = -0.5, colour = "white", size = 4) +
      geom_text(data = open_scores, aes(x = TOTAL, y = WEAPON_ID, label = ifelse(TOTAL == 0, NA, TOTAL), fill = GENDER), position = position_stack(vjust = 0.6), colour = "white", size = 4) + 
      facet_grid2(vars(ROOM), vars(YEAR), render_empty = FALSE, axes = "all", scales = "free", space = "free") +
      scale_fill_manual(values = color_table$Color) +
      theme(axis.text.x = element_blank(), axis.text.y = element_blank(), axis.ticks.x = element_blank(), axis.ticks.y = element_blank()) +
      scale_x_continuous(breaks = NULL) +
      scale_y_discrete(breaks = NULL) +
      theme(axis.title.x = element_text(size = 15), axis.title.y = element_text(size = 15), strip.text.x = element_text(size = 13), strip.text.y = element_text(size = 13), legend.text = element_text(size = 20), legend.title = element_text(size = 20)) + 
      theme(legend.position = "top", plot.title = element_text(hjust = 0.5)) +
      xlab("Mean SCORE of Clues per WEAPON through YEAR") +
      ylab("WEAPON") +
      theme(text = element_text(family = "mono"))
    
    plt <- girafe(ggobj = plt, width_svg = 18, height_svg = 11,
      options = list(
        opts_hover_inv(css = "opacity:0.7;"),
        opts_hover(css = "stroke-width:2;")
      ))
    
    # Print plot
    print(plt)

enter image description here

Upvotes: 0

Views: 47

Answers (1)

stefan
stefan

Reputation: 124093

If I understand you correctly, the issue is that when using facet_grid or facet_grid2 with scales="free" the scales are freed per row (or column), i.e. for each row any category present in one of the panels will be shown in all panels and you end up with some empty space in panels where the category is not present.

One option to get rid of the empty space would be to use a helper column which could be mapped on y. But even that option will leave some empty space when the number of categories differ per panel:

library(ggplot2)
library(ggh4x)
library(dplyr, warn = FALSE)

plt <- open_scores |>
  filter(TOTAL > 0) |>
  mutate(
    weapon_id = row_number(),
    .by = c(ROOM, YEAR)
  ) |>
  ggplot(aes(x = TOTAL, y = weapon_id)) +
  geom_col(aes(fill = GENDER), width = 0.6, position = "stack") +
  geom_text(aes(x = 1, label = WEAPON_ID),
    hjust = "inward",
    nudge_x = -0.5, colour = "white", size = 4
  ) +
  geom_text(aes(label = ifelse(TOTAL == 0, NA, TOTAL), group = GENDER),
    position = position_stack(vjust = 0.6),
    colour = "white", size = 4
  ) +
  facet_grid2(vars(ROOM), vars(YEAR),
    render_empty = FALSE, axes = "all",
    scales = "free", space = "free"
  ) +
  scale_fill_manual(values = color_table$Color) +
  scale_x_continuous(breaks = NULL) +
  scale_y_discrete(breaks = NULL) +
  theme(
    axis.text = element_blank(),
    axis.ticks = element_blank(),
    axis.title = element_text(size = 15),
    strip.text = element_text(size = 13),
    legend.text = element_text(size = 20),
    legend.title = element_text(size = 20)
  ) +
  theme(legend.position = "top", plot.title = element_text(hjust = 0.5)) +
  xlab("Mean SCORE of Clues per WEAPON through YEAR") +
  ylab("WEAPON") +
  theme(text = element_text(family = "mono"))

plt

UPDATE To make sure that we have the same weapon_id for both genders you could do:

weapon_id <- open_scores |>
  filter(TOTAL > 0) |>
  distinct(ROOM, YEAR, WEAPON_ID) |> 
  mutate(
    weapon_id = row_number(),
    .by = c(ROOM, YEAR)
  )
  
plt <- open_scores |>
  filter(TOTAL > 0) |>
  left_join(weapon_id, join_by(YEAR, WEAPON_ID, ROOM))  |> 
  ggplot(...) + 
  ...

Upvotes: 0

Related Questions