からっぽのしょこ

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

【R】2次元ランダムウォークのアニメーションの作図:全方向移動【gganimate】

はじめに

 gganimateパッケージについて理解したいシリーズです。
 この記事では、2次元ランダムウォークのアニメーションをR言語で作成します。

【前の内容】

www.anarchive-beta.com

【他の内容】

www.anarchive-beta.com

【目次】

2次元ランダムウォーク:全方向移動

 gganimateパッケージを利用して、平面上を移動するランダムウォークのアニメーション(gif画像)を作成します。transition_reveal()については、transition_reveal.Rmdを参照してください。

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

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


全方向移動の確認

まずは、平面上をランダムに移動する処理を確認します。

 単位円上の点を作成します。

# フレーム数を指定
N <- 101

# 単位円上の点を作成
df <- tibble::tibble(
  n = 1:N, # 時計回りならN:1
  d = seq(from = 0, to = 1, length.out = N), 
  r = 2 * pi * d, 
  x = cos(r), 
  y = sin(r)
)
head(df)
## # A tibble: 6 x 5
##       n     d      r     x      y
##   <int> <dbl>  <dbl> <dbl>  <dbl>
## 1     1  0    0      1     0     
## 2     2  0.01 0.0628 0.998 0.0628
## 3     3  0.02 0.126  0.992 0.125 
## 4     4  0.03 0.188  0.982 0.187 
## 5     5  0.04 0.251  0.969 0.249 
## 6     6  0.05 0.314  0.951 0.309

 0から1の値dを作成します。
 さらに、円周率\piを使ってr = 2 \pi dとします。rは、0から2 \piの値になります。
 x軸の値をx = \cos r、y軸の値をy = \sin rで計算します。

 作成した点を描画します。

# 単位円上の点を作図
anim <- ggplot(df, aes(x = x, y = y)) + 
  geom_point(color = "red", size = 5) + # 散布図
  geom_path(color = "hotpink") + # 折れ線
  gganimate::transition_reveal(d) + # フレーム
  coord_fixed(ratio = 1) + # アスペクト比
  labs(subtitle = "d = {frame_along}")

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

単位円上の点

 0 \leq d &lt; 1の(0 \leq r &lt; 2 \piの)の点が、原点から半径1の円上の点になるのが分かります。

 作成した点と原点を繋ぐ線を描画します。

# 原点から単位円上の点の方向を作図
anim <- df %>% 
  rbind(tibble::tibble(n = 1:N, d = NA, r = NA, x = 0, y = 0)) %>% # 原点を追加
  ggplot(aes(x = x, y = y)) + 
  geom_line(color = "hotpink") + # 折れ線グラフ
  geom_point(color = "red", fill = "red", size = 5) + # 散布図
  gganimate::transition_manual(n) + # フレーム
  coord_fixed(ratio = 1) + # アスペクト比
  labs(subtitle = "n = {current_frame}")

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

単位円上の点への移動

 作成した点(x, y)は、原点(0, 0)からx軸方向にx・y軸方向にy移動(加算)した点であり、また直線距離で1移動した点と言えます。
 つまり、dを0から1の(rを0から2 \piの)乱数として生成すると、現在の点(位置)からランダムな方向に距離1だけ移動できます。

1サンプル

 1個のサンプルを描画します。

 0から2 \piの一様乱数を生成します。

# 試行回数を指定
max_iter <- 100

# ランダムに値を生成
random_vec <- 2 * pi * runif(n = max_iter, min = 0, max = 1)
random_vec[1:5]
## [1] 0.9700719 2.3439519 0.6918857 2.7661949 4.0917664

 一様乱数は、runif()で生成できます。最小値の引数min0、最大値の引数max1を指定します。(本当は0以上1未満の乱数を生成しないといけないのですが、やり方が分かりませんでした。)
 生成した0から1の乱数に、2 * piを掛けます。max引数に2 * piを指定してもできますが、ここでは原理的な部分が分かりやすいように処理しています。

 生成した値からx軸とy軸の値を計算してデータフレームに格納して、過去の値の合計を求めます。

# 試行ごとに集計
random_df <- tibble::tibble(
  iteration = 0:max_iter, 
  x = c(0, cos(random_vec)), 
  y = c(0, sin(random_vec))
) %>% 
  dplyr::mutate(
    x = cumsum(x), 
    y = cumsum(y)
  ) # 各試行までの合計
head(random_df)
## # A tibble: 6 x 3
##   iteration      x     y
##       <int>  <dbl> <dbl>
## 1         0  0     0    
## 2         1  0.565 0.825
## 3         2 -0.133 1.54 
## 4         3  0.637 2.18 
## 5         4 -0.293 2.55 
## 6         5 -0.875 1.73

 0回目の結果(スタート地点・原点)に相当する行を加えておきます。
 x列とy列それぞれの累積和をcumsum()で計算します。

 アニメーションを作成します。

# 2次元ランダムウォークを作図
anim <- ggplot(random_df, aes(x = x, y = y)) + 
  geom_point(color = "hotpink", size = 5) + # 散布図
  geom_path(color = "hotpink", size = 1, alpha = 0.5) + # 折れ線
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") + # 水平線
  geom_vline(xintercept = 0, color = "red", linetype = "dashed") + # 垂直線
  gganimate::transition_reveal(along = iteration) + # フレーム
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = "Random Walk", 
       subtitle = "iter : {frame_along}")

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

