からっぽのしょこ

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

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

はじめに

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

【前の内容】

www.anarchive-beta.com

【他の内容】

www.anarchive-beta.com

【目次】

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

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

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

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


1サンプル

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

 移動する方向を示す値をランダムに生成します。

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

# ランダムに値を生成
random_val_vec <- sample(c(-1, 1), size = max_iter, replace = TRUE)

# ランダムに軸を生成
random_axis_vec <- sample(c("x", "y"), size = max_iter, replace = TRUE)

# 確認
random_val_vec[1:10]; random_axis_vec[1:10]
##  [1]  1  1 -1  1 -1 -1 -1  1 -1 -1
##  [1] "y" "y" "y" "y" "y" "x" "x" "x" "y" "y"

 正方向の移動(1)または負方向の移動(-1)をランダムに生成します。
 さらに、x軸方向の移動("x")またはy軸方向の移動("y")をランダムに生成します。

 1yの組み合わせが生成されれば、y軸を正の方向に移動します。

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

# 元になるデータフレームを確認
tibble::tibble(
  iteration = c(0, 0, 1:max_iter), 
  random_val = c(0, 0, random_val_vec), 
  axis = c("x", "y", random_axis_vec)
) %>% 
  head()
## # A tibble: 6 x 3
##   iteration random_val axis 
##       <dbl>      <dbl> <chr>
## 1         0          0 x    
## 2         0          0 y    
## 3         1          1 y    
## 4         2          1 y    
## 5         3         -1 y    
## 6         4          1 y

 0回目の結果(スタート地点・原点)に相当する行を加えておきます。

 x軸・y軸の値ごとに過去の値の合計を求めます。

# 試行ごとに集計
random_df <- tibble::tibble(
  iteration = c(0, 0, 1:max_iter), 
  random_val = c(0, 0, random_val_vec), 
  axis = c("x", "y", random_axis_vec)
) %>% 
  tidyr::pivot_wider(
    names_from = axis, 
    values_from = random_val, 
    values_fill = 0
  ) %>% # 横型に変形
  dplyr::mutate(
    x = cumsum(x), 
    y = cumsum(y)
  ) # 各試行までの合計
head(random_df)
## # A tibble: 6 x 3
##   iteration     x     y
##       <dbl> <dbl> <dbl>
## 1         0     0     0
## 2         1     0     1
## 3         2     0     2
## 4         3     0     1
## 5         4     0     2
## 6         5     0     1

 pivot_wider()で、axis列とrandom_val列を展開します。
 names_from引数にaxisを指定します。axis列の値はxyなので、x列とy列ができます。
 values_from引数にrandom_valを指定します。x列とy列の値は、random_val列の対応する値または欠損値NAになります。
 values_fill引数に0を指定すると、NAではなく0になります。

 簡単な例で試してみます。

# 簡単な例で確認
tibble::tibble(
  iteration = c(1, 2), 
  random_val = c(-1, 1), 
  axis = c("y", "x")
) %>% 
  tidyr::pivot_wider(
    names_from = axis, 
    values_from = random_val
  )
## # A tibble: 2 x 3
##   iteration     y     x
##       <dbl> <dbl> <dbl>
## 1         1    -1    NA
## 2         2    NA     1

 追加された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の線を引いています。

複数サンプル

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

 移動する方向を示す値をランダムに生成します。

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

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

# ランダムに値を生成
random_value_vec <- sample(x = c(-1, 1), size = max_iter*sample_size, replace = TRUE)

# ランダムに軸を生成
random_axis_vec <- sample(x = c("x", "y"), size = max_iter*sample_size, replace = TRUE)

# 確認
random_val_vec[1:10]; random_axis_vec[1:10]
##  [1]  1  1 -1  1 -1 -1 -1  1 -1 -1
##  [1] "x" "y" "y" "y" "x" "y" "x" "x" "x" "x"

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

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

# 元になるデータフレームを確認
tidyr::tibble(
  iteration = rep(c(0, 0, 1:max_iter), each = sample_size), 
  id = rep(1:sample_size, times = max_iter+2), 
  random_val = c(rep(0, times = sample_size*2), random_value_vec), 
  axis = c(rep(c("x", "y"), each = sample_size), random_axis_vec)
) %>% 
  dplyr::filter(iteration == 1) %>% # 1回目の結果を表示
  head()
## # A tibble: 6 x 4
##   iteration    id random_val axis 
##       <dbl> <int>      <dbl> <chr>
## 1         1     1          1 x    
## 2         1     2          1 y    
## 3         1     3          1 y    
## 4         1     4          1 y    
## 5         1     5          1 x    
## 6         1     6         -1 y

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

 2つの01からmax_iterまでの整数をそれぞれsample_size個に複製して、試行回数(iteration)列とします。
 1からsample_sizeまでの整数をmax_iter + 2回繰り返して、サンプル番号(id)列とします。
 sample_size * 2個の0の後にrandom_value_vecを続けて、乱数(random_val)列とします。
 sample_size個の"x""y"の後にrandom_axis_vecを続けて、軸(axis)列とします。

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

# 試行ごとに集計
random_df <- tidyr::tibble(
  iteration = rep(c(0, 0, 1:max_iter), each = sample_size), 
  id = rep(1:sample_size, times = max_iter+2) %>% 
    as.factor(), 
  random_val = c(rep(0, times = sample_size*2), random_value_vec), 
  axis = c(rep(c("x", "y"), each = sample_size), random_axis_vec)
) %>% 
  tidyr::pivot_wider(
    names_from = axis, 
    values_from = random_val, 
    values_fill = 0
  ) %>% # 横型に変形
  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
##        <dbl> <fct> <dbl> <dbl>
##  1         0 1         0     0
##  2         0 2         0     0
##  3         0 3         0     0
##  4         1 1         1     0
##  5         1 2         0     1
##  6         1 3         0     1
##  7         2 1         0     0
##  8         2 2        -1     1
##  9         2 3         1     1
## 10         3 1        -1     0
## 11         3 2         0     1
## 12         3 3         0     1

 作図用に、サンプル番号(id列の値)を因子型にしておきます。
 pivot_wider()で、axis列とrandom_val列を展開します。
 id列が同じ値でグループ化して、サンプルごとに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次元ランダムウォークを作図できました。この例では、x軸またはy軸に対して並行に移動しました。次は、平面上の全ての方向に移動する場合を扱います。

おわりに

 このタイプのランダムウォークは知ってましたが、360°方向に変化するパターンもあるんですね。というわけで次はそっちのタイプをやります。

【次の内容】

www.anarchive-beta.com