からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

transition_reveal関数【gganimate】

はじめに

 gganimateパッケージについて理解したいシリーズです。
 この記事では、transition_reveal関数の機能を確認します。

【他の内容】

www.anarchive-beta.com

【目次】

transition_reveal

 gganimateパッケージの関数transition_reveal()について解説します。transition_reveal()は、アニメーション(gif画像)のフレーム切り替えを制御します。

 利用するパッケージを読み込みます。

# 利用パッケージ
library(ggplot2)
library(gganimate)

 基本的に、パッケージ名::関数名()の記法で書きます。ただし作図コードに関してはごちゃごちゃしてしまうので、ggplot2パッケージの関数は関数名のみで、gganimateパッケージの関数はパッケージ名を明示して書きます。

データフレームの作成

 まずは、作図に利用するデータフレームを作成します。

# データフレームを作成
df <- tibble::tibble(
  x = 0:5, 
  y = 0:5, 
  along = 0:5
)
df
## # A tibble: 6 x 3
##       x     y along
##   <int> <int> <int>
## 1     0     0     0
## 2     1     1     1
## 3     2     2     2
## 4     3     3     3
## 5     4     4     4
## 6     5     5     5

 分かりやすい値を作成しておきます。tibble::tibble()を使ってデータフレームを作成していますが、data.frame()を使っても問題ありません。

 transition_reveal()along引数(第1引数)に、フレームの順序を示す列を指定します。この例では、along列が対応します。

 このデータを用いた散布図と折れ線グラフのアニメーションを通じて、transition_reveal()の機能を確認していきます。

グラフとの関係

 グラフの種類(geom_***())によって、transition_reveal()の挙動が変わります。

・散布図の場合

# 散布図を作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 棒グラフ
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal() + geom_point()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331064256g:plain
散布図

 フレーム列(along列)の値に従い、フレームごとに描画されるデータ(行)が切り替わります。よって、点が移動するように見えます。
 また、データ間が補完されます。詳しくは「along引数」節で確認します。

・棒グラフの場合

# 棒グラフを作成
anim <- ggplot(df, aes(x = 1, y = y)) + 
  geom_bar(stat = "identity", fill = "hotpink", color = "hotpink") + # 棒グラフ
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal() + geom_bar()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331064326g:plain
棒グラフ

 棒グラフの場合も同様です。y列の値に応じてバーが伸び縮みするように描画されます。

・折れ線グラフの場合

# 折れ線グラフを作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_line(size = 1, color = "hotpink") + # 棒グラフ
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal() + geom_line()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331064344g:plain
折れ線グラフ

 折れ線グラフgeom_line()geom_path()の場合は、過去のフレームのデータ(行)も描画されます。よって、データの履歴や軌跡のように見えます。

・散布図と折れ線グラフの場合

# 折れ線グラフを作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 棒グラフ
  geom_path(size = 1, color = "hotpink") + # 棒グラフ
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal() + geom_point() + geom_path()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331064401g:plain
散布図と折れ線グラフ

 散布図に関しては現フレームのデータのみが描画され、折れ線グラフに関しては過去フレームのデータも描画されます。

ラベル変数

 transition_reveal()で使えるラベル変数frame_alongを確認します。

 ラベル変数名を{}で挟んだ文字列をlabs()などに指定します。

# ラベル変数を指定
lv <- "frame_along" # 現フレームの値

# ラベル変数の設定
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal() + labs(", lv, ")"), 
       subtitle = paste0("frame : {", lv, "}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331064419g:plain
ラベル変数

 "{frame_along}"は、現在のフレームに対応する値に置き換わります。表示される値については「along引数」節で掘り下げます。

 解説用に複雑な作図コードになっていますが、title = "{frame_along}"のように指定してください。
 また、"{round(frame_along, 1)}"のように書くことで、ラベル変数の値に関数を使えます。

引数

 続いて、transition_reveal()で利用できる3つの引数(alongrangekeep_last)を確認します。

range

 range引数は、描画するフレームを2つの値を持つベクトルで指定します。

# 値を指定:(デフォルト:NULL)
r <- NULL
r <- c(2L, 3L)

# range(描画範囲)の設定
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along, range = r) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal(range = c(", paste0(r, collapse = ", "), "))"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 10, fps = 10)

