StatsBeginner: 初学者の統計学習ノート

統計学およびR、Pythonでのプログラミングの勉強の過程をメモっていくノート。たまにMacの話題。

ChatGPTにプログラミングを教わるとは、こういう感じです。

ChatGPTとのやり取りで一番楽しいのは、やっぱり、プログラミングを教えてもらうことですね。
今回は、とあるアニメーションの作り方を教えてもらいました。下にそのやりとりを掲載しておきますが、コードの中身は読み飛ばして、文章でのやり取りの部分と出力されたアニメーション動画を見るだけでも、ChatGPTにプログラミングを教わるとはどういうことなのか雰囲気がある程度分かるんじゃないでしょうか。
 
ポイントは、一発で何かが出来上がるのではなく、疑問をぶつけながら一緒にコードを完成させていく感じになるところですかね。今回の例だともはや、単にアニメーションの作成を依頼してるだけになってて、私はコードをほとんどみてませんが(笑)
 
ちなみに私はChatGPTに、
 

  • 言葉で指示した内容を実現するコードを書いてもらう
  • 読み解くのがめんどくさいコードを与えて意味や構造を説明してもらう
  • あるコードを実行してエラーが出た時に、コードとエラーメッセージを与えて解決方法を考えてもらう

 
というのを何度か試したのですが、人間に教わってるのとあまり変わらんというか、返事が一瞬で返ってくるぶん人間より有能かも知れません。やってて驚くのは、提案してくれるコードそのものよりも、「会話が成り立つこと」です。自然言語で頼んだことを、概ね理解してくれるのが凄い。
 
以下のやり取りでは、ChatGPT(GPT-4)に、セルラー・オートマトンの可視化のコードをつくってもらいました。最近Pythonでも似たようなことを試して、ライブラリのせいだと思いますが表示があんま綺麗じゃなかったので、今回はRのggplot2でやっています。
教えてもらったコードを実行して出力されたアニメーションも掲載しておきます。アニメーションは何回か修正してもらって、5回目で意図したとおりのものができました。
 

セルラー・オートマトンのルール30を、Rのggplot2で描画したいのですが、どのようなコードが考えられますか。
 

ChatGPT

セルラー・オートマトンのルール30を描画するために、R言語とggplot2パッケージを使用することができます。以下は、ルール30を実装し、結果をggplot2で描画する方法の一例です。
まず、必要なパッケージをインストールして読み込みます。

install.packages("ggplot2")
library(ggplot2)

次に、セルラー・オートマトンのルール30を実装する関数を定義します。

rule30 <- function(input) {
  n <- length(input)
  output <- numeric(n)
  for (i in 1:n) {
    left <- ifelse(i == 1, input[n], input[i - 1])
    center <- input[i]
    right <- ifelse(i == n, input[1], input[i + 1])
    
    output[i] <- as.integer(left != (center | right))
  }
  output
}
次に、初期状態を定義し、繰り返しルール30を適用して結果を格納します。
n_steps <- 100
initial_state <- numeric(n_steps)
initial_state[n_steps %/% 2] <- 1
states <- matrix(0, nrow = n_steps, ncol = n_steps)
states[1, ] <- initial_state

for (i in 2:n_steps) {
  states[i, ] <- rule30(states[(i - 1), ])
}
最後に、ggplot2を使って結果を描画します。
cells <- data.frame(
  x = rep(1:n_steps, times = n_steps),
  y = rep(1:n_steps, each = n_steps),
  state = as.factor(as.vector(states))
)

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank())

print(plot)これにより、セルラー・オートマトンのルール30がggplot2で描画されます。上記のコードは、100回のステップで実行されるルール30の例ですが、n_steps変数を変更することで、異なるステップ数で実行することができます。
 

出力結果


 

1秒に4ステップずつ追加して表示を更新していくような、アニメーションっぽいことはできますか?
 

ChatGPT

アニメーションのように表示を更新するために、gganimateパッケージを使用できます。まず、gganimateパッケージをインストールし、ggplot2とともに読み込みます。

