且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

gganimate 如何对有序条形时间序列进行排序?

更新时间:2023-02-26 15:58:47

条形排序由 ggplot 完成,不受 gganimate 影响.条形根据每个 ACH_DATEyearmonDIAG_RATE_65_PLUS 的总和进行排序.下面我将展示条形图的排序方式,然后提供用于创建动画图的代码,并在每一帧中按照从低到高的顺序进行排序.

要查看条形如何排序,首先让我们创建一些假数据:

图书馆(tidyverse)图书馆(gganimate)主题集(主题经典())# 假数据日期 = paste(rep(month.abb, each=10), 2017)set.seed(2)df = data.frame(NAME=c(replicate(12, sample(LETTERS[1:10]))),ACH_DATEyearmon=factor(dates, levels=unique(dates)),DIAG_RATE_65_PLUS=c(replicate(12, rnorm(10, 30, 5))))

现在让我们制作一个单条形图.条形是每个 NAMEDIAG_RATE_65_PLUS 的总和.注意 x 轴 NAME 值的顺序:

df %>%ggplot(aes(重新排序(名称,DIAG_RATE_65_PLUS),DIAG_RATE_65_PLUS))+geom_bar(stat = "identity", alpha = 0.66) +实验室(标题='{closest_state}')+主题(plot.title = element_text(hjust = 1, size = 22))

您可以在下面看到,当我们通过 NAME 显式求和 DIAG_RATE_65_PLUS 并按总和排序时,顺序是相同的:

df %>% group_by(NAME) %>%总结(DIAG_RATE_65_PLUS = sum(DIAG_RATE_65_PLUS))%>%安排(DIAG_RATE_65_PLUS)

 NAME DIAG_RATE_65_PLUS1 一个 336.12712 小时 345.23693 乙 346.71514 我 350.14805 东 356.43336 C 367.47687 天 368.22258 楼 368.37659 J 368.965510 克 387.1523

现在我们要创建一个动画,按 DIAG_RATE_65_PLUS 为每个 ACH_DATEyearmon 分别对 NAME 进行排序.为此,让我们首先生成一个名为 order 的新列,用于设置我们想要的排序:

df = df %>%安排(ACH_DATEyearmon,DIAG_RATE_65_PLUS)%>%变异(顺序 = 1:n())

现在我们创建动画.transition_states 为每个 ACH_DATEyearmon 生成帧.view_follow(fixed_y=TRUE)仅显示当前 ACH_DATEyearmon 的 x 值,并为所有帧保持相同的 y 轴范围.

请注意,我们使用 order 作为 x 变量,但随后我们运行 scale_x_continuous 将 x 标签更改为 NAME 值.我已将这些标签包含在图中,因此您可以看到它们随每个 ACH_DATEyearmon 而变化,但您当然可以像在示例中那样在实际图中删除它们.

p = df %>%ggplot(aes(order, DIAG_RATE_65_PLUS)) +geom_bar(stat = "identity", alpha = 0.66) +实验室(标题='{closest_state}')+主题(plot.title = element_text(hjust = 1, size = 22)) +scale_x_continuous(breaks=df$order,labels=df$NAME) +transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +view_follow(fixed_y=TRUE) +ease_aes('线性')动画(p,nframes = 60)anim_save("test.gif")

如果您关闭 view_follow(),您可以看到整个"图的样子(当然,您可以通过在之前停止代码来查看完整的非动画图transition_states 行).

p = df %>%ggplot(aes(order, DIAG_RATE_65_PLUS)) +geom_bar(stat = "identity", alpha = 0.66) +实验室(标题='{closest_state}')+主题(plot.title = element_text(hjust = 1, size = 22)) +scale_x_continuous(breaks=df$order,labels=df$NAME) +transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +#view_follow(fixed_y=TRUE) +ease_aes('线性')

更新:回答您的问题...

要按给定月份的值排序,请将数据转换为具有按该月排序的级别的因子.要绘制旋转图,而不是 coord_flip,我们将使用 ggstance 包中的 geom_barh(水平条形图).请注意,我们必须在 aesview_follow() 中切换 y 和 x,并且 y 轴 NAME 值的顺序现在是常数:

