
18 Treemaps
theme_j2me()
Graphs in this chapter use fmggp2::theme_j2me(), built from a Nokia N73 running a J2ME app: a black phone background, a silver frame, a pale screen panel, Ushahidi red, tan, brown, and black marks, and an Arial Narrow title.
18.1 Description
Treemaps display proportionally divided rectangular hierarchies for levels of categorical variables. The layout consists of ‘squarified’ tiles, which display the relative contribution of each categorical value to the overall graph space.
We’ll build a treemap using the treemapify package. Also check out the mosaic plots chapter.
18.2 Set up
PACKAGES:
Install packages.
show/hide
# pak::pak("wilkox/treemapify")
library(treemapify)
library(ggplot2)
library(dplyr) # for tibble() and data manipulation
library(tidyr) # for expand_grid()DATA:
In the early to mid 2000s, most mobile phone games were small J2ME (Java ME) apps that a phone downloaded over the air. Most were bought from the carrier’s deck (the games menu on the operator’s portal). They held on until the iPhone App Store and Android Market replaced them.
We’ll simulate one year of downloads for six made up titles in three genres. Each title gets a yearly_rate (the downloads we’d expect in a year) and a deck_share (the part of those downloads bought through the carrier deck instead of directly from the publisher’s site). tidyr::expand_grid() creates a row for each title in each channel, lambda splits the yearly rate between the two channels, and rpois() draws the downloads. Setting the seed with set.seed() means you’ll get the same downloads every time you run the code below.
show/hide
set.seed(2003) # same downloads every time the code runs
j2me_downloads <- tibble(
genre = c("Puzzle", "Puzzle", "Racing", "Racing", # three genres, two games each
"Sports", "Sports"),
title = c("Block Drop", "Gem Swap", "Street Rally", "Moto Sprint", # six games
"Pocket Soccer", "Pro Bowling"),
yearly_rate = c(1800, 900, 2400, 700, 1200, 1000), # average downloads per year
deck_share = c(0.80, 0.40, 0.35, 0.60, 0.55, 0.50) # share sold through the carrier's menu
) |>
tidyr::expand_grid(channel = c("Carrier deck", "Direct")) |> # each game in both channels
mutate(
lambda = yearly_rate *
if_else(channel == "Carrier deck", deck_share, 1 - deck_share), # average downloads in each channel
downloads = rpois(n(), lambda = lambda) # download counts around each average
) |>
select(genre, title, channel, lambda, downloads)
glimpse(j2me_downloads)
#> Rows: 12
#> Columns: 5
#> $ genre <chr> "Puzzle", "Puzzle", "Puzzle", …
#> $ title <chr> "Block Drop", "Block Drop", "G…
#> $ channel <chr> "Carrier deck", "Direct", "Car…
#> $ lambda <dbl> 1440, 360, 360, 540, 840, 1560…
#> $ downloads <int> 1493, 346, 387, 539, 849, 1549…18.3 Distribution
A treemap nests counts inside counts: channels inside titles, and titles inside genres. Every tile’s area is a share of the total downloads, so the question behind the graph is “how much of the whole does each piece take up?” Below we’ll move from the downloads, to a picture, to the formula.
COUNT IT:
Start with the rows for Block Drop and Street Rally.
show/hide
j2me_downloads |>
filter(title %in% c("Block Drop", "Street Rally"))Each simulated count (downloads) lands within a few percent of the rate we gave rpois() (lambda). Block Drop got most of its downloads through the carrier deck, while Street Rally got most of its downloads directly. Adding up the counts inside each genre gives the genre totals, and dividing by the overall total turns them into the shares the treemap draws.
show/hide
j2me_downloads |>
group_by(genre) |>
summarise(downloads = sum(downloads), expected = sum(lambda)) |>
mutate(share = round(downloads / sum(downloads), 3))DRAW IT:
A bar model shows the hierarchy one level at a time. Each genre is a bar, each title is a part of its genre’s bar, and each part is split by channel.
show/hide
ggplot(j2me_downloads, aes(x = downloads, y = title, fill = channel)) +
geom_col() +
facet_grid(genre ~ ., scales = "free_y", space = "free_y") +
labs(x = "Downloads", y = NULL, fill = "Channel") +
theme(legend.position = "bottom")
A treemap takes the same parts and packs them into one rectangle. Below, the silver borders outline the genres, the black borders outline the titles, and the red and tan fills split each title by channel. The Block Drop tile is about four fifths carrier deck downloads, and the Street Rally tile is mostly direct downloads.
show/hide
ggplot(j2me_downloads, aes(area = downloads, fill = channel,
subgroup = genre, subgroup2 = title)) +
treemapify::geom_treemap() +
treemapify::geom_treemap_subgroup2_border(color = "#15110E", size = 3) +
treemapify::geom_treemap_subgroup2_text(color = "#FFFFFF",
place = "center", min.size = 0, family = "sans") +
treemapify::geom_treemap_subgroup_border(color = "#9A9791", size = 6) +
labs(fill = "Channel") +
theme(legend.position = "bottom")
NAME IT:
Each title and channel count comes from a Poisson distribution:
\[ N_{\text{title, channel}} \sim \text{Poisson}(\lambda_{\text{title, channel}}) \]
Poisson counts have a useful property for treemaps: the sum of independent Poisson counts is also Poisson, with a rate equal to the sum of the rates. So each genre total is \(\text{Poisson}(\sum \lambda)\): 2,700 for puzzle, 3,100 for racing, and 2,200 for sports.
The standard deviation of a Poisson count is \(\sqrt{\lambda}\), so its relative wobble is \(\sqrt{\lambda} / \lambda = 1 / \sqrt{\lambda}\). For the Block Drop carrier deck tile (\(\lambda = 1{,}440\)) that’s under 3%, which is why the treemap’s tiles barely change shape from one simulation to the next. Try dividing every yearly_rate by 100 and the small tiles start to jump around.
18.4 Grammar
CODE:
Create labels with
labs()Initialize the graph with
ggplot()and providedataMap the
downloadstoarea,channeltofillandlabel,genretosubgroup, andtitletosubgroup2Add
geom_treemap()Add
geom_treemap_text()place: this controls where the boxes startcolor: text colormin.size: the minimum font size (when re-sizing)alpha: opacityfontface:italic/boldfamily:"sans"/"sansserif"/"mono"
show/hide
labs_treemap <- labs(
title = "J2ME downloads 2005",
caption = "Simulated data",
fill = "Channel")
ggp2_treemap <- ggplot(j2me_downloads,
aes(area = downloads,
fill = channel,
label = channel,
subgroup = genre,
subgroup2 = title)) +
treemapify::geom_treemap() +
treemapify::geom_treemap_text(
place = "bottomright",
color = "#FFFFFF",
min.size = 0,
alpha = 0.90,
fontface = "italic",
family = "sans")
ggp2_treemap +
labs_treemapGRAPH:
show/hide
labs_treemap <- labs(
title = "J2ME downloads 2005",
caption = "Simulated data",
fill = "Channel")
ggp2_treemap <- ggplot(j2me_downloads,
aes(area = downloads,
fill = channel,
label = channel,
subgroup = genre,
subgroup2 = title)) +
treemapify::geom_treemap() +
treemapify::geom_treemap_text(
place = "bottomright",
color = "#FFFFFF",
min.size = 0,
alpha = 0.90,
fontface = "italic",
family = "sans")
ggp2_treemap +
labs_treemap
18.5 More info
treemapify has multiple options for building treemaps. We cover a few of these below, but you should check out the package vignette.
18.5.1 Subgroup 2
ggplot2 builds layers in the order they’re written, so it’s advised to build the subgroups in order from “from deepest to shallowest”, with subgroup2 first (ending on subgroup)
Add
geom_treemap_subgroup2_border()- Set the
sizeandcolor
- Set the
Add
geom_treemap_subgroup2_text()place: this controls where the boxes startcolor: text colormin.size: the minimum font size (when re-sizing)alpha: opacityfontface:italic/boldfamily:"sans"/"sansserif"/"mono"
See full list of arguments here.
show/hide
ggp2_tm_subgroup <- ggp2_treemap +
treemapify::geom_treemap_subgroup2_border(
color = "#15110E",
size = 4.0) +
treemapify::geom_treemap_subgroup2_text(
place = "center",
alpha = 0.75,
color = "#FFFFFF",
min.size = 0,
family = "sans")
ggp2_tm_subgroup +
labs_treemap
18.5.2 Subgroup
Add
geom_treemap_subgroup_border()- Set the
sizeandcolor
- Set the
Add
geom_treemap_subgroup_text()place: this controls where the boxes startcolor: text colormin.size: the minimum font size (when re-sizing)alpha: opacityfontface:italic/boldfamily:"sans"/"sansserif"/"mono"
show/hide
ggp2_tm_subgroup2 <- ggp2_tm_subgroup +
treemapify::geom_treemap_subgroup_border(
color = "#9A9791",
size = 4) +
treemapify::geom_treemap_subgroup_text(
place = "topleft",
alpha = 0.65,
grow = TRUE,
color = "#FFFFFF",
min.size = 0,
family = "mono",
fontface = "bold")
ggp2_tm_subgroup2 +
labs_treemap