2次元ランダムウォーク

 目安として、x = 0y = 0の線を引いています。

複数サンプル

 続いて、複数個のサンプルを同時に描画します。

 0から2 \piの一様乱数を生成します。

# 試行回数を指定
max_iter <- 100

# サンプルサイズを指定
sample_size <- 10

# ランダムに値を生成
random_vec <- 2 * pi * runif(n = max_iter*sample_size, min = 0, max = 1)
random_vec[1:5]
## [1] 0.5864319 5.5377602 3.5582797 2.4157349 0.1761415

 試行回数max_iterとサンプルサイズsample_sizeを指定して、max_iter * sample_size個の値を生成します。

 生成した値をデータフレームに格納します。

# 元になるデータフレームを確認
tibble::tibble(
  iteration = rep(0:max_iter, each = sample_size), 
  id = rep(1:sample_size, times = max_iter+1), 
  x = c(rep(0, times = sample_size), cos(random_vec)), 
  y = c(rep(0, times = sample_size), sin(random_vec))
) %>% 
  dplyr::filter(iteration == 1) %>% # 1回目の結果を表示
  head()
## # A tibble: 6 x 4
##   iteration    id      x      y
##       <int> <int>  <dbl>  <dbl>
## 1         1     1  0.833  0.553
## 2         1     2  0.735 -0.678
## 3         1     3 -0.914 -0.405
## 4         1     4 -0.748  0.664
## 5         1     5  0.985  0.175
## 6         1     6 -0.821  0.571

 全てのサンプルに対して0回目の結果に相当する行を加えます。
 また、random_vecの1番目の要素をサンプル1の1回目の結果、2番目の要素をサンプル2の1回目の結果と続けて、さらにsample_size + 1番目の要素をサンプル1の2回目の結果と続けて割り当てることにします。

 0からmax_iterまでの整数をそれぞれsample_size個に複製して、試行回数(iteration)列とします。
 1からsample_sizeまでの整数をmax_iter + 1回繰り返して、サンプル番号(id)列とします。
 sample_size個の0の後にcos(random_vec), sin(random_vec)を続けて、x軸y軸(x, y)列とします。

 サンプルごとに過去の値を合計を求めます。

# 試行ごとに集計
random_df <- tibble::tibble(
  iteration = rep(0:max_iter, each = sample_size), 
  id = rep(1:sample_size, times = max_iter+1) %>% 
    as.factor(), 
  x = c(rep(0, times = sample_size), cos(random_vec)), 
  y = c(rep(0, times = sample_size), sin(random_vec))
) %>% 
  dplyr::group_by(id) %>% # サンプルごとにグループ化
  dplyr::mutate(
    x = cumsum(x), 
    y = cumsum(y)
  ) %>% # 各試行までの合計
  dplyr::ungroup() # グループ化の解除

# 3サンプルの3回目までの結果
random_df %>% 
  dplyr::filter(iteration <= 3, id %in% 1:3)
## # A tibble: 12 x 4
##    iteration id         x      y
##        <int> <fct>  <dbl>  <dbl>
##  1         0 1      0      0    
##  2         0 2      0      0    
##  3         0 3      0      0    
##  4         1 1      0.833  0.553
##  5         1 2      0.735 -0.678
##  6         1 3     -0.914 -0.405
##  7         2 1      0.559  1.52 
##  8         2 2      0.299 -1.58 
##  9         2 3     -1.20   0.555
## 10         3 1      0.811  0.547
## 11         3 2     -0.325 -2.36 
## 12         3 3     -1.29   1.55

 作図用に、サンプル番号(id列の値)を因子型にしておきます。
 サンプル番号(id列)でグループ化して、x軸の値(x列)とy軸の値(y列)をそれぞれcumsum()で累積和を計算します。

 アニメーションを作成します。

# 2次元ランダムウォークを作図
anim <- ggplot(random_df, aes(x = x, y = y, color = id)) + 
  geom_point(size = 5, show.legend = FALSE) + # 散布図
  geom_path(size = 1, alpha = 0.3, show.legend = FALSE) + # 折れ線
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") + # 水平線
  geom_vline(xintercept = 0, color = "red", linetype = "dashed") + # 垂直線
  gganimate::transition_reveal(along = iteration) + # フレーム
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = "Random Walk", 
       subtitle = "iter : {frame_along}")

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

2次元ランダムウォーク

 sample_size個の折れ線グラフが描画されます。

 最終結果は次のようになります。

# 最終結果
random_df %>% 
  dplyr::filter(iteration == max_iter) %>% 
  ggplot() + 
  geom_point(mapping = aes(x = x, y = y, color = id), 
             size = 5, show.legend = FALSE) + # 散布図
  geom_path(data = random_df, mapping = aes(x = x, y = y, color = id), 
            size = 1, alpha = 0.3, show.legend = FALSE) + # 折れ線
  geom_hline(yintercept = 0, color = "red", linetype = "dashed") + # 水平線
  geom_vline(xintercept = 0, color = "red", linetype = "dashed") + # 垂直線
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = "Random Walk", 
       subtitle = paste0("iter : ", max_iter))

2次元ランダムウォーク

 以上で、2次元ランダムウォークを作図できました。

おわりに

 試作段階では酷いコードだったんですが、清書してると思いの外きれいに処理できて良かったです。