Introduction
treemap 回答的是部分与整体:总量由哪些类别构成,每一块的面积就是它占的份额;矩形还能嵌套,于是可以再表达一层分组。
它拿精度换了紧凑。人判断长度远比判断面积准,所以"第一名比第二名多多少"该用排序条形图;treemap 的位置是把十几个类别连同它们的上层分组一起塞进一个矩形,一眼看出谁占大头。
读的时候按这个顺序:
- 先找最大的几块。 它们通常已经解释了大部分总量。
- 只比较明显的面积差。 两块接近时读标签里的数值,不要靠眼睛硬判。
- 再看颜色和分组边界。 扁平版每个国家一个颜色;分层版同一地区统一颜色,细外框补充空间层级。
- 最后看小块。 标签消失不是数据消失,只是那块空间放不下可读的文字。
Example Data
treemap 要的输入是:1 个分类变量 + 1 个非负、可相加的数值变量,要分层再加 1 个分组变量。面积是把总量切开分给各类别,所以数值必须能相加——比率、均值和可能为负的数切不出面积。
economy_countries 是 217 个经济体的宏观指标表,一行一个经济体。这里取 2023 年 GDP 最高的 10 个:Top 10 能给每个标签留下足够空间,再多塞两块,小矩形的名字就开始掉。
library(dplyr)
library(ggplot2)
library(treemapify)
library(biopalette)
# 公开地址,和数据集页上「下载 CSV」给的是同一个 —— 不写仓库相对路径:
# 那个目录不进仓库,读者 clone 下来也没有这个文件,这段代码就跑不了
economy <- read.csv(
"https://assets.evanzhou.org/tessera/csv/economy_countries.csv",
check.names = FALSE
)
plot_data <- economy |>
# gdp_usd > 0 不是防御性代码:负数和 0 在面积编码里没有对应的矩形
filter(year == 2023, !is.na(gdp_usd), gdp_usd > 0) |>
slice_max(gdp_usd, n = 10, with_ties = FALSE) |>
mutate(
# 世行的正式名太长,塞进矩形会被折成三行或者干脆消失
country_label = recode(
country,
"Russian Federation" = "Russia",
"Korea" = "South Korea",
.default = country
),
# 万亿美元,一位小数:原始数是 13 位整数,写在块里没人读
label = paste0(country_label, "\n", sprintf("$%.1fT", gdp_usd / 1e12))
)
# 显式固定地区顺序:treemapify 按因子水平安排分组容器的位置,
# 不写就跟着数据里的出现顺序走,换一年数据版面就重排了
region_levels <- c(
"North America",
"East Asia & Pacific",
"Europe & Central Asia",
"South Asia",
"Latin America & Caribbean"
)
plot_data <- plot_data |>
mutate(region = factor(region, levels = region_levels)) |>
arrange(desc(gdp_usd))
Palettes
两张图的阅读单位不同,因此不强行共用一种颜色逻辑。
- 扁平版把国家当阅读单位,用能容纳较多类别的 babel,十个国家各一色。
- 分层版把地区当阅读单位,改用 walter_white2;同一地区统一颜色,再由细外框强化层级。
两套填充都降了透明度,让颜色与纸白背景混合,避免大矩形过于刺激。国家名已经标在块内,所以不放十项图例;分层版的地区名也直接写在区域角落。
颜色在这里不编码大小——面积已经表示 GDP。颜色只负责保持身份:扁平版是国家的身份,分层版是地区的身份。
# 命名映射,不是按位置取色:后面还要 arrange() 和分组重排,
# 按位置给色的话同一个国家在两张图里会换颜色
country_colors <- setNames(
# babel 有 20 色,挑出的这 10 个两两之间色相拉得最开
get_palette("babel", type = "qualitative")[c(1, 2, 3, 4, 8, 9, 11, 12, 13, 20)],
plot_data$country_label
)
region_colors <- setNames(
get_palette("walter_white2", type = "qualitative"),
region_levels
)
PAPER <- "#F4F3EE"
INK <- "#353531"
Recipe
| No. | Method | Input Data | Palettes |
|---|---|---|---|
| 1 | treemapify |
plot_data |
country_colors |
| 2 | treemapify |
plot_data |
region_colors |
1 · 扁平 treemap
这一版不建立空间层级:十个国家在同一层比较,各用一种颜色。
国家名和数值都写在矩形里,所以不放图例——十项图例要读者在色块和列表之间来回找,而标签就在块上。
#| fig: basic
#| fig-width: 8
#| fig-height: 6
ggplot(plot_data, aes(area = gdp_usd, fill = country_label, label = label)) +
geom_treemap(
colour = PAPER, # 用纸白当缝,块与块之间靠底色分开,不画深色描边
linewidth = 1.1,
alpha = 0.62,
start = "topleft" # 从左上角开始铺,最大的块落在左上,符合读图起点
) +
geom_treemap_text(
colour = INK,
place = "centre",
grow = FALSE, # 不让字随块大小缩放,否则字号本身变成第二套面积编码
reflow = TRUE,
size = 9,
min.size = 5.5, # 小于这个字号就不画:宁可没有标签,也不要读不出的标签
padding.x = grid::unit(2.4, "mm"),
padding.y = grid::unit(2.4, "mm"),
start = "topleft"
) +
scale_fill_manual(values = country_colors, guide = "none") +
labs(
title = "The world's largest economies",
subtitle = "Area shows 2023 GDP; each country keeps its own color",
caption = "Source: World Bank via Tessera - current US$"
) +
theme_void(base_size = 12) +
theme(
plot.title = element_text(
colour = INK, size = 17, face = "bold", hjust = 0,
margin = margin(b = 4)
),
plot.subtitle = element_text(
colour = INK, size = 10.5, hjust = 0,
margin = margin(b = 12)
),
plot.caption = element_text(colour = INK, size = 8.5, hjust = 1),
plot.margin = margin(16, 18, 12, 18),
plot.background = element_rect(fill = PAPER, colour = NA),
panel.background = element_rect(fill = PAPER, colour = NA)
)
