1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

「ランダムウォーク」と「拡散」は、物理、化学、生物、統計、データサイエンスなど、いろいろな分野に登場する基本概念です。

たとえば、次のような現象を考えるときに関係します。

  • 水中の小さな粒子がふらふら動く
  • 分子が濃い場所から薄い場所へ広がる
  • 細胞内の物質がランダムに動く
  • ノイズを含む時系列データが時間とともにばらつく
  • ランダムな変動を積み重ねると、分布がどう変わるかを知りたい

この記事では、難しい理論から入るのではなく、Pythonでダミーデータを作りながら、

ランダムな1歩1歩の積み重ねが、なぜ「拡散」として見えるのか

を図で確認します。

この記事のゴール

この記事では、次のことを理解することを目指します。

  • ランダムウォークとは何か
  • たくさんのランダムウォークを集めると、分布が時間とともに広がること
  • 平均位置はあまり動かなくても、ばらつきは増えること
  • 分散は時間に比例して増えること
  • 典型的な移動距離は時間の平方根に比例して増えること
  • ランダムウォークの分布が、時間とともにガウス分布に近づくこと

まず用語を整理する

ランダムウォークとは

ランダムウォークとは、毎回ランダムに向きを決めて進む運動です。

この記事では、もっとも単純な1次元ランダムウォークを考えます。

粒子は最初に $x=0$ にいます。
そして、1ステップごとに、

  • 確率 $1/2$ で右に $+1$
  • 確率 $1/2$ で左に $-1$

だけ動くことにします。

つまり、各ステップの変位 $\Delta x_i$ は、

$$
\Delta x_i =
\begin{cases}
+1 & \text{確率 } 1/2 \
-1 & \text{確率 } 1/2
\end{cases}
$$

です。

$t$ ステップ後の位置 $x_t$ は、これまでのステップの和として書けます。

$$
x_t = \sum_{i=1}^{t} \Delta x_i
$$

一歩一歩はランダムなので、1個の粒子がどこに行くかは予測できません。

しかし、同じルールで動く粒子をたくさん用意すると、全体としてはきれいな規則性が見えてきます。

拡散とは

拡散とは、物質や粒子が時間とともに空間的に広がっていく現象です。

直感的には、

最初は一点に集まっていたものが、時間とともにじわじわ広がる

という現象です。

ランダムウォークでは、1つ1つの粒子はでたらめに動きます。
しかし、たくさんの粒子をまとめて見ると、分布が時間とともに広がります。

この「分布が広がる」という性質が、拡散の本質です。

ランダムウォークの基本的な性質

今回のランダムウォークでは、右に進む確率と左に進む確率が同じです。

そのため、平均的には右にも左にも偏りません。

$$
E[x_t] = 0
$$

ここで $E[x_t]$ は、たくさんの粒子で平均した位置です。

一方で、粒子の位置のばらつきは時間とともに大きくなります。

ステップ長を $l$ とすると、分散は

$$
\mathrm{Var}(x_t) = t l^2
$$

となります。

今回のコードでは $l=1$ とするので、

$$
\mathrm{Var}(x_t) = t
$$

です。

また、拡散係数 $D$ を使うと、1次元拡散では平均二乗変位が

$$
\langle x(t)^2 \rangle = 2Dt
$$

と表されます。

ここで $\langle x(t)^2 \rangle$ は、位置の二乗平均です。
平均が0に近い場合、これは分散とほぼ同じ意味になります。

今回のように、1ステップの時間を $\Delta t$、ステップ長を $l$ とすると、拡散係数は

$$
D = \frac{l^2}{2\Delta t}
$$

です。

今回は $l=1$, $\Delta t=1$ なので、

$$
D = \frac{1}{2}
$$

となります。

したがって、

$$
\langle x(t)^2 \rangle = 2Dt = t
$$

です。

重要なのは、次の違いです。

  • 分散は時間 $t$ に比例して増える
  • 典型的な移動距離は $\sqrt{t}$ に比例して増える

