← 返回 Tessera
地图2 张图economy_countries(Tessera dataset,217 个经济体,203 个有人均 GDP)2026-08-11

choropleth_map

一个国家级(或区域级)的指标,在地图上是怎么分布的?

需要的输入
区域标识 + 连续数值;边界数据另外取
示例数据
economy_countries(Tessera dataset,217 个经济体,203 个有人均 GDP)
依赖
sf · rnaturalearth · ggplot2 · dplyr · scales · biopalette
配色
mitonuclear_blue
成图预览另有 1 张在配方里
choropleth_map

什么时候用它

分级填色地图适合看空间格局:高值或低值是否连成区域,相邻国家是否呈现共同模式。地图的价值只来自位置;要精确比较具体数值,排序柱状图通常更诚实。

它不适合表达总量。大国在画布上天然更抢眼,俄罗斯和新加坡即使数值相同,视觉重量也相差悬殊。人口、GDP 总量这类指标还会与国土面积产生额外联想;这里选择人均 GDP,减少“面积大所以总量大”的误读。

为什么使用相对中位数

人均 GDP 从约 251 美元跨到约 257,000 美元,直接在线性色阶上着色会让大多数国家挤在最浅的一小段。先计算:

relative_log2 = log2(本国人均 GDP / 全球有效经济体中位数)

这样 0 有明确含义:等于全球中位数;-1 是中位数的一半,+1 是两倍,-2+2 分别是四分之一和四倍。变换压缩长尾,也让图例能直接写成相对中位数的倍数。

这不是在声称全球中位数是一条经济学阈值。它只是一个稳定、可复算的视觉基准,因此标题与图例必须明确写“relative to median”。

怎么读

  1. 先看成片区域。 地图用于识别空间聚集,不用于给国家排精确名次。
  2. 确认基准。 图例中的 Median 表示全球有效经济体中位数,不是零美元;它是读数刻度,不是色板的中性色。
  3. 看图例是连续还是分箱。 分箱边界是人为的,跨过边界不代表现实中突然发生跃迁。
  4. 灰色是缺失或未匹配。 它既不是最低档,也不是零。
  5. 注意面积偏差。 大面积国家更显眼,不代表数据更可靠或更重要。

常见陷阱

  • 用国家名称连接。 USAUnited StatesUnited States of America 很容易静默错配;属性表和边界表都应使用 ISO3 或底图自己的稳定代码。
  • 不检查匹配率。 属性表中的小岛国或特殊经济体可能不在当前底图,连接不会报错;必须显式列出未匹配代码。
  • 把 NA 放进色阶。 缺失统一使用不属于色板的中性灰,并进入图例。
  • 长尾仍用原始线性色阶。 一个极高值足以把其余国家压成近似同色;先对数变换,或使用具有解释意义的固定分箱。
  • 用分位数分箱却写成绝对水平。 分位数只表示在这批样本中的相对排名;换年份后边界会移动。本例使用相对全球中位数的固定倍数边界。
  • 不检查几何就直接修复。 先用 st_is_valid() 检查;当前 Natural Earth medium 边界全部有效。对跨日期变更线的经纬度多边形盲目运行 st_make_valid(),反而可能生成横贯高纬度的带状伪影。
  • 把南极留在主体图。 南极占据大量版面且没有此指标,通常裁到南纬 60°。
  • 导出巨型矢量地图。 世界边界包含大量顶点;网页与常规报告使用高分辨率 PNG/WebP 更轻。确实需要 SVG/PDF 时先简化几何。

配色

这里关心的是人均 GDP 从低到高的一条顺序,中位数只是方便解释倍数的基准,不代表两侧是性质相反的两类数据。因此使用 mitonuclear_blue:近白蓝表示较低值,深蓝表示较高值。连续版使用完整六色插值;分箱版从同一色板等距取五色,使两张图保持同一方向。

地图是大面积填色,对明度顺序比散点图更敏感。相邻颜色必须保持稳定的浅到深关系,最浅色也要与缺失灰区分;国界只用很细的浅色线,避免边框压过填色。定性色板用于地图时不能假装连续插值,应该切成固定档位后逐档分配。

配方

方法绘图系统什么时候选它
Ascale_fill_gradientn()ggplot2 + sf数值连续、方向是从低到高,希望保留完整梯度
Bcut() + scale_fill_manual()ggplot2 + sf读者只需要稳定的高低档位,或色板本身是定性的

数据与边界

economy_countries 提供 2023 年经济指标和 ISO3 代码。属性数据来自 Tessera CSV;Natural Earth 只负责国界几何,不提供着色数值。

library(dplyr)
library(sf)
library(rnaturalearth)
library(ggplot2)
library(scales)
library(biopalette)

economy <- read.csv(
  "content/tessera/data/csv/economy_countries.csv",
  na.strings = c("", "NA")
)

valid_gdp <- economy |>
  filter(!is.na(gdp_per_capita_usd), gdp_per_capita_usd > 0)

world_median <- median(valid_gdp$gdp_per_capita_usd)

economy_map_data <- economy |>
  mutate(
    relative_log2 = if_else(
      !is.na(gdp_per_capita_usd) & gdp_per_capita_usd > 0,
      log2(gdp_per_capita_usd / world_median),
      NA_real_
    )
  ) |>
  select(iso3, country, gdp_per_capita_usd, relative_log2)

