【问题标题】:How does gganimate order an ordered bar time-series?gganimate 如何订购有序条形时间序列?
【发布时间】:2019-03-08 11:43:10
【问题描述】:

我有一个时间序列数据,其中我在 y 轴 DIAG_RATE_65_PLUS 上绘制疾病的诊断率,在 x 轴 NAME 上绘制地理组以进行比较,作为一个简单的条形图。我的时间变量是ACH_DATEyearmon,动画正在循环播放,如标题所示。

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')

我重新排序了NAME,所以它的排名是DIAG_RATE_65_PLUS

gganimate 产生了什么:

我现在有两个问题:

1) gganimate 如何准确地重新排序数据?有一些总体上的一般重新排序,但每个月都没有框架,其中组由DIAG_RATE_65_PLUS 从最小到最大完美排序。理想情况下,我希望完美订购最后一个月的“2018 年 8 月”。之前所有月份的 x 轴都可以基于“2018 年 8 月”的排序 NAME

2) gganimate 中是否有一个选项可以让组在条形图中每个月“转移”到正确的排名?

我的评论查询图:

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')

使用hjust=2,标签不对齐并四处移动。

把上面的代码改成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')

【问题讨论】:

  • 您可以发布您的实际数据样本吗?我们可能会建议动画条形图的替代方案,以更好地处理您的数据。
  • @eipi10 您生成的 df 几乎完美地再现了我正在使用的 df,但我已将一小部分数据粘贴到 pastebin here。在您的最后一次编辑中,有没有办法合并geom_vline?它似乎与ggstance geoms 不兼容,但geom_hline 出于某种原因有效,但对我的情节没有用。在我的问题中添加了代码。
  • 为右对齐标签添加一些间距的一个技巧是使用 hjust = 1 并将标签更改为 aes(label = paste(country, " ")) 以便它们右对齐但有额外的空格。 hjust > 1 似乎有一个错误。在这里查看答案:stackoverflow.com/questions/53162821/…

标签: r animation ggplot2 dplyr gganimate


【解决方案1】:

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

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

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))))

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

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)) 

您可以在下面看到,当我们明确地将 DIAG_RATE_65_PLUSNAME 相加并按总和排序时,排序是相同的:

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

现在我们要创建一个动画,将NAMEDIAG_RATE_65_PLUS 分别为每个ACH_DATEyearmon 排序。为此,让我们首先生成一个名为 order 的新列,用于设置我们想要的顺序:

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

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

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

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")

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

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')

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

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

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")

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

【讨论】:

  • 谢谢,这是一种非常新颖的(仅限 AFAIK)方法来解决我的问题。为了完整起见,我将如何为我的图表设置动画,以便 x 轴仅按 NAME 的 2018 年 8 月值排序,并在其余月份保持此顺序?这可能是一个更普遍的 dplyr 问题,但它仍然让我感到困惑。
  • 另外,有什么方法可以合并coord_flip()?使用我的图表和您的数据,来自轴标签和刻度的帧不断闪烁。
  • 抱歉最后一个问题,我尝试过扩展帧数以使过渡变得平滑,但 x 轴文本似乎每个月都会旋转出图。是否有一个功能可以使文本在几个月循环时不会转出?编辑:在起始帖子中添加了情节。
【解决方案2】:

为了补充@eipi10 的出色答案,我认为这是一个值得更换geom_bar 以获得更大灵活性的案例。 geom_bar 通常对于离散类别非常方便,但它并不能让我们充分利用 gganimate 的丝滑动画荣耀。

例如,使用geom_tile,我们可以重新创建与 geom_bar 相同的外观,但在 x 轴上具有流畅的移动。这有助于保持对每个条的视觉跟踪,并查看哪些条的顺序变化最多。我认为这很好地解决了您问题的第二部分。

为了完成这项工作,我们可以在数据中添加一个新列,显示每个月应该使用的顺序。我们将此订单保存为双精度,而不是整数(使用* 1.0)。这将允许gganimate 在位置 1 和位置 2 之间设置动画时在位置 1.25 放置一个条。

df2 <- df %>%
  group_by(ACH_DATEyearmon) %>%
  mutate(ordering = min_rank(DIAG_RATE_65_PLUS) * 1.0) %>%
  ungroup() 

现在我们可以以类似的方式进行绘图,但使用geom_tile 而不是geom_bar。我想在顶部和轴上都显示NAME,所以我使用了两个具有不同y 值的geom_text 调用,一个在零处,一个在条形的高度。 vjust 让我们使用文本行单元垂直对齐每个。

这里的另一个技巧是关闭 coord_cartesian 中的剪辑,这让底部文本位于绘图区域下方,进入 x 轴文本通常所在的位置。

p <- df2 %>%
  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") +
  # text on top of bars
  geom_text(aes(y = DIAG_RATE_65_PLUS, label = NAME), vjust = -0.5) +
  # text in x-axis (requires clip = "off" in coord_cartesian)
  geom_text(aes(y = 0, label = NAME), vjust = 2) +
  coord_cartesian(clip = "off", expand = FALSE) +

  labs(title='{closest_state}', x = "") +
  theme(plot.title = element_text(hjust = 1, size = 22),
        axis.ticks.x = element_blank(),
        axis.text.x  = element_blank()) + 

  transition_states(ACH_DATEyearmon, 
                    transition_length = 2, state_length = 1) +
  ease_aes('cubic-in-out')

animate(p, nframes = 300, fps = 20, width = 400, height = 300)

回到您的第一个问题,这是我通过从 geom_tile 调用中删除 fill = "gray60" 制作的彩色版本。我按照 2017 年 8 月的顺序对 NAME 类别进行了排序,因此它们看起来像您所描述的那样是按顺序排列的。

可能有更好的方法来进行排序,但我通过将 df2 加入到只有 2017 年 8 月排序的表中来做到这一点。

Aug_order <- df %>%
  filter(ACH_DATEyearmon == "Aug 2017") %>%
  mutate(Aug_order = min_rank(DIAG_RATE_65_PLUS) * 1.0) %>%
  select(NAME, Aug_order)

df2 <- df %>%
  group_by(ACH_DATEyearmon) %>%
  mutate(ordering = min_rank(DIAG_RATE_65_PLUS) * 1.0) %>%
  ungroup() %>%
  left_join(Aug_order) %>%
  mutate(NAME = fct_reorder(NAME, -Aug_order))

【讨论】:

  • 谢谢,这对我的需要来说几乎是完美的。有没有办法将 coord_flip() 与此合并?我试过 eipi10 的编辑评论,但由于某种原因这与 geom_vline 不兼容。我在尝试合并 coord_flip() 和 geom 线时进行了编辑。我希望条形图的标签位于 y 轴的左侧,就像在标准 ggplot 中一样。
  • 请注意,在我的完整 df 中,标签要长得多,如果有任何区别的话。
  • 我尝试了另一个需要动画水平条形图的问题:stackoverflow.com/questions/53162821/…
猜你喜欢
  • 2019-07-18
  • 2018-06-17
  • 2021-06-06
  • 1970-01-01
  • 1970-01-01
  • 2019-03-20
  • 2015-04-17
  • 2013-10-06
  • 2015-06-18
相关资源
最近更新 更多