Bar of Pie Chart in R/ggplot?

I want to create a pie chart with anexpanded bar chart of one section. I have looked all over and can't find any examples compleated in R before. I was able to create a version in Microsoft XL to demonstrate what i'm after, where the values and titles have been redacted.

Anyone know how this could be done?

Thanks

image

Hi, and welcome!

A reproducible example, called a reprex is likely to attract more answers, because it doesn't require retracing the steps you may have already taken, like creating a pie chart and a stack bar chart separately. Do you have anything you can share?

This seemed like an interesting problem. I'm sure there are functions and packages out there that will do a lot of this work, but I reinvented a few wheels just because I wanted explore how to put things together. The result is some working code. It's ugly, a little chaotic, but functional. Hopefully it helps you put together a solution that fits your needs.

First, you will need to run all of this code in your session

#--------------------------------------------------------------------
# Source in all of these functions together.
#
# CONTENTS ----------------------------------------------------------
# 
# dual_chart - Master function. It will combine two charts and either 
#              print the plot object, or return the two plots in a list
#              where they can be further customized.
# dual_chart_combine - Does the work of putting the two plots side by side.
# dual_chart_pie - Construct the pie chart.
# dual_chart_bar - Construct the bar chart.


# dual_chart --------------------------------------------------------
# Master function. It will combine two charts and either 
# print the plot object, or return the two plots in a list
# where they can be further customized.

dual_chart <- function(data, primary_variable, level_for_detail, 
                       secondary_variable, 
                       ncol = 2, nrow = 1, ..., 
                       plot = TRUE){
  plots <- list(pie = dual_chart_pie(DF, 
                                     primary_variable, 
                                     level_for_detail), 
                bar = dual_chart_bar(DF, 
                                     primary_variable, 
                                     level_for_detail, 
                                     secondary_variable))
  
  if (plot){
    dual_chart_combine(plots$pie, 
                       plots$bar, 
                       ncol = ncol, 
                       nrow = nrow, 
                       ...)
  } else {
    plots
  }
}


# dual_chart_combine ------------------------------------------------
# Does the work of putting the two plots side by side.

dual_chart_combine <- function(pie_chart, bar_chart, 
                               ncol = 2, nrow = 1, ...){
  gridExtra::grid.arrange(
    grobs = list(pie_chart, bar_chart), 
    ncol = ncol, 
    nrow = nrow
  )
}


# dual_chart_pie ----------------------------------------------------
# Construct the pie chart

dual_chart_pie <- function(data, primary_variable, level_for_detail){
  require(dplyr)
  require(ggplot2)
  
  # Frequency and Relative Frequencies of the data
  primary_summary <- 
    data %>% 
    group_by_at(primary_variable) %>% 
    summarise_at(primary_variable, 
                 .funs = list(n = function(x) sum(!is.na(x)))) %>% 
    mutate_at("n", 
              .funs = list(p = function(x) x / sum(x)))
  
  # Determine the radian shift.
  # This allows us to place the level_for_detail on the right side of the
  #   pie chart, with the slice of the pie centered over the pi/2 position.
  # label_position determines the y-coordinate for the proportion label.
  cum_freq <- c(0, cumsum(primary_summary$p))
  radian_position <- cum_freq * 6.283
  
  radian_shift <- numeric(length(cum_freq) - 1)
  names(radian_shift) <- primary_summary[[primary_variable]]
  
  label_position <- numeric(length(cum_freq) - 1)
  
  for (i in seq_along(radian_shift)){
    radian_shift[i] <- mean(radian_position[c(i, i+1)])
    label_position[i] <- mean(cum_freq[c(i, i + 1)])
  }
  
  # Make the pie chart
  ggplot(data = primary_summary, 
         mapping = aes_string(y = "p", 
                              fill = primary_variable)) + 
    geom_bar(width = 1, 
             stat = "identity", 
             mapping = aes(x = "")) +
    geom_text(mapping = aes_string(y = "1 - label_position", 
                                   label = "p"), 
              x = 1) + 
    coord_polar("y", 
                start = pi / 2 + radian_shift[level_for_detail])
}

# dual_chart_bar ----------------------------------------------------
# Construct the bar chart

dual_chart_bar <- function(data, primary_variable, level_for_detail, 
                           secondary_variable){
  secondary_summary <- 
    data %>% 
    group_by_at(c(primary_variable, sub_variable)) %>% 
    summarise_at(sub_variable, 
                 .funs = list(n = function(x) sum(!is.na(x)))) %>% 
    ungroup() %>% 
    mutate_at("n", 
              .funs = list(p = function(x) x / sum(x))) %>% 
    filter_at(primary_variable, any_vars(. %in% level_for_detail))
  
  # label_position determines the y-coordinate for the proportion label.
  cum_freq <- c(0, cumsum(secondary_summary$p))
  
  label_position <- numeric(length(cum_freq) - 1)
  
  for (i in seq_along(label_position)){
    label_position[i] <- mean(cum_freq[c(i, i + 1)])
  }
  
  # Make the bar plot
  ggplot(data = secondary_summary,
         mapping = aes_string(y = "p", 
                              fill = sub_variable)) + 
    geom_bar(width = 1,
             stat = "identity", 
             mapping = aes(x = "")) + 
    geom_text(mapping = aes_string(y = "max(cum_freq) - label_position", 
                                   label = "p"), 
              x = 1)
}

Once that's all in there, you can get the crude plot with

dual_chart(DF, "pie_var", "C", "bar_var")

2 Likes

Here's an approach relying more on drawing the elements in a single plot space. This implementation isn't particularly general but might be okay if the output is a one-off.

First, some fake data:

my_data <- data.frame(
  stringsAsFactors = F,
  Section = c("a", "b", "b", "b", "c"),
  Detail = letters[22:26],
  Amount = c(0.51, 0.04, 0.05, 0.07, 0.33)
)

Now, loading some libraries and noting the coordinates of the wedge edges.

library(ggplot2); library(ggforce); library(dplyr)
my_data_aug <- my_data %>%
  mutate(arc_start = cumsum(lag(Amount, default = 0)) * 2*pi - 2,
         arc_end   = cumsum(Amount) * 2*pi - 2,
         x_pos = 0 + cos(arc_start - pi/2),
         y_pos = 1 - sin(arc_start - pi/2))

The detail for the bar chart section:

my_data_detail <- my_data_aug %>%
  filter(Section == "b") %>%
  purrr::map_df(rev) %>%    # This reverses the order
  mutate(Amount_scaled = Amount / sum(Amount) * 2)

The detail for the segment lines:

my_data_lines <- my_data_aug %>%
  filter(Section == "b" | lag(Section == "b")) %>%
  slice(1, n())

Now, all together:

ggplot(my_data_aug) +
  geom_arc_bar(aes(x0 = 0, y0 = 1,  
                   r0 = 0, r  = 1,
                   fill = Section,
                   start = arc_start,
                   end   = arc_end), color = NA) +
  geom_tile(data = my_data_detail,
            aes(x = 2, 
                y = cumsum(Amount_scaled) - Amount_scaled/2,
                height = Amount_scaled, fill = Detail)) +
  annotate("segment", 
           x = my_data_lines[1:2, "x_pos"],
           y = my_data_lines[1:2, "y_pos"],
           xend = 1.5,
           yend = c(2,0)) +
  coord_equal() +
  theme_void()

Rplot11

1 Like

Extra points for omitting the talk!

This topic was automatically closed 21 days after the last reply. New replies are no longer allowed.