からっぽのしょこ

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

transition_munal関数【gganimate】

はじめに

 gganimateパッケージについて理解したいシリーズ(予定)です。
 この記事では、transition_munal()関数の機能を深掘りします。

【他の内容】

www.anarchive-beta.com

【目次】

transition_manual

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

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

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

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

データフレームの作成

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

# データフレームを作成
df <- tibble::tibble(
  x = 0:5, 
  y = 0:5, 
  frame = 0:5
)
df
## # A tibble: 6 x 3
##       x     y frame
##   <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_manual()のフレームの引数frames(第1引数)に、フレームの順序を示す列を指定します。この例では、frame列が対応します。

 作成したデータをグラフで確認しておきます。

# 折れ線グラフを作成
ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 5, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 散布図
  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 = "geom_point()")

f:id:anemptyarchive:20220326233541p:plain
散布図

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

ラベル変数

 transition_manual()で使える3つのラベル変数(previous_framecurrent_framenext_frame)を確認します。

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

# ラベル変数を指定
lv <- "previous_frame" # 前のデータの値
#lv <- "current_frame"  # 現在のデータの値
#lv <- "next_frame"     # 次のデータの値

# ラベル変数の設定
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線グラフ
  gganimate::transition_manual(frames = frame, cumulative = TRUE) + # フレーム
  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_manual() + labs(", lv, ")"), 
       subtitle = paste0("frame : {", lv, "}"))

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

f:id:anemptyarchive:20220326233816g:plainf:id:anemptyarchive:20220326233818g:plainf:id:anemptyarchive:20220326233837g:plain
ラベル変数

 ピンク色の点が増える(次のフレームのグラフに変わる)タイミングで値が変わるのが分かります。  "{previous_frame}"は、前のフレーム名(指定した列の値)に置き換わります。空白から始まり0から4まで値が変わり、最後の値(5)は表示されないのが分かります。
 "{current_frame}"は、現在のフレーム名の値になります。frame列の値と同じ0から5まで変化するのが分かります。
 "{current_frame}"は、次のフレームの値になります。最初の値(0)は表示されず、1から5まで値が変わり最後は空白になるのが分かります。

 解説用に複雑な作図コードになっていますが、title = "{current_frame}"のように指定してください。

引数

 続いて、transition_manual()で利用できる2つの引数(framecumulative)を確認します。

cumulative

 cumulative引数は、現在のフレームのデータと一緒に、前のフレームのデータを描画するかを論理値で指定します。

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