install.packages("gganimate")
library(gganimate)
library(ggplot2)

gganimateを使用して1秒に4ステップずつ追加して表示を更新するには、以下のコードを実行します。

n_steps <- 100
n_steps_anim <- n_steps * 4
initial_state <- numeric(n_steps_anim)
initial_state[n_steps_anim %/% 2] <- 1
states <- matrix(0, nrow = n_steps_anim, ncol = n_steps_anim)
states[1, ] <- initial_state

for (i in 2:n_steps_anim) {
  states[i, ] <- rule30(states[(i - 1), ])
}

cells <- data.frame(
  x = rep(1:n_steps_anim, times = n_steps_anim),
  y = rep(1:n_steps_anim, each = n_steps_anim),
  state = as.factor(as.vector(states)),
  time = rep(1:n_steps_anim, each = n_steps_anim)
)

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time) +
  enter_fade() +
  exit_fade() +
  end_pause(25)

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600)
anim_save("rule30_animation.gif", anim)

これにより、rule30_animation.gifという名前のGIFアニメーションファイルが作成され、1秒に4ステップずつ表示が更新されます。n_steps変数を変更することで、異なるステップ数でアニメーションを実行できます。また、duration引数を変更することで、アニメーションの全体の長さを調整できます。
 

end_pauseって何の関数ですか?
 

ChatGPT

申し訳ありませんが、説明が誤っていました。end_pauseは関数ではなく、gganimateパッケージのanimate関数の引数です。end_pause引数は、アニメーションの最後に追加のフレーム数を指定して、アニメーションが一定時間止まるようにするために使用されます。これにより、アニメーションの最後に一時停止が発生し、アニメーション全体がより見やすくなります。
しかし、私の以前のコード例では、end_pause引数をanimate関数に渡す代わりに、誤ってend_pause()関数のように記述していました。正しいコード例は以下のようになります。

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600)

この例では、アニメーションの最後に25フレームの一時停止が追加されます。end_pauseの値を調整することで、この一時停止の長さを変更することができます。
 

要するにさっきのコードの

+
  end_pause(25)

を削除すればいいですか?
 

ChatGPT

はい、おっしゃる通りです。end_pause(25)を削除し、animate関数のend_pause引数に25を設定すれば、正しいコードになります。以下が修正済みのコード例です。

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time) +
  enter_fade() +
  exit_fade()

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600)
anim_save("rule30_animation.gif", anim)

この修正により、アニメーションの最後に25フレームの一時停止が追加されます。end_pause引数の値を調整することで、一時停止の長さを変更することができます。
 

以下のエラーが出ました。
> anim_save("rule30_animation.gif", anim)
Error: The animation object does not specify a save_animation method
 

ChatGPT

すみません。このエラーは、gganimateが指定されたファイル形式でアニメーションを保存するためのメソッドがないことを示しています。GIF形式でアニメーションを保存するには、gifskiとpngパッケージをインストールして、animate()関数のrenderer引数にgifski_renderer()を指定する必要があります。以下の手順で修正できます。
まず、gifskiとpngパッケージをインストールします。

install.packages("gifski")
install.packages("png")

そして、gifskiパッケージを読み込みます。

library(gifski)

animate()関数のrenderer引数にgifski_renderer()を指定してアニメーションを作成し、anim_save()関数でGIFファイルに保存します。

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = gifski_renderer())
anim_save("rule30_animation.gif", anim)

これで、エラーが解決され、アニメーションがGIF形式で保存されるはずです。rule30_animation.gifファイルが作成され、1秒に4ステップずつ表示が更新されます。
 

gifskiではなくavパッケージをつかってvideoファイルを作成するにはどうすればいいですか?
 

ChatGPT

avパッケージを使用してアニメーションをビデオファイルとして保存するには、まずavパッケージをインストールし、読み込みます。

install.packages("av")
library(av)

次に、animate()関数でアニメーションを作成し、renderer引数にav_renderer()を指定してビデオファイル(例えば、MP4形式)に保存します。

library(gganimate)
library(ggplot2)
library(av)