f:id:anemptyarchive:20220331064446g:plainf:id:anemptyarchive:20220331064448g:plain
描画範囲の引数

 デフォルトはNULLで、全てのデータを描画します。(NULLの図は、サブタイトルに表示する文字列とフレーム数の設定を変更しています。)
 c(始まりの値, 終わりの値)の形で指定することで、描画するデータ(行)を指定できます。この例では、整数型(integer)なので2L, 3Lのように指定しています。数値型(numeric)であれば2, 3のように指定します。

keep_last

 keep_last引数は、最後のフレームにおいて折れ線グラフ以外のグラフを描画するかを論理値で指定します。

# 値を指定:(デフォルト:TRUE)
b <- TRUE
#b <- FALSE

# keep_last(最後の描画)の設定
anim <- ggplot(head(df, 3), aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along, keep_last = b) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal(keep_last = ", b, ")"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 3, fps = 1)

f:id:anemptyarchive:20220331064540g:plainf:id:anemptyarchive:20220331064542g:plain
最後のフレームのグラフ描画の引数

 デフォルトはTRUEで、最後のフレームでもそれまでのフレームと同様に描画します。
 FALSEを指定すると、最後のフレームでは描画しません。

along

 along引数(第1引数)に指定する値の影響を確認します。

 等間隔でない値をフレーム列とします。

# 等間隔でないフレーム列を作成
df <- tibble::tibble(
  x = c(1, 2, 10), 
  y = c(1, 2, 10), 
  along = c(1, 2, 10)
)

# 折れ線グラフを作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 10, fps = 1)

f:id:anemptyarchive:20220331064732g:plain
フレーム切替の引数:等間隔でない

 データの大小関係に応じて補完されます。そのため(この例だと?)、データ間のフレーム数が変わり、データ全体で等間隔になります。(transition_states()の場合は、データ間のフレーム数は一定で、データ間ごとに等間隔に補完されます。)

 フレーム数(nframes引数の値)を増やしてみます。

# gif画像を作成
gganimate::animate(plot = anim, nframes = 100, fps = 10)

f:id:anemptyarchive:20220331064756g:plain
フレーム切替の引数:データ数とフレーム数が異なる場合

 ラベル変数の値が、各フレームに対応した値になっているのが分かります。(transition_states()の場合は、フレーム列の値がそのまま表示されます。)

 表示されている値は次のように再現できます。

# 確認
seq(from = 1, to = 10, length.out = 100)[1:15]
##  [1] 1.000000 1.090909 1.181818 1.272727 1.363636 1.454545 1.545455 1.636364
##  [9] 1.727273 1.818182 1.909091 2.000000 2.090909 2.181818 2.272727

 これまでの例で整数が表示されていた理由は謎。

 棒グラフの場合も同様です。

# 棒グラフを作成
anim <- ggplot(df, aes(x = 1, y = y)) + 
  geom_bar(stat = "identity", fill = "hotpink", color = "hotpink") + # 棒グラフ
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = 1, minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 101, fps = 10)

f:id:anemptyarchive:20220331064855g:plain
フレーム切替の引数:データ数とフレーム数が異なる場合

 割り切れる値のフレーム数を決め打ちで指定できます。または、{round(frame_along, 2)}で丸め込めます。

 重複しない不規則な値をフレーム列とします。

# 不規則なフレーム列を作成
df <- tibble::tibble(
  x = 1:5, 
  y = 1:5, 
  along = c(1, 3, 2, 5, 4)
)

# 折れ線グラフを作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 40, fps = 10)

f:id:anemptyarchive:20220331064943g:plain
フレーム切替の引数:不規則な値

 謎。上の行から読み込んで、データ(点)が合流すると消える?

 along列を昇順に並び替えてみます。

# 不規則なフレーム列を作成
df <- dplyr::arrange(df, along)
df
## # A tibble: 5 x 3
##       x     y along
##   <int> <int> <dbl>
## 1     1     1     1
## 2     3     3     2
## 3     2     2     3
## 4     5     5     4
## 5     4     4     5

f:id:anemptyarchive:20220331065037g:plain
フレーム切替の引数:不規則な値

 意図通りの変化になりました。along列が昇順になるようにしておくのが無難かもしれません。

 重複する値をフレーム列とします。