# cumulative(過去データの利用)の設定
anim <- ggplot(df, aes(x = x, y = y, group = 1)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  gganimate::transition_manual(frames = frame, cumulative = 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_manual(cumulative = ", b, ")"), 
       subtitle = paste0("current_frame : {current_frame}"))

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

f:id:anemptyarchive:20220326233901g:plainf:id:anemptyarchive:20220326233904g:plain
過去データの利用の引数

 デフォルトはFALSEで、過去のフレームのデータを使いません。TRUEを指定すると、過去のデータも描画されます。
 この例では、TRUEだと最大6個の点が表示され、FALSEだと1個の点が移動するように描画されます。

frames

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

 まずは、重複しないランダムな整数をフレーム列とします。

# データ数を指定
N <- 15

# ランダムに整数を生成
label_int <- sample(x = 1:N, size = N, replace = FALSE)

# データフレームを作成
df <- tibble::tibble(
  x = 1:N, 
  y = 1:N, 
  frame = label_int
)
head(df)
## # A tibble: 6 x 3
##       x     y frame
##   <int> <int> <int>
## 1     1     1    13
## 2     2     2     5
## 3     3     3     1
## 4     4     4    12
## 5     5     5    11
## 6     6     6     6


 このデータフレームを用いて、グラフを作成します。

# frames(フレーム切替)の設定
anim <- ggplot(df, aes(x = x, y = y, group = 1)) + 
  geom_point(size = 10, color = "hotpink") + # 散布図
  geom_path(color = "hotpink") + # 折れ線グラフ
  gganimate::transition_manual(frames = frame, cumulative = TRUE) + # フレーム
  scale_x_continuous(breaks = 1:N, minor_breaks = FALSE) + # x軸目盛
  labs(title = "transition_manual()", 
       subtitle = "frame : {current_frame}")

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

f:id:anemptyarchive:20220326234011g:plain
フレーム引数の例:数値

 frames引数に指定した列の値が小さい順に描画されます。

 昇順に並べ替えると次のようになります。

# フレーム列で昇順に並び替え
df %>% 
  dplyr::arrange(frame) %>% 
  head()
## # A tibble: 6 x 3
##       x     y frame
##   <int> <int> <int>
## 1     3     3     1
## 2    14    14     2
## 3    10    10     3
## 4    11    11     4
## 5     2     2     5
## 6     6     6     6

 点が表示される順番と一致しているのが分かります。

 次は、フレーム列の値が等間隔でない場合を見ます。

# ランダムな間隔の整数を生成
label_int <- sample(x = 1:(N*10), size = N, replace = FALSE) %>% 
  sort()
label_int
##  [1]   2   6  13  14  17  18  21  50  56  60  80  92 100 108 122

 分かりやすいようにsort()で昇順に並び替えておきます。

f:id:anemptyarchive:20220326234046g:plain
フレーム引数の例:数値

 値の間隔は影響せずに描画されました。

 フレーム列の値が重複する場合を見ます。

# ランダムに整数を生成
label_int <- sample(x = 1:N, size = N, replace = TRUE) %>% 
  sort()
label_int
##  [1]  1  3  3  4  4  7  7  9 10 11 12 12 15 15 15


f:id:anemptyarchive:20220326234141g:plain
フレーム引数の例:数値

 値が重複する複数の点(複数行のデータ)が同じフレームに描画されました。

 続いて、文字列を指定してみます。

# 文字列のラベルを作成
label_chr <- paste0("n=", 1:N)

# データフレームを作成
df <- tibble::tibble(
  x = 1:N, 
  y = 1:N, 
  frame = label_chr
)
head(df)
## # A tibble: 6 x 3
##       x     y frame
##   <int> <int> <chr>
## 1     1     1 n=1  
## 2     2     2 n=2  
## 3     3     3 n=3  
## 4     4     4 n=4  
## 5     5     5 n=5  
## 6     6     6 n=6

 n=は同じでその後の文字を通し番号とします。

f:id:anemptyarchive:20220326234153g:plain
フレーム引数の例:文字列

 意図しない順になりました。

 フレーム列で昇順に並び替えてみます。

# フレーム列で昇順に並び替え
df %>% 
  dplyr::arrange(frame)
## # A tibble: 15 x 3
##        x     y frame
##    <int> <int> <chr>
##  1     1     1 n=1  
##  2    10    10 n=10 
##  3    11    11 n=11 
##  4    12    12 n=12 
##  5    13    13 n=13 
##  6    14    14 n=14 
##  7    15    15 n=15 
##  8     2     2 n=2  
##  9     3     3 n=3  
## 10     4     4 n=4  
## 11     5     5 n=5  
## 12     6     6 n=6  
## 13     7     7 n=7  
## 14     8     8 n=8  
## 15     9     9 n=9

 辞書の並びのように、3文字目の1によって意図しない並びになっています。

 これは次のようにも確認できます。

# 昇順に並び替え
sort(label_chr)
##  [1] "n=1"  "n=10" "n=11" "n=12" "n=13" "n=14" "n=15" "n=2"  "n=3"  "n=4" 
## [11] "n=5"  "n=6"  "n=7"  "n=8"  "n=9"


 文字列型から因子型に変換して使います。

# 因子型のラベルを作成
label_fct <- factor(label_chr, levels = label_chr)

# データフレームを作成
df <- tibble::tibble(
  x = 1:N, 
  y = 1:N, 
  frame = label_fct
)
head(df)
## # A tibble: 6 x 3
##       x     y frame
##   <int> <int> <fct>
## 1     1     1 n=1  
## 2     2     2 n=2  
## 3     3     3 n=3  
## 4     4     4 n=4  
## 5     5     5 n=5  
## 6     6     6 n=6

 factor()のレベルのlevel引数にもlabel_chrを指定します。

 昇順に並び替えて確認します。

# 昇順に並び替え
sort(label_fct)
##  [1] n=1  n=2  n=3  n=4  n=5  n=6  n=7  n=8  n=9  n=10 n=11 n=12 n=13 n=14 n=15
## 15 Levels: n=1 n=2 n=3 n=4 n=5 n=6 n=7 n=8 n=9 n=10 n=11 n=12 n=13 ... n=15


f:id:anemptyarchive:20220326234211g:plain
フレーム引数の例:因子

 因子型のレベルを指定することで、意図したグラフになりました。
 ただし、このグラフを素直に作るなら、数値のフレーム列を作り"n={current_frame}"とします。

 以上で、ラベル変数と引数の機能を確認できました。次は、実用を想定してグラフを作成します。

利用例:サイコロ

 gganimateパッケージの使用例として、サイコロの出目の頻度を可視化します。

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

# 利用するパッケージ
library(tidyverse)
library(gganimate)


データの生成

 まずは、利用するデータを作成します。

# 試行回数を指定
N <- 30

# 面の数を指定
V <- 6

# サイコロを振る
result_mat <- rmultinom(n = N, size = 1, prob = rep(1 / V, times = V)) %>% 
  t()
result_mat[1:5, ]
##      [,1] [,2] [,3] [,4] [,5] [,6]
## [1,]    0    1    0    0    0    0
## [2,]    0    0    0    1    0    0
## [3,]    1    0    0    0    0    0
## [4,]    0    1    0    0    0    0
## [5,]    1    0    0    0    0    0

 サイコロを振った結果は、多項分布の乱数生成関数rmultinom()で再現できます。
 試行回数の引数nN、1試行当たりのサイコロの数の引数size1を指定します。サイコロの各面の確率の引数probは、デフォルトで一様になるので指定する必要はありませんが、1 / Vの確率であることを明示的に書いています。

 rmultinom()の出力をt()で転置しておきます。行が各試行、列が各面に対応しており、値が1の列番号が出た目を表します。

 出目の値を抽出します。

# 出目を抽出
result_vec <- which(t(result_mat) == 1, arr.ind = TRUE)[, "row"]
result_vec[1:5]
## [1] 2 4 1 2 1

 result_matに対して、値が1の列番号を抽出します。
 which()は、条件に合う要素の行番号と列番号を返します。result_matを転置して渡す必要があります。転置するので、行番号("row"列)を取り出します。

 サイコロを振った結果が得られたので、アニメーションで可視化していきます。

折れ線グラフによる可視化

 試行回数と出た目の値をデータフレームに格納します。

# 結果を格納
result_df <- tibble::tibble(
  v = c(NA_integer_, result_vec), 
  n = 0:N
)
head(result_df)
## # A tibble: 6 x 2
##       v     n
##   <int> <int>
## 1    NA     0
## 2     2     1
## 3     4     2
## 4     1     3
## 5     2     4
## 6     1     5

 グラフの変化を分かりやすくするために、0回目の結果に相当する行を追加します。出目はないので(整数型の)欠損値NA_integer_を使います。

 出目の推移のグラフを作成します。

# 出目の推移を作図
anim <- ggplot(result_df, aes(x = n, y = v)) + 
  geom_path(color = "hotpink") + # 折れ線グラフ
  geom_point(mapping = aes(color = factor(v)), size = 5) + # 散布図
  scale_color_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # 枠線の色:(不必要)
  gganimate::transition_manual(frames = n, cumulative = TRUE) + 
  labs(title = "geom_point() + geom_path() + transition_manual()", 
       subtitle = "n = {current_frame}", 
       color = "v")

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

f:id:anemptyarchive:20220326234236g:plain
出目の推移

 散布図と折れ線グラフを重ねて描画します。

 次は、頻度に注目します。

 各試行までに出た目の数をそれぞれカウントします。

# 出目を集計
count_df <- result_mat %>% 
  tibble::as_tibble() %>% # データフレームに変換
  cumsum() %>% # 試行ごとに集計
  dplyr::mutate(n = 1:N) %>% # 試行回数列を追加
  tidyr::pivot_longer(
    cols = -n, 
    names_to = "v", 
    names_prefix = "V", 
    names_ptypes = list(v = factor()), 
    values_to = "count"
  ) %>% # 縦型のデータフレームに変換
  rbind(
    tibble::tibble(
      n = rep(0, times = V), 
      v = factor(1:V), 
      count = rep(0, times = V)
    ), 
    .
  ) # 0回目の結果を追加

# 1回から3回目時点の結果
count_df %>% 
  dplyr::filter(n >= 1, n <= 3)
## # A tibble: 18 x 3
##        n v     count
##    <dbl> <fct> <dbl>
##  1     1 1         0
##  2     1 2         1
##  3     1 3         0
##  4     1 4         0
##  5     1 5         0
##  6     1 6         0
##  7     2 1         0
##  8     2 2         1
##  9     2 3         0
## 10     2 4         1
## 11     2 5         0
## 12     2 6         0
## 13     3 1         1
## 14     3 2         1
## 15     3 3         0
## 16     3 4         1
## 17     3 5         0
## 18     3 6         0

 cumsum()で累積和(1行目からn行目までの和)を計算します。
 最後にこちらも、0回目の結果に相当する行(V行のデータフレーム)を追加します。

 n列が3の6行を見ると、1回目と2回目の結果が反映されているのが分かります。

 各面の出現回数の推移のグラフを作成します

# 出目の推移を作図
anim <- ggplot() + 
  geom_line(data = count_df, mapping = aes(x = n, y = count, color = v), size = 1) + # 折れ線グラフ
  scale_color_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # 枠線の色:(不必要)
  gganimate::transition_manual(frames = n, cumulative = TRUE) + 
  labs(title = "geom_line() + transition_manual()", 
       subtitle = "n = {current_frame}")

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

f:id:anemptyarchive:20220326234317g:plain
出現回数の推移

 面(v列)ごとに色分けしてそれぞれ折れ線グラフを描画します。

 ここに装飾を付け足します。

 試行回数の列nと同じ値を持つframe列を作成しておきます。

# フレーム切替用の列を追加
count_df <- count_df %>% 
  dplyr::mutate(frame = n)
head(count_df)
## # A tibble: 6 x 4
##       n v     count frame
##   <dbl> <fct> <dbl> <dbl>
## 1     0 1         0     0
## 2     0 2         0     0
## 3     0 3         0     0
## 4     0 4         0     0
## 5     0 5         0     0
## 6     0 6         0     0


 各試行までの結果を複製します。

# 過去の試行を複製
count_df2 <- tibble::tibble()
for(i in 0:N) {
  # i回目までの結果を抽出
  tmp_df <- count_df %>% 
    dplyr::filter(n <= i) %>% # i回目までの結果を抽出
    dplyr::mutate(frame = i) # フレーム切替用の列を追加
  
  # 結果を結合
  count_df2 <- rbind(count_df2, tmp_df)
}

# 2フレームに表示するデータ
count_df2 %>% 
  dplyr::filter(frame == 2)
## # A tibble: 18 x 4
##        n v     count frame
##    <dbl> <fct> <dbl> <int>
##  1     0 1         0     2
##  2     0 2         0     2
##  3     0 3         0     2
##  4     0 4         0     2
##  5     0 5         0     2
##  6     0 6         0     2
##  7     1 1         0     2
##  8     1 2         1     2
##  9     1 3         0     2
## 10     1 4         0     2
## 11     1 5         0     2
## 12     1 6         0     2
## 13     2 1         0     2
## 14     2 2         1     2
## 15     2 3         0     2
## 16     2 4         1     2
## 17     2 5         0     2
## 18     2 6         0     2

 nフレーム(frame列がn)のときに、0からn回目の結果(n0からn)を持つデータフレームを作成します。本当はfor()を使わずにやりたい。

 出目の推移のグラフを作成します。

# 出目の出現回数の推移を作図
anim <- ggplot() + 
  geom_point(data = count_df, mapping = aes(x = n, y = count, color = v), 
             size = 5) + # 散布図
  geom_path(data = count_df2, mapping = aes(x = n, y = count, color = v), 
            size = 1) + # 折れ線グラフ
  gganimate::transition_manual(frames = frame, cumulative = FALSE) + 
  scale_color_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # 枠線の色:(不必要)
  labs(title = "geom_point() + geom_path() + transition_manual()", 
       subtitle = "n = {current_frame}")

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

f:id:anemptyarchive:20220326234341g:plain
出現回数の推移

 過去のデータを持つデータフレームを用意したので、couulativ引数をFALSEにします。

 棒グラフでも可視化します。

# 出目の出現回数の推移を作図
anim <- ggplot() + 
  geom_bar(data = count_df, mapping = aes(x = v, y = count, fill = v, color = v), 
           stat = "identity") + # 棒グラフ
  geom_point(data = result_df, mapping = aes(x = v, y = 0), 
             color = "hotpink", size = 5) + # 散布図
  scale_fill_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # バーの色:(不必要)
  scale_color_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # 枠線の色:(不必要)
  gganimate::transition_manual(frames = n) + 
  labs(title = "geom_bar() + geom_point() + transition_states()", 
       subtitle = "n = {current_frame}")

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

f:id:anemptyarchive:20220326234355g:plain
出現回数の推移


 次は、出現数の順位に注目します。

 各試行における出目の合計の順位を追加します。

# 出目の数により順位付け
rank_df <- count_df %>% 
  dplyr::group_by(n) %>% # グループ化
  dplyr::mutate(ranking = dplyr::row_number(-count)) %>% # 順位列を追加
  dplyr::ungroup() # グループ化の解除

# 出目の数により順位付け
rank_df2 <- count_df2 %>% 
  dplyr::group_by(n, frame) %>% # グループ化
  dplyr::mutate(ranking = dplyr::row_number(-count)) %>% # 順位列を追加
  dplyr::ungroup() # グループ化の解除
head(rank_df)
## # A tibble: 6 x 5
##       n v     count frame ranking
##   <dbl> <fct> <dbl> <dbl>   <int>
## 1     0 1         0     0       1
## 2     0 2         0     0       2
## 3     0 3         0     0       3
## 4     0 4         0     0       4
## 5     0 5         0     0       5
## 6     0 6         0     0       6

 row_number()で、昇順に整数を割り当てられます。ここでは、試行ごとに割り当てたいのでn列でグループ化して、出現回数count列が多い順に割り当てたいのでマイナス-を付けて大小関係を入れ変えています。
 過去データを重複して持つデータフレームについては、試行回数n列とフレーム番号frame列でグループ化します。

 出現回数の順位の推移のグラフを作成します。

# 出目の出現回数の順位の推移を作図
anim <- ggplot() + 
  geom_point(data = rank_df, mapping = aes(x = n, y = ranking, color = v, group = v), 
             size = 5) + # 散布図
  geom_path(data = rank_df2, mapping = aes(x = n, y = ranking, color = v, group = v), 
            size = 1) + # 折れ線グラフ
  gganimate::transition_manual(frames = frame, cumulative = FALSE) + 
  scale_y_reverse(breaks = 1:V, minor_breaks = FALSE) + # 
  scale_color_manual(values = c("pink", "limegreen", "red", "orange", "yellow", "mediumblue")) + # 枠線の色:(不必要)
  labs(title = "geom_point() + geom_path() + transition_manual()", 
       subtitle = "n = {current_frame}")

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

f:id:anemptyarchive:20220326234412g:plain
出現回数の順位の推移

 y軸をランキングにします。ただし、値が小さい(順位が高い)方を上にするためscale_y_reverse()を使います。

おわりに

 動いたから良しの精神でこれまでこの関数を使っていました。ようやく他のtransition_***()関数との違いも含めて少し理解できて良かったです。

 関係ないですが、久しぶりにライブに参加できてテンション上がってるのでセトリからこの曲をどうぞ!

 1st単独ホールツアーおめでとーーーーー最高だった!!!!!