典型的な移動距離は、RMS変位として

$$
\sqrt{\langle x(t)^2 \rangle}
$$

で表せます。

これは今回の場合、

$$
\sqrt{\langle x(t)^2 \rangle} = \sqrt{t}
$$

です。

つまり、時間が4倍になっても、典型的な距離は4倍ではなく2倍になります。

Google Colabで実行するコード

以下のコードは、Google Colabでそのまま実行できます。

このコードでは、

  • 10,000個の粒子
  • 500ステップ
  • 1次元ランダムウォーク
  • ステップ長 $1$
  • 右左に進む確率は同じ

という条件でシミュレーションします。

import os
import numpy as np
import matplotlib.pyplot as plt

# ============================================
# Random walk -> diffusion demo
# ============================================

SEED = 2026
rng = np.random.default_rng(SEED)

# -----------------------------
# 1. Parameters
# -----------------------------
n_particles = 10_000  # 粒子の数
n_steps = 500         # ステップ数
step_length = 1.0     # 1ステップで動く距離
dt = 1.0              # 1ステップあたりの時間

# 図の保存先
fig_dir = "fig"
os.makedirs(fig_dir, exist_ok=True)

# -----------------------------
# 2. Generate dummy data
# -----------------------------
# 各ステップで -1 または +1 に進む
steps = rng.choice(
    [-step_length, step_length],
    size=(n_particles, n_steps)
)

# 位置はステップの累積和
# 最初の位置 x=0 も入れる
positions = np.concatenate(
    [np.zeros((n_particles, 1)), np.cumsum(steps, axis=1)],
    axis=1
)

times = np.arange(n_steps + 1) * dt

# -----------------------------
# 3. Compute statistics
# -----------------------------
mean_x = positions.mean(axis=0)
var_x = positions.var(axis=0, ddof=0)
msd = np.mean(positions**2, axis=0)
rms = np.sqrt(msd)

# 1次元ランダムウォークに対応する拡散係数
D = step_length**2 / (2 * dt)

theory_var = 2 * D * times
theory_rms = np.sqrt(2 * D * times)

print("===== Simulation summary =====")
print(f"Number of particles: {n_particles}")
print(f"Number of steps: {n_steps}")
print(f"Step length: {step_length}")
print(f"dt: {dt}")
print(f"Diffusion coefficient D = step_length^2 / (2 dt) = {D:.3f}")
print()
print(f"Final mean position: {mean_x[-1]:.3f}")
print(f"Final variance: {var_x[-1]:.3f}")
print(f"Theoretical variance at t={n_steps}: {theory_var[-1]:.3f}")
print(f"Final MSD: {msd[-1]:.3f}")
print(f"Final RMS displacement: {rms[-1]:.3f}")
print(f"Theoretical RMS displacement: {theory_rms[-1]:.3f}")

# -----------------------------
# Figure 1: trajectories
# -----------------------------
plt.figure(figsize=(9, 5))

n_show = 20
for i in range(n_show):
    plt.plot(times, positions[i], linewidth=1, alpha=0.8)

plt.axhline(0, linestyle="--", linewidth=1)
plt.title("Figure 1. Example trajectories of 1D random walks")
plt.xlabel("time step")
plt.ylabel("position")
plt.tight_layout()
plt.savefig(f"{fig_dir}/fig1_random_walk_paths.png", dpi=160)
plt.show()

# -----------------------------
# Figure 2: distributions at several times
# -----------------------------
plt.figure(figsize=(9, 5))

selected_steps = [20, 100, 500]
bins = np.arange(-120, 121, 4)

for s in selected_steps:
    plt.hist(
        positions[:, s],
        bins=bins,
        density=True,
        histtype="step",
        linewidth=2,
        label=f"t = {s}"
    )