# ...(セルラー・オートマトンのルール30を描画するためのコード)

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time) +
  enter_fade() +
  exit_fade()

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = av_renderer("rule30_animation.mp4"))

これで、rule30_animation.mp4という名前のMP4ビデオファイルが作成されます。アニメーションは1秒に4ステップずつ表示が更新され、最後に25フレームの一時停止が追加されます。
もちろん、他のビデオ形式(例えば、WebMやAVI)にも対応しています。av_renderer()関数の引数に適切なファイル名を指定することで、異なる形式でビデオを保存できます。
 

出力結果


 

アニメーションはできました。しかしこのやり方だと、セルラーオートマトンが一行ずつ表示されているのですが、一般的には、一度表示された行は表示しっぱなしにして、だんたん画面がセルラーオートマトンで埋め尽くされていくようにします。そのように修正してもらえますか?あとついでにいうと、一般的には、一行目の真ん中が黒、他は白という初期状態からスタートします。
 

ChatGPT

ご指摘の通り、一般的にセルラーオートマトンは、新しい行が表示される度に既存の行がそのまま表示され続け、画面が徐々に埋め尽くされるように動作します。また、初期状態は真ん中のセルが黒で、他のセルが白です。この動作に修正するには、以下のようにコードを変更します。

n_steps <- 100
initial_state <- numeric(n_steps)
initial_state[n_steps %/% 2] <- 1
states <- matrix(0, nrow = n_steps, ncol = n_steps)
states[1, ] <- initial_state

for (i in 2:n_steps) {
  states[i, ] <- rule30(states[(i - 1), ])
}

cells <- data.frame(
  x = rep(1:n_steps, times = n_steps),
  y = rep(1:n_steps, each = n_steps),
  state = as.factor(as.vector(states)),
  time = rep(1:n_steps, each = n_steps)
)

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time)

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = av_renderer("rule30_animation.mp4"))

この修正により、アニメーションは一般的なセルラーオートマトンの動作に従い、新しい行が追加される度に既存の行が表示され続けます。また、初期状態は真ん中のセルが黒で、他のセルが白になります。このコードでアニメーションが正しく作成されるはずです。
 

出力結果


 

いや、これだとまだ、y=1の行、y=2の行、y=3の行…が順に1行ずつ表示される感じになっています。一度表示した行が消えてしまってます。また、そもそも、このセルラーオートマトンは、x軸がperiodに対応していて左から右に進んでいくものですよね?であれば、表示も、まずx=1の列(真ん中だけ黒くなっている)を表示し、次にその列は表示したままx=2の列を加え……というふうに左から右に広がっていくようにしたいです。
 

ChatGPT

申し訳ありません、理解しました。セルラーオートマトンが左から右に広がっていくように表示を修正するには、以下のようにコードを変更します。

n_steps <- 100
initial_state <- numeric(n_steps)
initial_state[n_steps %/% 2] <- 1
states <- matrix(0, nrow = n_steps, ncol = n_steps)
states[1, ] <- initial_state

for (i in 2:n_steps) {
  states[i, ] <- rule30(states[(i - 1), ])
}

cells <- data.frame(
  x = rep(1:n_steps, times = n_steps),
  y = rep(1:n_steps, each = n_steps),
  state = as.factor(as.vector(states)),
  time = rep(1:n_steps, each = n_steps)
)

cells$time <- pmin(cells$x, cells$time)

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time)

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = av_renderer("rule30_animation.mp4"))

この修正により、アニメーションは左から右に広がっていくように表示されます。また、既存の列が表示されたまま、新しい列が追加されます。このコードでアニメーションが正しく作成されるはずです。
 

出力


 

いや、これもおかしいです。3つの意味でおかしいです。
1.セルラーオートマトンが下から上に展開するようになっています。つまり、period(generation, step)がy軸になってしまっています。
2.最初にx=1の列とy=1の行が表示され、次にそれらが消えて、x=2の列とy=2の行が表示され…というふうに、L字型に表示されるようになってしまっています。
3.依然として、既存の表示が残らず、各ステップの情報しか表示されないです。
 

