0

Lets draw a bar chart with ggplot2 from the following data (already in a long format). The values of the variable are then placed in the middle of the bars via geom_text() directive.

stuff.dat<-read.csv(text="continent,stuff,num
America,apples,13
America,bananas,13
Europe,apples,30
Europe,bananas,21
total,apples,43
total,bananas,34")

library(ggplot2)
ggplot(stuff.dat, aes(x=continent, y=num,fill=stuff))+geom_col() + 
       geom_text(position = position_stack(vjust=0.5), 
                 aes(label=num))

Now it is necessary to add on top of the bars the "Apple-Bananas Index", which is defined as f=apples/bananas - just as manually added in the figure. How to program this in ggplot? How it would be possible to add it to the legend as a separate entry?

simple stacked bar chart

pogibas
  • 27,303
  • 19
  • 84
  • 117
astrsk
  • 375
  • 6
  • 20
  • You know that normal bar plot wouldn't need that text? This figure is just too hard to grasp and you need this additional info now – pogibas Sep 22 '17 at 20:59

1 Answers1

1

I think that the easiest way to achieve this is to prepare the data before you create the plot. I define a function abi() that computes the apple-banana-index from stuff.dat given a continent:

abi <- function(cont) {
  with(stuff.dat,
       num[continent == cont & stuff == "apples"] / num[continent == cont & stuff == "bananas"]
  )
}

And then I create a data frame with all the necessary data:

conts <- levels(stuff.dat$continent)
abi_df <- data.frame(continent = conts,
                     yf = aggregate(num ~ continent, sum, data = stuff.dat)$num + 5,
                     abi = round(sapply(conts, abi), 1))

Now, I can add that information to the plot:

library(ggplot2)
ggplot(stuff.dat, aes(x = continent, y = num, fill = stuff)) +
  geom_col() + 
  geom_text(position = position_stack(vjust = 0.5), aes(label = num)) +
  geom_text(data = abi_df, aes(y = yf, label = paste0("f = ", abi), fill = NA))

enter image description here

Adding fill = NA to the geom_text() is a bit of a hack and leads to a warning. But if fill is not set, plotting will fail with a message that stuff was not found. I also tried to move fill = stuff from ggplot() to geom_col() but this breaks the y⁻coordinate of the text labels inside the bars. There might be a cleaner solution to this, but I haven't found it yet.

Adding the additional legend is, unfortunately, not trivial, because one cannot easily add text outside the plot area. This actually needs two steps: first one adds text using annotation_custom(). Then, you need to turn clipping off to make the text visible (see, e.g., here). This is a possible solution:

p <- ggplot(stuff.dat, aes(x = continent, y = num, fill = stuff)) +
      geom_col() + 
      geom_text(position = position_stack(vjust = 0.5), aes(label = num)) +
      geom_text(data = abi_df, aes(y = yf, label = paste0("f = ", abi), fill = NA)) +
      guides(size = guide_legend(title = "f: ABI", override.aes = list(fill = 1))) +
      annotation_custom(grob = textGrob("f: ABI\n(Apple-\nBanana-\nIndex",
                                        gp = gpar(cex = .8), just = "left"),
                                      xmin = 3.8, xmax = 3.8, ymin = 17, ymax = 17)

# turn off clipping
library(grid)
gt <- ggplot_gtable(ggplot_build(p))
gt$layout$clip[gt$layout$name == "panel"] <- "off"
grid.draw(gt)

enter image description here

Stibu
  • 15,166
  • 6
  • 57
  • 71