使用gganimate与ggplot2遇错误:世界地图动画实现问题
问题描述
我想用rnaturalearth制作随年份变化的世界地图数值动画,已完成数据下载与合并:
baseBorderMap <- ne_countries(returnclass = 'sf') # 获取世界国家多边形的sf格式数据 baseBorderMap["51", "iso_a2_eh"] <- "NM" # 修改纳米比亚的国家代码 baseBorderMap["175", "iso_a2_eh"] <- "XK" # 修改科索沃的国家代码 aniData <- baseBorderMap %>% right_join(countryYearMam, by = "iso_a2_eh") # 将地图数据与含国家代码、数值、年份的数据集合并 aniData <- filter(aniData, !is.na(specimens)) # 移除无标本数据的记录
忽略年份时,用ggplot2可正常绘制静态地图:
mammalYear <- ggplot(data = aniData) + # 绘制地图 geom_sf(data = aniData, lwd = 0) + geom_sf(data = aniData, aes(fill = log(specimens)), lwd = 0.3) + # 按标本数量的对数设置填充色 scale_fill_viridis_c() + theme_bw() + theme(legend.title = element_blank())
但添加gganimate函数后,调用animate()或预览时出现错误:
timelapse_mammalYear <- mammalYear + transition_states(year, transition_length = 2, state_length = 1) + enter_fade() + exit_fade() > timelapse_mammalYear Error in vapply(from, nrow, integer(1)) : values must be length 1, but FUN(X[[8]]) result is length 0 In addition: Warning message: In max(area_diff) : no non-missing arguments to max; returning -Inf
补充信息:
- 所有列无NA值
- 数据集样例:
> dput(head(countryYearMam, 5)) structure(list(iso_a2_eh = c("AD", "AD", "AD", "AD", "AD"), year = c(1930L, 1950L, 1962L, 1965L, 1968L), specimens = c(1L, 12L, 24L, 14L, 3L)), row.names = c(NA, 5L), class = "data.frame")
- 年份范围:1793-2020;specimens范围:1-2601
请问该错误含义是什么?如何修改数据以适配gganimate实现地图动画?
错误原因与解决方案
错误含义
这个错误是因为gganimate处理sf地理数据的状态切换时,部分年份对应的地理多边形数据为空(长度为0)。具体来说,transition_states会按year分组处理数据,但有些年份下没有匹配到任何国家的地理多边形,导致vapply无法得到长度为1的结果;同时计算地理面积差异时没有有效值,触发警告。
解决方案
1. 补全年份-国家的完整组合
你的countryYearMam数据只有部分年份的部分国家数据,right_join后又过滤掉了无标本数据的记录,导致部分年份的地理数据不完整。需要为每个年份补全所有国家的记录,无标本数据的国家设为0(避免后续log计算报错):
# 生成所有年份与国家代码的完整组合 full_year_country <- expand.grid( iso_a2_eh = unique(baseBorderMap$iso_a2_eh), year = unique(countryYearMam$year), stringsAsFactors = FALSE ) # 合并原始数据,补全缺失的标本数据为0 full_data <- full_year_country %>% left_join(countryYearMam, by = c("iso_a2_eh", "year")) %>% mutate(specimens = ifelse(is.na(specimens), 0, specimens)) # 再与地图数据合并 aniData_full <- baseBorderMap %>% right_join(full_data, by = "iso_a2_eh")
2. 简化静态地图代码
无需重复调用geom_sf,同时处理log(0)的问题:
mammalYear <- ggplot(data = aniData_full) + geom_sf(aes(fill = log(specimens + 1)), lwd = 0.3) + # +1避免log(0)报错 scale_fill_viridis_c() + theme_bw() + theme(legend.title = element_blank())
3. 调整动画逻辑并生成动画
如果某些年份所有国家标本数都为0(比如早期年份),可以过滤掉这些年份,同时添加年份标题提升可读性:
# 过滤掉所有国家标本数都为0的年份 aniData_filtered <- aniData_full %>% group_by(year) %>% filter(sum(specimens) > 0) %>% ungroup() # 制作动画 timelapse_mammalYear <- ggplot(data = aniData_filtered) + geom_sf(aes(fill = log(specimens + 1)), lwd = 0.3) + scale_fill_viridis_c() + theme_bw() + theme(legend.title = element_blank()) + transition_states(year, transition_length = 2, state_length = 1) + enter_fade() + exit_fade() + labs(title = "年份: {closest_state}") # 添加动态年份标题 # 生成动画(可调整宽高适配需求) animate(timelapse_mammalYear, width = 1200, height = 800)
额外提示
- 检查
baseBorderMap中的iso_a2_eh是否与countryYearMam中的代码完全匹配,避免因代码不匹配导致的国家数据缺失 - 如果动画运行缓慢,可以用
ne_countries(scale = "medium")或"small"简化地理数据,降低多边形复杂度
内容的提问来源于stack exchange,提问作者user23851734
相关产品推荐
相关产品推荐