库(ggstance)# 根据 2017 年 8 月的值设置 NAME 顺序df = df %>%安排(DIAG_RATE_65_PLUS)%>%变异(NAME = factor(NAME, levels=unique(NAME[ACH_DATEyearmon=="Aug 2017"])))p=df%>%ggplot(aes(y=NAME, x=DIAG_RATE_65_PLUS)) +geom_barh(stat = "identity", alpha = 0.66) +实验室(标题='{closest_state}')+主题(plot.title = element_text(hjust = 1, size = 22)) +transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +view_follow(fixed_x=TRUE) +ease_aes('线性')动画(p,nframes = 60)anim_save("test3.gif")

对于平滑过渡,@JonSpring 的回答似乎处理得很好.

I have a time-series of data, where I'm plotting diagnosis rates for a disease on the y-axis DIAG_RATE_65_PLUS, and geographical groups for comparison on the x-axis NAME as a simple bar graph. My time variable is ACH_DATEyearmon, which the animation is cycling through as seen in the title.

df %>% ggplot(aes(reorder(NAME, DIAG_RATE_65_PLUS), DIAG_RATE_65_PLUS)) +
  geom_bar(stat = "identity", alpha = 0.66) +
  labs(title='{closest_state}') +
  theme(plot.title = element_text(hjust = 1, size = 22),
        axis.text.x=element_blank()) +
  transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 1) +
  ease_aes('linear')

I've reordered NAME so it gets ranked by DIAG_RATE_65_PLUS.

What gganimate produces:

I now have two questions:

1) How exactly does gganimate reorder the data? There is some overall general reordering, but each month has no frame where the groups are perfectly ordered by DIAG_RATE_65_PLUS from smallest to biggest. Ideally, I would like the final month "Aug 2018" to be ordered perfectly. All of the previous months can have their x-axis based on the ordered NAME for "Aug 2018`.

2) Is there an option in gganimate where the groups "shift" to their correct rank for each month in the bar chart?

Plots for my comment queries:

https://i.stack.imgur.com/s2UPw.gif https://i.stack.imgur.com/Z1wfd.gif

@JonSpring

    df %>%
  ggplot(aes(ordering, group = NAME)) +
  geom_tile(aes(y = DIAG_RATE_65_PLUS/2, 
                height = DIAG_RATE_65_PLUS,
                width = 0.9), alpha = 0.9, fill = "gray60") +
  geom_hline(yintercept = (2/3)*25, linetype="dotdash") +
  # text in x-axis (requires clip = "off" in coord_cartesian)
  geom_text(aes(y = 0, label = NAME), hjust = 2) + ## trying different hjust values
  theme(plot.title = element_text(hjust = 1, size = 22),
        axis.ticks.y = element_blank(), ## axis.ticks.y shows the ticks on the flipped x-axis (the now metric), and hides the ticks from the geog layer
        axis.text.y = element_blank()) + ## axis.text.y shows the scale on the flipped x-axis (the now metric), and hides the placeholder "ordered" numbers from the geog layer
  coord_cartesian(clip = "off", expand = FALSE) +
  coord_flip() +
  labs(title='{closest_state}', x = "") +
  transition_states(ACH_DATEyearmon, 
                    transition_length = 2, state_length = 1) +
  ease_aes('cubic-in-out')

With hjust=2, labels are not aligned and move around.

Changing the above code with hjust=1

@eipi10

df %>% 
  ggplot(aes(y=NAME, x=DIAG_RATE_65_PLUS)) +
  geom_barh(stat = "identity", alpha = 0.66) +
  geom_hline(yintercept=(2/3)*25, linetype = "dotdash") + #geom_vline(xintercept=(2/3)*25) is incompatible, but geom_hline works, but it's not useful for the plot
  labs(title='{closest_state}') +
  theme(plot.title = element_text(hjust = 1, size = 22)) +
  transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +
  view_follow(fixed_x=TRUE) +
  ease_aes('linear')

The bar ordering is done by ggplot and is not affected by gganimate. The bars are being ordered based on the sum of DIAG_RATE_65_PLUS within each ACH_DATEyearmon. Below I'll show how the bars are ordered and then provide code for creating the animated plot with the desired sorting from low to high in each frame.

To see how the bars are ordered, first let's create some fake data:

library(tidyverse)
library(gganimate)
theme_set(theme_classic())

# Fake data
dates = paste(rep(month.abb, each=10), 2017)