plt.title("Figure 2. Position distributions become wider over time")
plt.xlabel("position")
plt.ylabel("probability density")
plt.legend()
plt.tight_layout()
plt.savefig(f"{fig_dir}/fig2_distribution_broadening.png", dpi=160)
plt.show()

# -----------------------------
# Figure 3: variance grows linearly
# -----------------------------
plt.figure(figsize=(9, 5))

plt.plot(times, var_x, label="simulation: Var[x(t)]")
plt.plot(times, theory_var, linestyle="--", label="theory: 2Dt")

plt.title("Figure 3. Variance grows almost linearly with time")
plt.xlabel("time step")
plt.ylabel("variance")
plt.legend()
plt.tight_layout()
plt.savefig(f"{fig_dir}/fig3_variance_linear_growth.png", dpi=160)
plt.show()

# -----------------------------
# Figure 4: RMS displacement grows as sqrt(t)
# -----------------------------
plt.figure(figsize=(9, 5))

plt.plot(times, rms, label="simulation: RMS displacement")
plt.plot(times, theory_rms, linestyle="--", label="theory: sqrt(2Dt)")

plt.title("Figure 4. Typical displacement grows like square root of time")
plt.xlabel("time step")
plt.ylabel("RMS displacement")
plt.legend()
plt.tight_layout()
plt.savefig(f"{fig_dir}/fig4_rms_sqrt_growth.png", dpi=160)
plt.show()

# -----------------------------
# Figure 5: final distribution vs Gaussian approximation
# -----------------------------
plt.figure(figsize=(9, 5))

final_t = n_steps

x = np.linspace(-100, 100, 500)
gaussian = (
    1 / np.sqrt(4 * np.pi * D * final_t)
    * np.exp(-(x**2) / (4 * D * final_t))
)

plt.hist(
    positions[:, final_t],
    bins=np.arange(-120, 121, 4),
    density=True,
    alpha=0.5,
    label=f"simulation at t = {final_t}"
)

plt.plot(
    x,
    gaussian,
    linewidth=2,
    label="Gaussian approximation"
)

plt.title("Figure 5. Random walk distribution approaches a Gaussian shape")
plt.xlabel("position")
plt.ylabel("probability density")
plt.legend()
plt.tight_layout()
plt.savefig(f"{fig_dir}/fig5_gaussian_comparison.png", dpi=160)
plt.show()

print(f"\nFigures saved in: {fig_dir}")

実行結果の例

上のコードを実行すると、次のような出力が得られます。

===== Simulation summary =====
Number of particles: 10000
Number of steps: 500
Step length: 1.0
dt: 1.0
Diffusion coefficient D = step_length^2 / (2 dt) = 0.500

Final mean position: -0.152
Final variance: 503.154
Theoretical variance at t=500: 500.000
Final MSD: 503.178
Final RMS displacement: 22.432
Theoretical RMS displacement: 22.361

最後の時刻 $t=500$ における分散は、シミュレーションでは約 $503.154$ です。
理論値は $500$ なので、かなり近い値になっています。

また、RMS変位はシミュレーションで約 $22.432$、理論値は約 $22.361$ です。
こちらもよく一致しています。

図1:1個1個の粒子はふらふら動く

fig1_random_walk_paths.png

図1では、10,000個の粒子のうち、20個だけを選んで軌跡を表示しています。

各粒子は、右に行ったり左に行ったりしながら、かなり不規則に動いています。

重要なのは、1本1本の軌跡だけを見ると、きれいな法則があるようには見えないことです。

ある粒子は右の方へ進み、別の粒子は左の方へ進みます。
また、一度右へ進んだからといって、その後も右へ行き続けるわけではありません。

つまり、1個の粒子の動きはかなり偶然に左右されます。

図2:たくさん集めると分布が広がる

fig2_distribution_broadening.png

図2では、時刻 $t=20$, $t=100$, $t=500$ における粒子の位置分布を比較しています。

最初はすべての粒子が $x=0$ にいます。
しかし、時間が進むと、粒子は右にも左にも広がっていきます。