world_sf <- ne_countries(scale = "medium", returnclass = "sf") |>
  mutate(
    map_key = if_else(is.na(iso_a3) | iso_a3 == "-99", adm0_a3, iso_a3)
  )

# 当前版本全部有效;只有检查失败时才进入几何修复流程。
stopifnot(all(st_is_valid(world_sf)))

# ISO3 对 ISO3,不使用国家名。
world_map <- world_sf |>
  left_join(economy_map_data, by = c("map_key" = "iso3"))

# 属性表里存在、底图里不存在的代码必须显式检查。
unmatched <- setdiff(economy_map_data$iso3, world_sf$map_key)
if (length(unmatched)) {
  message("Natural Earth 未匹配:", paste(unmatched, collapse = ", "))
}

map_colors <- get_palette("mitonuclear_blue", type = "sequential")
missing_color <- "#D7D6D0"

map_theme <- theme_void(base_size = 13) +
  theme(
    legend.position = "bottom",
    legend.title = element_text(face = "bold", size = 11),
    legend.text = element_text(size = 10),
    plot.title = element_text(face = "bold", size = 16, hjust = 0),
    plot.subtitle = element_text(color = "grey35", hjust = 0),
    plot.caption = element_text(color = "grey45", hjust = 0),
    plot.margin = margin(14, 14, 10, 14)
  )

A · 从低到高的连续渐进色阶

色阶限制固定为 -5+5,即全球中位数的约 1/3232 倍;极少数超出范围的值压到端点。每个颜色位置固定,切换年份时不会因为样本极值变化而重新解释同一种颜色。

#| fig: continuous
#| fig-width: 10
#| fig-height: 6.3
ggplot(world_map) +
  geom_sf(
    aes(fill = relative_log2),
    color = "white",
    linewidth = 0.12
  ) +
  scale_fill_gradientn(
    colors = map_colors,
    limits = c(-5, 5),
    breaks = c(-4, -2, 0, 2, 4),
    labels = c("1/16×", "1/4×", "Median", "4×", "16×"),
    oob = squish,
    na.value = missing_color,
    name = "GDP per capita\nrelative to median"
  ) +
  coord_sf(xlim = c(-180, 180), ylim = c(-60, 85), expand = FALSE) +
  labs(
    title = "GDP per capita relative to the global median",
    subtitle = paste0(
      "2023 · log₂ ratio · median $",
      comma(round(world_median))
    ),
    caption = "Economic data: World Bank WDI · Boundaries: Natural Earth · Grey: unavailable"
  ) +
  map_theme +
  theme(legend.key.width = unit(1.55, "cm"))
choropleth_map — continuous
choropleth_map-continuous

B · 固定倍数分箱

五档边界固定为中位数的 1/4、约 0.7、约 1.44 倍。它们来自 log₂ 空间中的 -2-0.50.52,不是每次重算的分位数。

#| fig: binned
#| fig-width: 10
#| fig-height: 6.3
bin_breaks <- c(-Inf, -2, -0.5, 0.5, 2, Inf)
bin_levels <- c("≤ 1/4×", "1/4–0.7×", "0.7–1.4×", "1.4–4×", "≥ 4×")

world_map_binned <- world_map |>
  mutate(
    relative_band = cut(
      relative_log2,
      breaks = bin_breaks,
      labels = bin_levels,
      include.lowest = TRUE,
      ordered_result = TRUE
    )
  )

binned_colors <- map_colors[
  round(seq(1, length(map_colors), length.out = length(bin_levels)))
]
binned_palette <- setNames(binned_colors, bin_levels)

ggplot(world_map_binned) +
  geom_sf(
    aes(fill = relative_band),
    color = "white",
    linewidth = 0.12
  ) +
  scale_fill_manual(
    values = binned_palette,
    breaks = bin_levels,
    drop = FALSE,
    na.value = missing_color,
    na.translate = TRUE,
    name = "GDP per capita\nrelative to median"
  ) +
  coord_sf(xlim = c(-180, 180), ylim = c(-60, 85), expand = FALSE) +
  labs(
    title = "GDP per capita in five fixed bands",
    subtitle = "2023 · thresholds stay comparable across datasets and years",
    caption = "Economic data: World Bank WDI · Boundaries: Natural Earth · Grey: unavailable"
  ) +
  map_theme +
  theme(legend.key.width = unit(1.15, "cm"))
choropleth_map — binned
choropleth_map-binned

两种方法怎么选

  1. 连续数据、需要保留细微差别 → A。
  2. 定性色板、屏幕较小或读者只需要稳定档位 → B。
  3. 跨年份比较 → 固定中心、固定限制或固定倍数边界;不要每年按各自分位数重新切。
  4. 需要精确排名 → 不用地图,改画排序柱状图并标出有效样本量。

Palette Lab 固定使用相同的 relative_log2 指标:渐进与发散色板走连续插值,定性色板走五档固定分箱。改变的只是色板的呈现方式,国家、年份、指标和基准都不变。

运行环境6 个包 · 2026-08-11T19:27:26.968+0800

R version 4.5.1 (2025-06-13 ucrt) · x86_64-w64-mingw32

  • biopalette 0.1.0
  • dplyr 1.2.1
  • ggplot2 4.0.3
  • rnaturalearth 1.1.0
  • scales 1.4.0
  • sf 1.0.21