ChatGPT

失礼しました。ご指摘の通り、私の修正は問題点を解決していませんでした。以下のコードで、お求めのアニメーションを正しく作成できるはずです。

n_steps <- 100
initial_state <- numeric(n_steps)
initial_state[n_steps %/% 2] <- 1
states <- matrix(0, nrow = n_steps, ncol = n_steps)
states[1, ] <- initial_state

for (i in 2:n_steps) {
  states[i, ] <- rule30(states[(i - 1), ])
}

cells <- expand.grid(x = 1:n_steps, y = 1:n_steps)
cells$state <- as.factor("white")
cells$time <- 1

for (t in 1:n_steps) {
  current_states <- data.frame(
    x = 1:n_steps,
    y = rep(t, n_steps),
    state = as.factor(ifelse(states[t, ] == 0, "white", "black")),
    time = t
  )
  cells <- rbind(cells, current_states)
}

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_manual(frames = time)

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = av_renderer("rule30_animation.mp4"))

このコードでは、以下のようにアニメーションが正しく機能するはずです。
セルラーオートマトンが左から右に展開します(x軸がステップ数に対応)。
最初にx=1の列(真ん中のセルだけ黒)が表示され、次にその列が表示されたまま、x=2の列が追加され、以降は左から右に広がっていきます。
既存の表示が残り、各ステップの情報が重ねられて表示されます。
このコードで、期待されるセルラーオートマトンのアニメーションが作成されるはずです。
 

出力結果


 

惜しいです!セルラーオートマトン自体は左から右に展開しており、適切に修正されています。しかし依然として、各ステップの列だけが表示されるので、全ステップが表示から消えてしまっています。そもそも、前のステップが残るようにする上で、コードのどの部分を工夫されようとしてますか?修正意図を教えていただいたら、こっちで解決できるかも知れません。
 

ChatGPT

失礼しました。こちらで再度修正いたしました。今回は、transition_manual()を使わずに、transition_states()とshadow_mark()を使って各ステップで前のステップが残るようにしました。以下のコードが、期待されるセルラーオートマトンのアニメーションを作成できるはずです。

n_steps <- 100
initial_state <- numeric(n_steps)
initial_state[n_steps %/% 2] <- 1
states <- matrix(0, nrow = n_steps, ncol = n_steps)
states[1, ] <- initial_state

for (i in 2:n_steps) {
  states[i, ] <- rule30(states[(i - 1), ])
}

cells <- expand.grid(x = 1:n_steps, y = 1:n_steps)
cells$state <- as.factor("white")
cells$time <- 1

for (t in 1:n_steps) {
  current_states <- data.frame(
    x = 1:n_steps,
    y = rep(t, n_steps),
    state = as.factor(ifelse(states[t, ] == 0, "white", "black")),
    time = t
  )
  cells <- rbind(cells, current_states)
}

plot <- ggplot(cells, aes(x = x, y = y, fill = state)) +
  geom_tile(color = "black") +
  scale_fill_manual(values = c("white", "black")) +
  coord_flip() +
  theme_minimal() +
  theme(panel.grid = element_blank(),
        axis.ticks = element_blank(),
        axis.text = element_blank()) +
  transition_states(states = time, transition_length = 1, state_length = 1) +
  shadow_mark()

anim <- animate(plot, nframes = n_steps * 4, duration = n_steps, end_pause = 25, width = 600, height = 600, renderer = av_renderer("rule30_animation.mp4"))

このコードにより、以下の動作が期待できます。
セルラーオートマトンが左から右に展開する。
各ステップで前のステップが残る。
今回の修正で、お求めのアニメーションが作成されるはずです。
 

出力結果


 

これで完璧になりました。ありがとうございます。
 

ChatGPT

お手伝いできてうれしいです!正しくアニメーションが作成できたとのことで安心しました。
これからも何か質問やサポートが必要であれば、遠慮なくお聞きください。引き続きお手伝いできることがあれば喜んで対応いたします。