0

I have a ggplot2 plot with both positive and negative geom_bar values. For the geom_bar that shows the negative values, I’m having an issue where the position of geom_text is not displayed correctly. How can I fix this problem?

library(ggplot2)
library(scales)
library(dplyr)

data_schule_schulform <- structure(list(Schuljahr = c("2017", "2018", "2018", "2019", 
"2019", "2020", "2021", "2021", "2022", "2023", "2023", "2024", 
"2024", "2024"), Herkunftsschulform = c("Gymnasium", "Förderschule", 
"Gymnasium", "Förderschule", "Gymnasium", "Gymnasium", "Gesamtschule", 
"Gymnasium", "Gymnasium", "Gymnasium", "Sonstiges", "Förderschule", 
"Gymnasium", "Sonstiges"), Anzahl = c(7, 2, 2, 1, 6, 2, 1, 2, 
4, 1, 57, 1, 8, 44)), class = c("tbl_df", "tbl", "data.frame"
), row.names = c(NA, -14L))

data_2_schule_schulform <- structure(list(Schuljahr = c("2017", "2018", "2019", "2019", 
"2019", "2021", "2022", "2022", "2023", "2023", "2023", "2024", 
"2024", "2024", "2024"), Schulform = c("Hauptschule", "Hauptschule", 
"Förderschule", "Gymnasium", "Hauptschule", "Hauptschule", "Gymnasium", 
"Hauptschule", "Förderschule", "Gesamtschule", "Hauptschule", 
"Förderschule", "Gesamtschule", "Gymnasium", "Hauptschule"), 
    Anzahl = c(3, 1, 1, 1, 5, 3, 1, 4, 1, 1, 2, 1, 1, 1, 9)), class = c("tbl_df", 
"tbl", "data.frame"), row.names = c(NA, -15L))


p1 <- ggplot() +
     geom_bar(
       data = data_schule_schulform, 
       aes(fill = Herkunftsschulform, y = Anzahl, x = Schuljahr), 
       position = 'stack', stat = 'identity') +
     geom_text(
       data = data_schule_schulform, 
       aes(
         fill = Herkunftsschulform, y = Anzahl, x = Schuljahr, 
         label = ifelse(
           Anzahl >= 3, 
           comma(Anzahl, accuracy = 1L, big.mark = ".", decimal.mark = ","),
           ""
         )
       ),
       position = position_stack(vjust = 0.5), size = 3, color= "black", fontface= "bold") + 
     geom_bar(
       data = data_2_schule_schulform, 
       aes(fill = Schulform, y = Anzahl*-1, x = Schuljahr), 
       position = 'stack', stat = 'identity') +
     labs(
       title = "xxx",
       subtitle = "xxx",
       x = "",
       y = "",
       fill = ""  
     ) +
     scale_y_continuous(labels = function(x) format(x, big.mark = ".")) + 
     theme_minimal() +
     theme(
       axis.text.x = element_blank(),
       axis.text.y = element_text(size = 8)
     ) +
     geom_text(
       data = data_2_schule_schulform %>%
         filter (Anzahl >= 3),
       aes(fill = Schulform, y = Anzahl*-1, x = Schuljahr, label = comma(Anzahl, accuracy = 1L, big.mark = ".", decimal.mark = ",")),
       position = position_stack(vjust = 0.5), size = 3, color= "black", fontface= "bold") +
     geom_hline(yintercept = 0, color = "white", size = 2) +  # Add a horizontal line at y = 0
     theme(
       plot.margin = margin(b = 10),
       legend.position = "none"
     ) +
     guides(
       alpha = 'none'
     )
1
  • 1
    For me it renders as i.sstatic.net/jtvvIg1F.png . Can you be more specific regarding issues with value positions? Commented Nov 24 at 19:02

2 Answers 2

1

The main problem happens when you filter the second dataset right here:

       data = data_2_schule_schulform %>%
         filter (Anzahl >= 3),

This subsets the data_2_schule_schulform object inside the geom_text call making it "misalign" with the data_2_schule_schulform inside the geom_bar() call just above it. Removing that and using the same ifelse logic you used before is the first fix. Second, you're passing fill into geom_text which is being ignored since it doesn't support it. You should be using group instead. The quick fix is, thus:

geom_text(
  data = data = data_2_schule_schulform,
  aes(
    group = Schulform,
    y = Anzahl * -1,
    x = Schuljahr,
    label = ifelse(
      Anzahl >= 3,
      comma(Anzahl, accuracy = 1L, big.mark = ".", decimal.mark = ","),
      ""
    )
  )
)

Part of the confusion with your example is because you're using two different datasets in a same plot. If possible, consider stacking the datasets into a single one: this would simplify it immensely.