# 重複するフレーム列を作成
df <- tibble::tibble(
  x = 1:6, 
  y = 1:6, 
  along = c(1, 1, 2, 3, 4, 3)
)

# 折れ線グラフを作成
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  #geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(along = along) + # フレーム
  scale_x_continuous(breaks = df[["x"]], minor_breaks = FALSE) + # x軸目盛
  scale_y_continuous(breaks = df[["y"]], minor_breaks = FALSE) + # y軸目盛
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = paste0("transition_reveal()"), 
       subtitle = paste0("frame : {frame_along}"))

# gif画像を作成
gganimate::animate(plot = anim, nframes = 50, fps = 10)

f:id:anemptyarchive:20220331065101g:plain
フレーム切替の引数:重複する値

 謎。フレーム列が同じ値のデータは同時(同じフレーム)に描画されるが、隣り合ってると即合流したものとみなされ?消える?

 (よく分からないこともありましたが)以上で、ラベル変数と引数の機能を確認しました。次は、実用を想定してグラフを作成します。

利用例:確率分布

  gganimateパッケージの使用例として、正規分布(ガウス分布)の標準偏差(パラメータ)とグラフの形状の関係をアニメーションで可視化します。

 利用するパッケージを読み込みます。

# 利用パッケージ
library(ggplot2)
library(gganimate)


データの作成

 まずは、描画する正規分布のデータを作成します。

# x軸の値を作成
x_vals <- seq(from = -5, to = 5, by = 0.1)

# 標準偏差として利用する値を作成
sd_vec <- seq(from = 0.5, to = 3, by = 0.2)

# 標準偏差ごとにガウス分布の計算
dens_df <- tibble::tibble()
for(sd in sd_vec) {
  # ガウス分布の確率密度を計算
  tmp_df <- tibble::tibble(
    x = x_vals, 
    sigma = sd, 
    density = dnorm(x = x_vals, mean = 0, sd = sd)
  )
  
  # 計算結果を結合
  dens_df <- rbind(dens_df, tmp_df)
}
head(dens_df)
## # A tibble: 6 x 3
##       x sigma  density
##   <dbl> <dbl>    <dbl>
## 1  -5     0.5 1.54e-22
## 2  -4.9   0.5 1.11e-21
## 3  -4.8   0.5 7.76e-21
## 4  -4.7   0.5 5.19e-20
## 5  -4.6   0.5 3.33e-19
## 6  -4.5   0.5 2.06e-18

 グラフとして描画するx軸の値(確率変数Xがとり得る値)を作成してx_valsとします。
 標準偏差\sigmaとして利用する値を作成してsd_vecとします。

 for()を使って、sd_vecの要素ごとに繰り返し正規分布に従う確率密度を計算します。計算結果をdens_dfに追加していきます。
 正規分布の確率密度は、dnorm()で計算できます。確率変数の引数xx_vals、平均の引数meanにこの例では0、標準偏差の引数にsdを指定します。

作図

 アニメーションを作成する前に、全てのグラフを重ねて描画して確認します。

# ガウス分布を作図
ggplot(dens_df, aes(x = x, y = density, color = as.factor(sigma))) + 
  geom_line() + # 折れ線グラフ
  labs(title = "Gaussian Distribution", 
       color = "sigma")

f:id:anemptyarchive:20220331065206p:plain
正規分布

 sigma列をas.factor()で因子型に変換すると、標準偏差ごとに色付けされます。

 パラメータごとにフレームを切り替えて描画します。

# ガウス分布のアニメーションを作成:折れ線グラフ
anim <- ggplot(dens_df, aes(x = x, y = density, color = as.factor(sigma))) + 
  geom_line(show.legend = FALSE) + # 折れ線グラフ
  gganimate::transition_reveal(along = sigma) + # フレーム
  labs(title = "transition_reveal() + geom_line()", 
       subtitle = "sigma = {frame_along}")

# gif画像を作成
gganimate::animate(plot = anim, nframes = length(sd_vec), fps = 10)

f:id:anemptyarchive:20220331065222g:plain
正規分布:パラメータと分布の変化

 折れ線グラフの場合は、過去フレームのデータが残るので、描画される分布が増えていきます。

 散布図でも同様に描画します。

