如何用gganimate实现geom_bar/geom_col填充区块的移动动画?
问题描述
我在同一数据框中通过Group变量区分两类数据状态:
Original状态:对应10个等级的不等计数数据Transformed Internal状态:由原始数据归一化得到的等计数数据
尝试用gganimate实现从原始数据到归一化数据的无缝过渡动画,但两种方案均失败:
- 使用
geom_bar时,仅Rank 1过渡正常,其余等级过渡异常 - 使用
geom_tile时,过渡效果差,且第二个状态的柱状图显示异常
相关代码与数据
原始状态绘图代码
df |> filter(Group == "Original") |> ggplot(aes(original_rank, count)) + geom_bar(stat = "identity", aes(fill = transformed_rank)) + scale_fill_manual(values = RColorBrewer::brewer.pal(10, "RdYlGn"), name = "Original Rank", guide = guide_legend(byrow = TRUE))
转换后状态绘图代码
df |> filter(Group == "Transformed Internal") |> ggplot(aes(original_rank, count)) + geom_bar(stat = "identity", aes(fill = transformed_rank)) + scale_fill_manual(values = RColorBrewer::brewer.pal(10, "RdYlGn"), name = "Original Rank", guide = guide_legend(byrow = TRUE))
尝试的geom_bar动画代码
df |> ggplot(aes(original_rank, count)) + geom_bar(stat = "identity", aes(fill = transformed_rank)) + scale_fill_manual(values = RColorBrewer::brewer.pal(10, "RdYlGn"), name = "Original Rank", guide = guide_legend(byrow = TRUE)) + labs(title = "{closest_state}") + transition_states(Group)
尝试的geom_tile动画代码
df |> ggplot(aes(original_rank, count, fill = transformed_rank)) + geom_tile(aes(y = count/2, height = count, width = 0.9)) + scale_fill_manual(values = RColorBrewer::brewer.pal(10, "RdYlGn"), name = "IMD 2019 Decile", guide = guide_legend(byrow = TRUE)) + labs(title = "{closest_state}") + transition_states(Group)
示例数据
df <- structure(list(original_rank = structure(c(1L, 2L, 3L, 4L, 4L, 5L, 5L, 6L, 6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 9L, 10L, 10L, 1L, 1L, 1L, 1L, 2L, 2L, 3L, 3L, 4L, 5L, 5L, 6L, 6L, 7L, 7L, 8L, 9L, 9L, 10L), levels = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"), class = "factor"), transformed_rank = structure(c(1L, 2L, 3L, 4L, 4L, 5L, 5L, 6L, 6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 9L, 10L, 10L, 1L, 2L, 3L, 4L, 4L, 5L, 5L, 6L, 6L, 6L, 7L, 7L, 8L, 8L, 9L, 9L, 9L, 10L, 10L), levels = c("1", "2", "3", "4", "5", "6", "7", "8", "9", "10"), class = "factor"), count = c(179L, 2562L, 5886L, 1468L, 9084L, 1091L, 8698L, 1463L, 10170L, 5367L, 4699L, 6707L, 3381L, 6510L, 3642L, 10054L, 1663L, 8398L, 10165L, 179L, 2562L, 5886L, 1468L, 9084L, 1091L, 8698L, 1463L, 10170L, 5367L, 4699L, 6707L, 3381L, 6510L, 3642L, 10054L, 1663L, 8398L, 10165L), Group = structure(c(1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L, 2L), levels = c("Original", "Transformed Internal"), class = "factor")), row.names = c(NA, -38L), class = c("tbl_df", "tbl", "data.Frame"))
解决方案
问题根源在于原数据中部分original_rank在同一个Group下存在多行记录,导致geom_bar绘制时自动堆叠,transition_states无法正确匹配两个状态下的对应柱子。解决步骤如下:
- 聚合数据:对每个
Group和original_rank的计数求和,确保每个等级在每个状态下只有一行记录 - 优化动画参数:使用
transition_states的transition_length和state_length控制过渡时长,搭配ease_aes让过渡更平滑
完整代码
library(dplyr) library(ggplot2) library(gganimate) library(RColorBrewer) # 聚合数据,消除同一等级的多行堆叠问题 df_agg <- df |> group_by(Group, original_rank, transformed_rank) |> summarise(count = sum(count), .groups = "drop") # 生成平滑过渡动画 df_agg |> ggplot(aes(x = original_rank, y = count, fill = transformed_rank)) + geom_col(width = 0.9) + # 替代geom_bar(stat="identity"),效果一致更直观 scale_fill_manual(values = brewer.pal(10, "RdYlGn"), name = "Original Rank", guide = guide_legend(byrow = TRUE)) + labs(title = "{closest_state}") + transition_states(Group, transition_length = 2, state_length = 1) + ease_aes("cubic-in-out")
说明
- 聚合后的数据让
transition_states能准确识别两个状态下的对应柱子,实现所有等级的平滑过渡 ease_aes("cubic-in-out")设置了缓动效果,让动画过渡更自然- 若
Transformed Internal状态确实是等计数,聚合后该状态下所有original_rank的count值会相等,动画将清晰展示从不等高到等高的变化效果
内容的提问来源于stack exchange,提问作者Nautica
相关产品推荐
相关产品推荐

