Create Emoji Faces in a Waffle Plot with ggplot2 to Visualize Wealth Distribution

In this article, we’ll create Emoji / Chernoff faces arranged in a Waffle grid to visualize the wealth distribution in the U.S. population. Major techniques covered in this tutorial include:


packages and data cleanup

The majority of data cleanup is identical to this work where we created a Waffle plot in a 10 x 10 grid. If you are already familiar with it, you can directly skip to the Emoji visualization.

library(ggplot2)library(dplyr)library(ggmosaic)   # use the "happy" dataset of this packagelibrary(ggChernoff) # create Emoji faces

Prepare the dataset to create a Waffle grid. We start by first calculating the number of people in each wealth category.

h <- happy %>% as_tibble() %>% select(finrela)
# remove rows containing NA valuesh <- h[complete.cases(h), ]
# summarize the number of people in each wealth categorycounts <- h$finrela %>% table()

Calculate the head counts for each wealth category normalized to a total of 100 people.

nrows <- 10counts.normalized <- round( counts * (nrows^2 / sum(counts)) )
# check to make sure the sum of counts after rounding is 100 sum(counts.normalized)

Output:

[1] 100

Create a tidy dataset containing 100 people’s wealth condition.

d <- expand.grid(y = 1:nrows, x = 1:nrows)d <- d %>%   mutate(wealth = rep(names(counts.normalized), counts.normalized))

In the expand.grid() function above, we can also swap the x and y position, i.e., using expand.grid(x = 1:nrows, y = 1:nrows). This creates a Waffle plot with transposed layout.


The code above is identical to this work where we created the Waffle plot. Code below is specifically catered to create the Emoji / Chernoff faces.


An extra step to prepare the data to draw Chernoff faces: create a numerical variable to be mapped to the facial expression. Larger numbers correspond to happier Chernoff faces. Here we use the facial expression to represent different wealth status.

a <-  1:5names(a) <- d$wealth %>% unique()
d.smile <- d %>% as_tibble() %>% mutate(`wealth amount` = a[wealth])
head(d.smile, n = 3) # read for visualization

Output:

# A tibble: 3 × 4
y x wealth `wealth amount`
<int> <int> <chr> <int>
1 1 1 far below average 1
2 2 1 far below average 1
3 3 1 far below average 1

Visualization

Create Chernoff faces. To achieve this, we use the ggChernoff package. A main function is geom_chernoff(), which works similarly as geom_point(), but takes aesthetics smile, eyes, and brow. These aesthetics are mapped with numerical variables, and larger values correspond to happier faces. Note that for the fill aesthetic, the wealth amount variable is locally converted to a categorical variable to generate the desired discrete color scale, instead of a continuous color bar.

p1 <- d.smile %>%   ggplot(aes(x = x , y = y,              fill = factor(`wealth amount`),              smile = `wealth amount`,             eyes = `wealth amount`,             brow = `wealth amount`)) +  geom_chernoff(size = 12)
p1

Merge the legends. In ggplot2, each aesthetic has its own legend. The key to merge the legends of different aesthetics (e.g., fill, smile, brow, and eye) is to keep their legend titles and labels all the same.

legend.labels <- c("$", "$$", "$$$", "$$$$", "$$$$$")
p2 <- p1 + scale_fill_manual(values = c("snow4", "red", "yellow", "skyblue1", "green2"), name = "wealth amount", # rename legend title labels = legend.labels) + scale_smile_continuous(labels = legend.labels) + scale_eyes_continuous (labels = legend.labels) + scale_brow_continuous (labels = legend.labels)
p2

A final polish-up. Here we load font from Google font repository.

# Load fonts from Google font repositorylibrary(showtext)font_add_google(name = "Gochi Hand", family = "gochi")showtext_auto()
p3 <- p2 + ggtitle("wealth distribution in the US") + # theme_void() + theme( text = element_text(family = "gochi", size = 18), plot.title = element_text(hjust = .5, face = "bold", margin = margin(b = 10, unit = "pt")), panel.background = element_rect(fill = "snow"), axis.text = element_blank(), axis.title = element_blank(), axis.ticks = element_blank(), legend.position = "bottom" ) + coord_equal()
p3
library(ggplot2)library(dplyr)library(ggmosaic) # use the "happy" dataset of this packagelibrary(ggChernoff) # create Emoji faces
#Calculate the number of people in each wealth category. h <- happy %>% as_tibble() %>% select(finrela)
# remove rows containing NA valuesh <- h[complete.cases(h), ]
# summarize the number of people in each wealth categorycounts <- h$finrela %>% table()
# Calculate the head counts in each wealth category normalized to a total of 100 people.nrows <- 10counts.normalized <- round( counts * (nrows^2 / sum(counts)) )
# check to make sure the sum of counts after rounding is 100 sum(counts.normalized)
# Create a tidy dataset containing 100 people's wealth condition. d <- expand.grid(y = 1:nrows, x = 1:nrows)d <- d %>% mutate(wealth = rep(names(counts.normalized), counts.normalized))
# Create a numerical variable to be mapped to facial expression. a <- 1:5names(a) <- d$wealth %>% unique()
d.smile <- d %>% as_tibble() %>% mutate(`wealth amount` = a[wealth])
head(d.smile, n = 3) # ready for visualization


# Create Chernoff faces.p1 <- d.smile %>% ggplot(aes(x = x , y = y, fill = factor(`wealth amount`), smile = `wealth amount`, eyes = `wealth amount`, brow = `wealth amount`)) + geom_chernoff(size = 12)
p1

# Merge the legends.legend.labels <- c("$", "$$", "$$$", "$$$$", "$$$$$")
p2 <- p1 + scale_fill_manual(values = c("snow4", "red", "yellow", "skyblue1", "green2"), name = "wealth amount", # rename legend title labels = legend.labels) + scale_smile_continuous(labels = legend.labels) + scale_eyes_continuous (labels = legend.labels) + scale_brow_continuous (labels = legend.labels)
p2

# A final polish-up. library(showtext)font_add_google(name = "Gochi Hand", family = "gochi")showtext_auto()
p3 <- p2 + ggtitle("wealth distribution in the US") + # theme_void() + theme( text = element_text(family = "gochi", size = 18), plot.title = element_text(hjust = .5, face = "bold", margin = margin(b = 10, unit = "pt")), panel.background = element_rect(fill = "snow"), axis.text = element_blank(), axis.title = element_blank(), axis.ticks = element_blank(), legend.position = "bottom") + coord_equal()
p3




Continue Exploring — 🚀 one level up!


Check out the following article where we’ll combine bars, lines, and Emoji faces to illustrate the population distribution in happiness at different wealth conditions, as well as the relationship between happiness and the wealth status.



The above Emoji faces in a Waffle plot showed the univariate distribution of wealth, and the bar plot above showed the univariate distribution of happiness conditioned to each wealth status. As one step further, we can employ the mosaic plot to illustrate the bivariate distribution of both wealth and happiness. Check the article below how to create a mosaic plot to visualize the population composition at varied levels of wealth and health condition.