How to center stacked percent barchart labels

2019-01-06 11:38发布

问题:

I am trying to plot nice stacked percent barchart using ggplot2. I've read some material and almost manage to plot, what I want. Also, I enclose the material, it might be useful in one place:

How do I label a stacked bar chart in ggplot2 without creating a summary data frame?

Create stacked barplot where each stack is scaled to sum to 100%

R stacked percentage bar plot with percentage of binary factor and labels (with ggplot)

My problem is that I can't place labels where I want - in the middle of the bars.

You can see the problem in the picture above - labels looks awfull and also overlap each other.

What I am looking for right now is:

  1. How to place labels in the midde of the bars (areas)

  2. How to plot not all the labels, but for example which are greather than 10%?

  3. How to solve overlaping problem?

For the Q 1. @MikeWise suggested possible solution. However, I still can't deal with this problem.

Also, I enclose reproducible example, how I've plotted this grahp.

library('plyr')
library('ggplot2')
library('scales')
set.seed(1992)
n=68

Category <- sample(c("Black", "Red", "Blue", "Cyna", "Purple"), n, replace = TRUE, prob = NULL)
Brand <- sample("Brand", n, replace = TRUE, prob = NULL)
Brand <- paste0(Brand, sample(1:5, n, replace = TRUE, prob = NULL))
USD <- abs(rnorm(n))*100

df <- data.frame(Category, Brand, USD)

# Calculate the percentages
df = ddply(df, .(Brand), transform, percent = USD/sum(USD) * 100)


# Format the labels and calculate their positions
df = ddply(df, .(Brand), transform, pos = (cumsum(USD) - 0.5 * USD))

#create nice labes
df$label = paste0(sprintf("%.0f", df$percent), "%")  



ggplot(df, aes(x=reorder(Brand,USD,
                              function(x)+sum(x)),  y=percent, fill=Category))+
  geom_bar(position = "fill", stat='identity',  width = .7)+
  geom_text(aes(label=label, ymax=100, ymin=0), vjust=0, hjust=0,color = "white",  position=position_fill())+
  coord_flip()+
  scale_y_continuous(labels = percent_format())+
  ylab("")+
  xlab("")

回答1:

Here's how to center the labels and avoid plotting labels for small percentages. An additional issue in your data is that you have multiple bar sections for each colour. Instead, it seems to me all the bar sections of a given colour should be combined. The code below uses dplyr instead of plyr to set up the data for plotting:

library(dplyr)

# Initial data frame   
df <- data.frame(Category, Brand, USD)

# Calculate percentages and label positions
df.summary = df %>% group_by(Brand, Category) %>% 
  summarise(USD = sum(USD)) %>%   # Within each Brand, sum all values in each Category
  mutate(percent = USD/sum(USD),
         pos = cumsum(percent) - 0.5*percent)

To plot the data, use an ifelse statement to determine whether a label is plotted or not. In this case, I've avoided plotting a label for percentages less than 7%.

ggplot(df.summary, aes(x=reorder(Brand,USD,function(x)+sum(x)), y=percent, fill=Category)) +
  geom_bar(stat='identity',  width = .7, colour="black", lwd=0.1) +
  geom_text(aes(label=ifelse(percent >= 0.07, paste0(sprintf("%.0f", percent*100),"%"),""),
                y=pos), colour="white") +
  coord_flip() +
  scale_y_continuous(labels = percent_format()) +
  labs(y="", x="")

UPDATE: With ggplot2 version 2, it is no longer necessary to calculate the coordinates of the text labels to get them centered. Instead, you can use position=position_stack(vjust=0.5). For example:

ggplot(df.summary, aes(x=reorder(Brand, USD, sum), y=percent, fill=Category)) +
  geom_bar(stat="identity", width = .7, colour="black", lwd=0.1) +
  geom_text(aes(label=ifelse(percent >= 0.07, paste0(sprintf("%.0f", percent*100),"%"),"")),
                position=position_stack(vjust=0.5), colour="white") +
  coord_flip() +
  scale_y_continuous(labels = percent_format()) +
  labs(y="", x="")



回答2:

I followed the example and found the way how to put nice labels for simple stacked barchart. I think it might be usefull too.

df <- data.frame(Category, Brand, USD)

# Calculate percentages and label positions
df.summary = df %>% group_by(Brand, Category) %>% 
  summarise(USD = sum(USD)) %>%   # Within each Brand, sum all values in each Category
  mutate( pos = cumsum(USD)-0.5*USD)

ggplot(df.summary, aes(x=reorder(Brand,USD,function(x)+sum(x)), y=USD, fill=Category)) +
  geom_bar(stat='identity',  width = .7, colour="black", lwd=0.1) +
  geom_text(aes(label=ifelse(USD>100,round(USD,0),""),
                y=pos), colour="white") +
  coord_flip()+
  labs(y="", x="")