Create Ribbons and Streams in ggplot2 to Visualize Changing Popularity of Movie Genres

In this visualization, we’ll create a ribbon/stream plot, leveraging the ggstream package and its built-in dataset blockbusters, and visualize the changing popularity of different movie genres from 1980 to 2020.


Packages and data

library(ggplot2)library(dplyr)# install.packages("ggstream")library(ggstream)
theme_set(theme_minimal(base_size = 13)) # global theme
head(blockbusters, n = 4)

Output:

# A tibble: 4 × 3
year genre box_office
<dbl> <chr> <dbl>
1 1977 Action 2.98
2 1977 Adventure 0.209
3 1977 Comedy 0.516
4 1977 Drama 2.54

Visualization

As an exploratory analysis, let’s start with creating a bar plot displaying the box office of different movie genres.

p0 <- blockbusters %>%   ggplot(aes(x = year, y = box_office,              fill = genre, color = genre)) 
p0 + geom_col(alpha = .3)

Alternative to the bar plot, we can use geom_area() (a generic ggplot2 function) to connect bars of the same genre category horizontally, effectively forming a continuous band (though with rugged edges).

p0 + geom_area(alpha = .3)

geom_stream from the ggstream package generate ribbons similarly to geom_area, but with smoother ribbon outline. The smoothness is controlled by bw (bandwidth). A larger bw value creates more smoothness, while a smaller bw value generates more details, and may more faithfully reflects the underlying data pattern.

There are three options for the type argument: ridge, proportional, and mirror:

  • ridge: stack the ribbons from ground floor, as geom_area.
p0 + geom_stream(  alpha = .3, bw = .5, type = "ridge") 
  • proportional: stack the ribbons from ground floor, with a normalized total height of unit 1. It is reminiscent of the fill position.
p0 + geom_stream(  alpha = .3, bw = .5, type = "proportional") 
  • mirror: stack the ribbons symmetrically around the x-axis. It is not necessarily an easy way for data comparison, but is fun and engaging as an artwork. We’ll stick to this type for the following edits.
p1 <- p0 + geom_stream(  alpha = .3, bw = .5, type = "mirror") p1

Label the ribbons with the associated genre names using geom_stream_label. The texts are positioned automatically where the ribbon is the broadest.

p2 <- p1 +  geom_stream_label(    aes(label = genre),     bw = .5, type = "mirror", fontface = "bold")p2

Adjust the color palette, and add title and subtitle using the annotate way.

p3 <- p2 +   # color scale  scale_color_brewer(palette = "Set1") +  scale_fill_brewer(palette = "Set1") +  # titles  annotate(geom = "text",            label = c("Worldwide Blockbusters",  # title                     "the evolving popularity of genres"), # subtitle           x = 1978,            y = c(8, 6.5),            size = c(6, 5),            color = c("snow4", "snow3"),            hjust = 0,            fontface = c("bold", "italic")) p3

Remove the legend, and keep only the major x-axis grids.

p4 <- p3 +   theme(legend.position = "none",        panel.grid.major.y = element_blank(),        panel.grid.minor.y = element_blank(),        panel.grid.minor.x = element_blank()) p4
library(ggplot2)library(dplyr)# install.packages("ggstream")library(ggstream)
theme_set(theme_minimal(base_size = 13)) # global theme
head(blockbusters, n = 4)
# As an exploratory analysis to start with, first create a bar plotp0 <- blockbusters %>% ggplot(aes(x = year, y = box_office, fill = genre, color = genre))
p0 + geom_col(alpha = .3)
# Create an area plot p0 + geom_area(alpha = .3)

# ggstream creates a smoother version of area plot. # There are three options for the `type` argument: ## 1) ridge:p0 + geom_stream(alpha = .3, bw = .5, type = "ridge")
## 2) proportional:p0 + geom_stream(alpha = .3, bw = .5, type = "proportional")
## 3) mirror:p1 <- p0 + geom_stream(alpha = .3, bw = .5, type = "mirror") p1

# Label the ribbons with the associated genre names.p2 <- p1 + geom_stream_label( aes(label = genre), bw = .5, type = "mirror", fontface = "bold")p2

# Adjust the color palette, and add title and subtitle.p3 <- p2 + # color scale scale_color_brewer(palette = "Set1") + scale_fill_brewer(palette = "Set1") + # titles annotate(geom = "text", label = c("Worldwide Blockbusters", # title "the evolving popularity of genres"), # subtitle x = 1978, y = c(8, 6.5), size = c(6, 5), color = c("snow4", "snow3"), hjust = 0, fontface = c("bold", "italic")) p3

# Remove the legend, and keep only the major x-axis grids. p4 <- p3 + theme(legend.position = "none", panel.grid.major.y = element_blank(), panel.grid.minor.y = element_blank(), panel.grid.minor.x = element_blank()) p4




Continue Exploring — 🚀 one level up!


In addition to the generic function geom_area, and geom_stream from the ggstream package illustrated above, geom_alluvium from the ggalluvial package also renders smoothed ribbons in a similar manner. Check the following awesome stacked alluvium plot that displays the changing population of migration from worldwide to the U.S. from 1820 to 2009.



To visualize changes with time, continuous bands and ribbons shown above are all powerful tools, especially when the accumulated quantity at each time point is of interest. In addition to these graphic elements, annotated and highlighted line plots are a concise and eye-catching alternative.



For more complicated data, animations, such as the following population pyramids, offer a unique and engaging solution to vividly visualize the dynamics of the data pattern.