# ガウス分布のアニメーションを作成:散布図
anim <- ggplot(dens_df, aes(x = x, y = density, color = as.factor(sigma), group = x)) + 
  geom_point(show.legend = FALSE) + # 散布図
  gganimate::transition_reveal(along = sigma) + # フレーム
  labs(title = "transition_reveal() + geom_point()", 
       subtitle = "sigma = {frame_along}")

# gif画像を作成
gganimate::animate(plot = anim, nframes = length(sd_vec), fps = 10)

f:id:anemptyarchive:20220331065248g:plain
正規分布:パラメータと分布の変化

 group引数にx列を指定すると、標準偏差の値によって各点が変化する様子が分かります。
 ただし、パラメータ数よりもフレーム数を多く指定すると、データ間を自動で補完されます。

・コード(クリックで展開)

 フレーム数を10倍にしたグラフを作成します。

# ガウス分布のアニメーションを作成:散布図
anim <- ggplot(dens_df, aes(x = x, y = density, color = as.factor(sigma), group = x)) + 
  geom_point(show.legend = FALSE) + # 散布図
  gganimate::transition_reveal(along = sigma) + # フレーム
  labs(title = "transition_reveal() + geom_point()", 
       subtitle = "sigma = {frame_along}")

# gif画像を作成
gganimate::animate(plot = anim, nframes = length(sd_vec)*10, fps = 10)

 フレーム数の引数nframessd_vec10倍の値を指定します。

 パラメータ数を10倍にしたグラフを作成します。

# 標準偏差として利用する値を作成
tmp_sd_vec <- seq(from = 0.5, to = 3, length.out = length(sd_vec)*10)

# 標準偏差ごとにガウス分布の計算
tmp_dens_df <- tibble::tibble()
for(sd in sd_vec) {
  # ガウス分布の確率密度を計算
  tmp_df <- tibble::tibble(
    x = x_vals, 
    sigma = sd, 
    density = dnorm(x = x_vals, mean = 0, sd = sd)
  )
  
  # 計算結果を結合
  tmp_dens_df <- rbind(tmp_dens_df, tmp_df)
}

# ガウス分布のアニメーションを作成:散布図
anim <- ggplot(tmp_dens_df, aes(x = x, y = density, color = as.factor(sigma), group = x)) + 
  geom_point(show.legend = FALSE) + # 散布図
  gganimate::transition_reveal(along = sigma) + # フレーム
  labs(title = "transition_reveal() + geom_point()", 
       subtitle = "sigma = {frame_along}")

# gif画像を作成
gganimate::animate(plot = anim, nframes = length(tmp_sd_vec), fps = 10)

 sd_vecと同じ範囲で値の数が10倍(値の間隔が10分の1)になるように作成します。


f:id:anemptyarchive:20220331065305g:plainf:id:anemptyarchive:20220331065308g:plain
データ補完の是非

 補完された分布と、実際に計算した分布の違いが分かり・・・
 データ間の補完が不要な場合は、transition_manual()を使うのがいいと思います。

 最後に、x軸の値ごとにフレームを切り替えます。

# ガウス分布のアニメーションを作成:散布図
anim <- ggplot(dens_df, aes(x = x, y = density, color = as.factor(sigma), group = sigma)) + 
  geom_point(show.legend = FALSE) + # 散布図
  geom_path(show.legend = FALSE) + # 折れ線
  gganimate::transition_reveal(along = x) + # フレーム
  labs(title = "transition_reveal() + geom_point() + geom_path()", 
       subtitle = "x = {round(frame_along, 1)}")

# gif画像を作成
gganimate::animate(plot = anim, nframes = length(x_vals), fps = 10)

f:id:anemptyarchive:20220331065417g:plain
正規分布:x軸とy軸の変化

 group引数にsigma列を指定すると、x軸の値の変化によってy軸の値が変化する様子を確認できます。

利用例:ランダムウォーク

 ランダムウォークのアニメーションについては次の記事を参照してください。

www.anarchive-beta.com

www.anarchive-beta.com

www.anarchive-beta.com


おわりに

 微妙に分からないところがありました。それと、分布の計算のところのループ処理をtidyverse的に処理したいのですが、長らくできないでいます。