はじめに
「ランダムウォーク」と「拡散」は、物理、化学、生物、統計、データサイエンスなど、いろいろな分野に登場する基本概念です。
たとえば、次のような現象を考えるときに関係します。
- 水中の小さな粒子がふらふら動く
- 分子が濃い場所から薄い場所へ広がる
- 細胞内の物質がランダムに動く
- ノイズを含む時系列データが時間とともにばらつく
- ランダムな変動を積み重ねると、分布がどう変わるかを知りたい
この記事では、難しい理論から入るのではなく、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個の粒子はふらふら動く
図1では、10,000個の粒子のうち、20個だけを選んで軌跡を表示しています。
各粒子は、右に行ったり左に行ったりしながら、かなり不規則に動いています。
重要なのは、1本1本の軌跡だけを見ると、きれいな法則があるようには見えないことです。
ある粒子は右の方へ進み、別の粒子は左の方へ進みます。
また、一度右へ進んだからといって、その後も右へ行き続けるわけではありません。
つまり、1個の粒子の動きはかなり偶然に左右されます。
図2:たくさん集めると分布が広がる
図2では、時刻 $t=20$, $t=100$, $t=500$ における粒子の位置分布を比較しています。
最初はすべての粒子が $x=0$ にいます。
しかし、時間が進むと、粒子は右にも左にも広がっていきます。
図を見ると、次のことが分かります。
- $t=20$ では、分布はまだ狭い
- $t=100$ では、分布が少し広がる
- $t=500$ では、分布がかなり広がる
- 分布の中心はおおむね $0$ のまま
ここがとても大事です。
平均位置はほぼ $0$ のままですが、分布の幅は大きくなっています。
つまり、
平均は動かないが、ばらつきは増える
ということです。
これが拡散を見るときの基本的な考え方です。
図3:分散は時間にほぼ比例して増える
図3では、位置の分散 $\mathrm{Var}(x(t))$ を時間に対してプロットしています。
青い線がシミュレーション結果、破線が理論値です。
今回の設定では、
$$
D = \frac{1}{2}
$$
なので、
$$
2Dt = t
$$
です。
つまり、理論的には分散は時間 $t$ と同じ速度で増えていきます。
図を見ると、シミュレーション結果は理論線とほとんど重なっています。
この結果から、
ランダムウォークでは、位置の分散が時間に比例して増える
ことが分かります。
これは拡散を特徴づける重要な性質です。
図4:典型的な移動距離は時間の平方根で増える
図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:ランダムウォークの分布はガウス分布に近づく
図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つはランダム。でも、たくさん集めると法則が見える。」
この感覚を持っておくと、実験データやシミュレーション結果を見るときにも役立ちます。