That said, your code has several other problems you might consider reviewing:

  1. guides(alpha = 'none') is doing nothing.
  2. Both theme(legend.position = 'none') and labs(fill = 'none') are doing the same thing.
  3. geom_bar() should be used when you want the height of the bar to represent the count of cases. If you want the heights of the bars to represent values in the data, use geom_col(). In other words, geom_bar(stat = 'identity') is the same as geom_col() which you should be using.
  4. The comma function isn't actually doing anything since your numbers don't have decimals and the same goes for scale_y_continuous(labels = function(x) format(x, big.mark = ".")) since the numbers are all below 1000.
  5. size inside geom_hline is deprecated. Also, this horizontal line is actually making it hard to see the plot, consider removing it or making it smaller (e.g. linewidth = 0.8.

I took the liberty to make some adjustments to create a general solution to your problem.

  library(ggplot2)
  library(scales)
  library(dplyr)

  data_schule_schulform <- structure(
    list(
      Schuljahr = c(
        "2017",
        "2018",
        "2018",
        "2019",
        "2019",
        "2020",
        "2021",
        "2021",
        "2022",
        "2023",
        "2023",
        "2024",
        "2024",
        "2024"
      ),
      Herkunftsschulform = c(
        "Gymnasium",
        "Förderschule",
        "Gymnasium",
        "Förderschule",
        "Gymnasium",
        "Gymnasium",
        "Gesamtschule",
        "Gymnasium",
        "Gymnasium",
        "Gymnasium",
        "Sonstiges",
        "Förderschule",
        "Gymnasium",
        "Sonstiges"
      ),
      Anzahl = c(7, 2, 2, 1, 6, 2, 1, 2, 4, 1, 57, 1, 8, 44)
    ),
    class = c("tbl_df", "tbl", "data.frame"),
    row.names = c(NA, -14L)
  )

  data_2_schule_schulform <- structure(
    list(
      Schuljahr = c(
        "2017",
        "2018",
        "2019",
        "2019",
        "2019",
        "2021",
        "2022",
        "2022",
        "2023",
        "2023",
        "2023",
        "2024",
        "2024",
        "2024",
        "2024"
      ),
      Schulform = c(
        "Hauptschule",
        "Hauptschule",
        "Förderschule",
        "Gymnasium",
        "Hauptschule",
        "Hauptschule",
        "Gymnasium",
        "Hauptschule",
        "Förderschule",
        "Gesamtschule",
        "Hauptschule",
        "Förderschule",
        "Gesamtschule",
        "Gymnasium",
        "Hauptschule"
      ),
      Anzahl = c(3, 1, 1, 1, 5, 3, 1, 4, 1, 1, 2, 1, 1, 1, 9)
    ),
    class = c("tbl_df", "tbl", "data.frame"),
    row.names = c(NA, -15L)
  )

  df_text_positive <- data_schule_schulform |>
    mutate(
      label = ifelse(
        Anzahl >= 3,
        comma(Anzahl, accuracy = 1L, big.mark = ".", decimal.mark = ","),
        ""
      )
    )

  df_text_negative <- data_2_schule_schulform |>
    mutate(
      label = ifelse(
        Anzahl >= 3,
        comma(Anzahl, accuracy = 1L, big.mark = ".", decimal.mark = ","),
        ""
      )
    )

  
ggplot() +
  geom_col(
    data = data_schule_schulform,
    aes(fill = Herkunftsschulform, y = Anzahl, x = Schuljahr)
  ) +
  geom_text(
    data = df_text_positive,
    aes(
      group = Herkunftsschulform,
      y = Anzahl,
      x = Schuljahr,
      label = label
    ),
    position = position_stack(vjust = 0.5),
    size = 3,
    color = "black",
    fontface = "bold"
  ) +
  geom_col(
    data = data_2_schule_schulform,
    aes(fill = Schulform, y = Anzahl * -1, x = Schuljahr)
  ) +
  geom_text(
    data = df_text_negative,
    aes(
      group = Schulform,
      y = Anzahl * -1,
      x = Schuljahr,
      label = label
    ),
    position = position_stack(vjust = 0.5),
    size = 3,
    color = "black",
    fontface = "bold"
  ) +
  theme_minimal() +
  theme(
    legend.position = "none",
    axis.text.y = element_text(size = 8)
  )

Unfortunately, I can't post the finished image due to my low reputation. But the code above should work for your case.

Sign up to request clarification or add additional context in comments.

3 Comments

Hello Vinicius, Thank you so much for your efforts and support, it really helped! To make things unfortunately a bit more complicated: this plot is part of a patchwork plot with a total of 4 plots, 2 on top and 2 on the bottom. I’ve now set it so that the top and bottom plots each share the same Y-axis alignment, so that the positive and negative values start from the same position. However, this has caused geom_text to be displayed incorrectly again. Is there a solution for this within the existing code?
This a complex plot indeed! I'm not sure why patchwork would misalign the plots. Without seeing the actual code I suspect that there might still be some problem with the plots (before applying patchwork). Make sure that all four plots are working as intended before composing them with {patchwork}. You could maybe try using {ggalign} with ggalign::align_plots(p1, p2, p3, p4, ncol = 2) or {cowplot} with cowplot::plot_grid(p1, p2, p3, p4, ncol = 2).
Thank you again, Vinicius! I figured it out somehow. :)
-1

I copied your code and tried to reproduce your problem, but it seems you use a deprecated package ('comma') and I didn't want to start installing something I didn't need to. The suggestion was to switch to label_number()/label_comma() so could the issue be something to do with that?

Or it could maybe be that you multiply the y-values by -1.

New contributor
StephD is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.

2 Comments

comma here is a function (likely from scales::) not a package, and it is not deprecated, what makes you say that it is? Also, this isn't really resolving the question, so it isn't quite ready to be an answer ...
Ah, superseded, not deprecated, my bad (rdocumentation.org/packages/scales/versions/1.4.0/topics/comma). Point taken, thanks for the guidance

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.