set.seed(2)
df = data.frame(NAME=c(replicate(12, sample(LETTERS[1:10]))),
                ACH_DATEyearmon=factor(dates, levels=unique(dates)),
                DIAG_RATE_65_PLUS=c(replicate(12, rnorm(10, 30, 5))))

Now let's make a single bar plot. The bars are the sum of DIAG_RATE_65_PLUS for each NAME. Note the order of the x-axis NAME values:

df %>% 
  ggplot(aes(reorder(NAME, DIAG_RATE_65_PLUS), DIAG_RATE_65_PLUS)) +
  geom_bar(stat = "identity", alpha = 0.66) +
  labs(title='{closest_state}') +
  theme(plot.title = element_text(hjust = 1, size = 22)) 

You can see below that the ordering is the same when we explicitly sum DIAG_RATE_65_PLUS by NAME and sort by the sum:

df %>% group_by(NAME) %>% 
  summarise(DIAG_RATE_65_PLUS = sum(DIAG_RATE_65_PLUS)) %>% 
  arrange(DIAG_RATE_65_PLUS)

   NAME DIAG_RATE_65_PLUS
1     A          336.1271
2     H          345.2369
3     B          346.7151
4     I          350.1480
5     E          356.4333
6     C          367.4768
7     D          368.2225
8     F          368.3765
9     J          368.9655
10    G          387.1523

Now we want to create an animation that sorts NAME by DIAG_RATE_65_PLUS separately for each ACH_DATEyearmon. To do this, let's first generate a new column called order that sets the ordering we want:

df = df %>% 
  arrange(ACH_DATEyearmon, DIAG_RATE_65_PLUS) %>% 
  mutate(order = 1:n())

Now we create the animation. transition_states generates the frames for each ACH_DATEyearmon. view_follow(fixed_y=TRUE)shows x-values only for the current ACH_DATEyearmon and maintains the same y-axis range for all frames.

Note that we use order as the x variable, but then we run scale_x_continuous to change the x-labels to be the NAME values. I've included these labels in the plot so you can see that they change with each ACH_DATEyearmon, but you can of course remove them in your actual plot as you did in your example.

p = df %>% 
  ggplot(aes(order, DIAG_RATE_65_PLUS)) +
    geom_bar(stat = "identity", alpha = 0.66) +
    labs(title='{closest_state}') +
    theme(plot.title = element_text(hjust = 1, size = 22)) +
    scale_x_continuous(breaks=df$order, labels=df$NAME) +
    transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +
    view_follow(fixed_y=TRUE) +
    ease_aes('linear')

animate(p, nframes=60)

anim_save("test.gif")

If you turn off view_follow(), you can see what the "whole" plot looks like (and you can, of course, see the full, non-animated plot by stopping the code before the transition_states line).

p = df %>% 
  ggplot(aes(order, DIAG_RATE_65_PLUS)) +
    geom_bar(stat = "identity", alpha = 0.66) +
    labs(title='{closest_state}') +
    theme(plot.title = element_text(hjust = 1, size = 22)) +
    scale_x_continuous(breaks=df$order, labels=df$NAME) +
    transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +
    #view_follow(fixed_y=TRUE) +
    ease_aes('linear')

UPDATE: To answer your questions...

To order by a given month's values, turn the data into a factor with the levels ordered by that month. To plot a rotated graph, instead of coord_flip, we'll use geom_barh (horizontal bar plot) from the ggstance package. Note that we have to switch the y's and x's in aes and view_follow() and that the order of the y-axis NAME values is now constant:

library(ggstance)

# Set NAME order based on August 2017 values
df = df %>% 
  arrange(DIAG_RATE_65_PLUS) %>% 
  mutate(NAME = factor(NAME, levels=unique(NAME[ACH_DATEyearmon=="Aug 2017"])))

p = df %>% 
  ggplot(aes(y=NAME, x=DIAG_RATE_65_PLUS)) +
  geom_barh(stat = "identity", alpha = 0.66) +
  labs(title='{closest_state}') +
  theme(plot.title = element_text(hjust = 1, size = 22)) +
  transition_states(ACH_DATEyearmon, transition_length = 1, state_length = 50) +
  view_follow(fixed_x=TRUE) +
  ease_aes('linear')

animate(p, nframes=60)
anim_save("test3.gif")

For smooth transitions, it seems like @JonSpring's answer handles that well.