How can I do plot a waffle chart as an alternative to using piecharts in R?

No help files found with alias or concept or title matching ‘waffle’
using fuzzy matching.

The closest I found googling out there are mosaicplots.


locked by whuber Aug 10 '17 at 19:03

This question exists because it has historical significance, but it is not considered a good, on-topic question for this site so please do not use it as evidence that you can ask similar questions here. This question and its answers are frozen and cannot be changed. See the help center for guidance on writing a good question.

Read more about locked posts here.

  • $\begingroup$ I don't know, but why not use a better method? Dot charts are much better. $\endgroup$ – Peter Flom Nov 2 '11 at 11:49
  • 2
    $\begingroup$ For those who want to know what waffle charts are, Robert Kosara on the Eager Eyes blog has a piece about them. Take note of Jon Peltier's comments as well. $\endgroup$ – Andy W Nov 2 '11 at 12:14
  • $\begingroup$ Closest thing I could find is this. FWIW, I agree with Peter, I avoid pies and waffles when I visualize data. $\endgroup$ – user5594 Nov 2 '11 at 15:22

Now there is a package called waffle.

Example from the github page:

parts <- c(80, 30, 20, 10)
waffle(parts, rows=8)




  • $\begingroup$ I didn't know these were called "waffle charts". I like them -- good pie chart replacement $\endgroup$ – shadowtalker Mar 20 '15 at 21:39

I suspect that geom_tile from the package ggplot2 can do what you're looking for. Shane's answer on this StackOverflow question should get you started.

Edit: Here's an example, with a few other plots for comparison.


# Here's some data I had lying around
tb <- structure(list(region = c("Africa", "Asia", "Latin America", 
"Other", "US-born"), ncases = c(36L, 34L, 56L, 2L, 44L)), .Names = c("region", 
"ncases"), row.names = c(NA, -5L), class = "data.frame")

# A bar chart of counts
ggplot(tb, aes(x = region, weight = ncases, fill = region)) +

# Pie chart.  Forgive me, Hadley, for I must sin.
ggplot(tb, aes(x = factor(1), weight = ncases, fill = region)) +
    geom_bar(width = 1) +
    coord_polar(theta = "y") +
    labs(x = "", y = "")

# Percentage pie.
ggplot(tb, aes(x = factor(1), weight = ncases/sum(ncases), fill = region)) +
    geom_bar() +
    scale_y_continuous(formatter = 'percent') +
    coord_polar(theta = "y") +
    labs(x = "", y = "")

# Waffles
# How many rows do you want the y axis to have?
ndeep <- 5

# I need to convert my data into a data.frame with uniquely-specified x
# and y coordinates for each case
# Note - it's actually important to specify y first for a
# horizontally-accumulating waffle
# One y for each row; then divide the total number of cases by the number of
# rows and round up to get the appropriate number of x increments
tb4waffles <- expand.grid(y = 1:ndeep,
                          x = seq_len(ceiling(sum(tb$ncases) / ndeep)))

# Expand the counts into a full vector of region labels - i.e., de-aggregate
regionvec <- rep(tb$region, tb$ncases)

# Depending on the value of ndeep, there might be more spots on the x-y grid
# than there are cases - so fill those with NA
tb4waffles$region <- c(regionvec, rep(NA, nrow(tb4waffles) - length(regionvec)))

# Plot it
ggplot(tb4waffles, aes(x = x, y = y, fill = region)) + 
    geom_tile(color = "white") + # The color of the lines between tiles
    scale_fill_manual("Region of Birth",
                      values = RColorBrewer::brewer.pal(5, "Dark2")) +
    opts(title = "TB Cases by Region of Birth")

Example waffle plot

Clearly, there's extra work to be done on getting the aesthetics right (e.g., what the hell do those axes even mean?), but that's the mechanics of it. I leave "pretty" as an exercise for the reader.


Here's one in base r using @jbkunst 's data:

waffle <- function(x, rows, cols = seq_along(x), ...) {
  xx <- rep(cols, times = x)
  lx <- length(xx)
  m <- matrix(nrow = rows, ncol = (lx %/% rows) + (lx %% rows != 0))
  m[1:length(xx)] <- xx

  op <- par(no.readonly = TRUE)

  o <- cbind(c(row(m)), c(col(m))) + 1
  plot.window(xlim = c(0, max(o[, 2]) + 1), ylim = c(0, max(o[, 1]) + 1),
              asp = 1, xaxs = 'i', yaxs = 'i')
  rect(o[, 2], o[, 1], o[, 2] + .85, o[, 1] + .85, col = c(m), border = NA)

  invisible(list(m = m, o = o))

cols <- c("#F8766D", "#7CAE00", "#00BFC4", "#C77CFF")
m <- waffle(c(80, 30, 20, 10), rows = 8, cols = cols, mar = c(0,0,0,7),
            bg = 'cornsilk')
legend('right', legend = LETTERS[1:4], pch = 15, col = cols, pt.cex = 2,
       bty = 'n')

enter image description here

  • 2
    $\begingroup$ All the examples seem to have a high ink:information ratio. $\endgroup$ – Frank Harrell Mar 26 '15 at 1:03
  • 1
    $\begingroup$ I agree with @Frank Harrell. The example is singularly unconvincing. I love graphs beyond measure, but for this example it's reasonable to expect readers to understand a table with the four frequencies. If a graph is preferred, then a dot or bar chart is simpler (the frequencies can be added as annotation too). I can imagine some pedagogic value for very young children. $\endgroup$ – Nick Cox Mar 26 '15 at 2:02
  • 1
    $\begingroup$ So you're saying that when I present this plot at the annual bar chart convention, I should expect a lot of haters in the crowd? thanks for the heads up $\endgroup$ – rawr Mar 26 '15 at 3:54
  • $\begingroup$ Turn it round: The graph seems to be saying to readers: look here, you can count for yourselves to understand the graph! If the numbers are large, that's not possible. If the numbers are small, it's still not more helpful than other graphs. For small children, that's reinforcement so they understand graphics. Who else needs the message? $\endgroup$ – Nick Cox Mar 26 '15 at 19:07
  • 1
    $\begingroup$ Another discussion at perceptualedge.com/articles/visual_business_intelligence/… $\endgroup$ – Nick Cox Aug 10 '17 at 17:14

Not the answer you're looking for? Browse other questions tagged or ask your own question.