図を見ると、次のことが分かります。

  • $t=20$ では、分布はまだ狭い
  • $t=100$ では、分布が少し広がる
  • $t=500$ では、分布がかなり広がる
  • 分布の中心はおおむね $0$ のまま

ここがとても大事です。

平均位置はほぼ $0$ のままですが、分布の幅は大きくなっています。

つまり、

平均は動かないが、ばらつきは増える

ということです。

これが拡散を見るときの基本的な考え方です。

図3:分散は時間にほぼ比例して増える

fig3_variance_linear_growth.png

図3では、位置の分散 $\mathrm{Var}(x(t))$ を時間に対してプロットしています。

青い線がシミュレーション結果、破線が理論値です。

今回の設定では、

$$
D = \frac{1}{2}
$$

なので、

$$
2Dt = t
$$

です。

つまり、理論的には分散は時間 $t$ と同じ速度で増えていきます。

図を見ると、シミュレーション結果は理論線とほとんど重なっています。

この結果から、

ランダムウォークでは、位置の分散が時間に比例して増える

ことが分かります。

これは拡散を特徴づける重要な性質です。

図4:典型的な移動距離は時間の平方根で増える

fig4_rms_sqrt_growth.png

図4では、RMS変位を時間に対してプロットしています。

RMS変位は、

$$
\sqrt{\langle x(t)^2 \rangle}
$$

で定義されます。

これは、ざっくり言えば「原点からどれくらい離れているのが典型的か」を表す量です。

平均二乗変位が

$$
\langle x(t)^2 \rangle = 2Dt
$$

なので、RMS変位は

$$
\sqrt{\langle x(t)^2 \rangle} = \sqrt{2Dt}
$$

です。

つまり、RMS変位は時間 $t$ に比例するのではなく、$\sqrt{t}$ に比例します。

この違いは重要です。

たとえば、拡散によって典型的な距離を2倍にしたければ、時間は4倍必要です。
典型的な距離を10倍にしたければ、時間は100倍必要です。

拡散は、遠くまで一気に進む運動ではありません。
ランダムな動きの積み重ねなので、広がり方は意外とゆっくりです。

図5:ランダムウォークの分布はガウス分布に近づく

fig5_gaussian_comparison.png

図5では、$t=500$ における粒子の位置分布と、ガウス分布による近似を比較しています。

ガウス分布の形は、次の式で表されます。

$$
p(x,t)=
\frac{1}{\sqrt{4\pi Dt}}
\exp\left(
-\frac{x^2}{4Dt}
\right)
$$

ここで、$p(x,t)$ は時刻 $t$ に位置 $x$ にいる確率密度です。

この式は、1次元の拡散方程式

$$
\frac{\partial p}{\partial t}=
D
\frac{\partial^2 p}{\partial x^2}
$$

の基本的な解としても登場します。

今回のシミュレーションは、左右に1歩ずつ動く離散的なランダムウォークです。
一方、ガウス分布は連続的な近似です。

それでも、十分に時間が経つと、シミュレーションの分布はガウス分布にかなり近くなります。

これは、たくさんの独立なランダム変数の和がガウス分布に近づく、という中心極限定理とも深く関係しています。

ここまでのまとめ

今回のシミュレーションから、次のことが分かりました。

  • ランダムウォークでは、1つ1つの粒子の動きは不規則
  • しかし、たくさんの粒子を集めると、分布には規則性が見える
  • 平均位置はほぼ $0$ のまま
  • 位置の分散は時間に比例して増える
  • RMS変位は時間の平方根に比例して増える
  • 十分時間が経つと、位置分布はガウス分布に近づく

特に大事なのは、

$$
\langle x(t)^2 \rangle = 2Dt
$$

という関係です。

これは、ランダムウォークと拡散をつなぐ基本式です。

「平均が変わらない」と「何も起きていない」は違う

今回の結果で、初心者がつまずきやすいポイントがあります。

