-1

I'm trying to plot a basic bar chart per group.

As values are pretty big, I want to show for each bar (i.e. group) the % of each group within the bar.
I managed to show percentage of the total, but this is not what I'm expecting : in each bar, I would like that the sum of % equal 100%.

Is there an easy way to do it without changing the dataframe ?

(DF <- data.frame( year = rep(2015:2017, each = 4), 
                   Grp = c("Grp1", "Grp2", "Grp3", "Grp4"),
                   Value = trunc(rnorm(12, 2000000, 100000))) )


ggplot(DF) +
  geom_bar(aes(x = year, y = Value, fill = Grp), 
               stat = "identity", 
               position = position_stack()) +
  geom_text(aes(x = year, y = Value, group = Grp, 
                label = percent(Value/sum(Value))) ,
                position = position_stack(vjust = .5))
pogibas
  • 27,303
  • 19
  • 84
  • 117
Flecflec
  • 233
  • 3
  • 9

4 Answers4

5

You can create a new variable for percentile by year:

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

DF <- DF %>% group_by(year) %>% mutate(ValuePer=(Value/sum(Value))) %>% ungroup()

ggplot(DF, aes(year, ValuePer, fill = Grp)) +
  geom_bar(stat = "identity", position = "fill") +
  geom_text(aes(label = percent(ValuePer)),
        position = position_fill())+
  scale_y_continuous(labels = percent_format())

enter image description here

anubhuti mishra
  • 120
  • 2
  • 10
3

Use position = "fill" to turn scale into proportions and scale_y_continuous(labels = percent_format()) to turn this scale into percent.

DF <- data.frame( year = rep(2015:2017, each = 4), 
                   Grp = c("Grp1", "Grp2", "Grp3", "Grp4"),
                   Value = trunc(rnorm(12, 2000000, 100000)))

library(ggplot2)
library(scales)
ggplot(DF, aes(year, Value, fill = Grp)) +
    geom_bar(stat = "identity", position = "fill") +
    geom_text(aes(label = percent(Value / sum(Value))),
              position = position_fill()) +
    scale_y_continuous(labels = percent_format())

enter image description here

pogibas
  • 27,303
  • 19
  • 84
  • 117
  • 1
    And optionally use `vjust = 0.5` inside `position_fill` to center the text within each bar-piece. – Gregor Thomas Oct 12 '17 at 15:23
  • 1
    indeed @gregor is right : I still want "stack" and not "fill" (ie stack + normalize). What I try to get is that each bar =100% so for instance in 2015 I want 25%, 26%, 24% and 25% – Flecflec Oct 12 '17 at 16:37
2

OK gathering all your tricks, I finally get this : I need to adjust my DF, what I wanted to avoid, but it remains simple so it works

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

DF <- DF %>% group_by(year) %>% mutate(ValuePer=(Value/sum(Value))) %>% ungroup()

ggplot(DF, aes(year, Value, fill = Grp)) +
  geom_bar(stat = "identity", position = "stack") +
  geom_text(aes(label = percent(ValuePer)),
            position = position_stack()) +
  scale_y_continuous(labels = unit_format("M", 1e-6) )  
Flecflec
  • 233
  • 3
  • 9
1

I would use a single geom_text for each bar while filtering data by year (bar) using dplyr. Check if is that what you need:

(DF <- data.frame( year = rep(2015:2017, each = 4), 
                   Grp = c("Grp1", "Grp2", "Grp3", "Grp4"),
                   Value = trunc(rnorm(12, 2000000, 100000))) )
library(dplyr)

ggplot(DF) +
  geom_bar(aes(x = year, y = Value, fill = Grp), 
           stat = "identity", 
           position = position_stack()) +
  geom_text(data = DF %>% filter(year == 2015),
    aes(x = year, y = Value, 
                label = scales::percent(Value/sum(Value))) ,
            position = position_stack(vjust = .5)) +
  geom_text(data = DF %>% filter(year == 2016),
            aes(x = year, y = Value, 
                label = scales::percent(Value/sum(Value))) ,
            position = position_stack(vjust = .5)) +
  geom_text(data = DF %>% filter(year == 2017),
            aes(x = year, y = Value, 
                label = scales::percent(Value/sum(Value))) ,
            position = position_stack(vjust = .5))

Argument group is not necessary here. There may be more elegant solutions but that is the one I could think about. Tell me if this is the output you were waiting for: enter image description here

Maybe creating a new column doing the right computation. I could not figure out how the computation could be done right inside aes(), the way you did you just computed the overall %, the Value should be grouped by year instead.

At least you got yourself the actually value by the Y axis and the Year grouped % inside bars. I would advise changing this labels by stacking something like this:

scale_y_continuous(breaks = seq(0,8*10^6,10^6),
                 labels = c(0, paste(seq(1,8,1),'M')))

Resulting this:

enter image description here

You can adapt to your context.