それは、

平均位置がほぼ0なら、何も起きていないのでは?

と思ってしまうことです。

しかし、これは違います。

平均位置は $0$ のままでも、分布は時間とともに広がっています。
つまり、粒子全体としては拡散しています。

データ解析でも、平均値だけを見ると変化が見えないことがあります。
しかし、分散や分布の形を見ると、大きな変化が起きていることがあります。

この意味で、ランダムウォークと拡散は、統計的な見方の重要性を教えてくれる良い例でもあります。

少しだけ一般化する

今回は、右と左に同じ確率で動くランダムウォークを扱いました。

この場合、平均位置はほぼ $0$ のままでした。

しかし、右に動く確率が少し高い場合には、平均位置も右にずれていきます。

これは「拡散」に加えて「ドリフト」がある場合です。

イメージとしては、

  • 左右対称なランダムウォーク:拡散だけ
  • どちらかに偏ったランダムウォーク:拡散 + ドリフト

です。

今回の記事では扱いませんでしたが、偏りのあるランダムウォークを調べると、拡散と移流、あるいはノイズとトレンドの関係を考える入口になります。

実データを見るときの注意

今回のシミュレーションは、とても単純なモデルです。

実際のデータでは、次のような要素が加わります。

  • ステップの大きさが一定とは限らない
  • 右と左の確率が同じとは限らない
  • 各ステップが独立とは限らない
  • 空間が1次元とは限らない
  • 境界がある場合がある
  • 測定ノイズが入る
  • 粒子同士が相互作用する場合がある
  • 時間によって拡散係数が変わる場合がある

したがって、実データに対してすぐに

$$
\langle x(t)^2 \rangle = 2Dt
$$

を当てはめればよい、というわけではありません。

ただし、この式は出発点として非常に重要です。

まず単純なランダムウォークを理解しておくと、より複雑な拡散、異常拡散、ドリフト付き拡散、確率過程などを学ぶときに見通しが良くなります。

演習

最後に、コードを少し変えて遊べる演習を置いておきます。

演習1:粒子数を変える

次の値を変えてみてください。

n_particles = 100
n_particles = 1_000
n_particles = 10_000

粒子数が少ないと、分散や分布が理論値からどれくらい揺らぐでしょうか。

演習2:ステップ数を変える

次の値を変えてみてください。

n_steps = 100
n_steps = 500
n_steps = 2000

時間が長くなると、分布の幅やRMS変位はどう変わるでしょうか。

演習3:ステップ長を変える

次の値を変えてみてください。

step_length = 2.0

このとき、拡散係数

$$
D = \frac{l^2}{2\Delta t}
$$

はどう変わるでしょうか。

ステップ長を2倍にすると、$D$ は2倍ではなく4倍になります。
なぜそうなるのか、式と図を見比べて考えてみてください。

演習4:右に進みやすいランダムウォークにする

今のコードでは、右と左に進む確率が同じです。

次のようにすると、右に進む確率を少し高くできます。

p_right = 0.55

steps = rng.choice(
    [-step_length, step_length],
    size=(n_particles, n_steps),
    p=[1 - p_right, p_right]
)

このとき、平均位置、分散、分布の形はどう変わるでしょうか。

おわりに

この記事では、ランダムウォークのダミーデータをPythonで作り、そこから拡散の基本的な性質を確認しました。

1つ1つの粒子の動きはランダムでも、たくさん集めて統計的に見ると、

$$
\langle x(t)^2 \rangle = 2Dt
$$

というシンプルな関係が見えてきます。

このように、ランダムな現象でも、集団として見ると規則性が現れることがあります。

ランダムウォークは、拡散だけでなく、統計、物理、生物、化学、データサイエンス、機械学習の基礎にもつながる、とても良い教材です。

「1つ1つはランダム。でも、たくさん集めると法則が見える。」

この感覚を持っておくと、実験データやシミュレーション結果を見るときにも役立ちます。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?